├── .github └── workflows │ └── python-package.yml ├── .gitignore ├── CODEOWNERS ├── Makefile ├── README.md ├── pyproject.toml ├── setup.cfg ├── specs ├── bytecode-proof.md ├── copy-proof.md ├── ecc-proof.md ├── error_state │ ├── ErrorCodeStore.md │ ├── ErrorGasUintOverflow.md │ ├── ErrorInvalidCreationCode.md │ ├── ErrorInvalidJump.md │ ├── ErrorInvalidOpcode.md │ ├── ErrorOutOfGasAccountAccess.md │ ├── ErrorOutOfGasCall.md │ ├── ErrorOutOfGasConstant.md │ ├── ErrorOutOfGasCreate.md │ ├── ErrorOutOfGasDynamicMemoryExpansion.md │ ├── ErrorOutOfGasEXP.md │ ├── ErrorOutOfGasLog.md │ ├── ErrorOutOfGasMemoryCopy.md │ ├── ErrorOutOfGasSHA3.md │ ├── ErrorOutOfGasSloadSstore.md │ ├── ErrorOutOfGasStaticMemoryExpansion.md │ ├── ErrorOutOfGasTloadTstore.md │ ├── ErrorReturnDataOutOfBound.md │ ├── ErrorStackOverflow_Underflow.md │ └── ErrorWriteProtection.md ├── evm-proof.md ├── exp-proof-design-doc.md ├── exp-proof.md ├── introduction.md ├── lookup.md ├── opcode │ ├── 00STOP.md │ ├── 01ADD_03SUB.md │ ├── 02MUL_04DIV_06MOD.md │ ├── 05SDIV_07SMOD.md │ ├── 08ADDMOD.md │ ├── 09MULMOD.md │ ├── 0AEXP.md │ ├── 0bSIGNEXTEND.md │ ├── 10LT_11GT_14EQ.md │ ├── 12SLT_13SGT.md │ ├── 15ISZERO.md │ ├── 16AND_17OR_18XOR.md │ ├── 19NOT.md │ ├── 1aBYTE.md │ ├── 1bSHL_1cSHR.md │ ├── 1dSAR.md │ ├── 30ADDRESS.md │ ├── 31BALANCE.md │ ├── 32ORIGIN.md │ ├── 33CALLER.md │ ├── 34CALLVALUE.md │ ├── 35CALLDATALOAD.md │ ├── 36CALLDATASIZE.md │ ├── 37CALLDATACOPY.md │ ├── 38CODESIZE.md │ ├── 39CODECOPY.md │ ├── 3aGASPRICE.md │ ├── 3bEXTCODESIZE.md │ ├── 3cEXTCODECOPY.md │ ├── 3dRETURNDATASIZE.md │ ├── 3eRETURNDATACOPY.md │ ├── 3fEXTCODEHASH.md │ ├── 40BLOCKHASH.md │ ├── 41COINBASE_45GASLIMIT_48BASEFEE.md │ ├── 46CHAINID.md │ ├── 47SELFBALANCE.md │ ├── 50POP.md │ ├── 51MLOAD_52MSTORE_53MSTORE8.md │ ├── 54SLOAD_55SSTORE.md │ ├── 56JUMP.md │ ├── 57JUMPI.md │ ├── 59MSIZE.md │ ├── 5AGAS.md │ ├── 5BJUMPDEST.md │ ├── 80DUPX.md │ ├── 90SWAPX.md │ ├── 92TLOAD_93TSTORE.md │ ├── A0_A4LOG.md │ ├── F0CREATE_F5CREATE2.md │ ├── F1CALL_F2CALLCODE_F4DELEGATECALL_FASTATICCALL.md │ └── F3RETURN_FDREVERT.md ├── precompile │ ├── 01ecRecover.md │ ├── 04dataCopy.md │ ├── 06ecAdd.md │ ├── 07ecMul.md │ └── 08ecPairing.md ├── public_inputs.md ├── public_inputs_deprecated.rev1.png ├── sig-proof.md ├── state-proof.md ├── super_circuit.md ├── super_circuit.png ├── tables.md ├── transactions-proof.md ├── tx_circuit.rev1.png ├── virtual_state │ ├── begin_tx.md │ └── end_tx.md ├── withdrawal-proof.md └── word-encoding.md ├── src └── zkevm_specs │ ├── __init__.py │ ├── bytecode_circuit.py │ ├── copy_circuit.py │ ├── ecc_circuit.py │ ├── evm_circuit │ ├── __init__.py │ ├── execution │ │ ├── __init__.py │ │ ├── add_sub.py │ │ ├── addmod.py │ │ ├── address.py │ │ ├── balance.py │ │ ├── begin_tx.py │ │ ├── bitwise.py │ │ ├── block_ctx.py │ │ ├── blockhash.py │ │ ├── byte.py │ │ ├── calldatacopy.py │ │ ├── calldataload.py │ │ ├── calldatasize.py │ │ ├── caller.py │ │ ├── callop.py │ │ ├── callvalue.py │ │ ├── codecopy.py │ │ ├── codesize.py │ │ ├── comparator.py │ │ ├── create.py │ │ ├── dataCopy.py │ │ ├── end_block.py │ │ ├── end_tx.py │ │ ├── error_code_store.py │ │ ├── error_gas_uint_overflow.py │ │ ├── error_invalid_creation_code.py │ │ ├── error_invalid_jump.py │ │ ├── error_invalid_opcode.py │ │ ├── error_oog_account_access.py │ │ ├── error_oog_call.py │ │ ├── error_oog_constant.py │ │ ├── error_oog_create.py │ │ ├── error_oog_dynamic_memory_expansion.py │ │ ├── error_oog_exp.py │ │ ├── error_oog_log.py │ │ ├── error_oog_memory_copy.py │ │ ├── error_oog_sha3.py │ │ ├── error_oog_sload_sstore.py │ │ ├── error_oog_static_memory_expansion.py │ │ ├── error_return_data_out_of_bound.py │ │ ├── error_stack.py │ │ ├── error_write_protection.py │ │ ├── exp.py │ │ ├── extcodecopy.py │ │ ├── extcodehash.py │ │ ├── extcodesize.py │ │ ├── gas.py │ │ ├── gasprice.py │ │ ├── iszero.py │ │ ├── jump.py │ │ ├── jumpi.py │ │ ├── log.py │ │ ├── memory.py │ │ ├── msize.py │ │ ├── mul_div_mod.py │ │ ├── mulmod.py │ │ ├── not_.py │ │ ├── origin.py │ │ ├── pop.py │ │ ├── precompiles │ │ │ ├── ecadd.py │ │ │ ├── ecmul.py │ │ │ ├── ecpairing.py │ │ │ ├── ecrecover.py │ │ │ └── error_oog_precompile.py │ │ ├── push.py │ │ ├── return_revert.py │ │ ├── returndatacopy.py │ │ ├── returndatasize.py │ │ ├── sar.py │ │ ├── sdiv_smod.py │ │ ├── selfbalance.py │ │ ├── sha3.py │ │ ├── shl_shr.py │ │ ├── signextend.py │ │ ├── slt_sgt.py │ │ ├── stop.py │ │ └── storage.py │ ├── execution_state.py │ ├── instruction.py │ ├── main.py │ ├── opcode.py │ ├── precompile.py │ ├── step.py │ ├── table.py │ ├── typing.py │ └── util │ │ ├── __init__.py │ │ ├── call_gadget.py │ │ ├── memory_gadget.py │ │ └── precompile_gadget.py │ ├── exp_circuit.py │ ├── pi_circuit.py │ ├── py.typed │ ├── sig_circuit.py │ ├── state_circuit.py │ ├── tx_circuit.py │ ├── util │ ├── __init__.py │ ├── arithmetic.py │ ├── constraint_system.py │ ├── ec.py │ ├── hash.py │ ├── param.py │ ├── tables.py │ └── typing.py │ └── withdrawal_circuit.py └── tests ├── common.py ├── conftest.py ├── evm ├── precompiles │ ├── test_ecAdd.py │ ├── test_ecMul.py │ ├── test_ecPairing.py │ └── test_ecRecover.py ├── test_add_sub.py ├── test_addmod.py ├── test_address.py ├── test_balance.py ├── test_begin_tx.py ├── test_bitwise.py ├── test_block_ctx.py ├── test_blockhash.py ├── test_byte.py ├── test_calldatacopy.py ├── test_calldataload.py ├── test_calldatasize.py ├── test_caller.py ├── test_callop.py ├── test_callvalue.py ├── test_codecopy.py ├── test_codesize.py ├── test_comparator.py ├── test_create.py ├── test_dataCopy.py ├── test_end_block.py ├── test_end_tx.py ├── test_error_code_store.py ├── test_error_gas_uint_overflow.py ├── test_error_invalid_jump.py ├── test_error_invalid_opcode.py ├── test_error_invalild_creation_code.py ├── test_error_oog_account_access.py ├── test_error_oog_call.py ├── test_error_oog_constant.py ├── test_error_oog_create.py ├── test_error_oog_dynamic_memory_expansion.py ├── test_error_oog_exp.py ├── test_error_oog_log.py ├── test_error_oog_memory_copy.py ├── test_error_oog_sha3.py ├── test_error_oog_sload_store.py ├── test_error_oog_static_memory_expansion.py ├── test_error_return_data_out_of_bound.py ├── test_error_stack.py ├── test_error_write_protection.py ├── test_exp.py ├── test_extcodecopy.py ├── test_extcodehash.py ├── test_extcodesize.py ├── test_gas.py ├── test_gasprice.py ├── test_iszero.py ├── test_jump.py ├── test_jumpi.py ├── test_logs.py ├── test_memory.py ├── test_msize.py ├── test_mul_div_mod.py ├── test_mulmod.py ├── test_not.py ├── test_origin.py ├── test_pop.py ├── test_push.py ├── test_return_revert.py ├── test_returndatacopy.py ├── test_returndatasize.py ├── test_sar.py ├── test_sdiv_smod.py ├── test_selfbalance.py ├── test_sha3.py ├── test_shl_shr.py ├── test_signextend.py ├── test_sload.py ├── test_slt_sgt.py ├── test_sstore.py └── test_stop.py ├── test_bytecode_circuit.py ├── test_ecc_circuit.py ├── test_public_inputs.py ├── test_sig_circuit.py ├── test_state_circuit.py ├── test_tx_circuit.py └── test_withdrawal_circuit.py /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | python-version: [3.9] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Set up Python ${{ matrix.python-version }} 21 | uses: actions/setup-python@v2 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | make install 28 | - name: Lint 29 | run: make lint 30 | - name: Type check 31 | run: make type 32 | - name: Test with pytest 33 | run: make test 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.egg-info 3 | .python-version 4 | .mypy_cache 5 | .pytest_cache 6 | build 7 | .idea 8 | *.DS_Store 9 | .vscode 10 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @appliedzkp/zkevm-reviewers -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | help: ## Display this help screen 2 | @grep -h \ 3 | -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ 4 | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 5 | 6 | install: # Install the Python packages 7 | pip3 install .[test,lint] 8 | 9 | fmt: ## Format the code 10 | black . 11 | 12 | lint: ## Check whether the code is formatted correctly 13 | black . --check 14 | flake8 . 15 | 16 | type: ## Check the typing of the Python code 17 | MYPATH=src mypy . 18 | 19 | test: ## Run tests 20 | pytest --doctest-modules 21 | 22 | 23 | .PHONY: help install fmt lint test 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archiving notice 2 | 3 | This repository is no longer maintained. For details, please refer to the README on [zkevm-circuits](https://github.com/privacy-scaling-explorations/zkevm-circuits/blob/main/README.md) 4 | 5 | # [Deprecated] Zkevm Specifications 6 | 7 | [![Python package](https://github.com/privacy-scaling-explorations/zkevm-specs/actions/workflows/python-package.yml/badge.svg)](https://github.com/privacy-scaling-explorations/zkevm-specs/actions/workflows/python-package.yml) 8 | 9 | The project aims to define a validity snark proof for Ethereum transactions. 10 | 11 | ## The Written Specification 12 | 13 | We recommend the reader to start with [Introduction](./specs/introduction.md) 14 | 15 | ## Python Executable Specification 16 | 17 | We provide the [Beacon Chain](https://github.com/ethereum/eth2.0-specs) style Python executable specification to help implementors figure out the specified behavior. 18 | 19 | Installing dependencies(Python 3.9 is required) 20 | 21 | ``` 22 | make install 23 | ``` 24 | 25 | Run the tests 26 | 27 | ``` 28 | make test 29 | ``` 30 | 31 | ## Implementations 32 | 33 | See [privacy-scaling-explorations/zkevm-circuits](https://github.com/privacy-scaling-explorations/zkevm-circuits) 34 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | 8 | [tool.black] 9 | line-length = 100 10 | target-version = ['py39'] 11 | include = '\.pyi?$' 12 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = zkevm-specs 3 | version = 0.0.1 4 | author = zkevm-team 5 | description = Zkevm specifications 6 | long_description = file: README.md 7 | long_description_content_type = text/markdown 8 | url = https://github.com/privacy-scaling-explorations/zkevm-specs/ 9 | project_urls = 10 | Bug Tracker = https://github.com/privacy-scaling-explorations/zkevm-specs/issues 11 | classifiers = 12 | Programming Language :: Python :: 3 13 | License :: OSI Approved :: MIT License 14 | Operating System :: OS Independent 15 | 16 | [options] 17 | package_dir = 18 | = src 19 | packages = find: 20 | python_requires = >=3.9 21 | install_requires = 22 | pycryptodome==3.14.1 23 | py-ecc==6.0.0 24 | eth-keys==0.4.0 25 | rlp==3.0.0 26 | eth-utils==2.0.0 27 | 28 | [options.packages.find] 29 | where = src 30 | 31 | [options.extras_require] 32 | test = 33 | pytest == 7.4.3 34 | lint = 35 | black == 23.10.1 36 | mypy == 1.6.1 37 | flake8 == 6.1.0 38 | 39 | [flake8] 40 | max-line-length = 100 41 | select = F401 42 | per-file-ignores = 43 | # imported but unused 44 | __init__.py: F401 45 | 46 | [mypy] 47 | exclude = ['build'] 48 | ignore_missing_imports = true 49 | -------------------------------------------------------------------------------- /specs/error_state/ErrorInvalidCreationCode.md: -------------------------------------------------------------------------------- 1 | # ErrorInvalidCreationCode state 2 | 3 | ## Procedure 4 | 5 | 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`). 6 | 7 | ### EVM behavior 8 | 9 | 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. 10 | 11 | 12 | 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. 13 | 14 | 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. 15 | 16 | Overall it looks as the following: 17 | 18 | - Pop EVM word `offset` from the stack. 19 | - Go to `ErrorInvalidCreationCode` state when the current call context is `CREATE` or `CREATE2` and the first byte of the deployed bytecode is `0xEF`. 20 | 21 | ### Constraints 22 | 23 | 1. Current opcode is `RETURN` and `is_create` is True. 24 | 2. The first byte of contract code is `0xEF`. 25 | 3. Current call must be failed. 26 | 4. If it's a root call, it transits to `EndTx`. 27 | 5. if it is not root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. 28 | 29 | ### Lookups 30 | 31 | - Memory lookup. 32 | - Stack reads for `offset`. 33 | - Call context lookups for `is_success` and `rw_counter_end_of_reversion`. 34 | - Restore context lookups for non-root call. 35 | 36 | ## Code 37 | 38 | Please refer to `src/zkevm_specs/evm/execution/error_invalid_creation_code.py`. 39 | -------------------------------------------------------------------------------- /specs/error_state/ErrorInvalidJump.md: -------------------------------------------------------------------------------- 1 | # ErrorInvalidJump state 2 | 3 | ## Procedure 4 | 5 | This type of error only occurs when executing op code is `JUMP` or `JUMPI`. 6 | 7 | ### EVM behavior 8 | 9 | Pop one EVM word `dest` from the stack, then go to `ErrorInvalidJump` state when 10 | one of the following occurs: 11 | 12 | - `dest` is not within code length range 13 | - `dest` is a not `JUMPDEST` code , or data section of PUSH* 14 | 15 | ### Constraints 16 | 17 | 1. `dest` is out of range or not`JUMPDEST` code 18 | 2. Current call must be failed. 19 | 3. If it's a root call, it transits to `EndTx` 20 | 4. If it is not root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. 21 | 22 | ### Lookups 23 | 24 | - Byte code lookup. 25 | - Call Context lookup `CallContextFieldTag.IsSuccess` 26 | 27 | ## Code 28 | 29 | Please refer to `src/zkevm_specs/evm/execution/error_Invalid_jump.py`. 30 | -------------------------------------------------------------------------------- /specs/error_state/ErrorInvalidOpcode.md: -------------------------------------------------------------------------------- 1 | # ErrorInvalidOpcode state 2 | 3 | ## Procedure 4 | 5 | `ErrorInvalidOpcode` may occur when an invalid opcode is encountered in any step during the execution. 6 | 7 | ### EVM behavior 8 | 9 | 1. If it's a root call, it ends the execution. 10 | 2. Otherwise, it restores caller's context and switch to it. 11 | 12 | ### Constraints 13 | 14 | 1. Do a fixed lookup for `FixedTableTag.ResponsibleOpcode`. 15 | 2. Current call must be failed. Do a call context lookup for `CallContextFieldTag.IsSuccess`. 16 | 3. If it's a root call, it transits to `EndTx`. 17 | 4. If it is not root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. 18 | 19 | ## Code 20 | 21 | Please refer to `src/zkevm_specs/evm/execution/error_invalid_opcode.py`. 22 | -------------------------------------------------------------------------------- /specs/error_state/ErrorOutOfGasAccountAccess.md: -------------------------------------------------------------------------------- 1 | # ErrorOutOfGasAccountAccess state 2 | 3 | ## Procedure 4 | 5 | Handle the corresponding out of gas errors for `BALANCE`, `EXTCODESIZE`, and `EXTCODEHASH` opcodes which possibly touches an extra account. 6 | 7 | ### EVM behavior 8 | 9 | For the current `go-ethereum` code, the out of gas error may occur for `constant gas` or `dynamic gas`. The gas cost is calculated as: 10 | 11 | ``` 12 | gas_cost = constant_gas + dynamic_gas 13 | ``` 14 | 15 | The constant gas costs are the same for `BALANCE`, `EXTCODESIZE`, and `EXTCODEHASH`. 16 | 17 | ``` 18 | constant_gas = 0 19 | ``` 20 | 21 | The calculation of the dynamic gas costs of `BALANCE`, `EXTCODESIZE`, and `EXTCODEHASH` is the same. It depends on cold or warm access. 22 | 23 | ``` 24 | if is_warm: 25 | dynamic_gas = 100 26 | else: 27 | dynamic_gas = 2600 28 | ``` 29 | 30 | ### Constraints 31 | 32 | 1. Current opcode is one of `BALANCE`, `EXTCODESIZE`, and `EXTCODEHASH`. 33 | 2. Constrain `gas_left < gas_cost`. 34 | 3. Current call must fail. 35 | 4. If it's a root call, it transits to `EndTx`. 36 | 5. If it isn't a root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. 37 | 6. Constrain `rw_counter_end_of_reversion = rw_counter_end_of_step + reversible_counter`. 38 | 39 | ## Code 40 | 41 | Please refer to `src/zkevm_specs/evm/execution/error_oog_account_access.py`. 42 | -------------------------------------------------------------------------------- /specs/error_state/ErrorOutOfGasCall.md: -------------------------------------------------------------------------------- 1 | # ErrorOutOfGasCall state 2 | 3 | ## Procedure 4 | 5 | Handle the corresponding out of gas errors for `CALL`, `CALLCODE`, `DELEGATECALL` and `STATICCALL` opcodes. 6 | 7 | ### EVM behavior 8 | 9 | For this gadget, the core is to calculate gas required, there are multiple kinds of gas 10 | consumes in above call related opcodes: 11 | 1. memory expansion gas cost 12 | 2. gas cost if new account creates 13 | 3. transfer fee if has value 14 | 4. account access list cost (warm/cold) 15 | 16 | below is the total gas cost calculation which from [Spec of call related opcodes](../opcode/F1CALL_F4DELEGATECALL_FASTATICCALL.md). 17 | ``` 18 | GAS_COST_WARM_ACCESS := 100 19 | GAS_COST_ACCOUNT_COLD_ACCESS := 2600 20 | GAS_COST_CALL_EMPTY_ACCOUNT := 25000 21 | GAS_COST_CALL_WITH_VALUE := 9000 22 | gas_cost = ( 23 | GAS_COST_WARM_ACCESS if is_warm_access else GAS_COST_ACCOUNT_COLD_ACCESS 24 | + has_value * (GAS_COST_CALL_WITH_VALUE + is_account_empty * GAS_COST_CALL_EMPTY_ACCOUNT) 25 | + memory_expansion_gas_cost 26 | ) 27 | ``` 28 | 29 | ### Constraints 30 | 31 | 1. `gas_left < gas_cost`. 32 | 2. Current call must be failed. 33 | 3. If it's a root call, it transits to `EndTx`. 34 | 4. If it is not root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. 35 | 36 | ## Code 37 | 38 | Please refer to `src/zkevm_specs/evm/execution/error_oog_call.py`. 39 | -------------------------------------------------------------------------------- /specs/error_state/ErrorOutOfGasConstant.md: -------------------------------------------------------------------------------- 1 | # ErrorOutOfGasConstant state 2 | 3 | ## Procedure 4 | 5 | For opcodes which have non-zero constant gas cost, this type of error occurs when gas left of 6 | current step is less than the required constant gas. 7 | 8 | ### EVM behavior 9 | 10 | 1. If it's a root call, it ends the execution. 11 | 2. Otherwise, it restores caller's context and switch to it. 12 | 13 | ### Constraints 14 | 15 | 1. `remaining_gas_left < step_constant_gas_required`. 16 | 2. Current call must be failed. 17 | 3. If it's a root call, it transits to `EndTx`, and the call's `IsPersistent` must be false 18 | 4. If it is not root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. 19 | 20 | ### Lookups 21 | 22 | - 1 Call Context lookup `CallContextFieldTag.IsSuccess`. 23 | - 1 Call Context lookup `CallContextFieldTag.IsPersistent` (Only if Current instruction is root.). 24 | 25 | ## Code 26 | 27 | Please refer to `src/zkevm_specs/evm/execution/oog_constant.py`. 28 | -------------------------------------------------------------------------------- /specs/error_state/ErrorOutOfGasCreate.md: -------------------------------------------------------------------------------- 1 | # ErrorOutOfGasCreate state 2 | 3 | ## Procedure 4 | 5 | Handle the corresponding out-of-gas error and the max size of initial bytecode error for `CREATE` and `CREATE2` opcodes. 6 | 7 | ### EVM behavior 8 | 9 | The gas cost of `CREATE`/`CREATE2` is: 10 | ``` 11 | GAS_COST_CREATE := 32000 12 | KECCAK_WORD_GAS_COST := 6 13 | GAS_COST_INITCODE_WORD := 2 14 | 15 | keccak_gas_cost = KECCAK_WORD_GAS_COST * size if op_id == CREATE2 else 0 16 | initcode_gas_cost = GAS_COST_INITCODE_WORD * size 17 | gas_cost = ( 18 | GAS_COST_CREATE 19 | + memory_expansion_gas_cost 20 | + keccak_gas_cost 21 | + initcode_gas_cost 22 | ) 23 | 24 | ``` 25 | 26 | The memory expansion gas cost is calculated as: 27 | 28 | ``` 29 | next_memory_word_size = (destination_offset + copy_byte_size + 31) // 32 30 | # `current_memory_word_size` is fetched from the execution step. 31 | if next_memory_word_size <= current_memory_word_size: 32 | memory_expansion_gas_cost = 0 33 | else: 34 | memory_expansion_gas_cost = ( 35 | 3 * (next_memory_word_size - current_memory_word_size) 36 | + next_memory_word_size * next_memory_word_size // 512 37 | - current_memory_word_size * current_memory_word_size // 512 38 | ) 39 | ``` 40 | 41 | The limit of the max size of initial bytecode is `49,152` which is `2 * MAX_CODE_SIZE` 42 | 43 | ### Constraints 44 | 45 | 1. Current opcode is `CREATE` or `CREATE2`. 46 | 2. At least one of below conditions is met: 47 | - `gas_left < gas_cost`. 48 | - `len(init_code) > MAX_INIT_CODE_SIZE` 49 | 3. Common error constraints: 50 | - Current call fails. 51 | - Constrain `rw_counter_end_of_reversion = rw_counter_end_of_step + reversible_counter`. 52 | - If it's a root call, it transits to `EndTx`. 53 | - If it is not root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. 54 | 55 | ## Code 56 | 57 | Please refer to `src/zkevm_specs/evm/execution/error_oog_create.py`. 58 | -------------------------------------------------------------------------------- /specs/error_state/ErrorOutOfGasDynamicMemoryExpansion.md: -------------------------------------------------------------------------------- 1 | # ErrorOOGDynamicMemoryExpansion state 2 | 3 | ## Procedure 4 | 5 | Handle the corresponding out of gas errors for `RETURN` and `REVERT` opcodes due to memory expansion. 6 | 7 | ### EVM behavior 8 | 9 | For the current `go-ethereum` code, the out of gas error may occur for `constant gas` or `dynamic gas`. The gas cost is calculated as: 10 | 11 | ``` 12 | gas_cost = constant_gas + dynamic_gas 13 | 14 | ``` 15 | Where `constant_gas` = 0 and `dynamic_gas` = memory_expansion_cost. 16 | 17 | The memory expansion gas cost is calculated as: 18 | 19 | ``` 20 | next_memory_word_size = (destination_offset + copy_byte_size + 31) // 32 21 | # `current_memory_word_size` is fetched from the execution step. 22 | if next_memory_word_size <= current_memory_word_size: 23 | memory_expansion_gas_cost = 0 24 | else: 25 | memory_expansion_gas_cost = ( 26 | 3 * (next_memory_word_size - current_memory_word_size) 27 | + next_memory_word_size * next_memory_word_size // 512 28 | - current_memory_word_size * current_memory_word_size // 512 29 | ) 30 | ``` 31 | 32 | ### Constraints 33 | 34 | 1. Current opcode is one of `RETURN` and `REVERT`. 35 | 2. Constrain `gas_left < gas_cost`. 36 | 3. Current call must fail. 37 | 4. If it's a root call, it transits to `EndTx`. 38 | 5. If it isn't a root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. 39 | 6. Constrain `rw_counter_end_of_reversion = rw_counter_end_of_step + reversible_counter`. 40 | 41 | ### Lookups 42 | 43 | 1. `2` stack lookups for `RETURN` and `REVERT`. 44 | 2. `2` call context lookups for `is_success` and `rw_counter_end_of_reversion`. 45 | 46 | And restore context lookups for non-root call. 47 | 48 | ## Code 49 | 50 | Please refer to `src/zkevm_specs/evm/execution/error_oog_dynamic_memory_expansion.py`. 51 | -------------------------------------------------------------------------------- /specs/error_state/ErrorOutOfGasEXP.md: -------------------------------------------------------------------------------- 1 | # ErrorOutOfGasEXP state for EXP OOG error 2 | 3 | ## Procedure 4 | 5 | Handle the corresponding out of gas error for `EXP`. 6 | 7 | ### EVM behavior 8 | 9 | The EXP gas cost is calculated as: 10 | 11 | ``` 12 | # `exponent.bits()` returns the least number of bits needed to represent exponent. 13 | exponent_byte_size = (exponent.bits() + 7) // 8 14 | 15 | gas_cost = exponent_byte_size * 50 + 10 16 | ``` 17 | 18 | ### Constraints 19 | 20 | 1. Constrain `gas_left < gas_cost`. 21 | 2. Current call must fail. 22 | 3. If it's a root call, it transits to `EndTx`. 23 | 4. If it isn't a root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. 24 | 5. Constrain `rw_counter_end_of_reversion = rw_counter_end_of_step + reversible_counter`. 25 | 26 | ### Lookups 27 | 28 | 4 basic bus-mapping lookups + restore context lookups (for non-root call): 29 | 30 | 1. 2 stack read for `base` and `exponent`. 31 | 2. 2 call context lookups for `is_success` and `rw_counter_end_of_reversion`. 32 | 3. Restore context lookups for non-root call. 33 | -------------------------------------------------------------------------------- /specs/error_state/ErrorOutOfGasLog.md: -------------------------------------------------------------------------------- 1 | # ErrorOutOfGasLog state 2 | 3 | ## Procedure 4 | ### EVM behavior 5 | use `LOGN` denotes [log0, .., log4]. `N` stands for `topic_count` of circuit, that is 6 | `N = topic_count = opcode - Opcode.LOG0`. 7 | For this gadget, the core is to calculate gas required as following: 8 | 9 | ` let gas_cost = GAS_COST_LOG 10 | + GAS_COST_LOG * (opcode - Opcode.LOG0) 11 | + GAS_COST_LOGDATA * msize 12 | + memory_expansion_gas` 13 | 14 | ### Constraints 15 | 1. stack reads `mstart`, `msize` for gas calculation 16 | 2. `gas_left < gas_cost`. 17 | 3. error common constraint: 18 | - current call must be failed. 19 | - rw_counter_end_of_reversion constraint 20 | - If it's a root call, it transits to `EndTx`. 21 | - if it is not root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. 22 | 23 | ## Code 24 | 25 | Please refer to `src/zkevm_specs/evm/execution/error_oog_log.py`. -------------------------------------------------------------------------------- /specs/error_state/ErrorOutOfGasSHA3.md: -------------------------------------------------------------------------------- 1 | # ErrorOutOfGasSHA3 state 2 | 3 | ## Procedure 4 | 5 | Handle the corresponding out of gas errors for `SHA3`. 6 | 7 | ### EVM behavior 8 | 9 | The `SHA3` gas cost is calculated as: 10 | 11 | ``` 12 | gas_cost = static_gas + dynamic_gas 13 | 14 | static_gas = 30 15 | 16 | minimum_word_size = (size + 31) // 32 17 | dynamic_gas = 6 * minimum_word_size + memory_expansion_cost 18 | ``` 19 | 20 | The memory expansion gas cost is calculated as: 21 | 22 | ``` 23 | next_memory_word_size = (destination_offset + copy_byte_size + 31) // 32 24 | 25 | # `current_memory_word_size` is fetched from the execution step. 26 | if next_memory_word_size <= current_memory_word_size: 27 | memory_expansion_gas_cost = 0 28 | else: 29 | memory_expansion_gas_cost = ( 30 | 3 * (next_memory_word_size - current_memory_word_size) 31 | + next_memory_word_size * next_memory_word_size // 512 32 | - current_memory_word_size * current_memory_word_size // 512 33 | ) 34 | ``` 35 | 36 | ### Constraints 37 | 38 | 1. Stack reads `offset` and `size` for gas calculation 39 | 2. Constrain `gas_left < gas_cost`. 40 | 3. Current call must fail. 41 | 4. If it's a root call, it transits to `EndTx`. 42 | 5. If it isn't a root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. 43 | 6. Constrain `rw_counter_end_of_reversion = rw_counter_end_of_step + reversible_counter`. 44 | 45 | ## Code 46 | 47 | Please refer to `src/zkevm_specs/evm/execution/error_oog_sha3.py`. 48 | -------------------------------------------------------------------------------- /specs/error_state/ErrorOutOfGasSloadSstore.md: -------------------------------------------------------------------------------- 1 | # ErrorOutOfGasSloadSstore state for both SLOAD and SSTORE OOG errors 2 | 3 | ## Procedure 4 | 5 | Handle the corresponding out of gas errors for both `SLOAD` and `SSTORE` opcodes. 6 | 7 | ### EVM behavior 8 | 9 | For the current `go-ethereum` code, the out of gas error may occur for `constant gas` or `dynamic gas`. 10 | 11 | #### SLOAD gas cost 12 | 13 | For this gadget, SLOAD gas cost is calculated with EIP-2929 as: 14 | ``` 15 | if is_warm_access: 16 | gas_cost = GAS_COST_WARM_ACCESS # 100 17 | else: 18 | gas_cost = COLD_SLOAD_COST # 2100 19 | ``` 20 | 21 | #### SSTORE gas cost 22 | 23 | For this gadget, SSTORE gas cost is calculated with EIP-3529 as: 24 | ``` 25 | if value == value_prev: 26 | gas_cost = SLOAD_GAS # 100 27 | else: 28 | if value_prev == original_value: 29 | if original_value == 0: 30 | gas_cost = SSTORE_SET_GAS # 20000 31 | else: 32 | gas_cost = SSTORE_RESET_GAS # 2900 33 | else: 34 | gas_cost = SLOAD_GAS # 100 35 | 36 | if not is_warm_access: 37 | gas_cost += COLD_SLOAD_COST # 2100 38 | ``` 39 | 40 | #### SSTORE reentrancy sentry 41 | 42 | For SSTORE, the OOG error occurs when the gas left is less than or equal to `SSTORE_SENTRY` (2300). 43 | 44 | ### Constraints 45 | 46 | 1. For SLOAD, constrain `gas_left < gas_cost`. 47 | 2. For SSTORE, constrain `gas_left < gas_cost` or `gas_left <= SSTORE_SENTRY`. 48 | 3. Only for SSTORE, constrain `is_static == false`. 49 | 4. Current call must fail. 50 | 5. If it's a root call, it transits to `EndTx`. 51 | 6. If it isn't a root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. 52 | 7. Constrain `rw_counter_end_of_reversion = rw_counter_end_of_step + reversible_counter`. 53 | 54 | ### Lookups 55 | 56 | 7 bus-mapping lookups for SLOAD and 9 for SSTORE: 57 | 58 | 1. 5 call context lookups for `tx_id`, `is_static`, `callee_address`, `is_success` and `rw_counter_end_of_reversion`. 59 | 2. 1 stack read for `storage_key`. 60 | 3. 1 account storage access list read. 61 | 4. Only for SSTORE, 1 stack read for `value_to_store`. 62 | 5. Only for SSTORE, 1 account storage read. 63 | 64 | ## Code 65 | 66 | Please refer to `src/zkevm_specs/evm/execution/error_oog_sload_sstore.py`. -------------------------------------------------------------------------------- /specs/error_state/ErrorOutOfGasStaticMemoryExpansion.md: -------------------------------------------------------------------------------- 1 | # ErrorOOGStaticMemoryExpansion state 2 | 3 | ## Procedure 4 | 5 | Handle the corresponding out of gas errors for `MLOAD`, `MSTORE` and `MSTORE8` opcodes. 6 | 7 | ### EVM behavior 8 | 9 | For the current `go-ethereum` code, the out of gas error may occur for `constant gas` or `dynamic gas`. The gas cost is calculated as: 10 | 11 | ``` 12 | gas_cost = constant_gas + dynamic_gas 13 | ``` 14 | 15 | The constant gas is same for `MLOAD`, `MSTORE` and `MSTORE8`. 16 | 17 | ``` 18 | constant_gas = 3 19 | ``` 20 | 21 | They are also same for dynamic gas calculation. The dynamic gas is calculated as: 22 | 23 | ``` 24 | dynamic_gas = memory_expansion_cost 25 | ``` 26 | 27 | The memory expansion gas cost is calculated as: 28 | 29 | ``` 30 | next_memory_word_size = (destination_offset + copy_byte_size + 31) // 32 31 | # `current_memory_word_size` is fetched from the execution step. 32 | if next_memory_word_size <= current_memory_word_size: 33 | memory_expansion_gas_cost = 0 34 | else: 35 | memory_expansion_gas_cost = ( 36 | 3 * (next_memory_word_size - current_memory_word_size) 37 | + next_memory_word_size * next_memory_word_size // 512 38 | - current_memory_word_size * current_memory_word_size // 512 39 | ) 40 | ``` 41 | 42 | ### Constraints 43 | 44 | 1. Current opcode is one of `MLOAD`, `MSTORE` and `MSTORE8`. 45 | 2. Constrain `gas_left < gas_cost`. 46 | 3. Current call must fail. 47 | 4. If it's a root call, it transits to `EndTx`. 48 | 5. If it isn't a root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. 49 | 6. Constrain `rw_counter_end_of_reversion = rw_counter_end_of_step + reversible_counter`. 50 | 51 | ### Lookups 52 | 53 | 1. `1` stack pop. 54 | 2. `2` call context lookups for `is_success` and `rw_counter_end_of_reversion`. 55 | 56 | And restore context lookups for non-root call. 57 | 58 | ## Code 59 | Please refer to `src/zkevm_specs/evm/execution/error_oog_static_memory_expansion.py`. -------------------------------------------------------------------------------- /specs/error_state/ErrorOutOfGasTloadTstore.md: -------------------------------------------------------------------------------- 1 | # ErrorOutOfGasTloadTstore state for both TLOAD and TSTORE OOG errors 2 | 3 | ## Procedure 4 | 5 | Handle the corresponding out of gas errors for both `TLOAD` and `TSTORE` opcodes. 6 | 7 | ### EVM behavior 8 | 9 | The out of gas error may occur for `constant gas`. 10 | 11 | #### TLOAD gas cost 12 | 13 | For this gadget, TLOAD gas cost in EIP-1153 is specified as the cost of hot SLOAD, currently `100`. 14 | 15 | #### TSTORE gas cost 16 | 17 | For this gadget, TSTORE gas cost in EIP-1153 is specified as the cost of SSTORE on an already SSTOREd slot, currently `100`. 18 | 19 | ### Constraints 20 | 21 | 1. For TLOAD, constrain `gas_left < gas_cost`. 22 | 2. For TSTORE, constrain `gas_left < gas_cost`. 23 | 3. Only for TSTORE, constrain `is_static == false`. 24 | 4. Current call must fail. 25 | 5. If it's a root call, it transits to `EndTx`. 26 | 6. If it isn't a root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. 27 | 7. Constrain `rw_counter_end_of_reversion = rw_counter_end_of_step + reversible_counter`. 28 | 29 | ### Lookups 30 | 31 | 7 bus-mapping lookups for TLOAD and 8 for TSTORE: 32 | 33 | 1. 5 call context lookups for `tx_id`, `is_static`, `callee_address`, `is_success` and `rw_counter_end_of_reversion`. 34 | 2. 1 stack read for `transient_storage_key`. 35 | 3. Only for TSTORE, 1 stack read for `value_to_store`. 36 | 4. Only for TSTORE, 1 account transient storage read. 37 | 38 | ## Code 39 | 40 | > TODO -------------------------------------------------------------------------------- /specs/error_state/ErrorReturnDataOutOfBound.md: -------------------------------------------------------------------------------- 1 | # ErrorReturnDataOutOfBound state 2 | 3 | ## Procedure 4 | 5 | This type of error only occurs when executing op code is `RETURNDATACOPY`. 6 | 7 | ### EVM behavior 8 | 9 | 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: 10 | 1. `dataOffset`is u64 overflow, 11 | 2. `dataOffset` + `length` is u64 overflow, 12 | 3. `dataOffset` + `length` is larger than the length of last callee's return data. 13 | 14 | ### Constraints 15 | 1. current opcode is `RETURNDATACOPY` 16 | 2. at least one of below conditions is met: 17 | - `dataOffset`is u64 overflow, 18 | - `dataOffset` + `length` is u64 overflow 19 | - `dataOffset` + `length` is larger than the length of last callee's return data 20 | 3. common error constraints: 21 | - current call fails. 22 | - constrain `rw_counter_end_of_reversion = rw_counter_end_of_step + reversible_counter`. 23 | - If it's a root call, it transits to `EndTx`. 24 | - if it is not root call, it restores caller's context by reading to `rw_table`, then does step state transition to it. 25 | 26 | ## Code 27 | 28 | Please refer to src/zkevm_specs/evm_circuit/execution/error_return_data_out_of_bound.py -------------------------------------------------------------------------------- /specs/error_state/ErrorStackOverflow_Underflow.md: -------------------------------------------------------------------------------- 1 | # ErrorStackOverflow & underflow state 2 | 3 | ## Procedure 4 | ### EVM behavior 5 | 6 | stack overflow and underflow error can happen within any step which involves stack operation. 7 | each op code have fixed `min_stack_pointer` & `max_stack_pointer`. if current stack pointer < `min_stack_pointer`, 8 | overflow error happens, if current stack pointer > `max_stack_pointer`, underflow error happens. 9 | 10 | when any one type error occurs: 11 | 1. If it's a root call, it ends the execution. 12 | 2. Otherwise, it restores caller's context and switch to it. 13 | 14 | ### Circuit behavior 15 | 16 | 1. Do a bytecode lookup to get `opcode`. 17 | 2. Do a fixed lookup for `FixedTableTag.ResponsibleOpcode` with `opcode` and auxiliary `stack_pointer` to make sure it's indeed in pre-built pairs of `ErrorStack`. 18 | 19 | ## Code 20 | 21 | Please refer to `src/zkevm_specs/evm/execution/error_stack.py`. 22 | -------------------------------------------------------------------------------- /specs/error_state/ErrorWriteProtection.md: -------------------------------------------------------------------------------- 1 | # ErrorWriteProtection state 2 | 3 | ## Procedure 4 | State modifying opcodes, which include `[SSTORE, CREATE, CREATE2, CALL, SELFDESTRUCT, LOG0, LOG1, LOG2, LOG3, LOG4]`, throw write protection errors when executed in a read-only call context (static call). See [EIP-214](https://eips.ethereum.org/EIPS/eip-214). 5 | 6 | ### EVM behavior 7 | A write protection error is thrown if all the following conditions are met: 8 | - State modifying opcodes run in a read-only context. 9 | - If the opcode is `CALL` and the `value` argument is non-zero. 10 | 11 | ### Constraints 12 | 1. constrain this error happens in one of op codes `[SSTORE, CREATE, CREATE2, CALL, SELFDESTRUCT, LOG0, LOG1, LOG2, LOG3, LOG4]` 13 | 2. current call context must be readonly (this call must be a internal call since it requires at least one `staticcall` ahead). 14 | 3. for `CALL` op code, do stack read & check `value` is not zero. 15 | 4. common error steps constraints: 16 | - current call must be failed. 17 | - rw_counter_end_of_reversion constraint 18 | - it restores caller's context by reading to `rw_table`, then does step state transition to it. 19 | 20 | ## Code 21 | Please refer to `src/zkevm_specs/evm/execution/error_write_protection.py`. -------------------------------------------------------------------------------- /specs/introduction.md: -------------------------------------------------------------------------------- 1 | # ZKEVM Introduction 2 | 3 | Currently, every Ethereum node must validate every transaction in the Ethereum virtual machine. This means that every transaction adds work everyone must do to verify Ethereum's history. Worse still, each transaction needs to be verified by every new node. This makes the work required for each new node to sync to the network grow constantly. We want to build a proof of validity for the Ethereum blocks to avoid this. There are two goals: 4 | 1. Make a zkrollup that supports smart contracts 5 | 2. Create a proof of validity for every Ethereum block 6 | 7 | This means making a proof of validity for EVM + state reads / writes + signatures. 8 | 9 | To simplify we separate our proofs into two components: 10 | 1. **[State proof](./state-proof.md)**: State/memory/stack ops have been performed correctly. This does not check if the correct location has been read/written. We allow our prover to pick any location here and in the EVM proof confirm it is correct. 11 | 2. **[EVM proof](./evm-proof.md)**: This checks that the correct opcode is called at the correct time. It checks the validity of these opcodes and confirms that each of these opcodes and the state proof both performed the correct operations. 12 | 13 | Only after verifying both proofs are valid, we have confidence that Ethereum block is executed correctly. 14 | -------------------------------------------------------------------------------- /specs/lookup.md: -------------------------------------------------------------------------------- 1 | # Plookup Table 2 | 3 | We use the [halo2 lookup table](https://zcash.github.io/halo2/design/proving-system/lookup.html) as a primitive to check if some advised values are a row of a table. 4 | 5 | Example: 6 | 7 | This table has columns "a", "b", and "c". Where "a" and "b" are possible combinations of 0, 1, 2. "c" is the logic AND operations on "a" and "b". 8 | 9 | | a | b | c | 10 | | --- | --- | --- | 11 | | 0 | 0 | 0 | 12 | | 0 | 1 | 0 | 13 | | 0 | 2 | 0 | 14 | | 1 | 0 | 0 | 15 | | 1 | 1 | 1 | 16 | | 1 | 2 | 0 | 17 | | 2 | 0 | 0 | 18 | | 2 | 1 | 0 | 19 | | 2 | 2 | 2 | 20 | 21 | We can use the table to check the AND operation constraints on some variable x and y in the circuit by proving "x", "y", and "x & y" are cells of a row in the table. 22 | 23 | ## Fixed Table 24 | 25 | Rows of a fixed table are determined "before" proving time. 26 | 27 | The AND operation table is an example. 28 | 29 | ## Variable Table 30 | 31 | Rows of a variable table are determined "at" proving time. 32 | 33 | It allows prover to create their own table. An example would be the prover can witness a key-value mapping as a variable table. Note that we need extra check to guarantee the uniqueness of the mapping key. 34 | -------------------------------------------------------------------------------- /specs/opcode/00STOP.md: -------------------------------------------------------------------------------- 1 | # STOP opcode 2 | 3 | ## Procedure 4 | 5 | ### EVM behavior 6 | 7 | The `STOP` opcode terminates the call, then: 8 | 9 | 1. If it's a root call, it ends the execution. 10 | 2. Otherwise, it restores caller's context and switch to it. 11 | 12 | ### Circuit behavior 13 | 14 | The circuit first checks the `result` in call context is indeed success. Then: 15 | 16 | 1. If it's a root call, it transits to `EndTx`. 17 | 2. Otherwise, it restores caller's context by reading to `rw_table`, then does step state transition to it. 18 | 19 | ## Code 20 | 21 | Please refer to `src/zkevm_specs/evm/execution/stop.py`. 22 | -------------------------------------------------------------------------------- /specs/opcode/01ADD_03SUB.md: -------------------------------------------------------------------------------- 1 | # ADD and SUB opcodes 2 | 3 | ## Procedure 4 | 5 | ### EVM behavior 6 | 7 | Pop two EVM words `a` and `b` from the stack. Compute 8 | 9 | - if it's `ADD` opcode, compute `c = (a + b) % 2**256`, and push `c` back to the stack 10 | - if it's `SUB` opcode, compute `c = (a - b) % 2**256`, and push `c` back to the stack 11 | 12 | ### Circuit behavior 13 | 14 | The AddGadget takes argument of `a: [u8;32]`, `x: [u8;32]`, `y: [u8;32]`, `is_sub: bool`. 15 | 16 | It always computes `y = (a + x) % 2**256`, 17 | 18 | - when it's ADD (`is_sub == False`), we annotate stack as \[a, x, ...\] and \[y, ...\], 19 | - when it's SUB (`is_sub == True`), we annotate stack as \[a, y, ...\] and \[x, ...\]. 20 | 21 | ## Constraints 22 | 23 | 1. opcodeId checks 24 | 1. opId === OpcodeId(0x01) for `ADD` 25 | 2. opId === OpcodeId(0x03) for `SUB` 26 | 2. state transition: 27 | - gc + 3 28 | - stack_pointer + 1 29 | - pc + 1 30 | - gas + 3 31 | 3. Lookups: 3 busmapping lookups 32 | - `a` is at the top of the stack 33 | - `b` is at the second position of the stack 34 | - `c`, the result, is at the new top of the stack 35 | 36 | ## Exceptions 37 | 38 | 1. stack underflow: `1023 <= stack_pointer <= 1024` 39 | 2. out of gas: remaining gas is not enough 40 | 41 | ## Code 42 | 43 | See `src/zkevm_specs/opcode/add_sub.py` 44 | -------------------------------------------------------------------------------- /specs/opcode/08ADDMOD.md: -------------------------------------------------------------------------------- 1 | # ADDMOD opcode 2 | 3 | ## Procedure 4 | 5 | ### EVM behavior 6 | 7 | Pop three EVM words `a`, `b` and `N` from the stack. 8 | 9 | If n is zero 10 | push 0 into the stack 11 | else 12 | compute `r = (a + b) mod N`, and push `r` into to the stack 13 | 14 | ### Circuit behavior 15 | 16 | The AddModGadget takes argument of `a: [u8;32]`, `b: [u8;32]`, `N: [u8;32]` and keeps a cell for storing `d :[u8;32]` 17 | 18 | - Witness `(a_reduced,k) ← (a % N, a // N)` 19 | - Witness `(r,d) ← ( (a_reduced + b) % N, (a_reduced + b ) // N)` 20 | - Check `a == a_reduced + k * N` (a_reduced + k * N should not overflow 256 bits) 21 | - Check `a_reduced + b == d * N + r` in 512bit space (1) 22 | - Check `r `r==0`, if n is zero 26 | 27 | - witness `r ← (a_reduced + b) % 2^256` to satisfy &1 28 | - deactivate &2 29 | 30 | ## Constraints 31 | 32 | 1. opcodeId checks 33 | opId === OpcodeId(0x08) 34 | 2. state transition: 35 | - gc + 4 36 | - stack_pointer + 2 37 | - pc + 1 38 | - gas + 8 39 | 3. Lookups: 4 busmapping lookups 40 | - `a` is at the top of the stack 41 | - `b` is at the second position of the stack 42 | - `n` is at the third position of the stack 43 | - `r`, the result, is at the new top of the stack 44 | 45 | ## Exceptions 46 | 47 | 1. stack underflow: `1022 <= stack_pointer <= 1024` 48 | 2. out of gas: remaining gas is not enough 49 | 50 | ## Code 51 | 52 | See `src/zkevm_specs/opcode/addmod.py` 53 | -------------------------------------------------------------------------------- /specs/opcode/09MULMOD.md: -------------------------------------------------------------------------------- 1 | # MULMOD opcode 2 | 3 | ## Procedure 4 | 5 | ### EVM behavior 6 | 7 | 8 | Pop 3 EVM words `a`, `b` and `N` from the stack. 9 | 10 | If `N` is 0: 11 | push 0 into the stack. 12 | else: 13 | compute `r= (a * b) mod N` and push `r` into the stack. 14 | 15 | *Note* 16 | All intermediate calculations of this operation are not subject to the 2^256 modulo. 17 | 18 | ### Circuit behavior 19 | 20 | The MulModGadget takes arguments: 21 | - `a: [u8;32]` 22 | - `b: [u8;32]`, 23 | - `r: [u8;32]`, 24 | - `N: [u8;32]`, 25 | and keeps 5 words for storing: 26 | - `a_reduced: [u8;32]` , 27 | - `k: [u8;32]`, 28 | - `e: [u8;32]`, 29 | - `d: [u8;32]` 30 | - `zero: [u8;32]`. 31 | 32 | 33 | Witness `a_reduced ← a` if `n!=0` else `a_reduced ← 0` 34 | Witness `(e, d) ← ( (a_reduced * b) % 2^256, (a_reduced * b ) // 2^256)` 35 | Witness `(r, k) ← ( (a_reduced * b) % N, (a_reduced * b ) // N)` 36 | 37 | 1. Check `a_reduced = a mod N`. 38 | which uses `ModGadget` that in turn checks: 39 | - Check the equality ` j * N + a_reduced == a ` 40 | - Check (`a_reduced = 0` and `N == 0`) or `a_reduced < N` 41 | 42 | 2. Check `r = a * b mod N` 43 | which uses 2 `MulAddWords512Gadget` to check: 44 | ` a_reduced * b = k * N + r` in 2 steps 45 | - `a_reduced * b + zero == d * 2^256 + e` 46 | - `k * N + r == d * 2^256 + e` 47 | 48 | 1 `IsZeroGadget` and 1 `LtWordsGadget` that check: 49 | `(r == 0 and N == 0)` or `(r < N)` 50 | 51 | 52 | #### Note 53 | 54 | The first step on the computation, reducing `a` mod `N`, is taken in order 55 | to prevent overflow in the factor `k`, which could happen for high values 56 | of `a` and `b` and low `N`. This steps ensures: 57 | 58 | $$ 59 | k \leq \frac{(a \text{ mod } n) \cdot b }{n } \leq \frac{ (n-1) \cdot b}{n} < b \leq MAXU256 60 | $$ 61 | 62 | ## Constraints 63 | 64 | 1. opcodeID checks 65 | opId == OpcodeId(0x09) 66 | 2. state transition: 67 | - gc + 4 68 | - stack_pointer +2 69 | - pc + 1 70 | - gas + 8 71 | 3. Lookups: 4 busmapping lookups 72 | - `a` is on top of the stack. 73 | - `b` is in the second position of the stack. 74 | - `N` is in the third position of the stack. 75 | - `r`, the result is on top of the new stack. 76 | 77 | 78 | ## Exceptions 79 | 80 | 1. stack underflow: `1022 <= stack_pointer <= 1024`. 81 | 2. out of gas: Remaining gas is not enough. 82 | 83 | See `src/zkevm_specs/opcode/mulmod.py` 84 | -------------------------------------------------------------------------------- /specs/opcode/0AEXP.md: -------------------------------------------------------------------------------- 1 | # EXP opcode 2 | 3 | ## EVM Behaviour 4 | 5 | The `EXP` opcode performs an exponential operation. It reads the integer base `base` and integer exponent `exponent` from the top of the stack and pushes the exponentiation result, i.e. `base ^ exponent (mod 2^256)` to the top of the stack. 6 | 7 | ## Constraints 8 | 9 | 1. opId == 0x0A 10 | 2. State Transition: 11 | - rw_counter += 3 12 | - stack_pointer += 1 13 | - pc += 1 14 | - gas -= static_gas + dynamic_gas, where: 15 | - `static_gas = 10` 16 | - `dynamic_gas = 50 * byte_size(exponent)` 17 | 3. Lookups: 18 | - `base` is at the top of the stack (Read from RW Table) 19 | - `exponent` is at the second position of the stack (Read from RW Table) 20 | - `exponentiation` is at the top of the stack (Write to RW Table) 21 | - if `exponent == 2`: 22 | - Do a lookup to exponentiation table for 23 | ``` 24 | ( 25 | is_step=1, 26 | is_last=1, 27 | base_limbs, 28 | exponent_lo_hi, 29 | exponentiation_lo_hi, 30 | ) 31 | ``` 32 | - if `exponent > 2`: 33 | - Do a lookup to exponentiation table for: 34 | ``` 35 | ( 36 | is_step=1, 37 | is_last=0, 38 | base_limbs, 39 | exponent_lo_hi, 40 | exponentiation_lo_hi, 41 | ) 42 | ``` 43 | - Do a lookup to exponentiation table for: 44 | ``` 45 | ( 46 | is_step=1, 47 | is_last=1, 48 | base_limbs, 49 | exponent_lo_hi=[2, 0], 50 | base_sq_lo_hi, 51 | ) 52 | ``` 53 | where `base_sq_lo_hi` are the 128-bit low-high parts of `base * base (mod 2^256)` 54 | 55 | ## Exceptions 56 | 57 | 1. Stack underflow: The stack is empty or contains only one element. 58 | 2. Out of gas: The gas available is not sufficient to cover the cost of the exponentiation operation. 59 | 60 | ## Code 61 | 62 | Please refer to [EXP gadget](../../src/zkevm_specs/evm/execution/exp.py) 63 | -------------------------------------------------------------------------------- /specs/opcode/0bSIGNEXTEND.md: -------------------------------------------------------------------------------- 1 | # SIGNEXTEND opcode 2 | 3 | ## Procedure 4 | 5 | The `SIGNEXTEND` opcode extends the length of a signed integer. This is done by popping two values, first the `index` and then the `value`, from the stack. The byte at position `index`, with the position starting at `0` at the LSB in `value`, will be used to read the sign from (with the sign being the most significant bit). All bytes following the selected byte (`> index`) will be replaced by a value depending on this sign: 6 | 7 | - Sign is `0`: all bytes `> index` will be replaced with `0x00` 8 | - Sign is `1`: all bytes `> index` will be replaced with `0xFF` 9 | 10 | `result` is then pushed on the stack. If `index >= 31` no bytes will change and `result == value`. 11 | 12 | ## Constraints 13 | 14 | 1. opId = OpcodeId(0x0b) 15 | 2. state transition: 16 | - gc + 3 (2 stack reads + 1 stack write) 17 | - stack_pointer + 1 18 | - pc + 1 19 | - gas + 5 20 | 3. lookups: 1 fixed lookup + 3 busmapping lookups 21 | - The sign from the selected byte 22 | - `index` is at the top of the stack 23 | - `value` is at the new top of the stack 24 | - `result` is at the new top of the stack 25 | 26 | ## Exceptions 27 | 28 | 1. stack underflow: the stack is empty or only contains a single value 29 | 2. out of gas: the remaining gas is not enough 30 | 31 | ## Code 32 | 33 | Please refer to `src/zkevm_specs/opcode/signextend.py`. 34 | -------------------------------------------------------------------------------- /specs/opcode/10LT_11GT_14EQ.md: -------------------------------------------------------------------------------- 1 | # LT & GT & EQ opcodes 2 | 3 | ## Procedure 4 | 5 | The `LT`, `GT` and `EQ` opcodes compare the top two values on the stack, and push the 6 | result (0 or 1) back to the stack. 7 | 8 | We provide extra witness `c8s = b8s - a8s` to check if `a8s < b8s`. 9 | The circuit needs to check two constraints for `LT` opcode. 10 | 11 | 1. When result is 1, `c8s` cannot be equal to 0, or equivalently the sum of all limbs in `c8s` cannot be equal to 0. 12 | 13 | 2. we check if `a8s + c8s` equals to `b8s` or `b8s + 2^256`. 14 | For the former case, the result is 1 as `a8s < b8s`; while for the latter case, the result is 0 as the sum of `a8s` and `c8s` overflows when `a8s > b8s`. 15 | 16 | For `GT` opcode, we can simply swap the order of `a8s` and `b8s` and re-use the `LT` circuit. 17 | 18 | ## Constraints 19 | 20 | 1. opId = OpcodeId(0x10 | 0x11 | 0x14) 21 | 2. Constraints: 22 | - `sum(c8s) != 0` when `result = 1` 23 | - `a[15:0] + c[15:0] = b[15:0] + carry * 2^128` 24 | - `a[31:16] + c[31:16] + carry = b[31:16] + (1 - result[0]) * (sum(c8s) == 0) * 2^128` 25 | 3. state transition: 26 | - gc + 3 (2 stack reads + 1 stack write) 27 | - stack_pointer + 1 28 | - pc + 1 29 | - gas + 3 30 | 4. lookups: 3 busmapping lookups 31 | - `a` is at the top of the stack 32 | - `b` is at the new top of the stack 33 | - `result` is at the new top of the stack 34 | 35 | ## Exceptions 36 | 37 | 1. stack underflow: the stack is empty or only contains one element 38 | 2. out of gas: the remaining gas is not enough 39 | 40 | ## Code 41 | 42 | Please refer to `src/zkevm_specs/opcode/lt_gt.py`. 43 | -------------------------------------------------------------------------------- /specs/opcode/12SLT_13SGT.md: -------------------------------------------------------------------------------- 1 | # SLT & SGT opcodes 2 | 3 | ## Procedure 4 | 5 | The `SLT` and `SGT` opcodes compare the top two values on the stack, and push the result (0 or 1) back to the stack. 6 | 7 | The stack inputs `a` and `b` are 256-bits signed integers with the most significant bit being the sign (1 for negative and 0 for positive). The 256-bits are in the two's complement form of representing signed integers. 8 | 9 | #### Circuit Behaviour 10 | 11 | The `SignedComparatorGadget` takes arguments `a: [u8; 32]`, `b: [u8; 32]` and `is_sgt: bool`. 12 | 13 | It returns the result of `a < b` where: 14 | 15 | - `Stack = [b, a]` if `is_sgt == false` 16 | - `Stack = [a, b]` if `is_sgt == true` 17 | 18 | We basically swap the stack inputs if `is_sgt == true`, so that we only need to compare `a < b` in our gadget. 19 | 20 | The gadget is constructed with the following logic: 21 | 22 | ```python 23 | # a < 0 and b >= 0 24 | if a[31] >= 128 and b[31] < 128: 25 | result = 1 26 | # b < 0 and a >= 0 27 | elif b[31] >= 128 and a[31] < 128: 28 | result = 0 29 | # (a < 0 and b < 0) or (a >= 0 and b >= 0) 30 | else: 31 | if a_hi < b_hi: 32 | result = 1 33 | elif a_hi == b_hi and a_lo < b_lo: 34 | result = 1 35 | else: 36 | result = 0 37 | ``` 38 | 39 | where: 40 | 41 | - `a[31]` and `b[31]` represent the most significant bytes of `a` and `b` respectively. 42 | - `a[31] >= 128` (same for `b`) signifies that `a` (same for `b`) is a negative number. 43 | - `a_hi = a[16..32]` and `a_lo = a[0..16]` (same for `b`) with `a` (same for `b`) being represented in the little-endian form. 44 | 45 | ## Constraints 46 | 47 | - `OpcodeId` check: 48 | - opId === OpcodeId(0x12) for `SLT` 49 | - opId === OpcodeId(0x13) for `SGT` 50 | - State Transition: 51 | - gc -> gc + 3 52 | - stack pointer -> stack pointer + 1 53 | - pc -> pc + 1 54 | - gas -> gas + 3 55 | - Lookups: 56 | - `a` is at the top of the stack 57 | - `b` is at the second position of the stack 58 | - `result` is the new top of the stack 59 | 60 | ## Exceptions 61 | 62 | 1. Stack underflow: `1023 <= stack pointer <= 1024` 63 | 2. Out of gas: gas left \< 3 64 | 65 | ## Code 66 | 67 | See [`slt_sgt.py`](src/zkevm_specs/evm/execution/slt_sgt.py) 68 | -------------------------------------------------------------------------------- /specs/opcode/15ISZERO.md: -------------------------------------------------------------------------------- 1 | # ISZERO opcode 2 | 3 | ## Procedure 4 | 5 | Pop an EVM word `value` from the stack. If it is zero, push `1` back to the stack. Otherwise push `0` to stack. 6 | 7 | ## Constraints 8 | 9 | 1. state transition: 10 | - gc + 2 (1 stack read + 1 stack write) 11 | - stack_pointer + 0 (one pop and one push) 12 | - pc + 1 13 | - gas + 3 14 | 2. Lookups: 2 busmapping lookups 15 | - `value` is at the top of the stack 16 | - `result`, is at the new top of the stack 17 | 18 | ## Exceptions 19 | 20 | 1. stack underflow: 21 | - the stack is empty: `1024 == stack_pointer` 22 | 2. out of gas: remaining gas is not enough 23 | 24 | ## Code 25 | 26 | See `src/zkevm_specs/evm/execution/iszero.py` 27 | -------------------------------------------------------------------------------- /specs/opcode/16AND_17OR_18XOR.md: -------------------------------------------------------------------------------- 1 | # AND, OR, XOR opcodes 2 | 3 | ## Procedure 4 | 5 | Pop two EVM words `a` and `b` from the stack, and the output `c` is pushed to 6 | the stack. 7 | 8 | `a`, `b`, and `c` are all EVM words. We break three EVM words into 32 bytes and 9 | apply the lookup to the 32 chunks of `a`, `b`, and `c` to see if 10 | `a[i] OP b[i] == c[i]` holds for `i = 0..32`, where `OP` belongs to 11 | `[AND, OR, XOR]`. 12 | 13 | ## Constraints 14 | 15 | 1. opcodeId checks 16 | - opId == OpcodeId(0x16) for `AND` 17 | - opId == OpcodeId(0x17) for `OR` 18 | - opId == OpcodeId(0x18) for `XOR` 19 | 2. state transition: 20 | - gc + 3 21 | - stack_pointer + 1 22 | - pc + 1 23 | - gas + 3 24 | 3. Lookups: 35 busmapping lookups 25 | - `a` is at the top of the stack 26 | - `b` is at the second position of the stack 27 | - `c`, the result, is at the new top of the stack 28 | - Apply the lookup to 32 tuples of `a, b, c` chunks, 29 | `(a[i], b[i], c[i]), i = 0..32`, with opcode corresponding table 30 | (`BitwiseAnd`, `BitwiseOr`, and `BitwiseXor`). 31 | 32 | ## Exceptions 33 | 34 | 1. stack underflow: `1023 <= stack_pointer <= 1024` 35 | 2. out of gas: remaining gas is not enough 36 | 37 | ## Code 38 | 39 | See `src/zkevm_specs/evm/execution/bitwise.py` 40 | -------------------------------------------------------------------------------- /specs/opcode/19NOT.md: -------------------------------------------------------------------------------- 1 | # NOT opcode 2 | 3 | ## Procedure 4 | 5 | Pop one EVM word `a` from the stack and push output `b` onto the stack. 6 | 7 | We prove that `a = NOT b` by showing that the byte-wise relation 8 | `a[i] XOR b[i] == 255` holds for each of `i = 0..32`. 9 | 10 | ## Constraints 11 | 12 | 1. opcodeId checks 13 | - opId == OpcodeId(0x19) 14 | 2. state transition: 15 | - gc + 2 16 | - stack_pointer unchanged 17 | - pc + 1 18 | - gas + 3 19 | 3. Lookups: 2 busmapping lookups and 32 fixed table lookups 20 | - `a` is at the top of the stack 21 | - `b`, the result, is at the new top of the stack 22 | - Apply the lookup to 32 tuples of `a, b` chunks, `(a[i], b[i]), i = 0..32`. 23 | 24 | ## Exceptions 25 | 26 | 1. stack underflow: `stack_pointer = 1024` 27 | 2. out of gas: remaining gas is not enough 28 | 29 | ## Code 30 | 31 | See `src/zkevm_specs/evm/execution/bitwise.py` 32 | -------------------------------------------------------------------------------- /specs/opcode/1aBYTE.md: -------------------------------------------------------------------------------- 1 | # BYTE opcode 2 | 3 | ## Procedure 4 | 5 | The `BYTE` opcode retrieves a single byte from a value. This is done by popping two values, first the `index` and then the `value`, from the stack. The byte at position `index`, with the position starting at `0` at the MSB in `value`, will be copied to the LSB of `result`. `result` is then pushed on the stack. If `index >= 32` no byte will be copied and `result` will be `0`. 6 | 7 | ## Constraints 8 | 9 | 1. opId = OpcodeId(0x1a) 10 | 2. state transition: 11 | - gc + 3 (2 stack reads + 1 stack write) 12 | - stack_pointer + 1 13 | - pc + 1 14 | - gas + 3 15 | 3. lookups: 3 busmapping lookups 16 | - `index` is at the top of the stack 17 | - `value` is at the new top of the stack 18 | - `result` is at the new top of the stack 19 | 20 | ## Exceptions 21 | 22 | 1. stack underflow: the stack is empty or only contains a single value 23 | 2. out of gas: the remaining gas is not enough 24 | 25 | ## Code 26 | 27 | Please refer to `src/zkevm_specs/opcode/byte.py`. 28 | -------------------------------------------------------------------------------- /specs/opcode/30ADDRESS.md: -------------------------------------------------------------------------------- 1 | # ADDRESS opcode 2 | 3 | ## Procedure 4 | 5 | The `ADDRESS` opcode gets the address of currently executing account. 6 | 7 | ## EVM behaviour 8 | 9 | The `ADDRESS` opcode loads the callee address (20 bytes of data) from call 10 | context, then pushes this address to the stack. 11 | 12 | ## Circuit behaviour 13 | 14 | 1. Construct call context table in rw table 15 | 2. Do busmapping lookup for call context callee read operation 16 | 3. Do busmapping lookup for stack write operation 17 | 18 | ## Constraints 19 | 20 | 1. opId = 0x30 21 | 2. State transition: 22 | - gc + 2 (1 stack write, 1 call context read) 23 | - stack_pointer - 1 24 | - pc + 1 25 | - gas - 2 26 | 3. Lookups: 2 27 | - `address` is in the rw table {call context, call ID, callee} 28 | - `address` is on top of stack 29 | 30 | ## Exceptions 31 | 32 | 1. stack overflow: stack is full, stack pointer = 0 33 | 2. out of gas: remaining gas is not enough 34 | 35 | ## Code 36 | 37 | Please refer to `src/zkevm_specs/evm/execution/address.py`. 38 | -------------------------------------------------------------------------------- /specs/opcode/31BALANCE.md: -------------------------------------------------------------------------------- 1 | # BALANCE opcode 2 | 3 | ## Procedure 4 | 5 | The `BALANCE` opcode gets balance of the given account. 6 | 7 | ## EVM behaviour 8 | 9 | The `BALANCE` opcode pops `address` (20 bytes of data) off the stack and pushes 10 | the balance of the corresponding account onto the stack. If the given account 11 | doesn't exist (by checking non existing flag), then it will push 0 onto the 12 | stack instead. 13 | 14 | ## Circuit behaviour 15 | 16 | 1. Construct call context table in rw table. 17 | 2. Do bus-mapping lookup for stack read, call context read and account read 18 | operations. 19 | 3. Do bus-mapping lookup for transaction access list write and stack write 20 | operations. 21 | 22 | ## Constraints 23 | 24 | 1. opId = 0x31 25 | 2. State transition: 26 | - gc + 7 (1 stack read, 1 stack write, 3 call context reads, 1/2 account reads, 27 | 1 transaction access list write) 28 | - stack_pointer + 0 (one pop and one push) 29 | - pc + 1 30 | - gas: 31 | - the accessed `address` is warm: GAS_COST_WARM_ACCESS 32 | - the accessed `address` is cold: GAS_COST_ACCOUNT_COLD_ACCESS 33 | 3. Lookups: 7/8 busmapping lookups 34 | - `address` is at top of the stack. 35 | - 3 reads from call context for `tx_id`, `rw_counter_end_of_reversion`, and 36 | `is_persistent`. 37 | - `address` is added to the transaction access list if not already present. 38 | - 1 account `code_hash` lookup to determine account existence. 39 | - If account exists, lookup account `balance`. 40 | - The BALANCE result is at the new top of the stack. 41 | 4. Additional Constraints 42 | - value `is_warm` matches the gas cost for this opcode. 43 | 44 | ## Exceptions 45 | 46 | 1. stack underflow: if the stack starts empty 47 | 2. out of gas: remaining gas is not enough 48 | 49 | ## Code 50 | 51 | Please refer to `src/zkevm_specs/evm/execution/balance.py`. 52 | -------------------------------------------------------------------------------- /specs/opcode/32ORIGIN.md: -------------------------------------------------------------------------------- 1 | # ORIGIN opcode 2 | 3 | ## Procedure 4 | 5 | The `ORIGIN` opcode gets the execution origination address (tx.origin). 6 | 7 | ## EVM behaviour 8 | 9 | The `ORIGIN` opcode loads the sender address (20 bytes) of the transaction that originated this execution, then pushes the address to the stack. 10 | 11 | ## Circuit behaviour 12 | 13 | 1. Lookup call context in rw table for the txID 14 | 2. Lookup txID in Tx Table for the CallerAddress 15 | 3. Lookup stack write operation for the address 16 | 17 | ## Constraints 18 | 19 | 1. opId = 0x32 20 | 2. State transition: 21 | - gc + 2 (1 stack write, 1 call context read) 22 | - stack_pointer - 1 23 | - pc + 1 24 | - gas + 2 25 | 3. Lookups: 3 26 | - `tx_id` is on current call context 27 | - `address` is in the Tx Table 28 | - `address` is on the top of stack 29 | 30 | ## Exceptions 31 | 32 | 1. stack overflow: stack is full, stack pointer = 0 33 | 2. out of gas: remaining gas is not enough 34 | 35 | ## Code 36 | 37 | Please refer to [`origin.py`](src/zkevm_specs/evm/execution/origin.py) 38 | -------------------------------------------------------------------------------- /specs/opcode/33CALLER.md: -------------------------------------------------------------------------------- 1 | # CALLER opcode 2 | 3 | ## Procedure 4 | 5 | The `CALLER` opcode gets the caller address (msg.caller) from the current call. 6 | 7 | ## EVM behaviour 8 | 9 | The `CALLER` opcode loads an `address` (20 bytes of data) from call context, 10 | then pushes the `address` to the stack. 11 | 12 | ## Circuit behaviour 13 | 14 | 1. Construct call context table in rw table 15 | 2. Do busmapping lookup for call context caller read operation 16 | 3. Do busmapping lookup for stack write operation 17 | 18 | ## Constraints 19 | 20 | 1. opId = 0x33 21 | 2. State transition: 22 | - gc + 2 (1 stack write, 1 call context read) 23 | - stack_pointer - 1 24 | - pc + 1 25 | - gas + 2 26 | 3. Lookups: 2 27 | - `address` is in the rw table {call context, call ID, caller} 28 | - `address` is on top of stack 29 | 30 | ## Exceptions 31 | 32 | 1. stack overflow: stack is full, stack pointer = 0 33 | 2. out of gas: remaining gas is not enough 34 | 35 | ## Code 36 | 37 | Please refer to `src/zkevm_specs/evm/execution/caller.py`. 38 | -------------------------------------------------------------------------------- /specs/opcode/34CALLVALUE.md: -------------------------------------------------------------------------------- 1 | # CALLVALUE opcode 2 | 3 | ## Procedure 4 | 5 | The `CALLVALUE` opcode gets the call value (msg.value) from the current call. 6 | 7 | ## EVM behaviour 8 | 9 | The `CALLVALUE` opcode loads a `word` (32 bytes of data) from call context -> 10 | call value, then pushes the `word` to the stack. 11 | 12 | ## Circuit behaviour 13 | 14 | 1. Construct call context table in rw table 15 | 2. Do busmapping lookup for call context call value read operation 16 | 3. Do busmapping lookup for stack write operation 17 | 18 | ## Constraints 19 | 20 | 1. opId = 0x34 21 | 2. State transition: 22 | - gc + 2 (1 stack write, 1 call context read) 23 | - stack_pointer - 1 24 | - pc + 1 25 | - gas + 2 26 | 3. Lookups: 2 27 | - `word` is in the rw table {call context, call ID, call value} 28 | - `word` is on top of stack 29 | 30 | ## Exceptions 31 | 32 | 1. stack overflow: stack is full, stack pointer = 0 33 | 2. out of gas: remaining gas is not enough 34 | 35 | ## Code 36 | 37 | Please refer to `src/zkevm_specs/evm/execution/callvalue.py`. 38 | -------------------------------------------------------------------------------- /specs/opcode/35CALLDATALOAD.md: -------------------------------------------------------------------------------- 1 | # CALLDATALOAD opcode 2 | 3 | ## Procedure 4 | 5 | The `CALLDATALOAD` opcode gets input data of current environment. 6 | 7 | ## EVM Behaviour 8 | 9 | Stack input is the byte offset to read call data from. Stack output is a 32-byte value starting from the given offset of the call data. All bytes after the end of the call data are set to `0`. 10 | 11 | ## Constraints 12 | 13 | 1. opId == 0x35 14 | 2. State Transition: 15 | - if is_root_call: 16 | - rw_counter += 4 (1 stack read, 2 call context read, 1 stack write) 17 | - if is_internal_call: 18 | - rw_counter += rw_counter_offset ∈ {5, 6, ..., 36, 37} (1 stack read, 3 call context reads, i ∈ {0, 1, ..., 31, 32} memory reads, 1 stack write) 19 | - stack_pointer unchanged 20 | - pc + 1 21 | - gas - 3 22 | 3. Lookups: 23 | - `offset` is at the top of the stack 24 | - if is_root_call (where `src_addr = offset`): 25 | - `tx_id` is in the RW table (call context) 26 | - `calldata_length` is in the RW table (call context) 27 | - i ∈ {0, 1, ..., 31, 32} lookups for `i in range(32)`: if `buffer.read_flag(i)` then the i'th byte of the element on top of the stack `calldata_word[i]` is in the TX table {tx id, call data, src_addr + i} 28 | - if is_internal_call (where `src_addr = offset + calldata_offset`): 29 | - `calldata_length` is in the RW table (call context) 30 | - `calldata_offset` is in the RW table (call context) 31 | - `caller_id` is in the RW table (call context) 32 | - i ∈ {0, 1, ..., 31, 32} lookups for `i in range(32)`: if `buffer.read_flag(i)` then the i'th byte of the element on top of the stack `calldata_word[i]` is in the RW table {memory, src_addr + i, caller_id} 33 | 34 | ## Exceptions 35 | 36 | 1. Stack underflow: stack is empty, stack pointer = 1024 37 | 2. Out of gas: remaining gas is not enough for this opcode 38 | 39 | ## Code 40 | 41 | Please refer to `src/zkevm_specs/evm/execution/calldataload.py`. 42 | -------------------------------------------------------------------------------- /specs/opcode/36CALLDATASIZE.md: -------------------------------------------------------------------------------- 1 | # CALLDATASIZE opcode 2 | 3 | ## Procedure 4 | 5 | The `CALLDATASIZE` opcode gets the call data size (msg.data.size) from the current call. 6 | 7 | ## EVM behaviour 8 | 9 | The `CALLDATASIZE` opcode loads a `u64` (5 bytes of data) from call context -> 10 | call data length, then pushes the `u64` to the stack. 11 | 12 | ## Circuit behaviour 13 | 14 | 1. Construct call context table in rw table 15 | 2. Do busmapping lookup for call context call data length read operation 16 | 3. Do busmapping lookup for stack write operation 17 | 18 | ## Constraints 19 | 20 | 1. opId = 0x36 21 | 2. State transition: 22 | - gc + 2 (1 stack write, 1 call context read) 23 | - stack_pointer - 1 24 | - pc + 1 25 | - gas + 2 26 | 3. Lookups: 2 27 | - `u64` is in the rw table {call context, call ID, call data length} 28 | - `u64` is on top of stack 29 | 30 | ## Exceptions 31 | 32 | 1. stack overflow: stack is full, stack pointer = 0 33 | 2. out of gas: remaining gas is not enough 34 | 35 | ## Code 36 | 37 | Please refer to `src/zkevm_specs/evm/execution/calldatasize.py`. 38 | -------------------------------------------------------------------------------- /specs/opcode/37CALLDATACOPY.md: -------------------------------------------------------------------------------- 1 | # CALLDATACOPY opcode 2 | 3 | ## Procedure 4 | 5 | The `CALLDATACOPY` opcode pops `memory_offset`, `data_offset`, and `length` from the stack. 6 | It then copies `length` bytes from call data buffer at the offset `data_offset` to the memory at the 7 | address `memory_offset`. EVM also pads 0 to the end of call data buffer in the case of out-of-bound 8 | access to the call data buffer. 9 | 10 | 1. A const gas cost `3 gas` 11 | 2. A dynamic cost: the memory expansion cost and copier cost in terms of the number of 12 | words copied. Note that when `length = 0`, the memory expansion cost is 0 regardless of 13 | `memory_offset` value. 14 | 15 | ## Circuit behaviour 16 | 17 | Because the `length` is dynamic, it requires multiple step slots to fully verify the `CALLDATACOPY`. 18 | In the `CALLDATACOPY` circuit, it only constrains the stack pops, state transition, and lookups to 19 | retrieve the additional information such as tx id, call data offset, etc. 20 | Then the gadget transits to an internal state called `CopyToMemory`, which can loop itself for 21 | copying data to the memory if `length > 0`. 22 | 23 | ## Constraints 24 | 25 | 1. opId = 0x37 26 | 2. State transition: 27 | - rw_counter 28 | - +5 for root calls (3 stack read, 2 call context reads) 29 | - +6 for internal calls (3 stack read, 3 call context reads) 30 | - stack_pointer + 3 31 | - pc + 1 32 | - gas + 3 + dynamic cost (memory expansion and copier cost when `length > 0`) 33 | - memory_size 34 | - `max(prev_memory_size, (memory_offset + length + 31) / 32)` if `length > 0` 35 | - `prev_memory_size` if `length = 0`. 36 | 3. Lookups: 5 (root calls) or 6 (internal calls) 37 | - `memory_offset` is at the top of the stack 38 | - `data_offset` is at the second position of the stack 39 | - `length` is at the third position of the stack 40 | - for root calls (`is_root = 1`): 41 | - `tx_id` is in the rw table (call context) 42 | - `call_data_length` is in the rw table (call context) 43 | - for internal calls (`is_root = 0`): 44 | - `caller_id` is in the rw table (call context) 45 | - `call_data_length` is in the rw table (call context) 46 | - `call_data_offset` is in the rw table (call context) 47 | 48 | ## Exceptions 49 | 50 | 1. stack underflow: `1021 <= stack_pointer <= 1024` 51 | 2. out of gas: remaining gas is not enough 52 | 53 | ## Code 54 | 55 | Please refer to `src/zkevm_specs/evm/execution/calldatacopy.py`. 56 | -------------------------------------------------------------------------------- /specs/opcode/38CODESIZE.md: -------------------------------------------------------------------------------- 1 | # CODESIZE opcode 2 | 3 | ## Procedure 4 | 5 | ### EVM behaviour 6 | 7 | The `CODESIZE` opcode pushes the size of code running in the current environment to the top of the stack. 8 | 9 | ### Circuit behaviour 10 | 11 | 1. Lookup the code size from the bytecode table 12 | 2. Do busmapping lookup for stack write operation 13 | 14 | ## Constraints 15 | 16 | 1. opId = 0x38 17 | 2. State transition: 18 | - gc + 1 (1 stack write) 19 | - stack_pointer - 1 20 | - pc + 1 21 | - gas + 2 22 | 3. Lookups: 2 23 | - `codesize` (bytecode_length) from the bytecode table 24 | - `codesize` is on top of stack 25 | 26 | ## Exceptions 27 | 28 | 1. stack overflow: stack is full, stack pointer = 0 29 | 2. out of gas: remaining gas is not enough 30 | 31 | ## Code 32 | 33 | Please refer to [codesize.py](src/zkevm_specs/evm/execution/codesize.py). 34 | -------------------------------------------------------------------------------- /specs/opcode/39CODECOPY.md: -------------------------------------------------------------------------------- 1 | # CODECOPY opcode 2 | 3 | ## Procedure 4 | 5 | The `CODECOPY` opcode pops `memory_offset`, `code_offset` and `size` from the stack. 6 | 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. 7 | 8 | The gas cost of `CODECOPY` opcode consists of two parts: 9 | 10 | 1. A constant gas cost: `3 gas` 11 | 2. A dynamic gas cost: cost of memory expansion and copying (variable depending on the `size` copied to memory) 12 | 13 | ## Circuit Behaviour 14 | 15 | The `CODECOPY` circuit constrains the values popped from stack, call context/account read lookups and CopyTable lookups to verify the copy of bytes. The copy of a dynamic number of bytes is verified by the CopyCircuit outside the `CODECOPY` gadget. 16 | 17 | ## Constraints 18 | 19 | 1. opId = 0x39 20 | 2. State Transitions: 21 | - rw_counter -> rw_counter + 3 (3 stack reads) 22 | - stack_pointer -> stack_pointer + 3 23 | - pc -> pc + 1 24 | - gas -> 3 + dynamic_cost (memory expansion and copier cost when `size > 0`) 25 | - memory_size 26 | - `prev_memory_size` if `size = 0` 27 | - `max(prev_memory_size, (memory_offset + size + 31) / 32)` if `size > 0` 28 | 3. Lookups: 29 | - `memory_offset` is at the top of the stack 30 | - `code_offset` is at the second position of the stack 31 | - `size` is at the third position of the stack 32 | - `code_size` from the bytecode table 33 | 34 | ## Exceptions 35 | 36 | 1. Stack Underflow: `1021 <= stack_pointer <= 1024` 37 | 2. Out-of-Gas: remaining gas is not enough 38 | 39 | ## Code 40 | 41 | Please refer to `src/zkevm_specs/evm/execution/codecopy.py` 42 | -------------------------------------------------------------------------------- /specs/opcode/3aGASPRICE.md: -------------------------------------------------------------------------------- 1 | # GASPRICE opcode 2 | 3 | ## Procedure 4 | 5 | The `GASPRICE` opcode gets the price of gas in the current environment. 6 | 7 | ## EVM behaviour 8 | 9 | The `GASPRICE` opcode loads a 256-bit value from the Tx context (the gas price), and pushes it to the stack. 10 | 11 | ## Circuit behaviour 12 | 13 | 1. Construct Tx Context table 14 | 2. Bus-mapping lookup for stack write operation 15 | 16 | ## Constraints 17 | 18 | 1. opId == 0x3A 19 | 2. State transition: 20 | - gc + 2 (1 stack write + 1 Call Context read) 21 | - `stack_pointer` - 1 22 | - pc + 1 23 | - gas + 2 24 | 3. Lookups: 25 | - `tx_id` is on current call context 26 | - `gasprice` is on the top of stack 27 | - `gasprice` is in the transaction context table 28 | 29 | ## Exceptions 30 | 31 | 1. Stack overflow: stack is full, stack pointer = 0 32 | 2. out of gas: remaining gas is not enough 33 | 34 | ## Code 35 | 36 | Please refer to [`gasprice.py`](src/zkevm_specs/evm/execution/gasprice.py) 37 | -------------------------------------------------------------------------------- /specs/opcode/3bEXTCODESIZE.md: -------------------------------------------------------------------------------- 1 | # EXTCODESIZE opcode 2 | 3 | ## Procedure 4 | 5 | The `EXTCODESIZE` opcode gets code size of the given account. 6 | 7 | ## EVM behaviour 8 | 9 | The `EXTCODESIZE` opcode pops `address` (20 bytes of data) off the stack and 10 | pushes the code size of the corresponding account onto the stack. If the given 11 | account doesn't exist (by checking that `code_hash == 0`), then it will push 0 12 | onto the stack instead. 13 | 14 | ## Circuit behaviour 15 | 16 | 1. Construct call context table in rw table. 17 | 2. Do bus-mapping lookup for stack read, call context read and account read 18 | operations. 19 | 3. Do bus-mapping lookup for transaction access list write and stack write 20 | operations. 21 | 22 | ## Constraints 23 | 24 | 1. opId = 0x3b 25 | 2. State transition: 26 | - gc + 7 (1 stack read, 1 stack write, 3 call context reads, 1 account read, 27 | 1 transaction access list write) 28 | - stack_pointer + 0 (one pop and one push) 29 | - pc + 1 30 | - gas: 31 | - the accessed `address` is warm: WARM_STORAGE_READ_COST 32 | - the accessed `address` is cold: COLD_ACCOUNT_ACCESS_COST 33 | 3. Lookups: 7 bus-mapping lookups 34 | - `address` is popped from the stack. 35 | - 3 from call context for `tx_id`, `rw_counter_end_of_reversion`, and 36 | `is_persistent`. 37 | - `address` is added to the transaction access list if not already present. 38 | - Lookup account `code_hash`, then get `code_size` if `code_hash != 0`, 39 | otherwise skip the `code_size` lookup. 40 | - The EXTCODESIZE result is at the new top of the stack. 41 | 4. Additional Constraints 42 | - value `is_warm` matches the gas cost for this opcode. 43 | 44 | ## Exceptions 45 | 46 | 1. stack underflow: if the stack starts empty 47 | 2. out of gas: remaining gas is not enough 48 | 49 | ## Code 50 | 51 | Please refer to `src/zkevm_specs/evm/execution/extcodesize.py`. 52 | -------------------------------------------------------------------------------- /specs/opcode/3cEXTCODECOPY.md: -------------------------------------------------------------------------------- 1 | # EXTCODECOPY opcode 2 | 3 | ## Procedure 4 | 5 | The `EXTCODECOPY` opcode pops `address`, `memory_offset`, `code_offset` and `size` from the stack. It then copies `size` bytes of code of `address` 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. 6 | 7 | The gas cost of `EXTCODECOPY` opcode is dynamic and is based on the cost of memory expansion and copying (variable depending on the `size` copied to memory) and accessing the address (variable depending on whether the `address` is warm or not) 8 | 9 | ## Circuit Behaviour 10 | 11 | The `EXTCODECOPY` circuit constrains the values popped from stack, call context/address read lookups and CopyTable lookups to verify the copy of bytes. The copy of a dynamic number of bytes is verified by the CopyCircuit outside the `EXTCODECOPY` gadget. 12 | 13 | ## Constraints 14 | 15 | 1. opId = 0x3c 16 | 2. State transition: 17 | - stack pointer + 4 18 | - pc + 1 19 | - gas -> dynamic gas cost 20 | - memory_size 21 | - `prev_memory_size` if `size = 0` 22 | - `max(prev_memory_size, (memory_offset + size + 31) / 32)` if `size > 0` 23 | 3. Lookups: 10 24 | - `address` is at the top of the stack 25 | - `memory_offset` is at the second position of the stack 26 | - `code_offset` is at the third position of the stack 27 | - `size` is at the fourth position of the stack 28 | - `code_hash` from the address (will be 0 when the account doesn't exist). 29 | - `code_size` from the bytecode table if account exists (`code_hash != 0`) 30 | - `tx_id`, `rw_counter_end_of_reversion`, `is_persistent` from call context 31 | - `address` is added to the transaction access list if not already present 32 | 33 | ## Exceptions 34 | 35 | 1. Stack Underflow: `1020 <= stack pointer <= 1024` 36 | 2. Out-of-Gas: remaining gas is not enough 37 | 38 | ## Code 39 | 40 | Please refer to [extcodecopy](src/zkevm_specs/evm/execution/extcodecopy.py). 41 | -------------------------------------------------------------------------------- /specs/opcode/3dRETURNDATASIZE.md: -------------------------------------------------------------------------------- 1 | # RETURNDATASIZE opcode 2 | 3 | ## Procedure 4 | 5 | ### EVM behaviour 6 | 7 | The `RETURNDATASIZE` opcode gets size of output data from the previous call from the current environment. 8 | 9 | ### Circuit behaviour 10 | 11 | 1. Construct call context table in rw table. 12 | 2. Do a busmapping lookup for CallContext last Call's ReturnDataLength read. 13 | 3. Do a busmapping lookup for a stack write operation corresponding to the RETURNDATASIZE result. 14 | 15 | ## Constraints 16 | 17 | 1. opId == 0x3D 18 | 2. State transition: 19 | - gc + 2 20 | - stack_pointer - 1 21 | - pc + 1 22 | - gas + 2 23 | 3. Lookups: 2 24 | - ReturnDataLength is in the rw table {call context, call ID, ReturnDataLength}. 25 | - ReturnDataLength is on top of stack. 26 | 27 | ## Exceptions 28 | 29 | 1. stack overflow: stack is full, stack pointer = 0 30 | 2. out of gas: remaining gas is not enough 31 | 32 | ## Code 33 | 34 | Please refer to [returndatasize.py](src/zkevm_specs/evm/execution/returndatasize.py). 35 | -------------------------------------------------------------------------------- /specs/opcode/3fEXTCODEHASH.md: -------------------------------------------------------------------------------- 1 | # EXTCODEHASH opcode 2 | 3 | ## Procedure 4 | 5 | The `EXTCODEHASH` opcode pops `address` off the stack and pushes the code hash 6 | of the corresponding account onto the stack. If the corresponding account 7 | doesn't exist then it will push 0 onto the stack instead. Since we use 8 | `code_hash = 0` to encode non-existing accounts, we can use the lookup result 9 | directly. 10 | 11 | ## Constraints 12 | 13 | 1. opId = 0x3f 14 | 2. State transition: 15 | - gc + 7 (1 stack read, 1 stack write, 3 call context reads, 1 account read, 16 | 1 transaction access list write) 17 | - stack_pointer + 0 (one pop and one push) 18 | - pc + 1 19 | - gas: 20 | - the accessed `address` is warm: WARM_STORAGE_READ_COST 21 | - the accessed `address` is cold: COLD_ACCOUNT_ACCESS_COST 22 | 3. Lookups: 7 busmapping lookups 23 | - `address` is popped from the stack 24 | - 3 from call context for `tx_id`, `rw_counter_end_of_reversion`, and 25 | `is_persistent`. 26 | - `address` is added to the transaction access list if not already present. 27 | - lookup account `code_hash`. 28 | - The EXTCODEHASH result is at the new top of the stack. 29 | 4. Additional Constraints 30 | - value `is_warm` matches the gas cost for this opcode. 31 | 32 | ## Exceptions 33 | 34 | 1. stack underflow: if the stack starts empty 35 | 2. out of gas: remaining gas is not enough 36 | 37 | ## Code 38 | 39 | Please refer to `src/zkevm_specs/evm/execution/extcodehash.py`. 40 | -------------------------------------------------------------------------------- /specs/opcode/40BLOCKHASH.md: -------------------------------------------------------------------------------- 1 | # BLOCKHASH opcode 2 | 3 | ## Procedure 4 | 5 | The `BLOCKHASH` opcode pops `block_number` off the stack and pushes the hash from chosen block. If the chosen block number is not in valid range (last 256 blocks, not including current block), it will push 0 onto the stack. 6 | 7 | ## Constraints 8 | 9 | 1. opId == 0x40 10 | 2. State transition: 11 | - gc + 2 (1 stack read + 1 stack write) 12 | - stack_pointer + 0 (one pop and one push) 13 | - pc + 1 14 | - gas + 20 15 | 3. Lookups: 3 16 | - `block_number` is popped from the stack. 17 | - `current_block_number` is in the block context table. 18 | - `block_hash` is in the block context table. 19 | 4. Additional Constraint 20 | - `block_number` should be in valid range. 21 | 22 | ## Exceptions 23 | 24 | 1. stack underflow: if the stack starts empty 25 | 2. out of gas: remaining gas is not enough 26 | 27 | ## Code 28 | 29 | Please refer to [blockhash.py](src/zkevm-specs/evm/execution/blockhash.py). 30 | -------------------------------------------------------------------------------- /specs/opcode/41COINBASE_45GASLIMIT_48BASEFEE.md: -------------------------------------------------------------------------------- 1 | # block context op codes 2 | 3 | ## Procedure 4 | 5 | The block context opcodes get the corresponding op data from current block context, and then push it to the stack. The `opcodes` contain `[COINBASE, TIMESTAMP, NUMBER, PREVRANDAO, GASLIMIT, BASEFEE]`. 6 | 7 | ## EVM behavior 8 | 9 | The opcode loads the corresponding op n bytes of data from block context, and then push it to the stack. 10 | 11 | n bytes length and 32 bytes Word: 12 | 13 | - `COINBASE` 20 bytes length 14 | - `TIMESTAMP` 8 bytes length 15 | - `NUMBER` 8 bytes length 16 | - `PREVRANDAO` 32 bytes length, Word 17 | - `GASLIMIT` 8 bytes length 18 | - `BASEFEE` 32 bytes length, Word 19 | 20 | ## Circuit behavior 21 | 22 | 1. construct block context table 23 | 2. do busmapping lookup for stack write operation 24 | 3. other implicit check: bytes length 25 | 26 | ## Constraints 27 | 28 | 1. opcodeId checks 29 | - opId == OpcodeId(0x41) for `COINBASE` 30 | - opId == OpcodeId(0x42) for `TIMESTAMP` 31 | - opId == OpcodeId(0x43) for `NUMBER` 32 | - opId == OpcodeId(0x44) for `PREVRANDAO` 33 | - opId == OpcodeId(0x45) for `GASLIMIT` 34 | - opId == OpcodeId(0x48) for `BASEFEE` 35 | 2. State transition: 36 | - gc + 1 (1 stack write) 37 | - stack_pointer - 1 38 | - pc + 1 39 | - gas + 2 40 | 3. Lookups: 2 41 | - `OP` is on the top of stack 42 | - `OP` is in the block context table 43 | 4. Others: 44 | 45 | - `COINBASE` 20 bytes length 46 | - `TIMESTAMP` 8 bytes length 47 | - `NUMBER` 8 bytes length 48 | - `PREVRANDAO` 32 bytes length, Word 49 | - `GASLIMIT` 8 bytes length 50 | - `BASEFEE` 32 bytes length, Word 51 | 52 | ## Exceptions 53 | 54 | 1. stack overflow: stack is full, stack pointer = 0 55 | 2. out of gas: remaining gas is not enough 56 | 57 | ## Code 58 | 59 | Please refer to `src/zkevm_specs/evm_circuit/execution/block_ctx.py`. -------------------------------------------------------------------------------- /specs/opcode/46CHAINID.md: -------------------------------------------------------------------------------- 1 | # CHAINID opcode 2 | 3 | ## Procedure 4 | 5 | The `CHAINID` opcode gets the chain id where a transaction is meant executed at. 6 | 7 | ## EVM behaviour 8 | 9 | The `CHAINID` opcode loads a 256-bit value from the chain configuration (the chain id), and pushes it to the stack. 10 | 11 | ## Circuit behaviour 12 | 13 | 1. Construct Block Context table 14 | 2. Bus-mapping lookup for stack write operation 15 | 16 | ## Constraints 17 | 18 | 1. opId == 0x46 19 | 2. State transition: 20 | - gc + 1 (1 stack write) 21 | - `stack_pointer` - 1 22 | - pc + 1 23 | - gas + 2 24 | 3. Lookups: 25 | - `chainid` is on the top of stack 26 | - `chainid` is in the block context table 27 | 28 | ## Exceptions 29 | 30 | 1. Stack overflow: stack is full, stack pointer = 0 31 | 2. out of gas: remaining gas is not enough 32 | 33 | ## Code 34 | 35 | Please refer to [`block_ctx.py`](src/zkevm_specs/evm/execution/block_ctx.py) 36 | -------------------------------------------------------------------------------- /specs/opcode/47SELFBALANCE.md: -------------------------------------------------------------------------------- 1 | # SELFBALANCE opcode 2 | 3 | ## Procedure 4 | 5 | The `SELFBALANCE` opcode pushes the balance (32 bytes of data) of the currently executing address onto the stack. 6 | 7 | ## Circuit behaviour 8 | 9 | 1. Construct call context table in rw table 10 | 2. Do busmapping lookup for stack write operation 11 | 12 | ## Constraints 13 | 14 | 1. opId = 0x47 15 | 2. State transition: 16 | - gc + 3 (1 stack write, 1 account balance read, and 1 callee address read) 17 | - stack_pointer - 1 18 | - pc + 1 19 | - gas + 5 20 | 3. Lookups: 3 21 | - `callee_address` is the callee address of the call context 22 | - `self_balance` is the balance of `callee_address` 23 | - `self_balance` is on the top of stack 24 | 25 | ## Exceptions 26 | 27 | 1. stack overflow: stack is full, stack pointer = 0 28 | 2. out of gas: remaining gas is not enough 29 | 30 | ## Code 31 | 32 | Please refer to `src/zkevm_specs/evm/execution/selfbalance.py`. 33 | -------------------------------------------------------------------------------- /specs/opcode/50POP.md: -------------------------------------------------------------------------------- 1 | # POP opcode 2 | 3 | ## Procedure 4 | 5 | A stack initialize empty with stack pointer to 1024, pop operation can only happen when stack is not empty, and it will increase by 1 of stack pointer. 6 | 7 | Even the popped value will never be used, it still does lookup to ensure the stack pointer is in range, since we don't explicitly verify stack pointer is in range each step, we instead let state circuit to verify that. 8 | 9 | ## Constraints 10 | 11 | 1. opId = OpcodeId(0x50) 12 | 2. state transition: 13 | - gc + 1 14 | - stack_pointer + 1 15 | - pc + 1 16 | - gas + 2 17 | 3. Lookups: 1 busmapping lookups 18 | - A value is indeed at the top of the stack 19 | 20 | ## Exceptions 21 | 22 | 1. stack underflow: when stack is empty 23 | 2. gas out: remaining gas is not enough 24 | 25 | ## Code 26 | 27 | Please refer to `src/zkevm-specs/evm/execution/pop.py`. 28 | -------------------------------------------------------------------------------- /specs/opcode/56JUMP.md: -------------------------------------------------------------------------------- 1 | # JUMP opcode 2 | 3 | ## Procedure 4 | 5 | JUMP is an op code regarding flow control of evm. it takes the value of top stack 6 | to use as the destination, which changes program counter to it. 7 | 8 | ### EVM behavior 9 | 10 | Pop one EVM word `dest` from the stack. then do the following: 11 | 12 | - check `dest` is within code length range 13 | - check `dest` is a `JUMPDEST` and not data section of PUSH\* 14 | - set program counter = `dest` 15 | 16 | ### Circuit behavior 17 | 18 | 1. construct byte code table with tuple (code_hash, index, bytecode, is_code) in each row, the table validity will be ensured by another byte code circuit. 19 | 2. basic constraints: 20 | 21 | - opId == OpcodeId(0x56) 22 | - gc + 1 (1 stack read) 23 | - pc = `dest` 24 | - stack_pointer + 1 25 | - gas + 8 26 | 27 | 3. Lookups: 1 busmapping lookups + 1 byte code lookup 28 | 29 | - lookup `dest` at the top of the stack 30 | - lookup `dest` position is `JUMPDEST` code in byte code table 31 | 32 | ## Exceptions 33 | 34 | 1. stack underflow: when stack is empty `stack_pointer = 1024` 35 | 2. out of gas: remaining gas is not enough 36 | 3. invalid jump (AKA ErrInvalidJump):\ 37 | the `dest` is not `JUMPDEST` or not code or out of range 38 | 39 | ## Code 40 | 41 | refer to src/zkevm_specs/evm/execution/jump.py 42 | -------------------------------------------------------------------------------- /specs/opcode/57JUMPI.md: -------------------------------------------------------------------------------- 1 | # JUMPI opcode 2 | 3 | ## Procedure 4 | 5 | JUMPI is an op code regarding flow control of evm. it pops two values at the top of the stack and conditionally changes program counter . 6 | 7 | ### EVM behavior 8 | 9 | Pop two EVM words `dest` and `cond` from the stack. then do the following: 10 | 11 | 1. check `cond` is zero 12 | 13 | - set pc = pc + 1 14 | 15 | 2. check `cond` is not zero 16 | 17 | - check `dest` is within code length range 18 | - check `dest` is a JUMPDEST 19 | - check `dest` is not data section of PUSH\* 20 | - set pc = `dest` 21 | 22 | ### Circuit behavior 23 | 24 | 1. Construct byte code table with tuple of (code_hash, index, bytecode, is_code) in each row, the table validity will be ensured by another byte code circuit. 25 | 2. basic constraints: 26 | 27 | - opId === OpcodeId(0x57) 28 | - gc + 2 (2 stack read) 29 | - pc = pc + 1 if `cond` is zero else `dest` 30 | - stack_pointer + 2 31 | - gas + 10 32 | 33 | 3. Lookups: 34 | 35 | - lookup `dest` and `cond` are the top two of the stack 36 | - Conditional lookup `dest` position is `JUMPDEST` code in byte code table 37 | when `cond` is not zero 38 | 39 | ## Exceptions 40 | 41 | 1. stack underflow: when stack is empty ` stack_pointer in [1023,1024]` 42 | 2. out of gas: remaining gas is not enough 43 | 3. invalid jump (AKA ErrInvalidJump):\ 44 | the `dest` is not JUMPDEST or not code or out of range 45 | 46 | ## Code 47 | 48 | Refer to src/zkevm_specs/evm/execution/jumpi.py 49 | -------------------------------------------------------------------------------- /specs/opcode/59MSIZE.md: -------------------------------------------------------------------------------- 1 | # MSIZE opcode 2 | 3 | ## Procedure 4 | 5 | The `MSIZE` opcode returns the memory size of the current execution and places it on the top of the stack. The memory size returned by the opcode is in bytes, while internally the memory size is stored in words, so the opcode needs to multiply the state variable by 32. 6 | 7 | ## Constraints 8 | 9 | 1. opId = OpcodeId(0x59) 10 | 2. state transition: 11 | - gc + 1 (1 stack write) 12 | - stack_pointer - 1 13 | - pc + 1 14 | - gas + 2 15 | 3. lookups: 1 busmapping lookup 16 | - `value` is pushed on top of the stack for `MSIZE` 17 | 18 | ## Exceptions 19 | 20 | 1. out of gas: the remaining gas is not enough 21 | 2. stack overflow: when stack is full, which means stack pointer is 0 before msize opcode 22 | 23 | ## Code 24 | 25 | Please refer to `src/zkevm_specs/evm/execution/msize.py`. 26 | -------------------------------------------------------------------------------- /specs/opcode/5AGAS.md: -------------------------------------------------------------------------------- 1 | # GAS opcode 2 | 3 | ## Procedure 4 | 5 | The `GAS` opcode gets the amount of available gas, including the corresponding reduction for the cost of the instruction itself. 6 | 7 | ## EVM behaviour 8 | 9 | The `GAS` opcode loads a 64-bit value for the available gas from the current state, and pushes it to the stack. 10 | 11 | ## Circuit behaviour 12 | 13 | 1. bus-mapping lookup for stack write operation 14 | 2. compare against current state's gas left 15 | 16 | ## Constraints 17 | 18 | 1. opId == 0x5A 19 | 2. State transition: 20 | - gc + 1 (1 stack write) 21 | - `stack_pointer` - 1 22 | - pc + 1 23 | - gas + 2 24 | 3. Lookups: 25 | - `gas` is on the top of stack 26 | 4. Others: 27 | - Equality constraint: `gas == curr.gas_left - 2` 28 | 29 | ## Exceptions 30 | 31 | 1. Stack overflow: stack is full, stack pointer = 0 32 | 2. out of gas: remaining gas is not enough 33 | 34 | ## Code 35 | 36 | Please refer to [`gas.py`](src/zkevm_specs/evm/execution/gas.py) 37 | -------------------------------------------------------------------------------- /specs/opcode/5BJUMPDEST.md: -------------------------------------------------------------------------------- 1 | # JUMPDEST opcode 2 | 3 | ## Procedure 4 | 5 | JUMPDEST is a special opcode which only marks an address that can be jumped to. In other words, it is metadata to annotate possible jump destinations. 6 | 7 | For example, in the below opcode sequences, JUMPDEST marks '004D' can be jumped to, if there is no JUMPDEST annotation, Jumping to '004D' will fail as jump opcode will require JUMPDEST as a valid destination. 8 | 9 | ``` 10 | 004D 5B JUMPDEST 11 | 004E 34 CALLVALUE 12 | 004F 80 DUP1 13 | 0050 15 ISZERO 14 | 0051 60 PUSH1 0x58 15 | 0053 57 *JUMPI 16 | 0054 60 PUSH1 0x00 17 | 0056 80 DUP1 18 | 0057 FD *REVERT 19 | 0058 5B JUMPDEST 20 | 0059 50 POP 21 | 005A 60 PUSH1 0x62 22 | 005C 60 PUSH1 0x04 23 | 005E 35 CALLDATALOAD 24 | 005F 60 PUSH1 0x86 25 | 0061 56 *JUMP" 26 | ``` 27 | 28 | ## Constraints 29 | 30 | 1. opId = OpcodeId(0x5B) 31 | 2. state transition: 32 | - gc 33 | - stack_pointer 34 | - pc + 1 35 | - gas + 1 36 | 3. lookups: 37 | none (since there is no operands required) 38 | 39 | ## Exceptions 40 | 41 | 1. gas out: remaining gas is not enough 42 | 43 | ## Code 44 | 45 | none 46 | -------------------------------------------------------------------------------- /specs/opcode/80DUPX.md: -------------------------------------------------------------------------------- 1 | # DUPX opcode 2 | 3 | ## Procedure 4 | 5 | dupx represents op codes of dup1....dup16. which picks up value at 'x' position inside the stack, then push the value to stack. 6 | 7 | ## Constraints 8 | 9 | 1. opId = OpcodeId(0x80...0x8F) 10 | 2. state transition: 11 | - gc + 2 (read + write) 12 | - stack_pointer - 1 13 | - pc + 1 14 | - gas + 3 15 | 3. lookups: 1 range lookup + 2 bussmapping lookups: 16 | - position 'x' range from \[1..16\] 17 | - operand must come from position 'x' inside 18 | - stack 19 | - position 'x' value equals stack top value when this operation done 20 | 21 | ## Exceptions 22 | 23 | 1. gas out: remaining gas is not enough 24 | 2. stack overflow: when stack is full, which means stack pointer is 0 before dupx 25 | 3. stack underflow: when stack length is less than dup position 'x' 26 | 27 | ## Code 28 | 29 | Please refer to `src/zkevm_specs/opcode/stack.py`. 30 | -------------------------------------------------------------------------------- /specs/opcode/90SWAPX.md: -------------------------------------------------------------------------------- 1 | # SWAPX op code 2 | 3 | ## Procedure 4 | 5 | Swapx represents op codes of swap1....swap16. which swaps the top of the stack with the 'x+1'th last element. For example, SWAP3 means swapping the top of the stack with the 4th last element 6 | 7 | ## Constraints 8 | 9 | 1. opId = OpcodeId(0x90...0x9F) 10 | 2. state transition:\ 11 | gc + 4 (2 reads + 2 writes)\ 12 | stack_pointer\ 13 | pc + 1\ 14 | gas + 3 15 | 3. lookups: 1 range lookup + 4 busmapping lookups:\ 16 | position 'x' range from \[1..16\]\ 17 | first operand must come from top of stack\ 18 | second operand must come from position 'x+1' inside stack\ 19 | when this operation done, the top stack value must equals 'x+1'th value before 20 | when this operation done, the 'x+1'th stack value must equals the top value before 21 | 22 | ## Exceptions 23 | 24 | 1. gas out:\ 25 | remaining gas is not enough 26 | 2. stack underflow:\ 27 | when stack length is less than swap position 'x + 1', i.e stack have 3 elements but the op is swap3, swap4, ..., swap16. Underflow condition for `swap_x`: `0 <= x + stack_pointer - 1024 <= 16`, which requires a `Range17` lookup check. 28 | 29 | ## Code 30 | 31 | refer to src/zkevm_specs/opcode/stack.py 32 | -------------------------------------------------------------------------------- /specs/precompile/01ecRecover.md: -------------------------------------------------------------------------------- 1 | # ecRecover precompile 2 | 3 | ## Procedure 4 | 5 | To recover the signer from a signature. It returns signer's address if input signature is valid, otherwise returns 0. 6 | 7 | ## EVM behavior 8 | 9 | ### Inputs 10 | 11 | The length of inputs is 128 bytes. The first 32 bytes is keccak hash of the message, and the following 96 bytes are v, r, s values. The value v is either 27 or 28. 12 | 13 | ### Output 14 | 15 | The recovered 20-byte address right aligned to 32 byte. If an address can't be recovered or not enough gas was given, then the output is 0. 16 | 17 | ### Gas cost 18 | 19 | A constant gas cost: 3000 20 | 21 | ## Constraints 22 | 23 | 1. If gas_left < gas_required, then is_success == false and return data is zero. 24 | 1. v, r and s are valid 25 | - v is 27 or 28 26 | - both of r and s are less than `secp256k1N (0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141)` 27 | - both of r and s are greater than `1` 28 | 2. `sig_table` lookups 29 | 3. recovered address is zero if the signature can't be recovered. 30 | 31 | ## Code 32 | 33 | Please refer to `src/zkevm_specs/evm_circuit/execution/precompiles/ecrecover.py`. 34 | -------------------------------------------------------------------------------- /specs/precompile/04dataCopy.md: -------------------------------------------------------------------------------- 1 | # dataCopy precompile 2 | 3 | ## Procedure 4 | 5 | The `dataCopy` precompile returns its input. 6 | 7 | ### Circuit behavior 8 | 9 | 1. Do a busmapping lookup for CallContext CalleeAddress. 10 | 2. Do a busmapping lookup for CallContext CallerId. 11 | 3. Do a busmapping lookup for CallContext CallDataOffset. 12 | 4. Do a busmapping lookup for CallContext CallDataLength. 13 | 5. Do a busmapping lookup for CallContext ReturnDataOffset. 14 | 6. Do a busmapping lookup for CallContext ReturnDataLength. 15 | 7. Do a CopyTable lookup to verify the copy from calldata to current call context memory. 16 | 8. Do a CopyTable lookup to verify the copy from calldata to precompile call context memory. 17 | 18 | ### Gas cost 19 | 20 | The gas cost of `dataCopy` precompile consists of two parts: 21 | 22 | 1. A constant gas cost: `15 gas` 23 | 2. A dynamic gas cost: cost of copying (variable depending on the `size` copied to memory) 24 | 25 | ## Constraints 26 | 27 | 1. prId == 0x04 28 | 2. state transition: 29 | - rw_counter + 5 + 2 * `copy_length` 30 | - gas + 15 + dynamic cost (copier cost) 31 | 32 | ## Code 33 | 34 | Please refer to `src/zkevm_specs/native/execution/dataCopy.py`. 35 | -------------------------------------------------------------------------------- /specs/precompile/06ecAdd.md: -------------------------------------------------------------------------------- 1 | # ecAdd precompile 2 | 3 | ## Procedure 4 | 5 | The `ecAdd` precompile add two given points and return result point over alt_bn128 curve. Firstly, the input is divided into four parts to get two points $x$ and $y$. Secondly, the alt_bn128 points are initialized with given pairs of $x$ and $y$. Finally, the result point is returned. 6 | 7 | ### Circuit behavior 8 | 9 | Two points are recovered from input. The field is expressed as `32` bytes for each and the input includes two points so the input size is `128` bytes. 10 | 11 | ``` 12 | input[0; 31] (32 bytes): x1 13 | input[32; 63] (32 bytes): y1 14 | input[64; 95] (32 bytes): x2 15 | input[96; 127] (32 bytes): y2 16 | ``` 17 | 18 | These two points are added and the result is returned. The result size is `64` bytes and $x$ and $y$ are in Montgomery. 19 | 20 | ``` 21 | input[0; 31] (32 bytes): x 22 | input[32; 63] (32 bytes): y 23 | ``` 24 | 25 | ### Gas cost 26 | 27 | A constant gas cost: 150 28 | 29 | If the input is not valid, all gas provided is consumed. 30 | 31 | ## Constraints 32 | 33 | 1. `ecc_table` lookup 34 | 2. If `is_valid` is false, 35 | - output is zero 36 | - consume all the remaining gas 37 | 38 | ## Code 39 | 40 | Please refer to `src/zkevm_specs/evm_circuit/execution/precompiles/ec_add.py`. 41 | -------------------------------------------------------------------------------- /specs/precompile/07ecMul.md: -------------------------------------------------------------------------------- 1 | # ecMul precompile 2 | 3 | ## Procedure 4 | 5 | The `ecMul` precompile calculates scalar multiplication on a given point and return result point over alt_bn128 curve. 6 | 7 | ### Circuit behavior 8 | 9 | The inputs include two parts, the first 64 bytes is the point, and the following 32 bytes is the scalar used for the multiplication. 10 | 11 | ``` 12 | input[0; 31] (32 bytes): x 13 | input[32; 63] (32 bytes): y 14 | input[64; 95] (32 bytes): s 15 | ``` 16 | 17 | The multiplication result is returned and the size is `64` bytes. 18 | 19 | ``` 20 | input[0; 31] (32 bytes): x 21 | input[32; 63] (32 bytes): y 22 | ``` 23 | 24 | ### Gas cost 25 | 26 | A constant gas cost: 6000 27 | 28 | If the input is not valid, all gas provided is consumed. 29 | 30 | ## Constraints 31 | 32 | 1. `ecc_table` lookup 33 | 2. If `s` is zero or `p` is an infinite point 34 | - output is zero 35 | 3. If `is_valid` is false, 36 | - output is zero 37 | - consume all the remaining gas 38 | 39 | ## Code 40 | 41 | Please refer to `src/zkevm_specs/evm_circuit/execution/precompiles/ec_mul.py`. 42 | -------------------------------------------------------------------------------- /specs/precompile/08ecPairing.md: -------------------------------------------------------------------------------- 1 | # ecPairing precompile 2 | 3 | ## Procedure 4 | 5 | The `ecPairing` precompile computes the bilinear map between two given points in the groups G1 and G2, respectively, over the alt_bn128 curve. 6 | 7 | ### Circuit behavior 8 | 9 | The input is arbitrarily many pairs of elliptic curve points. Each pair is given as a six 32-bytes values and is constructed as follows 10 | 11 | ``` 12 | input[0; 31] (32 bytes): x1 13 | input[32; 63] (32 bytes): y1 14 | input[64; 95] (32 bytes): x2 15 | input[96; 127] (32 bytes): y2 16 | input[128; 159] (32 bytes): x3 (result) 17 | input[160; 191] (32 bytes): y3 (result) 18 | ``` 19 | 20 | The first two 32-bytes values represent the first point (px, py) from group G1, the next four 32-bytes values represent the other point (qx, qy) from group G2. 21 | 22 | The bn254Pairing code first checks that a multiple of 6 elements have been sent, and then performs the pairings check(s). The check that is performed for two pairs is e(p1, q1) = e(-p2, q2) which is equivalent to the check e(p1, q1) * e(p2, q2) = 1. 23 | 24 | The output is 1 if all pairing checks were successful, otherwise it returns 0. 25 | 26 | ``` 27 | input[0; 31] (32 bytes): success 28 | ``` 29 | 30 | The pairing checks fail if not having a multiple of 6 32-bytes values or in the case of the points not being on the curve. In these cases all the provided gas is consumed. For these cases, the variable is_valid is set to 0. The variable output denotes whether the pairing checks were successful (in the case of is_valid = 1) 31 | ### Gas cost 32 | 33 | 1. A constant gas cost: 45,000 34 | 2. A dynamic gas cost: 34,000 * (len(data) / 192) 35 | 36 | If the input is not valid, all gas provided is consumed. 37 | 38 | ## Constraints 39 | 40 | 1. If the length of the input is not a multiple of 192 bytes 41 | - output is 0 42 | 2. If the input is empty which means it's a successful call, 43 | - `input_rlc` is zero 44 | - output is 1 45 | 3. `ecc_table` lookup 46 | 4. If `is_valid` is false, 47 | - output is 0 48 | - consume all the remaining gas 49 | 50 | ## Code 51 | 52 | Please refer to `src/zkevm_specs/evm_circuit/execution/precompiles/ec_pairing.py`. 53 | -------------------------------------------------------------------------------- /specs/public_inputs_deprecated.rev1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacy-scaling-explorations/zkevm-specs/6058c68e133c35224fe9060bcd9b50961594f6d3/specs/public_inputs_deprecated.rev1.png -------------------------------------------------------------------------------- /specs/super_circuit.md: -------------------------------------------------------------------------------- 1 | # Super Circuit 2 | 3 | The zkEVM is composed of many circuits, each of which is responsible for checking some EVM aspects. 4 | These circuits need to be aggregated or combined together somehow. We could say that the Super 5 | Circuit is one of the possible strategies to combine all other sub-circuits. 6 | 7 | The Super Circuit approach is quite simple, all sub-circuits are put together and their tables are shared among all of them. It contains the following circuits: 8 | - [x] EVM Circuit 9 | - [x] State Circuit 10 | - [ ] MPT Circuit 11 | - [x] Keccak Circuit 12 | - [x] Tx Circuit 13 | - [x] Bytecode Circuit 14 | - [x] Copy Circuit 15 | - [ ] Block Circuit 16 | - [x] PublicInputs Circuit 17 | - [x] Withdrawal Circuit 18 | 19 | and the following tables: 20 | - [x] RW Table 21 | - [x] MPT Table 22 | - [x] Keccak Table 23 | - [x] Tx Table 24 | - [x] Bytecode Table 25 | - [x] Copy Table 26 | - [x] Block Table 27 | - [x] Withdrawal Table 28 | 29 | The following diagram shows the relation between these circuits and tables: 30 | 31 | ![](./super_circuit.png) 32 | 33 | **Note**: There are some smaller sub-circuits, like the ECDSA circuit and opcode related 34 | sub-circuits do not interact with other tables and circuits in a way that affects the Super 35 | Circuits, so they are not shown in the diagram for clarity. 36 | 37 | Putting together all sub-circuits in one allows them to easily make lookups to whatever table is 38 | necessary without the need of having them copied. The main drawback is that the Super Circuit proof 39 | is very large, and therefore its verification is costly. To alleviate this problem a new circuit is 40 | introduced: the Root Circuit, whose function is to verify the Super Circuit proof, generating a much 41 | smaller proof which is cheaper to verify. 42 | -------------------------------------------------------------------------------- /specs/super_circuit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacy-scaling-explorations/zkevm-specs/6058c68e133c35224fe9060bcd9b50961594f6d3/specs/super_circuit.png -------------------------------------------------------------------------------- /specs/tx_circuit.rev1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacy-scaling-explorations/zkevm-specs/6058c68e133c35224fe9060bcd9b50961594f6d3/specs/tx_circuit.rev1.png -------------------------------------------------------------------------------- /specs/virtual_state/end_tx.md: -------------------------------------------------------------------------------- 1 | # END_TX 2 | 3 | ## Procedure 4 | 5 | The `end_tx` gadget handles refund and coinbase tip as well as cumulative gas within a block. 6 | 7 | 8 | ## Constraints 9 | 10 | 1. Calculate effective refund (c.f. below list) 11 | 2. Add [(effective_refund + gas_left) * gas_price] back to caller's balance 12 | 3. Check effective tip = tx gas price - base fee 13 | 4. Add gas_used * effective_tip to coinbase's balance (and create coinbase account if needs be) 14 | 5. End transaction 15 | 1. Check that the current cumulative gas used is null if this is the 1st transaction else corresponds to previous transaction's 16 | 2. Increase cumulative gas used by the gas used 17 | 3. If the next execution state is BeginTx 18 | 1. The next transaction's id is correctly incremented 19 | 2. Transition to new context (rw_counter = 9 + transfer.rw - is_first_tx) 20 | 4. Elif the next execution step is EndBlock 21 | 1. Transition to new context (rw_counter = 9 + transfer.rw - is_first_tx, and same call id) 22 | 23 | Gas refund: it is capped to gas_used // MAX_REFUND_QUOTIENT_OF_GAS_USED (c.f. EIP 3529) 24 | 25 | ### Lookups 26 | 27 | #### RW accesses 28 | In all cases, we bump read-write-counter by 9 + transfer.rw - is_first_tx: 29 | - CallContext TxId 30 | - CallContext IsPersistent 31 | - RWLookup Read TxRefund 32 | - AccountRead coinbase's codehash 33 | - UpdateBalance 34 | - AccountWrite caller's balance 35 | - TransferTo 36 | - AccountWrite coinbase's balance 37 | - EndTx 38 | - RWLookup Write TxReceipt.PostStateOrStatus 39 | - RWLookup Write TxReceipt.LogLength 40 | - RWLookup Write current transaction's TxReceipt.CumulativeGasUsed 41 | - If not first tx, RWLookup Read previous transaction's TxReceipt.CumulativeGasUsed 42 | - If next execution state is BeginTx, CallContext TxId (does not increase rw counter) 43 | 44 | 45 | #### Other Lookups 46 | - TxContext Gas 47 | - TxContext GasPrice 48 | - TxContext CallerAddress 49 | - BlockContext BaseFee 50 | - BlockContext Coinbase 51 | 52 | ## Exceptions -------------------------------------------------------------------------------- /src/zkevm_specs/__init__.py: -------------------------------------------------------------------------------- 1 | from . import bytecode_circuit 2 | from . import copy_circuit 3 | from . import evm_circuit 4 | from . import exp_circuit 5 | from . import state_circuit 6 | from . import tx_circuit 7 | from . import util 8 | from . import pi_circuit 9 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/__init__.py: -------------------------------------------------------------------------------- 1 | from .execution import * 2 | from .execution_state import * 3 | from .main import * 4 | from .opcode import * 5 | from .precompile import * 6 | from .step import * 7 | from .table import * 8 | from .typing import * 9 | from .util import * 10 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/add_sub.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | from ..opcode import Opcode 3 | 4 | 5 | def add_sub(instruction: Instruction): 6 | opcode = instruction.opcode_lookup(True) 7 | 8 | is_sub, _ = instruction.pair_select(opcode, Opcode.SUB, Opcode.ADD) 9 | 10 | a = instruction.stack_pop() 11 | b = instruction.stack_pop() 12 | c = instruction.stack_push() 13 | 14 | instruction.constrain_equal_word( 15 | instruction.add_words([instruction.select_word(is_sub, c, a), b])[0], 16 | instruction.select_word(is_sub, a, c), 17 | ) 18 | 19 | instruction.step_state_transition_in_same_context( 20 | opcode, 21 | rw_counter=Transition.delta(3), 22 | program_counter=Transition.delta(1), 23 | stack_pointer=Transition.delta(1), 24 | ) 25 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/addmod.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | from ..opcode import Opcode 3 | from zkevm_specs.util import FQ, Word 4 | 5 | 6 | # Returns 1 when a is lower than b, 0 otherwise 7 | def lt_u256(instruction: Instruction, a: Word, b: Word) -> FQ: 8 | # decode RLC to bytes for a and b 9 | a_lo, a_hi = a.to_lo_hi() 10 | b_lo, b_hi = b.to_lo_hi() 11 | 12 | a_lt_b_lo, _ = instruction.compare(a_lo, b_lo, 16) 13 | a_lt_b_hi, a_eq_b_hi = instruction.compare(a_hi, b_hi, 16) 14 | 15 | a_lt_b = instruction.select( 16 | a_lt_b_hi, FQ(1), instruction.select(a_eq_b_hi * a_lt_b_lo, FQ(1), FQ(0)) 17 | ) 18 | 19 | return a_lt_b 20 | 21 | 22 | def addmod(instruction: Instruction): 23 | opcode = instruction.opcode_lookup(True) 24 | instruction.constrain_equal(opcode, Opcode.ADDMOD) 25 | 26 | a = instruction.stack_pop() 27 | b = instruction.stack_pop() 28 | n = instruction.stack_pop() 29 | pushed_r = instruction.stack_push() 30 | 31 | # witness 32 | if n.int_value() == 0: 33 | a_reduced = a.int_value() 34 | k = 0 35 | d = 0 36 | r = Word((a_reduced + b.int_value()) % (2**256)) 37 | else: 38 | a_reduced = a.int_value() % n.int_value() 39 | k = a.int_value() // n.int_value() 40 | d = (a_reduced + b.int_value()) // n.int_value() 41 | r = pushed_r 42 | 43 | # check a == a_reduced + k * n 44 | overflow = instruction.mul_add_words(Word(k), n, Word(a_reduced), a) 45 | instruction.constrain_zero(overflow) 46 | 47 | # check a_reduced + b ≡ d * n + r in 512 bit space 48 | a_reduced_plus_b, overflow = instruction.add_words([Word(a_reduced), b]) 49 | instruction.mul_add_words_512( 50 | Word(d), n, r, Word.from_lo(overflow) if n.int_value() > 0 else Word(0), a_reduced_plus_b 51 | ) 52 | 53 | # check that r= 32 always returns all zeros 16 | is_msb_sum_zero = instruction.is_zero(FQ(sum(index[1:]))) 17 | 18 | # Byte 0: 19 | # Check byte per byte if we need to copy the value 20 | # to result. We're only directly checking the LSB byte 21 | # of the index here, so also make sure the byte 22 | # is only copied when index < 32. 23 | is_byte_selected = [instruction.is_equal(FQ(index[0]), FQ(31 - idx)) for idx in range(32)] 24 | 25 | selected_byte = FQ(0) 26 | for cell, is_selected in zip(value, is_byte_selected): 27 | selected_byte += is_selected * is_msb_sum_zero * FQ(cell) 28 | 29 | instruction.constrain_equal_word( 30 | Word.from_lo(selected_byte), 31 | c, 32 | ) 33 | 34 | instruction.step_state_transition_in_same_context( 35 | opcode, 36 | rw_counter=Transition.delta(3), 37 | program_counter=Transition.delta(1), 38 | stack_pointer=Transition.delta(1), 39 | ) 40 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/calldatasize.py: -------------------------------------------------------------------------------- 1 | from ...util import Word 2 | from ..instruction import Instruction, Transition 3 | from ..table import CallContextFieldTag 4 | from ..opcode import Opcode 5 | 6 | 7 | def calldatasize(instruction: Instruction): 8 | opcode = instruction.opcode_lookup(True) 9 | instruction.constrain_equal(opcode, Opcode.CALLDATASIZE) 10 | 11 | # check [rw_table, call_context] table for call data length and compare 12 | # against stack top after push. 13 | instruction.constrain_equal_word( 14 | Word.from_lo(instruction.call_context_lookup(CallContextFieldTag.CallDataLength)), 15 | instruction.stack_push(), 16 | ) 17 | 18 | instruction.step_state_transition_in_same_context( 19 | opcode, 20 | rw_counter=Transition.delta(2), 21 | program_counter=Transition.delta(1), 22 | stack_pointer=Transition.delta(-1), 23 | ) 24 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/caller.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | from ..table import CallContextFieldTag 3 | from ..opcode import Opcode 4 | 5 | 6 | def caller(instruction: Instruction): 7 | opcode = instruction.opcode_lookup(True) 8 | instruction.constrain_equal(opcode, Opcode.CALLER) 9 | 10 | address_word = instruction.call_context_lookup_word(CallContextFieldTag.CallerAddress) 11 | # check [rw_table, call_context] table for caller address and compare with 12 | # stack top after push 13 | instruction.constrain_equal_word( 14 | address_word, 15 | instruction.stack_push(), 16 | ) 17 | 18 | instruction.step_state_transition_in_same_context( 19 | opcode, 20 | rw_counter=Transition.delta(2), 21 | program_counter=Transition.delta(1), 22 | stack_pointer=Transition.delta(-1), 23 | ) 24 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/callvalue.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | from ..table import CallContextFieldTag 3 | from ..opcode import Opcode 4 | 5 | 6 | def callvalue(instruction: Instruction): 7 | opcode = instruction.opcode_lookup(True) 8 | instruction.constrain_equal(opcode, Opcode.CALLVALUE) 9 | 10 | # check [rw_table, call_context] table for call value and compare against 11 | # stack top after push. 12 | instruction.constrain_equal_word( 13 | instruction.call_context_lookup_word(CallContextFieldTag.Value), 14 | instruction.stack_push(), 15 | ) 16 | 17 | instruction.step_state_transition_in_same_context( 18 | opcode, 19 | rw_counter=Transition.delta(2), 20 | program_counter=Transition.delta(1), 21 | stack_pointer=Transition.delta(-1), 22 | ) 23 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/codecopy.py: -------------------------------------------------------------------------------- 1 | from ...util import N_BYTES_MEMORY_ADDRESS, FQ 2 | from ..instruction import Instruction, Transition 3 | from ..table import CopyDataTypeTag 4 | 5 | 6 | def codecopy(instruction: Instruction): 7 | opcode = instruction.opcode_lookup(True) 8 | 9 | memory_offset_word, code_offset_word, size_word = ( 10 | instruction.stack_pop(), 11 | instruction.stack_pop(), 12 | instruction.stack_pop(), 13 | ) 14 | 15 | memory_offset, size = instruction.memory_offset_and_length(memory_offset_word, size_word) 16 | code_offset = instruction.word_to_fq(code_offset_word, N_BYTES_MEMORY_ADDRESS) 17 | 18 | code_size = instruction.bytecode_length(instruction.curr.code_hash) 19 | 20 | next_memory_size, memory_expansion_gas_cost = instruction.memory_expansion_dynamic_length( 21 | memory_offset, size 22 | ) 23 | gas_cost = instruction.memory_copier_gas_cost(size, memory_expansion_gas_cost) 24 | 25 | if instruction.is_zero(size) == FQ(0): 26 | copy_rwc_inc, _ = instruction.copy_lookup( 27 | instruction.curr.code_hash, 28 | CopyDataTypeTag.Bytecode, 29 | instruction.curr.call_id, 30 | CopyDataTypeTag.Memory, 31 | code_offset, 32 | code_size, 33 | memory_offset, 34 | size, 35 | instruction.curr.rw_counter + instruction.rw_counter_offset, 36 | ) 37 | else: 38 | copy_rwc_inc = FQ(0) 39 | 40 | instruction.step_state_transition_in_same_context( 41 | opcode, 42 | rw_counter=Transition.delta(instruction.rw_counter_offset + copy_rwc_inc), 43 | program_counter=Transition.delta(1), 44 | stack_pointer=Transition.delta(3), 45 | memory_word_size=Transition.to(next_memory_size), 46 | dynamic_gas_cost=gas_cost, 47 | ) 48 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/codesize.py: -------------------------------------------------------------------------------- 1 | from ...util import Word 2 | from ..instruction import Instruction, Transition 3 | from ..opcode import Opcode 4 | 5 | 6 | def codesize(instruction: Instruction): 7 | opcode = instruction.opcode_lookup(True) 8 | instruction.constrain_equal(opcode, Opcode.CODESIZE) 9 | 10 | code_size = instruction.bytecode_length(instruction.curr.code_hash) 11 | 12 | instruction.constrain_equal_word(Word.from_lo(code_size), instruction.stack_push()) 13 | 14 | instruction.step_state_transition_in_same_context( 15 | opcode, 16 | rw_counter=Transition.delta(1), 17 | program_counter=Transition.delta(1), 18 | stack_pointer=Transition.delta(-1), 19 | ) 20 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/comparator.py: -------------------------------------------------------------------------------- 1 | from ...util import FQ, Word 2 | from ..instruction import Instruction, Transition 3 | from ..opcode import Opcode 4 | 5 | 6 | def cmp(instruction: Instruction): 7 | opcode = instruction.opcode_lookup(True) 8 | 9 | is_eq = instruction.is_equal(opcode, Opcode.EQ) 10 | is_gt = instruction.is_equal(opcode, Opcode.GT) 11 | 12 | a = instruction.stack_pop() 13 | b = instruction.stack_pop() 14 | c = instruction.stack_push() 15 | 16 | # swap a and b if the opcode is GT 17 | (aa, bb) = (b, a) if is_gt == 1 else (a, b) 18 | 19 | a_lo, a_hi = aa.to_lo_hi() 20 | b_lo, b_hi = bb.to_lo_hi() 21 | 22 | # `a[0..16] <= b[0..16]` 23 | lt_lo, eq_lo = instruction.compare(a_lo, b_lo, 16) 24 | 25 | # `a[16..32] <= b[16..32]` 26 | lt_hi, eq_hi = instruction.compare(a_hi, b_hi, 16) 27 | 28 | lt = instruction.select(lt_hi, FQ(1), eq_hi * lt_lo) 29 | eq = eq_lo * eq_hi 30 | 31 | result = eq if is_eq == 1 else lt 32 | 33 | instruction.constrain_equal_word( 34 | Word.from_lo(FQ(result)), 35 | c, 36 | ) 37 | 38 | instruction.step_state_transition_in_same_context( 39 | opcode, 40 | rw_counter=Transition.delta(3), 41 | program_counter=Transition.delta(1), 42 | stack_pointer=Transition.delta(1), 43 | ) 44 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/error_code_store.py: -------------------------------------------------------------------------------- 1 | from zkevm_specs.evm_circuit.table import RW, CallContextFieldTag 2 | from zkevm_specs.util.param import ( 3 | GAS_COST_CODE_DEPOSIT, 4 | MAX_CODE_SIZE, 5 | N_BYTES_MEMORY_ADDRESS, 6 | N_BYTES_STACK, 7 | ) 8 | from ...util import FQ 9 | from ..instruction import Instruction 10 | from ..opcode import Opcode 11 | from ...util import N_BYTES_GAS 12 | 13 | 14 | def error_code_store(instruction: Instruction): 15 | # retrieve op code associated to oog constant error 16 | opcode = instruction.opcode_lookup(True) 17 | # Even though these are `CREATE`/`CREATE2` errors, as we cannot get the contract bytecode length 18 | # when processing these opcodes, 19 | # we handle them in the `RETURN` opcode where the bytecode length is available. 20 | instruction.constrain_equal(opcode, Opcode.RETURN) 21 | 22 | # the call must be coming from CREATE or CREATE2 23 | instruction.constrain_equal(FQ(instruction.curr.is_create), FQ(1)) 24 | 25 | # pop length (which is the bytecode size) from stack 26 | return_length_word = instruction.stack_lookup(RW.Read, 1) 27 | return_length = instruction.word_to_fq(return_length_word, N_BYTES_MEMORY_ADDRESS) 28 | 29 | # current call context can't be readonly 30 | is_static = instruction.call_context_lookup(CallContextFieldTag.IsStatic) 31 | instruction.constrain_equal(is_static, FQ(0)) 32 | 33 | # check if bytecode size exceeds MAX_CODE_SIZE 34 | over_max_code_size, _ = instruction.compare(FQ(MAX_CODE_SIZE), return_length, N_BYTES_STACK) 35 | 36 | # check gas left is less than total gas required 37 | gas_cost_code_store = FQ(GAS_COST_CODE_DEPOSIT) * return_length 38 | insufficient_gas, _ = instruction.compare( 39 | instruction.curr.gas_left, gas_cost_code_store, N_BYTES_GAS 40 | ) 41 | 42 | # make sure this call hits at least one of [CodeStoreOutOfGas, MaxCodeSizeExceeded] 43 | instruction.constrain_not_zero(insufficient_gas + over_max_code_size) 44 | 45 | instruction.constrain_error_state( 46 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter.n 47 | ) 48 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/error_invalid_creation_code.py: -------------------------------------------------------------------------------- 1 | from zkevm_specs.evm_circuit.table import RW 2 | from zkevm_specs.util.param import ( 3 | INVALID_FIRST_BYTE_CONTRACT_CODE, 4 | N_BYTES_MEMORY_ADDRESS, 5 | ) 6 | from ...util import FQ 7 | from ..instruction import Instruction 8 | from ..opcode import Opcode 9 | 10 | 11 | def error_invalid_creation_code(instruction: Instruction): 12 | opcode = instruction.opcode_lookup(True) 13 | 14 | # opcode must be `RETURN` 15 | instruction.constrain_equal(opcode, Opcode.RETURN) 16 | 17 | # the call must be coming from CREATE or CREATE2 18 | instruction.constrain_equal(FQ(instruction.curr.is_create), FQ(1)) 19 | 20 | # pop offset from stack only 21 | return_offset = instruction.word_to_fq(instruction.stack_pop(), N_BYTES_MEMORY_ADDRESS) 22 | 23 | # lookup the first byte of deployed bytecode from memory 24 | first_byte = instruction.memory_lookup(RW.Read, return_offset) 25 | 26 | # verify if the first byte is `0xEF`, which is introduced in EIP-3541 27 | instruction.constrain_equal(first_byte, FQ(INVALID_FIRST_BYTE_CONTRACT_CODE)) 28 | 29 | instruction.constrain_error_state( 30 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter 31 | ) 32 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/error_invalid_jump.py: -------------------------------------------------------------------------------- 1 | from ...util import FQ 2 | from ..instruction import Instruction 3 | from ..opcode import Opcode 4 | from ...util import N_BYTES_PROGRAM_COUNTER 5 | 6 | 7 | def error_invalid_jump(instruction: Instruction): 8 | opcode = instruction.opcode_lookup(True) 9 | # current executing op code must be JUMP or JUMPI 10 | instruction.constrain_in(opcode, [FQ(Opcode.JUMP), FQ(Opcode.JUMPI)]) 11 | _, is_jumpi = instruction.pair_select(opcode, Opcode.JUMP, Opcode.JUMPI) 12 | code_length = instruction.bytecode_length(instruction.curr.code_hash) 13 | dest = instruction.stack_pop() 14 | # if `JUMPI`, pop `condition` 15 | if is_jumpi == FQ(1): 16 | condition = instruction.stack_pop() 17 | # if condition is zero, jump will not happen, so constrain condition not zero 18 | instruction.constrain_not_zero_word(condition) 19 | # lookup value from bytecode table. N_BYTES_PROGRAM_COUNTER is 64 bits 20 | dest_value = instruction.word_to_u64(dest) 21 | 22 | within_range, _ = instruction.compare(dest_value, code_length, N_BYTES_PROGRAM_COUNTER) 23 | 24 | # if not out of range, check `dest` is invalid 25 | if within_range == FQ(1): 26 | value, is_code = instruction.bytecode_lookup_pair(instruction.curr.code_hash, dest_value) 27 | # value is not `JUMPDEST` or `is_code` is false 28 | is_jump_dest = value == Opcode.JUMPDEST 29 | instruction.constrain_zero(is_code * FQ(is_jump_dest)) 30 | 31 | instruction.constrain_error_state( 32 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter 33 | ) 34 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/error_invalid_opcode.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction 2 | 3 | 4 | # Gadget for invalid opcodes. It verifies by a fixed lookup for ResponsibleOpcode. 5 | def error_invalid_opcode(instruction: Instruction): 6 | # Fixed lookup for invalid opcode. 7 | opcode = instruction.opcode_lookup(True) 8 | instruction.responsible_opcode_lookup(opcode) 9 | 10 | instruction.constrain_error_state( 11 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter 12 | ) 13 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/error_oog_account_access.py: -------------------------------------------------------------------------------- 1 | from zkevm_specs.evm_circuit.table import CallContextFieldTag 2 | from zkevm_specs.util.param import ( 3 | GAS_COST_ACCOUNT_COLD_ACCESS, 4 | GAS_COST_WARM_ACCESS, 5 | N_BYTES_ACCOUNT_ADDRESS, 6 | ) 7 | from ...util import FQ 8 | from ..instruction import Instruction 9 | from ..opcode import Opcode 10 | from ...util import N_BYTES_GAS 11 | 12 | 13 | def error_oog_account_access(instruction: Instruction): 14 | # retrieve op code associated to oog constant error 15 | opcode = instruction.opcode_lookup(True) 16 | ( 17 | is_balance, 18 | is_ext_code_size, 19 | is_ext_code_hash, 20 | ) = instruction.multiple_select( 21 | opcode, (Opcode.BALANCE, Opcode.EXTCODESIZE, Opcode.EXTCODEHASH) 22 | ) 23 | 24 | # Constrain opcode must be one of `BALANCE`, `EXTCODESIZE` and `EXTCODEHASH`. 25 | instruction.constrain_equal(is_balance + is_ext_code_size + is_ext_code_hash, FQ(1)) 26 | 27 | # pop `address` 28 | address = instruction.word_to_fq(instruction.stack_pop(), N_BYTES_ACCOUNT_ADDRESS) 29 | 30 | # calculate gas_cost 31 | tx_id = instruction.call_context_lookup(CallContextFieldTag.TxId) 32 | is_warm = instruction.read_account_to_access_list(tx_id, address) 33 | gas_cost = GAS_COST_WARM_ACCESS if is_warm == FQ(1) else GAS_COST_ACCOUNT_COLD_ACCESS 34 | 35 | # check gas left is less than total gas required 36 | insufficient_gas, _ = instruction.compare(instruction.curr.gas_left, FQ(gas_cost), N_BYTES_GAS) 37 | instruction.constrain_equal(insufficient_gas, FQ(1)) 38 | 39 | instruction.constrain_error_state( 40 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter 41 | ) 42 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/error_oog_call.py: -------------------------------------------------------------------------------- 1 | from zkevm_specs.evm_circuit.util.call_gadget import CallGadget 2 | from ...util import FQ 3 | from ..instruction import Instruction 4 | from ..table import CallContextFieldTag 5 | from ...util import N_BYTES_GAS 6 | from ..opcode import Opcode 7 | 8 | 9 | # Handle the corresponding out of gas errors for CALL, CALLCODE, DELEGATECALL 10 | # and STATICCALL opcodes. 11 | def error_oog_call(instruction: Instruction): 12 | # retrieve op code associated to oog call error 13 | opcode = instruction.opcode_lookup(True) 14 | is_call, is_callcode, is_delegatecall, is_staticcall = instruction.multiple_select( 15 | opcode, (Opcode.CALL, Opcode.CALLCODE, Opcode.DELEGATECALL, Opcode.STATICCALL) 16 | ) 17 | 18 | # Constrain opcode must be CALL, CALLCODE, DELEGATECALL or STATICCALL. 19 | instruction.constrain_equal(is_call + is_callcode + is_delegatecall + is_staticcall, FQ(1)) 20 | 21 | tx_id = instruction.call_context_lookup(CallContextFieldTag.TxId) 22 | 23 | # init CallGadget to handle stack vars. 24 | call = CallGadget(instruction, FQ(0), is_call, is_callcode, is_delegatecall, is_staticcall) 25 | 26 | # TODO: handle PrecompiledContract oog cases 27 | 28 | # Add callee to access list 29 | is_warm_access = instruction.read_account_to_access_list(tx_id, call.callee_address) 30 | 31 | # verify gas cost 32 | gas_cost = call.gas_cost(instruction, is_warm_access) 33 | 34 | # verify gas is insufficient 35 | gas_not_enough, _ = instruction.compare(instruction.curr.gas_left, gas_cost, N_BYTES_GAS) 36 | instruction.constrain_equal(gas_not_enough, FQ(1)) 37 | 38 | # Both CALL and CALLCODE opcodes have an extra stack pop `value` relative to 39 | # DELEGATECALL and STATICCALL. 40 | instruction.constrain_error_state( 41 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter 42 | ) 43 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/error_oog_constant.py: -------------------------------------------------------------------------------- 1 | from ...util import FQ 2 | from ..instruction import Instruction, FixedTableTag 3 | from ..opcode import Opcode 4 | from ...util import N_BYTES_GAS 5 | 6 | 7 | def error_oog_constant(instruction: Instruction): 8 | # retrieve op code associated to oog constant error 9 | opcode = instruction.opcode_lookup(True) 10 | const_gas_entry = instruction.fixed_lookup( 11 | FixedTableTag.OpcodeConstantGas, opcode, FQ(Opcode(opcode.expr().n).constant_gas_cost()) 12 | ) 13 | 14 | # check gas left is less than const gas required 15 | gas_not_enough, _ = instruction.compare( 16 | instruction.curr.gas_left, const_gas_entry.value1, N_BYTES_GAS 17 | ) 18 | instruction.constrain_equal(gas_not_enough, FQ(1)) 19 | 20 | instruction.constrain_error_state( 21 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter 22 | ) 23 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/error_oog_dynamic_memory_expansion.py: -------------------------------------------------------------------------------- 1 | from ...util import FQ 2 | from ..instruction import Instruction 3 | from ..opcode import Opcode 4 | from ...util import N_BYTES_GAS 5 | 6 | 7 | def error_oog_dynamic_memory_expansion(instruction: Instruction): 8 | # retrieve op code associated to oog error 9 | opcode = instruction.opcode_lookup(True) 10 | is_return, is_revert = instruction.multiple_select(opcode, (Opcode.RETURN, Opcode.REVERT)) 11 | 12 | # Constrain opcode must be RETURN or REVERT. 13 | instruction.constrain_equal(is_return + is_revert, FQ(1)) 14 | 15 | # get gas cost of memory expansion 16 | offset_word = instruction.stack_pop() 17 | size_word = instruction.stack_pop() 18 | offset, size = instruction.memory_offset_and_length(offset_word, size_word) 19 | (_, memory_expansion_gas_cost) = instruction.memory_expansion(offset, size) 20 | 21 | # check gas left is less than total gas required 22 | gas_not_enough, _ = instruction.compare( 23 | instruction.curr.gas_left, memory_expansion_gas_cost, N_BYTES_GAS 24 | ) 25 | instruction.constrain_equal(gas_not_enough, FQ(1)) 26 | 27 | instruction.constrain_error_state( 28 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter 29 | ) 30 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/error_oog_exp.py: -------------------------------------------------------------------------------- 1 | from zkevm_specs.evm_circuit.table import RW 2 | from zkevm_specs.util.param import ( 3 | GAS_COST_EXP_PER_BYTE, 4 | GAS_COST_SLOW, 5 | ) 6 | from ...util import FQ 7 | from ..instruction import Instruction 8 | from ..opcode import Opcode 9 | from ...util import N_BYTES_GAS 10 | 11 | 12 | def error_oog_exp(instruction: Instruction): 13 | # retrieve op code associated to oog exp error 14 | opcode = instruction.opcode_lookup(True) 15 | instruction.constrain_equal(opcode, Opcode.EXP) 16 | 17 | exponent = instruction.stack_lookup(RW.Read, 1) 18 | 19 | # get total gas cost 20 | exponent_byte_size = instruction.byte_size(exponent) 21 | dynamic_gas_cost = GAS_COST_EXP_PER_BYTE * exponent_byte_size 22 | 23 | # check gas left is less than total gas required 24 | insufficient_gas, _ = instruction.compare( 25 | instruction.curr.gas_left, dynamic_gas_cost + GAS_COST_SLOW, N_BYTES_GAS 26 | ) 27 | instruction.constrain_equal(insufficient_gas, FQ(1)) 28 | 29 | instruction.constrain_error_state( 30 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter 31 | ) 32 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/error_oog_log.py: -------------------------------------------------------------------------------- 1 | from zkevm_specs.util.param import GAS_COST_LOG, GAS_COST_LOGDATA, N_BYTES_MEMORY_ADDRESS 2 | from ...util import FQ 3 | from ..instruction import Instruction 4 | from ..opcode import Opcode 5 | from ...util import N_BYTES_GAS 6 | 7 | 8 | def error_oog_log(instruction: Instruction): 9 | # retrieve op code associated to oog constant error 10 | opcode = instruction.opcode_lookup(True) 11 | # constrain op in [log0, log4] range 12 | instruction.range_lookup(opcode - Opcode.LOG0, 5) 13 | 14 | # pop `mstart`, `msize` from stack 15 | mstart = instruction.word_to_fq(instruction.stack_pop(), N_BYTES_MEMORY_ADDRESS) 16 | msize = instruction.word_to_fq(instruction.stack_pop(), N_BYTES_MEMORY_ADDRESS) 17 | 18 | # get total gas cost 19 | _, memory_expansion_gas = instruction.memory_expansion_dynamic_length(mstart, msize) 20 | gas_cost = ( 21 | GAS_COST_LOG 22 | + GAS_COST_LOG * (opcode - Opcode.LOG0) 23 | + GAS_COST_LOGDATA * msize 24 | + memory_expansion_gas 25 | ) 26 | 27 | # check gas left is less than total gas required 28 | insufficient_gas, _ = instruction.compare(instruction.curr.gas_left, gas_cost, N_BYTES_GAS) 29 | instruction.constrain_equal(insufficient_gas, FQ(1)) 30 | 31 | instruction.constrain_error_state( 32 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter 33 | ) 34 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/error_oog_sha3.py: -------------------------------------------------------------------------------- 1 | from zkevm_specs.util.param import ( 2 | GAS_COST_COPY_SHA3, 3 | GAS_COST_SHA3, 4 | N_BYTES_MEMORY_WORD_SIZE, 5 | ) 6 | from ...util import FQ 7 | from ..instruction import Instruction 8 | from ..opcode import Opcode 9 | from ...util import N_BYTES_GAS 10 | 11 | 12 | def error_oog_sha3(instruction: Instruction): 13 | # retrieve op code associated to oog constant error 14 | opcode = instruction.opcode_lookup(True) 15 | instruction.constrain_equal(opcode, Opcode.SHA3) 16 | 17 | # pop `offset` and `size` 18 | offset_word = instruction.stack_pop() 19 | size_word = instruction.stack_pop() 20 | memory_offset, copy_size = instruction.memory_offset_and_length(offset_word, size_word) 21 | 22 | # dynamic gas cost includes memory_expansion_cost + GAS_COST_COPY_SHA3 * minimum_word_size 23 | _, memory_expansion_cost = instruction.memory_expansion_dynamic_length(memory_offset, copy_size) 24 | minimum_word_size, _ = instruction.constant_divmod( 25 | copy_size.expr() + 31, FQ(32), N_BYTES_MEMORY_WORD_SIZE 26 | ) 27 | dynamic_gas = minimum_word_size * FQ(GAS_COST_COPY_SHA3) + memory_expansion_cost 28 | 29 | # check gas left is less than total gas required 30 | insufficient_gas, _ = instruction.compare( 31 | instruction.curr.gas_left, FQ(GAS_COST_SHA3) + dynamic_gas, N_BYTES_GAS 32 | ) 33 | instruction.constrain_equal(insufficient_gas, FQ(1)) 34 | 35 | instruction.constrain_error_state( 36 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter 37 | ) 38 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/error_oog_static_memory_expansion.py: -------------------------------------------------------------------------------- 1 | from zkevm_specs.util.param import GAS_COST_FASTEST, N_BYTES_MEMORY_ADDRESS 2 | from ...util import FQ 3 | from ..instruction import Instruction 4 | from ..opcode import Opcode 5 | from ...util import N_BYTES_GAS 6 | 7 | 8 | def error_oog_static_memory_expansion(instruction: Instruction): 9 | # retrieve op code associated to oog constant error 10 | opcode = instruction.opcode_lookup(True) 11 | is_mload, is_mstore, is_mstore8 = instruction.multiple_select( 12 | opcode, (Opcode.MLOAD, Opcode.MSTORE, Opcode.MSTORE8) 13 | ) 14 | 15 | # Constrain opcode must be one of MLOAD, MSTORE or MSTORE8. 16 | instruction.constrain_equal(is_mload + is_mstore + is_mstore8, FQ(1)) 17 | 18 | # pop `offset` 19 | offset = instruction.word_to_fq(instruction.stack_pop(), N_BYTES_MEMORY_ADDRESS) 20 | 21 | # get total gas cost 22 | constant_gas = FQ(GAS_COST_FASTEST) 23 | size = 1 if is_mstore8 else 32 24 | _, memory_expansion_gas = instruction.memory_expansion_dynamic_length(offset, FQ(size)) 25 | gas_cost = constant_gas + memory_expansion_gas 26 | 27 | # check gas left is less than total gas required 28 | insufficient_gas, _ = instruction.compare(instruction.curr.gas_left, gas_cost, N_BYTES_GAS) 29 | instruction.constrain_equal(insufficient_gas, FQ(1)) 30 | 31 | instruction.constrain_error_state( 32 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter 33 | ) 34 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/error_return_data_out_of_bound.py: -------------------------------------------------------------------------------- 1 | from zkevm_specs.evm_circuit.table import CallContextFieldTag, RW 2 | from zkevm_specs.util.param import MAX_N_BYTES 3 | from ..instruction import Instruction 4 | from ..opcode import Opcode 5 | 6 | 7 | def error_return_data_out_of_bound(instruction: Instruction): 8 | opcode = instruction.opcode_lookup(True) 9 | instruction.constrain_equal(opcode, Opcode.RETURNDATACOPY) 10 | 11 | # skip first stack value, `memOffset` 12 | data_offset = instruction.word_to_fq(instruction.stack_lookup(RW.Read, 1), MAX_N_BYTES) 13 | length = instruction.word_to_fq(instruction.stack_lookup(RW.Read, 2), MAX_N_BYTES) 14 | 15 | # get the length of last callee's return data 16 | return_data_length = instruction.call_context_lookup( 17 | CallContextFieldTag.LastCalleeReturnDataLength, RW.Read 18 | ) 19 | 20 | # verify if this call meets any one of error conditions 21 | end = data_offset + length 22 | is_data_offset_u64_overflow = instruction.is_u64_overflow(data_offset) 23 | is_end_u64_overflow = instruction.is_u64_overflow(end) 24 | is_end_over_return_data_len, _ = instruction.compare(return_data_length, end, MAX_N_BYTES) 25 | 26 | instruction.constrain_not_zero( 27 | is_data_offset_u64_overflow + is_end_u64_overflow + is_end_over_return_data_len 28 | ) 29 | 30 | instruction.constrain_error_state( 31 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter 32 | ) 33 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/error_stack.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction 2 | 3 | 4 | def error_stack(instruction: Instruction): 5 | # retrieve op code associated to stack error 6 | opcode = instruction.opcode_lookup(True) 7 | instruction.responsible_opcode_lookup(opcode, instruction.curr.stack_pointer) 8 | 9 | instruction.constrain_error_state( 10 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter 11 | ) 12 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/error_write_protection.py: -------------------------------------------------------------------------------- 1 | from zkevm_specs.evm_circuit.table import RW, CallContextFieldTag 2 | 3 | from ...util import FQ 4 | from ..instruction import Instruction 5 | from ..opcode import Opcode 6 | 7 | 8 | # There are some op codes which modify state. 9 | # There are `[SSTORE, CREATE, CREATE2, CALL, SELFDESTRUCT, LOG0, LOG1, LOG2, LOG3, LOG4]`. 10 | # When execution call context is read only (static call) and internal call, 11 | # these op codes running will encounter write protection error 12 | def error_write_protection(instruction: Instruction): 13 | # retrieve op code associated to oog constant error 14 | opcode = instruction.opcode_lookup(True) 15 | ( 16 | is_sstore, 17 | is_create, 18 | is_create2, 19 | is_call, 20 | is_selfdestruct, 21 | is_log0, 22 | is_log1, 23 | is_log2, 24 | is_log3, 25 | is_log4, 26 | ) = instruction.multiple_select( 27 | opcode, 28 | ( 29 | Opcode.SSTORE, 30 | Opcode.CREATE, 31 | Opcode.CREATE2, 32 | Opcode.CALL, 33 | Opcode.SELFDESTRUCT, 34 | Opcode.LOG0, 35 | Opcode.LOG1, 36 | Opcode.LOG2, 37 | Opcode.LOG3, 38 | Opcode.LOG4, 39 | ), 40 | ) 41 | 42 | # Spec 1. opcode must be [SSTORE, CREATE, CREATE2, CALL, SELFDESTRUCT, LOG0, LOG1, LOG2, LOG3, LOG4]. 43 | instruction.constrain_equal( 44 | is_sstore 45 | + is_create 46 | + is_create2 47 | + is_call 48 | + is_selfdestruct 49 | + is_log0 50 | + is_log1 51 | + is_log2 52 | + is_log3 53 | + is_log4, 54 | FQ(1), 55 | ) 56 | 57 | # Spec 2. 58 | # current call context must be readonly 59 | is_static = instruction.call_context_lookup(CallContextFieldTag.IsStatic) 60 | instruction.constrain_equal(is_static, FQ(1)) 61 | 62 | # Spec 3. for `CALL` op code, `value` must be non-zero 63 | if is_call == FQ(1): 64 | # the first 2 stacks are `gas`` and `callee_address` and the 3rd one is `value` 65 | value = instruction.stack_lookup(RW.Read, 2) 66 | instruction.constrain_not_zero_word(value) 67 | 68 | instruction.constrain_error_state( 69 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter 70 | ) 71 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/exp.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | from zkevm_specs.util import FQ, GAS_COST_EXP_PER_BYTE, Word 3 | 4 | 5 | def exp(instruction: Instruction): 6 | opcode = instruction.opcode_lookup(True) 7 | 8 | base = instruction.stack_pop() 9 | exponent = instruction.stack_pop() 10 | exponentiation = instruction.stack_push() 11 | 12 | base_lo, base_hi = base.to_lo_hi() 13 | exponent_lo, exponent_hi = exponent.to_lo_hi() 14 | exponentiation_lo, exponentiation_hi = exponentiation.to_lo_hi() 15 | 16 | exponent_is_zero = instruction.is_zero(exponent_hi) * instruction.is_zero(exponent_lo) 17 | exponent_is_one = instruction.is_zero(exponent_hi) * instruction.is_equal(exponent_lo, FQ.one()) 18 | 19 | if exponent_is_zero == FQ.one(): 20 | instruction.constrain_equal(exponentiation_lo, FQ.one()) 21 | instruction.constrain_zero(exponentiation_hi) 22 | elif exponent_is_one == FQ.one(): 23 | instruction.constrain_equal(exponentiation_lo, base_lo) 24 | instruction.constrain_equal(exponentiation_hi, base_hi) 25 | else: 26 | base_limbs = base.to_64s() 27 | identifier = FQ(instruction.curr.rw_counter + instruction.rw_counter_offset) 28 | single_step = instruction.is_zero(exponent_hi) * instruction.is_equal(exponent_lo, FQ(2)) 29 | 30 | # lookup to enforce the is_first step 31 | res = instruction.exp_lookup(identifier, single_step, base_limbs, exponent) 32 | # lookup to enforce the is_last step 33 | int_res = instruction.exp_lookup(identifier, FQ.one(), base_limbs, Word((FQ(2), FQ.zero()))) 34 | # intermediary result should be base^2 35 | # constrain base * base + 0 == base^2 36 | instruction.mul_add_words(base, base, Word(0), int_res) 37 | 38 | # constrain exponentiation result to what we looked up from the exp table. 39 | instruction.constrain_equal_word(res, exponentiation) 40 | 41 | exponent_byte_size = instruction.byte_size(exponent) 42 | dynamic_gas_cost = GAS_COST_EXP_PER_BYTE * exponent_byte_size 43 | 44 | instruction.step_state_transition_in_same_context( 45 | opcode, 46 | program_counter=Transition.delta(1), 47 | rw_counter=Transition.delta(3), 48 | stack_pointer=Transition.delta(1), 49 | dynamic_gas_cost=dynamic_gas_cost, 50 | ) 51 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/extcodehash.py: -------------------------------------------------------------------------------- 1 | from ...util import EXTRA_GAS_COST_ACCOUNT_COLD_ACCESS, FQ 2 | from ..instruction import Instruction, Transition 3 | from ..opcode import Opcode 4 | from ..table import AccountFieldTag, CallContextFieldTag 5 | 6 | 7 | def extcodehash(instruction: Instruction): 8 | opcode = instruction.opcode_lookup(True) 9 | instruction.constrain_equal(opcode, Opcode.EXTCODEHASH) 10 | 11 | address = instruction.word_to_address(instruction.stack_pop()) 12 | 13 | tx_id = instruction.call_context_lookup(CallContextFieldTag.TxId) 14 | is_warm = instruction.add_account_to_access_list(tx_id, address, instruction.reversion_info()) 15 | 16 | # We already define code_hash to be 0 when the account doesn't exist. 17 | code_hash = instruction.account_read_word(address, AccountFieldTag.CodeHash) 18 | 19 | instruction.constrain_equal_word( 20 | code_hash, 21 | instruction.stack_push(), 22 | ) 23 | 24 | instruction.step_state_transition_in_same_context( 25 | opcode, 26 | rw_counter=Transition.delta(7), 27 | program_counter=Transition.delta(1), 28 | stack_pointer=Transition.same(), 29 | dynamic_gas_cost=instruction.select(is_warm, FQ(0), FQ(EXTRA_GAS_COST_ACCOUNT_COLD_ACCESS)), 30 | ) 31 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/extcodesize.py: -------------------------------------------------------------------------------- 1 | from ...util import ( 2 | EXTRA_GAS_COST_ACCOUNT_COLD_ACCESS, 3 | FQ, 4 | Word, 5 | ) 6 | from ..instruction import Instruction, Transition 7 | from ..opcode import Opcode 8 | from ..table import AccountFieldTag, CallContextFieldTag 9 | 10 | 11 | def extcodesize(instruction: Instruction): 12 | opcode = instruction.opcode_lookup(True) 13 | instruction.constrain_equal(opcode, Opcode.EXTCODESIZE) 14 | 15 | address = instruction.word_to_address(instruction.stack_pop()) 16 | 17 | tx_id = instruction.call_context_lookup(CallContextFieldTag.TxId) 18 | is_warm = instruction.add_account_to_access_list(tx_id, address, instruction.reversion_info()) 19 | 20 | code_hash = instruction.account_read_word(address, AccountFieldTag.CodeHash) 21 | # Check account existence with code_hash != 0 22 | exists = FQ(1) - instruction.is_zero_word(code_hash) 23 | 24 | if exists == 1: 25 | code_size = instruction.bytecode_length(code_hash) 26 | else: # exists == 0 27 | code_size = FQ(0) 28 | 29 | instruction.constrain_equal_word( 30 | Word.from_lo(instruction.select(exists, code_size, FQ(0))), 31 | instruction.stack_push(), 32 | ) 33 | 34 | instruction.step_state_transition_in_same_context( 35 | opcode, 36 | rw_counter=Transition.delta(7), 37 | program_counter=Transition.delta(1), 38 | stack_pointer=Transition.same(), 39 | dynamic_gas_cost=instruction.select(is_warm, FQ(0), FQ(EXTRA_GAS_COST_ACCOUNT_COLD_ACCESS)), 40 | reversible_write_counter=Transition.delta(1), 41 | ) 42 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/gas.py: -------------------------------------------------------------------------------- 1 | from ...util import Word 2 | from ..instruction import Instruction, Transition 3 | from ..opcode import Opcode 4 | 5 | 6 | def gas(instruction: Instruction): 7 | opcode = instruction.opcode_lookup(True) 8 | instruction.constrain_equal(opcode, Opcode.GAS) 9 | 10 | instruction.constrain_equal_word( 11 | Word.from_lo(instruction.curr.gas_left - Opcode.GAS.constant_gas_cost()), 12 | instruction.stack_push(), 13 | ) 14 | 15 | instruction.step_state_transition_in_same_context( 16 | opcode, 17 | rw_counter=Transition.delta(1), 18 | program_counter=Transition.delta(1), 19 | stack_pointer=Transition.delta(-1), 20 | ) 21 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/gasprice.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | from ..opcode import Opcode 3 | from ..table import CallContextFieldTag, TxContextFieldTag 4 | 5 | 6 | def gasprice(instruction: Instruction): 7 | tx_id = instruction.call_context_lookup(CallContextFieldTag.TxId) 8 | 9 | opcode = instruction.opcode_lookup(True) 10 | instruction.constrain_equal(opcode, Opcode.GASPRICE) 11 | 12 | # fetch gasPrice from rw table 13 | # fetch from the Tx context table the gasPrice 14 | instruction.constrain_equal_word( 15 | instruction.tx_context_lookup_word(tx_id, TxContextFieldTag.GasPrice), 16 | instruction.stack_push(), 17 | ) 18 | 19 | instruction.step_state_transition_in_same_context( 20 | opcode, 21 | rw_counter=Transition.delta(2), 22 | program_counter=Transition.delta(1), 23 | stack_pointer=Transition.delta(-1), 24 | ) 25 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/iszero.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | from ...util import Word 3 | 4 | 5 | def iszero(instruction: Instruction): 6 | opcode = instruction.opcode_lookup(True) 7 | 8 | value = instruction.stack_pop() 9 | 10 | instruction.constrain_equal_word( 11 | Word.from_lo(instruction.is_zero_word(value)), 12 | instruction.stack_push(), 13 | ) 14 | 15 | instruction.step_state_transition_in_same_context( 16 | opcode, 17 | rw_counter=Transition.delta(2), 18 | program_counter=Transition.delta(1), 19 | stack_pointer=Transition.same(), 20 | ) 21 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/jump.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | from ..opcode import Opcode 3 | 4 | 5 | def jump(instruction: Instruction): 6 | opcode = instruction.opcode_lookup(True) 7 | instruction.constrain_equal(opcode, Opcode.JUMP) 8 | 9 | # Do not check 'dest' is within MaxCodeSize(24576) range in successful case 10 | # as byte code lookup can ensure it. 11 | dest_word = instruction.stack_pop() 12 | instruction.constrain_zero(dest_word.hi.expr()) 13 | dest = dest_word.lo.expr() 14 | 15 | # Verify `dest` is code within byte code table 16 | instruction.constrain_equal(Opcode.JUMPDEST, instruction.opcode_lookup_at(dest, True)) 17 | 18 | instruction.step_state_transition_in_same_context( 19 | opcode, 20 | rw_counter=Transition.delta(1), 21 | program_counter=Transition.to(dest), 22 | stack_pointer=Transition.delta(1), 23 | ) 24 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/jumpi.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | from ..opcode import Opcode 3 | from zkevm_specs.util import FQ 4 | 5 | 6 | def jumpi(instruction: Instruction): 7 | opcode = instruction.opcode_lookup(True) 8 | instruction.constrain_equal(opcode, Opcode.JUMPI) 9 | 10 | # Do not check 'dest' is within MaxCodeSize(24576) range in successful case 11 | # as byte code lookup can ensure it. 12 | dest_word = instruction.stack_pop() 13 | instruction.constrain_zero(dest_word.hi.expr()) 14 | dest = dest_word.lo.expr() 15 | 16 | cond = instruction.stack_pop() 17 | 18 | # check `cond` is zero or not 19 | if instruction.is_zero_word(cond): 20 | pc_diff = FQ(1) 21 | else: 22 | pc_diff = dest - instruction.curr.program_counter 23 | # assert Opcode.JUMPDEST == instruction.opcode_lookup_at(dest_value, True) 24 | instruction.constrain_equal(Opcode.JUMPDEST, instruction.opcode_lookup_at(dest, True)) 25 | 26 | instruction.step_state_transition_in_same_context( 27 | opcode, 28 | rw_counter=Transition.delta(2), 29 | program_counter=Transition.delta(pc_diff), 30 | stack_pointer=Transition.delta(2), 31 | ) 32 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/memory.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | from ..opcode import Opcode 3 | from ..table import RW 4 | from ...util import FQ 5 | 6 | 7 | def memory(instruction: Instruction): 8 | opcode = instruction.opcode_lookup(True) 9 | 10 | address = instruction.word_to_address(instruction.stack_pop()) 11 | 12 | is_mload = instruction.is_equal(opcode, Opcode.MLOAD) 13 | is_mstore8 = instruction.is_equal(opcode, Opcode.MSTORE8) 14 | is_store = FQ(1) - is_mload 15 | is_not_mstore8 = FQ(1) - is_mstore8 16 | 17 | value = instruction.stack_push() if is_mload == FQ(1) else instruction.stack_pop() 18 | value_le_bytes = value.to_le_bytes() 19 | 20 | memory_offset = instruction.curr.memory_word_size 21 | next_memory_size, memory_expansion_gas_cost = instruction.memory_expansion( 22 | memory_offset, address.expr() + FQ(1) + (is_not_mstore8 * FQ(31)) 23 | ) 24 | 25 | if is_mstore8 == FQ(1): 26 | instruction.is_equal(instruction.memory_lookup(RW.Write, address), FQ(value_le_bytes[0])) 27 | 28 | if is_not_mstore8 == FQ(1): 29 | for idx in range(32): 30 | instruction.is_equal( 31 | instruction.memory_lookup( 32 | RW.Write if is_store == FQ(1) else RW.Read, address.expr() + idx 33 | ), 34 | FQ(value_le_bytes[31 - idx]), 35 | ) 36 | 37 | instruction.step_state_transition_in_same_context( 38 | opcode, 39 | rw_counter=Transition.delta(34 - (is_mstore8 * 31)), 40 | program_counter=Transition.delta(1), 41 | stack_pointer=Transition.delta(is_store * 2), 42 | memory_word_size=Transition.to(next_memory_size), 43 | dynamic_gas_cost=memory_expansion_gas_cost, 44 | ) 45 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/msize.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | from ...util import N_BYTES_WORD, Word 3 | 4 | 5 | def msize(instruction: Instruction): 6 | opcode = instruction.opcode_lookup(True) 7 | 8 | instruction.constrain_equal_word( 9 | Word.from_lo(instruction.curr.memory_word_size * N_BYTES_WORD), instruction.stack_push() 10 | ) 11 | 12 | instruction.step_state_transition_in_same_context( 13 | opcode, 14 | rw_counter=Transition.delta(1), 15 | program_counter=Transition.delta(1), 16 | stack_pointer=Transition.delta(-1), 17 | ) 18 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/not_.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition, FixedTableTag 2 | from ...util import FQ 3 | 4 | 5 | def not_opcode(instruction: Instruction): 6 | opcode = instruction.opcode_lookup(True) 7 | 8 | a = instruction.stack_pop() 9 | a_le_bytes = a.to_le_bytes() 10 | b = instruction.stack_push() 11 | b_le_bytes = b.to_le_bytes() 12 | 13 | for i in range(32): 14 | byte_a = FQ(a_le_bytes[i]) 15 | byte_b = FQ(b_le_bytes[i]) 16 | instruction.fixed_lookup(FixedTableTag.BitwiseXor, byte_a, byte_b, FQ(255)) 17 | 18 | instruction.step_state_transition_in_same_context( 19 | opcode, 20 | rw_counter=Transition.delta(2), 21 | program_counter=Transition.delta(1), 22 | stack_pointer=Transition.same(), 23 | ) 24 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/origin.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | from ..opcode import Opcode 3 | from ..table import CallContextFieldTag, TxContextFieldTag 4 | 5 | 6 | def origin(instruction: Instruction): 7 | tx_id = instruction.call_context_lookup(CallContextFieldTag.TxId) 8 | 9 | opcode = instruction.opcode_lookup(True) 10 | instruction.constrain_equal(opcode, Opcode.ORIGIN) 11 | 12 | address_word = instruction.tx_context_lookup_word(tx_id, TxContextFieldTag.CallerAddress) 13 | instruction.constrain_equal_word( 14 | address_word, 15 | instruction.stack_push(), 16 | ) 17 | 18 | instruction.step_state_transition_in_same_context( 19 | opcode, 20 | rw_counter=Transition.delta(2), 21 | program_counter=Transition.delta(1), 22 | stack_pointer=Transition.delta(-1), 23 | ) 24 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/pop.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | 3 | 4 | def pop(instruction: Instruction): 5 | opcode = instruction.opcode_lookup(True) 6 | 7 | y = instruction.stack_pop() 8 | 9 | instruction.step_state_transition_in_same_context( 10 | opcode, 11 | rw_counter=Transition.delta(1), 12 | program_counter=Transition.delta(1), 13 | stack_pointer=Transition.delta(1), 14 | ) 15 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/precompiles/ecadd.py: -------------------------------------------------------------------------------- 1 | from zkevm_specs.evm_circuit.instruction import Instruction 2 | from zkevm_specs.evm_circuit.table import ( 3 | CallContextFieldTag, 4 | EccOpTag, 5 | FixedTableTag, 6 | ) 7 | from zkevm_specs.util import FQ, Word, Bn254AddGas 8 | 9 | 10 | def ecAdd(instruction: Instruction): 11 | is_success = instruction.call_context_lookup(CallContextFieldTag.IsSuccess) 12 | address_word = instruction.call_context_lookup_word(CallContextFieldTag.CalleeAddress) 13 | address = instruction.word_to_address(address_word) 14 | instruction.fixed_lookup( 15 | FixedTableTag.PrecompileInfo, 16 | FQ(instruction.curr.execution_state), 17 | address, 18 | FQ(Bn254AddGas), 19 | ) 20 | 21 | # Get p, q and out from aux_data 22 | px: Word = instruction.curr.aux_data[0] 23 | py: Word = instruction.curr.aux_data[1] 24 | qx: Word = instruction.curr.aux_data[2] 25 | qy: Word = instruction.curr.aux_data[3] 26 | outx: FQ = instruction.curr.aux_data[4] 27 | outy: FQ = instruction.curr.aux_data[5] 28 | 29 | # if is_success is false, outx and outy are zero 30 | if is_success == FQ.zero(): 31 | instruction.constrain_zero(outx) 32 | instruction.constrain_zero(outy) 33 | 34 | # ecc table lookup 35 | instruction.ecc_lookup(FQ(EccOpTag.Add), px, py, qx, qy, FQ.zero(), outx, outy, is_success) 36 | 37 | # consume all the gas if is_success is false 38 | gas_left = FQ.zero() 39 | if is_success == FQ(1): 40 | gas_left = instruction.curr.gas_left - FQ(Bn254AddGas) 41 | 42 | # Restore caller state to next StepState 43 | instruction.step_state_transition_to_restored_context( 44 | rw_counter_delta=instruction.rw_counter_offset, 45 | return_data_offset=FQ.zero(), 46 | return_data_length=FQ(64) if is_success == FQ(1) else FQ.zero(), 47 | gas_left=gas_left, 48 | ) 49 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/precompiles/ecmul.py: -------------------------------------------------------------------------------- 1 | from zkevm_specs.evm_circuit.instruction import Instruction 2 | from zkevm_specs.evm_circuit.table import ( 3 | CallContextFieldTag, 4 | EccOpTag, 5 | FixedTableTag, 6 | RW, 7 | ) 8 | from zkevm_specs.util import FQ, Word, Bn254ScalarMulGas 9 | 10 | 11 | def ecMul(instruction: Instruction): 12 | is_success = instruction.call_context_lookup(CallContextFieldTag.IsSuccess, RW.Read) 13 | address_word = instruction.call_context_lookup_word(CallContextFieldTag.CalleeAddress) 14 | address = instruction.word_to_address(address_word) 15 | instruction.fixed_lookup( 16 | FixedTableTag.PrecompileInfo, 17 | FQ(instruction.curr.execution_state), 18 | address, 19 | FQ(Bn254ScalarMulGas), 20 | ) 21 | 22 | # Get p, s and out from aux_data 23 | px: Word = instruction.curr.aux_data[0] 24 | py: Word = instruction.curr.aux_data[1] 25 | s: Word = instruction.curr.aux_data[2] 26 | outx: FQ = instruction.curr.aux_data[3] 27 | outy: FQ = instruction.curr.aux_data[4] 28 | 29 | # output is zero if 30 | # - is_success is false, 31 | # - scalar is zero or 32 | # - p is an infinite point 33 | if ( 34 | is_success == FQ.zero() 35 | or s.int_value() == 0 36 | or (px.int_value() == 0 and py.int_value() == 0) 37 | ): 38 | instruction.constrain_zero(outx) 39 | instruction.constrain_zero(outy) 40 | 41 | # ecc table lookup 42 | instruction.ecc_lookup(FQ(EccOpTag.Mul), px, py, s, Word(0), FQ.zero(), outx, outy, is_success) 43 | 44 | # consume all the gas if is_success is false 45 | gas_left = FQ.zero() 46 | if is_success == FQ(1): 47 | gas_left = instruction.curr.gas_left - FQ(Bn254ScalarMulGas) 48 | 49 | # Restore caller state to next StepState 50 | instruction.step_state_transition_to_restored_context( 51 | rw_counter_delta=instruction.rw_counter_offset, 52 | return_data_offset=FQ.zero(), 53 | return_data_length=FQ(64) if is_success == FQ(1) else FQ.zero(), 54 | gas_left=gas_left, 55 | ) 56 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/precompiles/error_oog_precompile.py: -------------------------------------------------------------------------------- 1 | from zkevm_specs.evm_circuit.execution.precompiles.ecpairing import BYTES_PER_PAIRING 2 | from zkevm_specs.evm_circuit.instruction import Instruction 3 | from zkevm_specs.evm_circuit.precompile import Precompile 4 | from zkevm_specs.evm_circuit.table import CallContextFieldTag 5 | from zkevm_specs.util import FQ 6 | from zkevm_specs.util.param import N_BYTES_GAS, Bn254PairingPerPointGas, IdentityPerWordGas 7 | 8 | 9 | def error_oog_precompile(instruction: Instruction): 10 | address_word = instruction.call_context_lookup_word(CallContextFieldTag.CalleeAddress) 11 | address = instruction.word_to_address(address_word) 12 | calldata_len = instruction.call_context_lookup(CallContextFieldTag.CallDataLength) 13 | 14 | # the address must be one of precompiles 15 | instruction.constrain_equal(instruction.precompile(address), FQ.one()) 16 | 17 | # TODO: Handle OOG of SHA256, RIPEMD160, BIGMODEXP and BLAKE2F. 18 | ### total gas cost 19 | # constant gas cost 20 | precompile = Precompile(address) 21 | gas_cost = precompile.base_gas_cost() 22 | # dynamic gas cost 23 | if precompile == Precompile.BN254PAIRING: 24 | pairs = calldata_len / BYTES_PER_PAIRING 25 | gas_cost += Bn254PairingPerPointGas * pairs 26 | elif precompile == Precompile.DATACOPY: 27 | gas_cost += instruction.memory_copier_gas_cost(calldata_len, FQ(0), IdentityPerWordGas) 28 | 29 | # check gas left is less than total gas required 30 | insufficient_gas, _ = instruction.compare(instruction.curr.gas_left, gas_cost, N_BYTES_GAS) 31 | instruction.constrain_equal(insufficient_gas, FQ(1)) 32 | 33 | instruction.constrain_error_state( 34 | instruction.rw_counter_offset + instruction.curr.reversible_write_counter 35 | ) 36 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/push.py: -------------------------------------------------------------------------------- 1 | from ...util import N_BYTES_PROGRAM_COUNTER 2 | from ..instruction import Instruction, Transition 3 | from ..opcode import Opcode 4 | 5 | 6 | def push(instruction: Instruction): 7 | opcode = instruction.opcode_lookup(True) 8 | num_pushed = opcode - Opcode.PUSH0 9 | code_length = instruction.bytecode_length(instruction.curr.code_hash) 10 | code_length_left = code_length - instruction.curr.program_counter - 1 11 | is_out_of_bound, _ = instruction.compare(code_length_left, num_pushed, N_BYTES_PROGRAM_COUNTER) 12 | num_padding = is_out_of_bound * (num_pushed - code_length_left) 13 | 14 | value = instruction.stack_push() 15 | value_le_bytes = value.to_le_bytes() 16 | is_pushed = instruction.continuous_selectors(num_pushed, 32) 17 | is_padding = instruction.continuous_selectors(num_padding, 32) 18 | 19 | for idx in range(32): 20 | if is_pushed[idx] * (1 - is_padding[idx]) == 1: 21 | index = instruction.curr.program_counter + num_pushed - idx 22 | instruction.constrain_equal( 23 | value_le_bytes[idx], instruction.opcode_lookup_at(index, False) 24 | ) 25 | else: 26 | instruction.constrain_zero(value_le_bytes[idx]) 27 | 28 | instruction.step_state_transition_in_same_context( 29 | opcode, 30 | rw_counter=Transition.delta(1), 31 | program_counter=Transition.delta(1 + num_pushed), 32 | stack_pointer=Transition.delta(-1), 33 | ) 34 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/returndatacopy.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | from ..table import CallContextFieldTag, RW, CopyDataTypeTag 3 | from zkevm_specs.util import N_BYTES_MEMORY_WORD_SIZE 4 | 5 | 6 | def returndatacopy(instruction: Instruction): 7 | opcode = instruction.opcode_lookup(True) 8 | 9 | memory_offset_word, offset_word, size_word = ( 10 | instruction.stack_pop(), 11 | instruction.stack_pop(), 12 | instruction.stack_pop(), 13 | ) 14 | 15 | last_callee_id = instruction.call_context_lookup(CallContextFieldTag.LastCalleeId) 16 | return_data_length = instruction.call_context_lookup( 17 | CallContextFieldTag.LastCalleeReturnDataLength, RW.Read 18 | ) 19 | return_data_offset = instruction.call_context_lookup( 20 | CallContextFieldTag.LastCalleeReturnDataOffset, RW.Read 21 | ) 22 | 23 | # out-of-bound-check 24 | instruction.range_check( 25 | return_data_length 26 | - (instruction.word_to_fq(offset_word, 8) + instruction.word_to_fq(size_word, 8)), 27 | N_BYTES_MEMORY_WORD_SIZE, 28 | ) 29 | 30 | memory_offset, size = instruction.memory_offset_and_length(memory_offset_word, size_word) 31 | next_memory_size, memory_expansion_gas_cost = instruction.memory_expansion_dynamic_length( 32 | memory_offset, size 33 | ) 34 | gas_cost = instruction.memory_copier_gas_cost(size, memory_expansion_gas_cost) 35 | 36 | copy_rwc_inc, _ = instruction.copy_lookup( 37 | last_callee_id, 38 | CopyDataTypeTag.Memory, 39 | instruction.curr.call_id, 40 | CopyDataTypeTag.Memory, 41 | return_data_offset, 42 | return_data_offset + size, 43 | memory_offset, 44 | size, 45 | instruction.curr.rw_counter + instruction.rw_counter_offset, 46 | ) 47 | 48 | assert copy_rwc_inc == size * 2 49 | instruction.step_state_transition_in_same_context( 50 | opcode, 51 | rw_counter=Transition.delta(instruction.rw_counter_offset + copy_rwc_inc), 52 | program_counter=Transition.delta(1), 53 | stack_pointer=Transition.delta(3), 54 | memory_word_size=Transition.to(next_memory_size), 55 | dynamic_gas_cost=gas_cost, 56 | ) 57 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/returndatasize.py: -------------------------------------------------------------------------------- 1 | from ..opcode import Opcode 2 | from ...util import Word, FQ 3 | from ..instruction import Instruction, Transition 4 | from ..table import CallContextFieldTag 5 | from ..opcode import Opcode 6 | 7 | 8 | def returndatasize(instruction: Instruction): 9 | opcode = instruction.opcode_lookup(True) 10 | instruction.constrain_equal(opcode, Opcode.RETURNDATASIZE) 11 | 12 | # check [rw_table, call_context] table for last call return data length and compare 13 | # against stack top after push. 14 | instruction.constrain_equal_word( 15 | Word( 16 | ( 17 | instruction.call_context_lookup(CallContextFieldTag.LastCalleeReturnDataLength), 18 | FQ(0), 19 | ) 20 | ), 21 | instruction.stack_push(), 22 | ) 23 | 24 | instruction.step_state_transition_in_same_context( 25 | opcode, 26 | rw_counter=Transition.delta(2), 27 | program_counter=Transition.delta(1), 28 | stack_pointer=Transition.delta(-1), 29 | ) 30 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/selfbalance.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | from ..table import AccountFieldTag, CallContextFieldTag 3 | from ..opcode import Opcode 4 | 5 | 6 | def selfbalance(instruction: Instruction): 7 | opcode = instruction.opcode_lookup(True) 8 | instruction.constrain_equal(opcode, Opcode.SELFBALANCE) 9 | 10 | callee_address_word = instruction.call_context_lookup_word(CallContextFieldTag.CalleeAddress) 11 | callee_address = instruction.word_to_address(callee_address_word) 12 | balance = instruction.account_read_word(callee_address, AccountFieldTag.Balance) 13 | instruction.constrain_equal_word(instruction.stack_push(), balance) 14 | 15 | instruction.step_state_transition_in_same_context( 16 | opcode, 17 | rw_counter=Transition.delta(3), 18 | program_counter=Transition.delta(1), 19 | stack_pointer=Transition.delta(-1), 20 | ) 21 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/sha3.py: -------------------------------------------------------------------------------- 1 | from ..instruction import Instruction, Transition 2 | from ..table import CopyDataTypeTag 3 | from zkevm_specs.util import FQ, GAS_COST_COPY_SHA3 4 | 5 | 6 | def sha3(instruction: Instruction): 7 | opcode = instruction.opcode_lookup(True) 8 | 9 | # byte offset in memory. 10 | offset = instruction.stack_pop() 11 | # byte size to read in memory. 12 | size = instruction.stack_pop() 13 | # sha3 value pushed to stack. 14 | sha3_value = instruction.stack_push() 15 | 16 | # convert RLC encoded stack elements to FQ. 17 | memory_offset, length = instruction.memory_offset_and_length(offset, size) 18 | 19 | if instruction.is_zero(length) == FQ.zero(): 20 | copy_rwc_inc, rlc_acc = instruction.copy_lookup( 21 | instruction.curr.call_id, 22 | CopyDataTypeTag.Memory, 23 | instruction.curr.call_id, 24 | CopyDataTypeTag.RlcAcc, 25 | memory_offset, 26 | memory_offset + length, 27 | FQ.zero(), 28 | length, 29 | instruction.curr.rw_counter + instruction.rw_counter_offset, 30 | ) 31 | else: 32 | copy_rwc_inc, rlc_acc = FQ.zero(), FQ.zero() 33 | 34 | keccak256_output = instruction.keccak_lookup(length, rlc_acc) 35 | instruction.constrain_equal_word( 36 | keccak256_output, 37 | sha3_value, 38 | ) 39 | 40 | # calculate memory expansion gas costs. 41 | next_memory_size, memory_expansion_gas_cost = instruction.memory_expansion_dynamic_length( 42 | memory_offset, length 43 | ) 44 | gas_cost = instruction.memory_copier_gas_cost( 45 | length, memory_expansion_gas_cost, GAS_COST_COPY_SHA3 46 | ) 47 | 48 | instruction.step_state_transition_in_same_context( 49 | opcode, 50 | rw_counter=Transition.delta(instruction.rw_counter_offset + copy_rwc_inc), 51 | program_counter=Transition.delta(1), 52 | stack_pointer=Transition.delta(1), # 2 stack reads and 1 stack write 53 | memory_word_size=Transition.to(next_memory_size), 54 | dynamic_gas_cost=gas_cost, 55 | ) 56 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/execution/slt_sgt.py: -------------------------------------------------------------------------------- 1 | from ...util import FQ 2 | from ..instruction import Instruction, Transition 3 | from ..opcode import Opcode 4 | 5 | 6 | def scmp(instruction: Instruction): 7 | opcode = instruction.opcode_lookup(True) 8 | 9 | is_sgt, _ = instruction.pair_select(opcode, Opcode.SGT, Opcode.SLT) 10 | 11 | a = instruction.stack_pop() 12 | b = instruction.stack_pop() 13 | c = instruction.stack_push() 14 | 15 | # swap a and b if the opcode is SGT 16 | aa = b if is_sgt == 1 else a 17 | bb = a if is_sgt == 1 else b 18 | 19 | # decode RLC to bytes for a and b 20 | a8s = aa.to_le_bytes() 21 | b8s = bb.to_le_bytes() 22 | c8s = c.to_le_bytes() 23 | 24 | a_lo, a_hi = aa.to_lo_hi() 25 | b_lo, b_hi = bb.to_lo_hi() 26 | assert c8s[31] == 0 27 | cc = instruction.bytes_to_fq(c8s[:31]) 28 | 29 | a_lt_b_lo, _ = instruction.compare(a_lo, b_lo, 16) 30 | a_lt_b_hi, a_eq_b_hi = instruction.compare(a_hi, b_hi, 16) 31 | 32 | a_lt_b = instruction.select( 33 | a_lt_b_hi, FQ(1), instruction.select(a_eq_b_hi * a_lt_b_lo, FQ(1), FQ(0)) 34 | ) 35 | 36 | # a < 0 and b >= 0 => a < b == true 37 | if a8s[31].n >= 128 and b8s[31].n < 128: 38 | instruction.constrain_equal(cc, FQ(1)) 39 | # b < 0 and a >= 0 => a < b == false 40 | elif b8s[31].n >= 128 and a8s[31].n < 128: 41 | instruction.constrain_equal(cc, FQ(0)) 42 | # (a < 0 and b < 0) or (a >= 0 and b >= 0) 43 | else: 44 | instruction.constrain_equal(cc, a_lt_b) 45 | 46 | instruction.step_state_transition_in_same_context( 47 | opcode, 48 | rw_counter=Transition.delta(3), 49 | program_counter=Transition.delta(1), 50 | stack_pointer=Transition.delta(1), 51 | ) 52 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/main.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from ..util import FQ 4 | from .execution import EXECUTION_STATE_IMPL 5 | from .execution_state import ExecutionState 6 | from .instruction import Instruction 7 | from .step import StepState 8 | from .table import Tables 9 | 10 | 11 | DUMMY_STEP_STATE = StepState(ExecutionState.EndBlock, rw_counter=-1) 12 | 13 | 14 | def verify_steps( 15 | tables: Tables, 16 | steps: List[StepState], 17 | begin_with_first_step: bool = False, 18 | end_with_last_step: bool = False, 19 | success: bool = True, 20 | ): 21 | if end_with_last_step: 22 | steps.append(DUMMY_STEP_STATE) 23 | 24 | exception = None 25 | for idx, (curr, next) in enumerate(zip(steps, steps[1:])): 26 | try: 27 | verify_step( 28 | Instruction( 29 | tables=tables, 30 | curr=curr, 31 | next=next, 32 | is_first_step=begin_with_first_step and idx == 0, 33 | is_last_step=end_with_last_step and idx == len(steps) - 2, 34 | ) 35 | ) 36 | except AssertionError as e: 37 | exception = e 38 | break 39 | if success: 40 | if exception: 41 | raise exception 42 | assert exception is None 43 | else: 44 | assert exception is not None 45 | 46 | 47 | def verify_step(instruction: Instruction): 48 | if instruction.is_first_step: 49 | instruction.constrain_in( 50 | instruction.curr.execution_state, 51 | [FQ(ExecutionState.BeginTx), FQ(ExecutionState.EndBlock)], 52 | ) 53 | instruction.constrain_equal(instruction.curr.rw_counter, FQ(1)) 54 | 55 | if instruction.is_last_step: 56 | instruction.constrain_equal(instruction.curr.execution_state, ExecutionState.EndBlock) 57 | else: 58 | instruction.constrain_execution_state_transition() 59 | 60 | if instruction.curr.execution_state in EXECUTION_STATE_IMPL: 61 | EXECUTION_STATE_IMPL[instruction.curr.execution_state](instruction) 62 | else: 63 | raise NotImplementedError 64 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/util/__init__.py: -------------------------------------------------------------------------------- 1 | from .memory_gadget import BufferReaderGadget 2 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/util/memory_gadget.py: -------------------------------------------------------------------------------- 1 | from ...util import N_BYTES_MEMORY_ADDRESS, FQ, Expression 2 | from ..instruction import Instruction 3 | 4 | 5 | class BufferReaderGadget: 6 | def __init__( 7 | self, inst: Instruction, max_bytes: int, addr_start: FQ, addr_end: FQ, bytes_left: FQ 8 | ): 9 | self.instruction = inst 10 | self.selectors = inst.continuous_selectors(bytes_left, max_bytes) 11 | # Here we are just generating witness data, no need to use inst here 12 | self.bound_dist = [FQ(max(0, addr_end.n - addr_start.n - i)) for i in range(max_bytes)] 13 | self.bound_dist_is_zero = [inst.is_zero(bound_dist) for bound_dist in self.bound_dist] 14 | 15 | # constraint on bound_dist[0] 16 | inst.constrain_equal( 17 | self.bound_dist[0], addr_end - inst.min(addr_end, addr_start, N_BYTES_MEMORY_ADDRESS) 18 | ) 19 | # constraints on bound_dist[1:] 20 | for i in range(1, max_bytes): 21 | diff = self.bound_dist[i - 1] - self.bound_dist[i] 22 | # diff == 0 if bound_dist[i - 1] == 0; otherwise 1 23 | inst.constrain_equal( 24 | diff, inst.select(self.bound_dist_is_zero[i - 1], FQ.zero(), FQ.one()) 25 | ) 26 | 27 | def constrain_byte(self, idx: int, byte: Expression): 28 | # bytes[idx] == 0 when selectors[idx] == 0 29 | self.instruction.constrain_zero(byte * (1 - self.selectors[idx])) 30 | # bytes[idx] == 0 when bound_dist[idx] == 0 31 | self.instruction.constrain_zero(byte * self.bound_dist_is_zero[idx]) 32 | 33 | def num_bytes(self) -> FQ: 34 | return FQ(sum(self.selectors)) 35 | 36 | def has_data(self, idx: int) -> FQ: 37 | return self.selectors[idx] 38 | 39 | def read_flag(self, idx: int) -> FQ: 40 | return self.selectors[idx] * (1 - self.bound_dist_is_zero[idx]) 41 | -------------------------------------------------------------------------------- /src/zkevm_specs/evm_circuit/util/precompile_gadget.py: -------------------------------------------------------------------------------- 1 | from zkevm_specs.evm_circuit.precompile import Precompile 2 | from ...util import FQ 3 | from ..instruction import Instruction 4 | 5 | 6 | class PrecompileGadget: 7 | address: FQ 8 | 9 | def __init__( 10 | self, 11 | instruction: Instruction, 12 | callee_addr: FQ, 13 | precompile_return_len: FQ, 14 | calldata_len: FQ, 15 | ): 16 | # next execution state must be one of precompiles 17 | instruction.constrain_equal(instruction.precompile(callee_addr), FQ.one()) 18 | 19 | ### precompiles' specific constraints 20 | precompile = Precompile(callee_addr) 21 | if precompile == Precompile.DATACOPY: 22 | # input length is the same as return data length 23 | instruction.constrain_equal(precompile_return_len, calldata_len) 24 | elif precompile == Precompile.ECRECOVER: 25 | # The input different from 128 is allowed and is then right padded with zeros 26 | # We only ensure hat the return length is either 32 or 0. 27 | is_128 = instruction.is_equal(precompile_return_len, FQ(32)) 28 | is_zero = instruction.is_equal(precompile_return_len, FQ.zero()) 29 | instruction.constrain_equal(is_128 + is_zero, FQ.one()) 30 | elif precompile == Precompile.BN254ADD: 31 | # input length is 128 bytes 32 | instruction.constrain_equal(calldata_len, FQ(128)) 33 | elif precompile == Precompile.BN254SCALARMUL: 34 | # input length is 96 bytes 35 | instruction.constrain_equal(calldata_len, FQ(96)) 36 | elif precompile == Precompile.BN254PAIRING: 37 | # input length is 192 * n bytes 38 | instruction.constrain_equal(FQ(calldata_len.n % 192), FQ.zero()) 39 | -------------------------------------------------------------------------------- /src/zkevm_specs/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/privacy-scaling-explorations/zkevm-specs/6058c68e133c35224fe9060bcd9b50961594f6d3/src/zkevm_specs/py.typed -------------------------------------------------------------------------------- /src/zkevm_specs/util/__init__.py: -------------------------------------------------------------------------------- 1 | from .arithmetic import * 2 | from .constraint_system import * 3 | from .hash import * 4 | from .param import * 5 | from .typing import * 6 | from .ec import * 7 | from .tables import * 8 | -------------------------------------------------------------------------------- /src/zkevm_specs/util/hash.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | from Crypto.Hash import keccak 3 | 4 | from .typing import U256 5 | 6 | 7 | def keccak256(data: Union[str, bytes, bytearray]) -> bytes: 8 | if isinstance(data, str): 9 | data = bytes.fromhex(data) 10 | return keccak.new(digest_bits=256).update(data).digest() 11 | 12 | 13 | EMPTY_HASH: U256 = U256(int.from_bytes(keccak256(""), "big")) 14 | EMPTY_CODE_HASH: U256 = EMPTY_HASH 15 | EMPTY_TRIE_HASH: U256 = U256(int.from_bytes(keccak256("80"), "big")) 16 | -------------------------------------------------------------------------------- /src/zkevm_specs/util/tables.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, Set 2 | from .arithmetic import ( 3 | FQ, 4 | RLC, 5 | Word, 6 | ) 7 | from eth_utils import keccak 8 | 9 | 10 | class KeccakTable: 11 | # The columns are: (is_enabled, input_rlc, input_len, output) 12 | table: Set[Tuple[FQ, FQ, FQ, Word]] 13 | 14 | def __init__(self): 15 | self.table = set() 16 | self.table.add((FQ(0), FQ(0), FQ(0), Word(0))) # Add all 0s row 17 | 18 | def add(self, input: bytes, keccak_randomness: FQ): 19 | output = keccak(input) 20 | self.table.add( 21 | ( 22 | FQ(1), 23 | RLC(bytes(reversed(input)), keccak_randomness, n_bytes=64).expr(), 24 | FQ(len(input)), 25 | Word(output), 26 | ) 27 | ) 28 | 29 | def lookup(self, is_enabled: FQ, input_rlc: FQ, input_len: FQ, output: Word, assert_msg: str): 30 | assert (is_enabled, input_rlc, input_len, output) in self.table, ( 31 | f"{assert_msg}: {(is_enabled, input_rlc, input_len, output)} " 32 | + "not found in the lookup table" 33 | ) 34 | -------------------------------------------------------------------------------- /src/zkevm_specs/util/typing.py: -------------------------------------------------------------------------------- 1 | from typing import NewType 2 | 3 | U8 = NewType("U8", int) 4 | U64 = NewType("U64", int) 5 | U128 = NewType("U128", int) 6 | U160 = NewType("U160", int) 7 | U256 = NewType("U256", int) 8 | 9 | 10 | def is_circuit_code(func): 11 | """ 12 | A no-op decorator just to mark the function 13 | """ 14 | 15 | def wrapper(*args, **kargs): 16 | return func(*args, **kargs) 17 | 18 | return wrapper 19 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | # This will make pytest rewrite the asserts in `src/zkevm_specs/` so that they 4 | # print the values used in the assertions automatically. 5 | pytest.register_assert_rewrite("zkevm_specs") 6 | -------------------------------------------------------------------------------- /tests/evm/test_add_sub.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | ExecutionState, 5 | StepState, 6 | Opcode, 7 | verify_steps, 8 | Tables, 9 | Block, 10 | Bytecode, 11 | RWDictionary, 12 | ) 13 | from zkevm_specs.util import Word 14 | from common import generate_nasty_tests, rand_word 15 | 16 | 17 | TESTING_DATA = [ 18 | (Opcode.ADD, 0x030201, 0x060504), 19 | (Opcode.SUB, 0x090705, 0x060504), 20 | (Opcode.ADD, rand_word(), rand_word()), 21 | (Opcode.SUB, rand_word(), rand_word()), 22 | ] 23 | 24 | generate_nasty_tests(TESTING_DATA, (Opcode.ADD, Opcode.SUB)) 25 | 26 | 27 | @pytest.mark.parametrize("opcode, a, b", TESTING_DATA) 28 | def test_add_sub(opcode: Opcode, a: int, b: int): 29 | c = Word((a + b if opcode == Opcode.ADD else a - b) % 2**256) 30 | a = Word(a) 31 | b = Word(b) 32 | 33 | bytecode = Bytecode().add(a, b).stop() if opcode == Opcode.ADD else Bytecode().sub(a, b).stop() 34 | bytecode_hash = Word(bytecode.hash()) 35 | 36 | tables = Tables( 37 | block_table=set(Block().table_assignments()), 38 | tx_table=set(), 39 | withdrawal_table=set(), 40 | bytecode_table=set(bytecode.table_assignments()), 41 | rw_table=set( 42 | RWDictionary(9) 43 | .stack_read(1, 1022, a) 44 | .stack_read(1, 1023, b) 45 | .stack_write(1, 1023, c) 46 | .rws 47 | ), 48 | ) 49 | 50 | verify_steps( 51 | tables=tables, 52 | steps=[ 53 | StepState( 54 | execution_state=ExecutionState.ADD, 55 | rw_counter=9, 56 | call_id=1, 57 | is_root=True, 58 | is_create=False, 59 | code_hash=bytecode_hash, 60 | program_counter=66, 61 | stack_pointer=1022, 62 | gas_left=3, 63 | ), 64 | StepState( 65 | execution_state=ExecutionState.STOP, 66 | rw_counter=12, 67 | call_id=1, 68 | is_root=True, 69 | is_create=False, 70 | code_hash=bytecode_hash, 71 | program_counter=67, 72 | stack_pointer=1023, 73 | gas_left=0, 74 | ), 75 | ], 76 | ) 77 | -------------------------------------------------------------------------------- /tests/evm/test_addmod.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | ExecutionState, 5 | StepState, 6 | verify_steps, 7 | Tables, 8 | Block, 9 | Bytecode, 10 | RWDictionary, 11 | ) 12 | from zkevm_specs.util import Word 13 | 14 | MAXU256 = (2**256) - 1 15 | 16 | TESTING_DATA = [ 17 | (MAXU256, MAXU256, 0), 18 | (MAXU256, MAXU256, 1), 19 | (MAXU256, MAXU256, MAXU256), 20 | (MAXU256, 0, MAXU256), 21 | (MAXU256, 1, MAXU256), 22 | (MAXU256, 1, 1), 23 | (MAXU256, 0, 0), 24 | (0, 1, 0), 25 | (0, 0, 0), 26 | ] 27 | 28 | 29 | @pytest.mark.parametrize("a, b, n", TESTING_DATA) 30 | def test_addmod(a: int, b: int, n: int): 31 | if n == 0: 32 | r = Word(0) 33 | else: 34 | r = Word((a + b) % n) 35 | 36 | a = Word(a) 37 | b = Word(b) 38 | n = Word(n) 39 | 40 | bytecode = Bytecode().addmod(a, b, n).stop() 41 | bytecode_hash = Word(bytecode.hash()) 42 | 43 | tables = Tables( 44 | block_table=set(Block().table_assignments()), 45 | tx_table=set(), 46 | withdrawal_table=set(), 47 | bytecode_table=set(bytecode.table_assignments()), 48 | rw_table=set( 49 | RWDictionary(9) 50 | .stack_read(1, 1021, a) 51 | .stack_read(1, 1022, b) 52 | .stack_read(1, 1023, n) 53 | .stack_write(1, 1023, r) 54 | .rws 55 | ), 56 | ) 57 | 58 | verify_steps( 59 | tables=tables, 60 | steps=[ 61 | StepState( 62 | execution_state=ExecutionState.ADDMOD, 63 | rw_counter=9, 64 | call_id=1, 65 | is_root=True, 66 | is_create=False, 67 | code_hash=bytecode_hash, 68 | program_counter=99, 69 | stack_pointer=1021, 70 | gas_left=8, 71 | ), 72 | StepState( 73 | execution_state=ExecutionState.STOP, 74 | rw_counter=13, 75 | call_id=1, 76 | is_root=True, 77 | is_create=False, 78 | code_hash=bytecode_hash, 79 | program_counter=100, 80 | stack_pointer=1023, 81 | gas_left=0, 82 | ), 83 | ], 84 | ) 85 | -------------------------------------------------------------------------------- /tests/evm/test_address.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | Bytecode, 5 | CallContextFieldTag, 6 | ExecutionState, 7 | RWDictionary, 8 | StepState, 9 | Tables, 10 | verify_steps, 11 | ) 12 | from zkevm_specs.util import Word, U160 13 | from common import rand_address 14 | 15 | TESTING_DATA = ( 16 | 0x00, 17 | 0x10, 18 | 0x030201, 19 | 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 20 | rand_address(), 21 | ) 22 | 23 | 24 | @pytest.mark.parametrize("address", TESTING_DATA) 25 | def test_address(address: U160): 26 | bytecode = Bytecode().address() 27 | bytecode_hash = Word(bytecode.hash()) 28 | 29 | tables = Tables( 30 | block_table=set(), 31 | tx_table=set(), 32 | withdrawal_table=set(), 33 | bytecode_table=set(bytecode.table_assignments()), 34 | rw_table=set( 35 | RWDictionary(9) 36 | .call_context_read(1, CallContextFieldTag.CalleeAddress, Word(address)) 37 | .stack_write(1, 1023, Word(address)) 38 | .rws 39 | ), 40 | ) 41 | 42 | verify_steps( 43 | tables=tables, 44 | steps=[ 45 | StepState( 46 | execution_state=ExecutionState.ADDRESS, 47 | rw_counter=9, 48 | call_id=1, 49 | is_root=True, 50 | is_create=False, 51 | code_hash=bytecode_hash, 52 | program_counter=0, 53 | stack_pointer=1024, 54 | gas_left=2, 55 | ), 56 | StepState( 57 | execution_state=ExecutionState.STOP, 58 | rw_counter=11, 59 | call_id=1, 60 | is_root=True, 61 | is_create=False, 62 | code_hash=bytecode_hash, 63 | program_counter=1, 64 | stack_pointer=1023, 65 | gas_left=0, 66 | ), 67 | ], 68 | ) 69 | -------------------------------------------------------------------------------- /tests/evm/test_byte.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from random import randrange 3 | from zkevm_specs.util import Word 4 | from zkevm_specs.evm_circuit import ( 5 | ExecutionState, 6 | StepState, 7 | verify_steps, 8 | Tables, 9 | Block, 10 | Bytecode, 11 | RWDictionary, 12 | ) 13 | 14 | 15 | def gen_test_data(): 16 | u256_max = 115792089237316195423570985008687907853269984665640564039457584007913129639935 17 | x = randrange(u256_max) 18 | return [(i, x, (x >> (248 - i * 8)) & 0xFF) for i in range(1, 32)] 19 | 20 | 21 | @pytest.mark.parametrize("a, b, c", gen_test_data()) 22 | def test_byte(a: int, b: int, c: int): 23 | a = Word(a) 24 | b = Word(b) 25 | c = Word(c) 26 | 27 | bytecode = Bytecode().byte(a, b).stop() 28 | bytecode_hash = Word(bytecode.hash()) 29 | 30 | tables = Tables( 31 | block_table=set(Block().table_assignments()), 32 | tx_table=set(), 33 | withdrawal_table=set(), 34 | bytecode_table=set(bytecode.table_assignments()), 35 | rw_table=set( 36 | RWDictionary(9) 37 | .stack_read(1, 1022, a) 38 | .stack_read(1, 1023, b) 39 | .stack_write(1, 1023, c) 40 | .rws 41 | ), 42 | ) 43 | 44 | verify_steps( 45 | tables=tables, 46 | steps=[ 47 | StepState( 48 | execution_state=ExecutionState.BYTE, 49 | rw_counter=9, 50 | call_id=1, 51 | is_root=True, 52 | is_create=False, 53 | code_hash=bytecode_hash, 54 | program_counter=66, 55 | stack_pointer=1022, 56 | gas_left=3, 57 | ), 58 | StepState( 59 | execution_state=ExecutionState.STOP, 60 | rw_counter=12, 61 | call_id=1, 62 | is_root=True, 63 | is_create=False, 64 | code_hash=bytecode_hash, 65 | program_counter=67, 66 | stack_pointer=1023, 67 | gas_left=0, 68 | ), 69 | ], 70 | ) 71 | -------------------------------------------------------------------------------- /tests/evm/test_calldatasize.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | ExecutionState, 5 | StepState, 6 | verify_steps, 7 | Tables, 8 | CallContextFieldTag, 9 | Bytecode, 10 | RWDictionary, 11 | ) 12 | from zkevm_specs.util import Word, U64 13 | 14 | 15 | TESTING_DATA = ( 16 | 0x00, 17 | 0x10, 18 | 0x302010, 19 | ) 20 | 21 | 22 | @pytest.mark.parametrize("calldatasize", TESTING_DATA) 23 | def test_calldatasize(calldatasize: U64): 24 | bytecode = Bytecode().calldatasize() 25 | bytecode_hash = Word(bytecode.hash()) 26 | 27 | tables = Tables( 28 | block_table=set(), 29 | tx_table=set(), 30 | withdrawal_table=set(), 31 | bytecode_table=set(bytecode.table_assignments()), 32 | rw_table=set( 33 | RWDictionary(9) 34 | .call_context_read(1, CallContextFieldTag.CallDataLength, calldatasize) 35 | .stack_write( 36 | 1, 37 | 1023, 38 | Word( 39 | calldatasize, 40 | ), 41 | ) 42 | .rws 43 | ), 44 | ) 45 | 46 | verify_steps( 47 | tables=tables, 48 | steps=[ 49 | StepState( 50 | execution_state=ExecutionState.CALLDATASIZE, 51 | rw_counter=9, 52 | call_id=1, 53 | is_root=True, 54 | is_create=False, 55 | code_hash=bytecode_hash, 56 | program_counter=0, 57 | stack_pointer=1024, 58 | gas_left=2, 59 | ), 60 | StepState( 61 | execution_state=ExecutionState.STOP, 62 | rw_counter=11, 63 | call_id=1, 64 | is_root=True, 65 | is_create=False, 66 | code_hash=bytecode_hash, 67 | program_counter=1, 68 | stack_pointer=1023, 69 | gas_left=0, 70 | ), 71 | ], 72 | ) 73 | -------------------------------------------------------------------------------- /tests/evm/test_caller.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | ExecutionState, 5 | StepState, 6 | verify_steps, 7 | Tables, 8 | CallContextFieldTag, 9 | Bytecode, 10 | RWDictionary, 11 | ) 12 | from zkevm_specs.util import Word, U160 13 | from common import rand_address 14 | 15 | 16 | TESTING_DATA = ( 17 | 0x00, 18 | 0x10, 19 | 0x030201, 20 | 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 21 | rand_address(), 22 | ) 23 | 24 | 25 | @pytest.mark.parametrize("caller", TESTING_DATA) 26 | def test_caller(caller: U160): 27 | bytecode = Bytecode().caller() 28 | bytecode_hash = Word(bytecode.hash()) 29 | 30 | tables = Tables( 31 | block_table=set(), 32 | tx_table=set(), 33 | withdrawal_table=set(), 34 | bytecode_table=set(bytecode.table_assignments()), 35 | rw_table=set( 36 | RWDictionary(9) 37 | .call_context_read(1, CallContextFieldTag.CallerAddress, Word(caller)) 38 | .stack_write(1, 1023, Word(caller)) 39 | .rws 40 | ), 41 | ) 42 | 43 | verify_steps( 44 | tables=tables, 45 | steps=[ 46 | StepState( 47 | execution_state=ExecutionState.CALLER, 48 | rw_counter=9, 49 | call_id=1, 50 | is_root=True, 51 | is_create=False, 52 | code_hash=bytecode_hash, 53 | program_counter=0, 54 | stack_pointer=1024, 55 | gas_left=2, 56 | ), 57 | StepState( 58 | execution_state=ExecutionState.STOP, 59 | rw_counter=11, 60 | call_id=1, 61 | is_root=True, 62 | is_create=False, 63 | code_hash=bytecode_hash, 64 | program_counter=1, 65 | stack_pointer=1023, 66 | gas_left=0, 67 | ), 68 | ], 69 | ) 70 | -------------------------------------------------------------------------------- /tests/evm/test_callvalue.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | ExecutionState, 5 | StepState, 6 | verify_steps, 7 | Tables, 8 | CallContextFieldTag, 9 | Bytecode, 10 | RWDictionary, 11 | ) 12 | from zkevm_specs.util import Word, U256 13 | 14 | 15 | TESTING_DATA = ( 16 | 0x00, 17 | 0x10, 18 | 0x302010, 19 | 0xF0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F, 20 | ) 21 | 22 | 23 | @pytest.mark.parametrize("callvalue", TESTING_DATA) 24 | def test_callvalue(callvalue: U256): 25 | bytecode = Bytecode().callvalue() 26 | bytecode_hash = Word(bytecode.hash()) 27 | 28 | tables = Tables( 29 | block_table=set(), 30 | tx_table=set(), 31 | withdrawal_table=set(), 32 | bytecode_table=set(bytecode.table_assignments()), 33 | rw_table=set( 34 | RWDictionary(9) 35 | .call_context_read(1, CallContextFieldTag.Value, Word(callvalue)) 36 | .stack_write(1, 1023, Word(callvalue)) 37 | .rws 38 | ), 39 | ) 40 | 41 | verify_steps( 42 | tables=tables, 43 | steps=[ 44 | StepState( 45 | execution_state=ExecutionState.CALLVALUE, 46 | rw_counter=9, 47 | call_id=1, 48 | is_root=True, 49 | is_create=False, 50 | code_hash=bytecode_hash, 51 | program_counter=0, 52 | stack_pointer=1024, 53 | gas_left=2, 54 | ), 55 | StepState( 56 | execution_state=ExecutionState.STOP, 57 | rw_counter=11, 58 | call_id=1, 59 | is_root=True, 60 | is_create=False, 61 | code_hash=bytecode_hash, 62 | program_counter=1, 63 | stack_pointer=1023, 64 | gas_left=0, 65 | ), 66 | ], 67 | ) 68 | -------------------------------------------------------------------------------- /tests/evm/test_codesize.py: -------------------------------------------------------------------------------- 1 | from zkevm_specs.evm_circuit import ( 2 | Bytecode, 3 | ExecutionState, 4 | RWDictionary, 5 | StepState, 6 | Tables, 7 | verify_steps, 8 | ) 9 | from zkevm_specs.util import Word 10 | 11 | 12 | def test_codesize(): 13 | bytecode = Bytecode().codesize().stop() 14 | codesize = len(bytecode.code) 15 | bytecode_hash = Word(bytecode.hash()) 16 | 17 | tables = Tables( 18 | block_table=set(), 19 | tx_table=set(), 20 | withdrawal_table=set(), 21 | bytecode_table=set(bytecode.table_assignments()), 22 | rw_table=set(RWDictionary(9).stack_write(1, 1023, Word(codesize)).rws), 23 | ) 24 | 25 | verify_steps( 26 | tables=tables, 27 | steps=[ 28 | StepState( 29 | execution_state=ExecutionState.CODESIZE, 30 | rw_counter=9, 31 | call_id=1, 32 | is_root=True, 33 | is_create=False, 34 | code_hash=bytecode_hash, 35 | program_counter=0, 36 | stack_pointer=1024, 37 | gas_left=2, 38 | ), 39 | StepState( 40 | execution_state=ExecutionState.STOP, 41 | rw_counter=10, 42 | call_id=1, 43 | is_root=True, 44 | is_create=False, 45 | code_hash=bytecode_hash, 46 | program_counter=1, 47 | stack_pointer=1023, 48 | gas_left=0, 49 | ), 50 | ], 51 | ) 52 | -------------------------------------------------------------------------------- /tests/evm/test_gas.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | Block, 5 | Bytecode, 6 | ExecutionState, 7 | StepState, 8 | Tables, 9 | Transaction, 10 | verify_steps, 11 | RWDictionary, 12 | ) 13 | from zkevm_specs.util import Word 14 | from common import rand_range 15 | 16 | # Start with different values for `gas` before calling the `GAS` opcode. 17 | TESTING_DATA = tuple([i for i in range(2, 10)] + [rand_range(2**64) for i in range(0, 10)]) 18 | 19 | 20 | @pytest.mark.parametrize("gas", TESTING_DATA) 21 | def test_gas(gas: int): 22 | tx = Transaction() 23 | 24 | bytecode = Bytecode().gas().stop() 25 | bytecode_hash = Word(bytecode.hash()) 26 | 27 | # since the GAS opcode returns the value of available gas after deducting the cost 28 | # of calling the GAS opcode itself, we should expect gas_left = gas - 2 29 | gas_left = gas - 2 30 | 31 | tables = Tables( 32 | block_table=set(Block().table_assignments()), 33 | tx_table=set(tx.table_assignments()), 34 | withdrawal_table=set(), 35 | bytecode_table=set(bytecode.table_assignments()), 36 | rw_table=set(RWDictionary(2).stack_write(1, 1023, Word(gas_left)).rws), 37 | ) 38 | 39 | verify_steps( 40 | tables=tables, 41 | steps=[ 42 | StepState( 43 | execution_state=ExecutionState.GAS, 44 | rw_counter=2, 45 | call_id=1, 46 | is_root=True, 47 | is_create=False, 48 | code_hash=bytecode_hash, 49 | program_counter=0, 50 | stack_pointer=1024, 51 | gas_left=gas, 52 | ), 53 | StepState( 54 | execution_state=ExecutionState.STOP, 55 | rw_counter=3, 56 | call_id=1, 57 | is_root=True, 58 | is_create=False, 59 | code_hash=bytecode_hash, 60 | program_counter=1, 61 | stack_pointer=1023, 62 | gas_left=gas_left, 63 | ), 64 | ], 65 | ) 66 | -------------------------------------------------------------------------------- /tests/evm/test_gasprice.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | Block, 5 | Bytecode, 6 | CallContextFieldTag, 7 | ExecutionState, 8 | StepState, 9 | Tables, 10 | Transaction, 11 | verify_steps, 12 | RWDictionary, 13 | ) 14 | from zkevm_specs.util import Word, U256 15 | 16 | TESTING_DATA = ( 17 | 0x00, 18 | 0x10, 19 | 0x302010, 20 | 0xF0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0F, 21 | ) 22 | 23 | 24 | @pytest.mark.parametrize("gasprice", TESTING_DATA) 25 | def test_gasprice(gasprice: U256): 26 | tx = Transaction(gas_price=gasprice) 27 | 28 | bytecode = Bytecode().gasprice().stop() 29 | bytecode_hash = Word(bytecode.hash()) 30 | 31 | tables = Tables( 32 | block_table=set(Block().table_assignments()), 33 | tx_table=set(tx.table_assignments()), 34 | withdrawal_table=set(), 35 | bytecode_table=set(bytecode.table_assignments()), 36 | rw_table=set( 37 | RWDictionary(9) 38 | .call_context_read(1, CallContextFieldTag.TxId, tx.id) 39 | .stack_write(1, 1023, Word(gasprice)) 40 | .rws 41 | ), 42 | ) 43 | 44 | verify_steps( 45 | tables=tables, 46 | steps=[ 47 | StepState( 48 | execution_state=ExecutionState.GASPRICE, 49 | rw_counter=9, 50 | call_id=1, 51 | is_root=True, 52 | is_create=False, 53 | code_hash=bytecode_hash, 54 | program_counter=0, 55 | stack_pointer=1024, 56 | gas_left=2, 57 | ), 58 | StepState( 59 | execution_state=ExecutionState.STOP, 60 | rw_counter=11, 61 | call_id=1, 62 | is_root=True, 63 | is_create=False, 64 | code_hash=bytecode_hash, 65 | program_counter=1, 66 | stack_pointer=1023, 67 | gas_left=0, 68 | ), 69 | ], 70 | ) 71 | -------------------------------------------------------------------------------- /tests/evm/test_iszero.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | ExecutionState, 5 | StepState, 6 | verify_steps, 7 | Tables, 8 | Block, 9 | Bytecode, 10 | RWDictionary, 11 | ) 12 | from zkevm_specs.util import Word 13 | 14 | 15 | TESTING_DATA = ( 16 | bytes([0]), 17 | bytes([7]), 18 | ) 19 | 20 | 21 | @pytest.mark.parametrize("value_be_bytes", TESTING_DATA) 22 | def test_iszero(value_be_bytes: bytes): 23 | value = int.from_bytes(value_be_bytes, "big") 24 | result = 0x1 if value == 0x0 else 0x0 25 | value = Word(value) 26 | result = Word(result) 27 | 28 | bytecode = Bytecode().push1(value_be_bytes).iszero().stop() 29 | bytecode_hash = Word(bytecode.hash()) 30 | 31 | tables = Tables( 32 | block_table=set(Block().table_assignments()), 33 | tx_table=set(), 34 | withdrawal_table=set(), 35 | bytecode_table=set(bytecode.table_assignments()), 36 | rw_table=set(RWDictionary(9).stack_read(1, 1023, value).stack_write(1, 1023, result).rws), 37 | ) 38 | 39 | verify_steps( 40 | tables=tables, 41 | steps=[ 42 | StepState( 43 | execution_state=ExecutionState.ISZERO, 44 | rw_counter=9, 45 | call_id=1, 46 | is_root=True, 47 | is_create=False, 48 | code_hash=bytecode_hash, 49 | program_counter=2, 50 | stack_pointer=1023, 51 | gas_left=3, 52 | ), 53 | StepState( 54 | execution_state=ExecutionState.STOP, 55 | rw_counter=11, 56 | call_id=1, 57 | is_root=True, 58 | is_create=False, 59 | code_hash=bytecode_hash, 60 | program_counter=3, 61 | stack_pointer=1023, 62 | gas_left=0, 63 | ), 64 | ], 65 | ) 66 | -------------------------------------------------------------------------------- /tests/evm/test_jump.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | ExecutionState, 5 | StepState, 6 | Opcode, 7 | verify_steps, 8 | Tables, 9 | Block, 10 | Bytecode, 11 | RWDictionary, 12 | ) 13 | from zkevm_specs.util import Word 14 | 15 | 16 | TESTING_DATA = ((Opcode.JUMP, 7),) 17 | 18 | 19 | @pytest.mark.parametrize("opcode, dest", TESTING_DATA) 20 | def test_jump(opcode: Opcode, dest: int): 21 | dest_bytes = dest.to_bytes(1, "little") 22 | block = Block() 23 | # Jumps to PC=7 24 | # PUSH1 80 PUSH1 40 PUSH1 07 JUMP JUMPDEST STOP 25 | bytecode = Bytecode().push1(0x80).push1(0x40).push1(dest_bytes).jump().jumpdest().stop() 26 | bytecode_hash = Word(bytecode.hash()) 27 | 28 | tables = Tables( 29 | block_table=set(block.table_assignments()), 30 | tx_table=set(), 31 | withdrawal_table=set(), 32 | bytecode_table=set(bytecode.table_assignments()), 33 | rw_table=set(RWDictionary(9).stack_read(1, 1021, Word(dest)).rws), 34 | ) 35 | 36 | verify_steps( 37 | tables=tables, 38 | steps=[ 39 | StepState( 40 | execution_state=ExecutionState.JUMP, 41 | rw_counter=9, 42 | call_id=1, 43 | is_root=True, 44 | is_create=False, 45 | code_hash=bytecode_hash, 46 | program_counter=6, 47 | stack_pointer=1021, 48 | gas_left=8, 49 | ), 50 | StepState( 51 | execution_state=ExecutionState.STOP, 52 | rw_counter=10, 53 | call_id=1, 54 | is_root=True, 55 | is_create=False, 56 | code_hash=bytecode_hash, 57 | program_counter=int.from_bytes(dest_bytes, "little"), 58 | stack_pointer=1022, 59 | gas_left=0, 60 | ), 61 | ], 62 | ) 63 | -------------------------------------------------------------------------------- /tests/evm/test_msize.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | ExecutionState, 5 | StepState, 6 | verify_steps, 7 | Tables, 8 | Block, 9 | Bytecode, 10 | RWDictionary, 11 | ) 12 | from zkevm_specs.util import Word, N_BYTES_WORD 13 | 14 | TESTING_DATA = [i for i in range(0, 7)] 15 | 16 | 17 | @pytest.mark.parametrize("memory_word_size", TESTING_DATA) 18 | def test_msize(memory_word_size: int): 19 | value = memory_word_size * N_BYTES_WORD 20 | value = Word(value) 21 | 22 | bytecode = Bytecode().msize().stop() 23 | bytecode_hash = Word(bytecode.hash()) 24 | 25 | tables = Tables( 26 | block_table=set(Block().table_assignments()), 27 | tx_table=set(), 28 | withdrawal_table=set(), 29 | bytecode_table=set(bytecode.table_assignments()), 30 | rw_table=set(RWDictionary(9).stack_write(1, 1022, value).rws), 31 | ) 32 | 33 | verify_steps( 34 | tables=tables, 35 | steps=[ 36 | StepState( 37 | execution_state=ExecutionState.MSIZE, 38 | rw_counter=9, 39 | call_id=1, 40 | is_root=True, 41 | is_create=False, 42 | code_hash=bytecode_hash, 43 | program_counter=0, 44 | stack_pointer=1023, 45 | memory_word_size=memory_word_size, 46 | gas_left=2, 47 | ), 48 | StepState( 49 | execution_state=ExecutionState.STOP, 50 | rw_counter=10, 51 | call_id=1, 52 | is_root=True, 53 | is_create=False, 54 | code_hash=bytecode_hash, 55 | program_counter=1, 56 | stack_pointer=1022, 57 | memory_word_size=memory_word_size, 58 | gas_left=0, 59 | ), 60 | ], 61 | ) 62 | -------------------------------------------------------------------------------- /tests/evm/test_mulmod.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | ExecutionState, 5 | StepState, 6 | verify_steps, 7 | Tables, 8 | Block, 9 | Bytecode, 10 | RWDictionary, 11 | ) 12 | from zkevm_specs.util import Word 13 | 14 | MAXU256 = (2**256) - 1 15 | 16 | 17 | TESTING_DATA = [ 18 | (1, 1, 2), 19 | (1, 1, 0), 20 | (0, 2, 3), 21 | (MAXU256, MAXU256, MAXU256), 22 | (MAXU256, MAXU256, 1), 23 | (MAXU256, 1, MAXU256), 24 | (MAXU256, 2, 2), 25 | (0, 0, 0), 26 | ] 27 | 28 | 29 | @pytest.mark.parametrize("a, b, n", TESTING_DATA) 30 | def test_mulmod(a: int, b: int, n: int): 31 | if n == 0: 32 | r = Word(0) 33 | else: 34 | r = Word((a * b) % n) 35 | 36 | a = Word(a) 37 | b = Word(b) 38 | n = Word(n) 39 | 40 | bytecode = Bytecode().mulmod(a, b, n).stop() 41 | bytecode_hash = Word(bytecode.hash()) 42 | 43 | tables = Tables( 44 | block_table=set(Block().table_assignments()), 45 | tx_table=set(), 46 | withdrawal_table=set(), 47 | bytecode_table=set(bytecode.table_assignments()), 48 | rw_table=set( 49 | RWDictionary(9) 50 | .stack_read(1, 1021, a) 51 | .stack_read(1, 1022, b) 52 | .stack_read(1, 1023, n) 53 | .stack_write(1, 1023, r) 54 | .rws 55 | ), 56 | ) 57 | 58 | verify_steps( 59 | tables=tables, 60 | steps=[ 61 | StepState( 62 | execution_state=ExecutionState.MULMOD, 63 | rw_counter=9, 64 | call_id=1, 65 | is_root=True, 66 | is_create=False, 67 | code_hash=bytecode_hash, 68 | program_counter=99, 69 | stack_pointer=1021, 70 | gas_left=8, 71 | ), 72 | StepState( 73 | execution_state=ExecutionState.STOP, 74 | rw_counter=13, 75 | call_id=1, 76 | is_root=True, 77 | is_create=False, 78 | code_hash=bytecode_hash, 79 | program_counter=100, 80 | stack_pointer=1023, 81 | gas_left=0, 82 | ), 83 | ], 84 | ) 85 | -------------------------------------------------------------------------------- /tests/evm/test_not.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | ExecutionState, 5 | StepState, 6 | verify_steps, 7 | Tables, 8 | Bytecode, 9 | RWDictionary, 10 | ) 11 | from zkevm_specs.util import Word 12 | 13 | from common import rand_word 14 | 15 | NOT_TESTING_DATA = [ 16 | 0, 17 | 0x030201, 18 | 0x090807, 19 | (1 << 256) - 1, 20 | (1 << 256) - 0x030201, 21 | rand_word(), 22 | ] 23 | 24 | 25 | @pytest.mark.parametrize("a", NOT_TESTING_DATA) 26 | def test_not(a: int): 27 | b = Word(a ^ ((1 << 256) - 1)) 28 | 29 | bytecode = Bytecode().not_(Word(a)).stop() 30 | bytecode_hash = Word(bytecode.hash()) 31 | 32 | tables = Tables( 33 | block_table=set(), 34 | tx_table=set(), 35 | withdrawal_table=set(), 36 | bytecode_table=set(bytecode.table_assignments()), 37 | rw_table=set(RWDictionary(9).stack_read(1, 1023, Word(a)).stack_write(1, 1023, b).rws), 38 | ) 39 | 40 | verify_steps( 41 | tables=tables, 42 | steps=[ 43 | StepState( 44 | execution_state=ExecutionState.NOT, 45 | rw_counter=9, 46 | call_id=1, 47 | is_root=True, 48 | is_create=False, 49 | code_hash=bytecode_hash, 50 | program_counter=33, 51 | stack_pointer=1023, 52 | gas_left=3, 53 | ), 54 | StepState( 55 | execution_state=ExecutionState.STOP, 56 | rw_counter=11, 57 | call_id=1, 58 | is_root=True, 59 | is_create=False, 60 | code_hash=bytecode_hash, 61 | program_counter=34, 62 | stack_pointer=1023, 63 | gas_left=0, 64 | ), 65 | ], 66 | ) 67 | -------------------------------------------------------------------------------- /tests/evm/test_origin.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | Bytecode, 5 | CallContextFieldTag, 6 | ExecutionState, 7 | StepState, 8 | Tables, 9 | Transaction, 10 | verify_steps, 11 | RWDictionary, 12 | ) 13 | from zkevm_specs.util import Word, U256 14 | from common import rand_address 15 | 16 | TESTING_DATA = ( 17 | 0x00, 18 | 0x10, 19 | 0x302010, 20 | 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 21 | rand_address(), 22 | ) 23 | 24 | 25 | @pytest.mark.parametrize("origin", TESTING_DATA) 26 | def test_origin(origin: U256): 27 | tx = Transaction(caller_address=origin) 28 | 29 | bytecode = Bytecode().origin().stop() 30 | bytecode_hash = Word(bytecode.hash()) 31 | 32 | tables = Tables( 33 | block_table=set(), 34 | tx_table=set(tx.table_assignments()), 35 | withdrawal_table=set(), 36 | bytecode_table=set(bytecode.table_assignments()), 37 | rw_table=set( 38 | RWDictionary(9) 39 | .call_context_read(1, CallContextFieldTag.TxId, tx.id) 40 | .stack_write(1, 1023, Word(origin)) 41 | .rws 42 | ), 43 | ) 44 | 45 | verify_steps( 46 | tables=tables, 47 | steps=[ 48 | StepState( 49 | execution_state=ExecutionState.ORIGIN, 50 | rw_counter=9, 51 | call_id=1, 52 | is_root=True, 53 | is_create=False, 54 | code_hash=bytecode_hash, 55 | program_counter=0, 56 | stack_pointer=1024, 57 | gas_left=2, 58 | ), 59 | StepState( 60 | execution_state=ExecutionState.STOP, 61 | rw_counter=11, 62 | call_id=1, 63 | is_root=True, 64 | is_create=False, 65 | code_hash=bytecode_hash, 66 | program_counter=1, 67 | stack_pointer=1023, 68 | gas_left=0, 69 | ), 70 | ], 71 | ) 72 | -------------------------------------------------------------------------------- /tests/evm/test_pop.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | Bytecode, 5 | ExecutionState, 6 | StepState, 7 | Tables, 8 | verify_steps, 9 | RWDictionary, 10 | ) 11 | from zkevm_specs.util import Word, U256 12 | from common import rand_word 13 | 14 | TESTING_DATA = ( 15 | rand_word(), 16 | rand_word(), 17 | rand_word(), 18 | ) 19 | 20 | 21 | @pytest.mark.parametrize("y", TESTING_DATA) 22 | def test_pop(y: U256): 23 | bytecode = Bytecode().pop().stop() 24 | bytecode_hash = Word(bytecode.hash()) 25 | 26 | tables = Tables( 27 | block_table=set(), 28 | tx_table=set(), 29 | withdrawal_table=set(), 30 | bytecode_table=set(bytecode.table_assignments()), 31 | rw_table=set(RWDictionary(1).stack_read(1, 1023, Word(y)).rws), 32 | ) 33 | 34 | verify_steps( 35 | tables=tables, 36 | steps=[ 37 | StepState( 38 | execution_state=ExecutionState.POP, 39 | rw_counter=1, 40 | call_id=1, 41 | is_root=True, 42 | is_create=False, 43 | code_hash=bytecode_hash, 44 | program_counter=0, 45 | stack_pointer=1023, 46 | gas_left=2, 47 | ), 48 | StepState( 49 | execution_state=ExecutionState.STOP, 50 | rw_counter=2, 51 | call_id=1, 52 | is_root=True, 53 | is_create=False, 54 | code_hash=bytecode_hash, 55 | program_counter=1, 56 | stack_pointer=1024, 57 | gas_left=0, 58 | ), 59 | ], 60 | ) 61 | -------------------------------------------------------------------------------- /tests/evm/test_push.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | ExecutionState, 5 | StepState, 6 | verify_steps, 7 | Tables, 8 | Block, 9 | Bytecode, 10 | RWDictionary, 11 | ) 12 | from zkevm_specs.util import GAS_COST_FASTEST, GAS_COST_QUICK, Word 13 | from common import rand_bytes 14 | 15 | TESTING_DATA = tuple( 16 | [ 17 | (bytes([])), # PUSH0 18 | (bytes([1])), 19 | (bytes([2, 1])), 20 | (bytes([i for i in range(31, 0, -1)])), 21 | (bytes([i for i in range(32, 0, -1)])), 22 | ] 23 | + [(rand_bytes(i + 1)) for i in range(32)] 24 | ) 25 | 26 | 27 | @pytest.mark.parametrize("value_be_bytes", TESTING_DATA) 28 | def test_push(value_be_bytes: bytes): 29 | n_bytes = len(value_be_bytes) 30 | is_push0 = n_bytes == 0 31 | 32 | value = Word(int.from_bytes(value_be_bytes, "big")) 33 | 34 | bytecode = Bytecode().push(value_be_bytes, n_bytes=n_bytes) 35 | bytecode_hash = Word(bytecode.hash()) 36 | 37 | tables = Tables( 38 | block_table=set(Block().table_assignments()), 39 | tx_table=set(), 40 | withdrawal_table=set(), 41 | bytecode_table=set(bytecode.table_assignments()), 42 | rw_table=set(RWDictionary(8).stack_write(1, 1023, value).rws), 43 | ) 44 | 45 | gas_left = 10 46 | if is_push0: 47 | gas_cost = GAS_COST_QUICK 48 | else: 49 | gas_cost = GAS_COST_FASTEST 50 | 51 | verify_steps( 52 | tables=tables, 53 | steps=[ 54 | StepState( 55 | execution_state=ExecutionState.PUSH, 56 | rw_counter=8, 57 | call_id=1, 58 | is_root=True, 59 | is_create=False, 60 | code_hash=bytecode_hash, 61 | program_counter=0, 62 | stack_pointer=1024, 63 | gas_left=gas_left, 64 | ), 65 | StepState( 66 | execution_state=ExecutionState.STOP, 67 | rw_counter=9, 68 | call_id=1, 69 | is_root=True, 70 | is_create=False, 71 | code_hash=bytecode_hash, 72 | program_counter=1 + len(value_be_bytes), 73 | stack_pointer=1023, 74 | gas_left=gas_left - gas_cost, 75 | ), 76 | ], 77 | ) 78 | -------------------------------------------------------------------------------- /tests/evm/test_returndatasize.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | ExecutionState, 5 | StepState, 6 | verify_steps, 7 | Tables, 8 | CallContextFieldTag, 9 | Bytecode, 10 | RWDictionary, 11 | ) 12 | from zkevm_specs.util import Word, U64 13 | 14 | 15 | TESTING_DATA = ( 16 | 0x00, 17 | 0x10, 18 | 0x302010, 19 | ) 20 | 21 | 22 | @pytest.mark.parametrize("returndatasize", TESTING_DATA) 23 | def test_returndatasize(returndatasize: U64): 24 | bytecode = Bytecode().returndatasize() 25 | bytecode_hash = Word(bytecode.hash()) 26 | 27 | tables = Tables( 28 | block_table=set(), 29 | tx_table=set(), 30 | withdrawal_table=set(), 31 | bytecode_table=set(bytecode.table_assignments()), 32 | rw_table=set( 33 | RWDictionary(9) 34 | .call_context_read(1, CallContextFieldTag.LastCalleeReturnDataLength, returndatasize) 35 | .stack_write(1, 1023, Word(returndatasize)) 36 | .rws 37 | ), 38 | ) 39 | 40 | verify_steps( 41 | tables=tables, 42 | steps=[ 43 | StepState( 44 | execution_state=ExecutionState.RETURNDATASIZE, 45 | rw_counter=9, 46 | call_id=1, 47 | is_root=True, 48 | is_create=False, 49 | code_hash=bytecode_hash, 50 | program_counter=0, 51 | stack_pointer=1024, 52 | gas_left=2, 53 | ), 54 | StepState( 55 | execution_state=ExecutionState.STOP, 56 | rw_counter=11, 57 | call_id=1, 58 | is_root=True, 59 | is_create=False, 60 | code_hash=bytecode_hash, 61 | program_counter=1, 62 | stack_pointer=1023, 63 | gas_left=0, 64 | ), 65 | ], 66 | ) 67 | -------------------------------------------------------------------------------- /tests/evm/test_selfbalance.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zkevm_specs.evm_circuit import ( 4 | ExecutionState, 5 | StepState, 6 | verify_steps, 7 | Tables, 8 | Block, 9 | Bytecode, 10 | CallContextFieldTag, 11 | AccountFieldTag, 12 | RWDictionary, 13 | ) 14 | from zkevm_specs.util import Word, U256, U160 15 | from common import rand_address, rand_word 16 | 17 | 18 | TESTING_DATA = [(0, 0), (0, 10), (rand_address(), rand_word())] 19 | 20 | 21 | @pytest.mark.parametrize("callee_address, balance", TESTING_DATA) 22 | def test_selfbalance(callee_address: U160, balance: U256): 23 | bytecode = Bytecode().selfbalance() 24 | bytecode_hash = Word(bytecode.hash()) 25 | 26 | tables = Tables( 27 | block_table=Block(), 28 | tx_table=set(), 29 | withdrawal_table=set(), 30 | bytecode_table=set(bytecode.table_assignments()), 31 | rw_table=set( 32 | RWDictionary(9) 33 | .call_context_read(1, CallContextFieldTag.CalleeAddress, Word(callee_address)) 34 | .account_read(callee_address, AccountFieldTag.Balance, Word(balance)) 35 | .stack_write(1, 1023, Word(balance)) 36 | .rws 37 | ), 38 | ) 39 | 40 | verify_steps( 41 | tables=tables, 42 | steps=[ 43 | StepState( 44 | execution_state=ExecutionState.SELFBALANCE, 45 | rw_counter=9, 46 | call_id=1, 47 | is_root=True, 48 | is_create=False, 49 | code_hash=bytecode_hash, 50 | program_counter=0, 51 | stack_pointer=1024, 52 | gas_left=5, 53 | ), 54 | StepState( 55 | execution_state=ExecutionState.STOP, 56 | rw_counter=12, 57 | call_id=1, 58 | is_root=True, 59 | is_create=False, 60 | code_hash=bytecode_hash, 61 | program_counter=1, 62 | stack_pointer=1023, 63 | gas_left=0, 64 | ), 65 | ], 66 | ) 67 | --------------------------------------------------------------------------------