diff --git a/specs/opcode/41COINBASE.md b/specs/opcode/41COINBASE.md new file mode 100644 index 000000000..7ec0cade6 --- /dev/null +++ b/specs/opcode/41COINBASE.md @@ -0,0 +1,39 @@ +# Coinbase op code + +## Procedure + +The `COINBASE` opcode get the coinbase address from current block and push to the stack + +## EVM behavior + +The `COINBASE` opcode loads an `address` (20 bytes of data) from block context. +then push the `address` to the stack. + +## Circuit behavior + +1. construct block context table +2. do busmapping lookup for stack write operation +3. other implicit check: bytes length + +## Constraints + +1. opId = 0x41 +2. State transition: + - gc + 1 (1 stack write) + - stack_pointer - 1 + - pc + 1 + - gas + 2 +3. Lookups: 2 + - `address` is on the top of stack + - `address` is in the block context table +4. Others: + - `address` is 20 bytes length + +## Exceptions + +1. stack overflow: stack is full, stack pointer = 0 +2. out of gas: remaining gas is not enough + +## Code + +Please refer to `src/zkevm_specs/evm/execution/coinbase.py`. diff --git a/src/zkevm_specs/evm/execution/__init__.py b/src/zkevm_specs/evm/execution/__init__.py index 92cfd8e2f..977878b7c 100644 --- a/src/zkevm_specs/evm/execution/__init__.py +++ b/src/zkevm_specs/evm/execution/__init__.py @@ -5,5 +5,6 @@ from .push import * from .jump import * from .jumpi import * +from .block_coinbase import * # Error cases diff --git a/src/zkevm_specs/evm/execution/block_coinbase.py b/src/zkevm_specs/evm/execution/block_coinbase.py new file mode 100644 index 000000000..3b47cdc28 --- /dev/null +++ b/src/zkevm_specs/evm/execution/block_coinbase.py @@ -0,0 +1,22 @@ +from ..instruction import Instruction, Transition +from ..table import BlockContextFieldTag +from ..opcode import Opcode + + +def coinbase(instruction: Instruction): + opcode = instruction.opcode_lookup(True) + instruction.constrain_equal(opcode, Opcode.COINBASE) + address = instruction.stack_push() + # in real circuit also check address raw data is 160 bit length (20 bytes) + # check block table for coinbase address + instruction.constrain_equal( + address, + instruction.bytes_to_rlc(instruction.int_to_bytes(instruction.block_lookup(BlockContextFieldTag.Coinbase), 20)), + ) + + instruction.constrain_same_context_state_transition( + opcode, + rw_counter=Transition.delta(1), + program_counter=Transition.delta(1), + stack_pointer=Transition.delta(-1), + ) diff --git a/src/zkevm_specs/evm/main.py b/src/zkevm_specs/evm/main.py index 4bffd8217..381771de8 100644 --- a/src/zkevm_specs/evm/main.py +++ b/src/zkevm_specs/evm/main.py @@ -7,6 +7,7 @@ push, jump, jumpi, + coinbase, ) from .execution_state import ExecutionState from .instruction import Instruction @@ -48,6 +49,8 @@ def verify_step( jump(instruction) elif instruction.curr.execution_state == ExecutionState.JUMPI: jumpi(instruction) + elif instruction.curr.execution_state == ExecutionState.COINBASE: + coinbase(instruction) # Error cases else: raise NotImplementedError diff --git a/src/zkevm_specs/evm/typing.py b/src/zkevm_specs/evm/typing.py index d6456ed35..dff4214bf 100644 --- a/src/zkevm_specs/evm/typing.py +++ b/src/zkevm_specs/evm/typing.py @@ -9,7 +9,7 @@ class Block: coinbase: U160 gas_limit: U64 block_number: U256 - time: U256 + time: U64 difficulty: U256 base_fee: U256 @@ -22,7 +22,7 @@ def __init__( coinbase: U160 = 0x10, gas_limit: U64 = int(15e6), block_number: U256 = 0, - time: U256 = 0, + time: U64 = 0, difficulty: U256 = 0, base_fee: U256 = int(1e9), history_hashes: Sequence[U256] = [], diff --git a/tests/evm/test_coinbase.py b/tests/evm/test_coinbase.py new file mode 100644 index 000000000..e82f43995 --- /dev/null +++ b/tests/evm/test_coinbase.py @@ -0,0 +1,67 @@ +import pytest + +from zkevm_specs.evm import ( + ExecutionState, + StepState, + Opcode, + verify_steps, + Tables, + RWTableTag, + RW, + Block, + Bytecode, +) +from zkevm_specs.util import RLCStore, U160 + + +TESTING_DATA = ((Opcode.COINBASE, 0x030201),) + + +@pytest.mark.parametrize("opcode, address", TESTING_DATA) +def test_coinbase(opcode: Opcode, address: U160): + rlc_store = RLCStore() + + coinbase_rlc = rlc_store.to_rlc(address.to_bytes(20, "little")) + + bytecode = Bytecode(f"{opcode.hex()}00") + bytecode_hash = rlc_store.to_rlc(bytecode.hash, 32) + block = Block(address) + tables = Tables( + block_table=set(block.table_assignments(rlc_store)), + tx_table=set(), + bytecode_table=set(bytecode.table_assignments(rlc_store)), + rw_table=set( + [ + (9, RW.Write, RWTableTag.Stack, 1, 1023, coinbase_rlc, 0, 0), + ] + ), + ) + + verify_steps( + rlc_store=rlc_store, + tables=tables, + steps=[ + StepState( + execution_state=ExecutionState.COINBASE, + rw_counter=9, + call_id=1, + is_root=True, + is_create=False, + opcode_source=bytecode_hash, + program_counter=0, + stack_pointer=1024, + gas_left=2, + ), + StepState( + execution_state=ExecutionState.STOP, + rw_counter=10, + call_id=1, + is_root=True, + is_create=False, + opcode_source=bytecode_hash, + program_counter=1, + stack_pointer=1023, + gas_left=0, + ), + ], + )