Skip to content
This repository was archived by the owner on Jul 5, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ __pycache__
build
.idea
*.DS_Store
.vscode
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@ We provide the [Beacon Chain](https://github.com/ethereum/eth2.0-specs) style Py

Installing dependencies(Python 3.9 is required)

```python
pip3 install pytest
pip3 install .
```
make install
```

Run the tests

```python
pytest
```
make test
```

## Implementations
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ requires = [
build-backend = "setuptools.build_meta"

[tool.black]
line-length = 120
line-length = 100
target-version = ['py39']
include = '\.pyi?$'
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ classifiers =
package_dir =
= src
packages = find:
python_requires = >=3.6
python_requires = >=3.9
install_requires =
pycryptodome==3.11.0
py-ecc==6.0.0
Expand Down
10 changes: 8 additions & 2 deletions src/zkevm_specs/bytecode.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ def select(

@is_circuit_code
def check_bytecode_row(
row: Row, prev_row: Row, push_table: Set[Tuple[int, int]], keccak_table: Set[Tuple[int, int, int]], r: int
row: Row,
prev_row: Row,
push_table: Set[Tuple[int, int]],
keccak_table: Set[Tuple[int, int, int]],
r: int,
):
row = Row(*[v if isinstance(v, RLC) else FQ(v) for v in row])
prev_row = Row(*[v if isinstance(v, RLC) else FQ(v) for v in prev_row])
Expand Down Expand Up @@ -62,7 +66,9 @@ def check_bytecode_row(
# padding needs to be boolean
assert_bool(row.padding)
# push_data_left := is_code ? byte_push_size : push_data_left_prev - 1
assert row.push_data_left == select(row.is_code, row.byte_push_size, prev_row.push_data_left - 1)
assert row.push_data_left == select(
row.is_code, row.byte_push_size, prev_row.push_data_left - 1
)

# Padding
if row.q_first == 0:
Expand Down
14 changes: 11 additions & 3 deletions src/zkevm_specs/evm/execution/begin_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ def begin_tx(instruction: Instruction):
rw_counter_end_of_reversion = instruction.call_context_lookup(
CallContextFieldTag.RwCounterEndOfReversion, call_id=call_id
)
is_persistent = instruction.call_context_lookup(CallContextFieldTag.IsPersistent, call_id=call_id)
is_persistent = instruction.call_context_lookup(
CallContextFieldTag.IsPersistent, call_id=call_id
)

if instruction.is_first_step:
instruction.constrain_equal(instruction.curr.rw_counter, 1)
Expand All @@ -39,7 +41,11 @@ def begin_tx(instruction: Instruction):

# TODO: Handle gas cost of tx level access list (EIP 2930)
tx_call_data_gas_cost = instruction.tx_context_lookup(tx_id, TxContextFieldTag.CallDataGasCost)
gas_left = tx_gas - (GAS_COST_CREATION_TX if tx_is_create == 1 else GAS_COST_TX) - tx_call_data_gas_cost
gas_left = (
tx_gas
- (GAS_COST_CREATION_TX if tx_is_create == 1 else GAS_COST_TX)
- tx_call_data_gas_cost
)
instruction.constrain_gas_left_not_underflow(gas_left)

# Prepare access list of caller and callee
Expand Down Expand Up @@ -84,7 +90,9 @@ def begin_tx(instruction: Instruction):
(CallContextFieldTag.LastCalleeReturnDataOffset, 0),
(CallContextFieldTag.LastCalleeReturnDataLength, 0),
]:
instruction.constrain_equal(instruction.call_context_lookup(tag, call_id=call_id), value)
instruction.constrain_equal(
instruction.call_context_lookup(tag, call_id=call_id), value
)

instruction.step_state_transition_to_new_context(
rw_counter=Transition.delta(19),
Expand Down
3 changes: 2 additions & 1 deletion src/zkevm_specs/evm/execution/block_timestamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ def timestamp(instruction: Instruction):
timestamp = instruction.stack_push()
# check block table for timestamp
instruction.constrain_equal(
timestamp, instruction.int_to_rlc(instruction.block_context_lookup(BlockContextFieldTag.Timestamp), 8)
timestamp,
instruction.int_to_rlc(instruction.block_context_lookup(BlockContextFieldTag.Timestamp), 8),
)

instruction.step_state_transition_in_same_context(
Expand Down
12 changes: 9 additions & 3 deletions src/zkevm_specs/evm/execution/end_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ def end_tx(instruction: Instruction):
# Handle gas refund (refund is capped to gas_used // MAX_REFUND_QUOTIENT_OF_GAS_USED in EIP 3529)
tx_gas = instruction.tx_context_lookup(tx_id, TxContextFieldTag.Gas)
gas_used = tx_gas - instruction.curr.gas_left
max_refund, _ = instruction.constant_divmod(gas_used, MAX_REFUND_QUOTIENT_OF_GAS_USED, N_BYTES_GAS)
max_refund, _ = instruction.constant_divmod(
gas_used, MAX_REFUND_QUOTIENT_OF_GAS_USED, N_BYTES_GAS
)
refund = instruction.tx_refund_read(tx_id)
effective_refund = instruction.min(max_refund, refund, 8)

# Add effective_refund * gas_price back to caller's balance
tx_gas_price = instruction.tx_gas_price(tx_id)
value, carry = instruction.mul_word_by_u64(tx_gas_price, instruction.curr.gas_left + effective_refund)
value, carry = instruction.mul_word_by_u64(
tx_gas_price, instruction.curr.gas_left + effective_refund
)
instruction.constrain_zero(carry)
tx_caller_address = instruction.tx_context_lookup(tx_id, TxContextFieldTag.CallerAddress)
instruction.add_balance(tx_caller_address, [value])
Expand All @@ -33,7 +37,9 @@ def end_tx(instruction: Instruction):
if instruction.next.execution_state == ExecutionState.BeginTx:
# Check next tx_id is increased by 1
instruction.constrain_equal(
instruction.call_context_lookup(CallContextFieldTag.TxId, call_id=instruction.next.rw_counter),
instruction.call_context_lookup(
CallContextFieldTag.TxId, call_id=instruction.next.rw_counter
),
tx_id + 1,
)

Expand Down
4 changes: 3 additions & 1 deletion src/zkevm_specs/evm/execution/push.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ def push(instruction: Instruction):
for idx in range(32):
index = instruction.curr.program_counter + num_pushed - idx
if idx == 0 or selectors[idx - 1]:
instruction.constrain_equal(value_le_bytes[idx], instruction.opcode_lookup_at(index, False))
instruction.constrain_equal(
value_le_bytes[idx], instruction.opcode_lookup_at(index, False)
)
else:
instruction.constrain_zero(value_le_bytes[idx])

Expand Down
44 changes: 34 additions & 10 deletions src/zkevm_specs/evm/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,14 @@ def constrain_zero(self, value: FQ):
assert value == 0, ConstraintUnsatFailure(f"Expected value to be 0, but got {value}")

def constrain_equal(self, lhs: FQ, rhs: FQ):
assert lhs == rhs, ConstraintUnsatFailure(f"Expected values to be equal, but got {lhs} and {rhs}")
assert lhs == rhs, ConstraintUnsatFailure(
f"Expected values to be equal, but got {lhs} and {rhs}"
)

def constrain_bool(self, num: FQ):
assert num.n in [0, 1], ConstraintUnsatFailure(f"Expected value to be a bool, but got {num}")
assert num.n in [0, 1], ConstraintUnsatFailure(
f"Expected value to be a bool, but got {num}"
)

def constrain_gas_left_not_underflow(self, gas_left: FQ):
self.bytes_range_lookup(gas_left, N_BYTES_GAS)
Expand Down Expand Up @@ -125,7 +129,9 @@ def constrain_step_state_transition(self, **kwargs: Transition):
for key, transition in kwargs.items():
curr, next = getattr(self.curr, key), getattr(self.next, key)
if transition.kind == TransitionKind.Same:
assert next == curr, ConstraintUnsatFailure(f"State {key} should be same as {curr}, but got {next}")
assert next == curr, ConstraintUnsatFailure(
f"State {key} should be same as {curr}, but got {next}"
)
elif transition.kind == TransitionKind.Delta:
assert next == curr + transition.value, ConstraintUnsatFailure(
f"State {key} should transit to {curr + transition.value}, but got {next}"
Expand Down Expand Up @@ -213,7 +219,9 @@ def select(self, condition: bool, when_true: FQ, when_false: FQ) -> FQ:
def pair_select(self, value: int, lhs: int, rhs: int) -> Tuple[bool, bool]:
return value == lhs, value == rhs

def constant_divmod(self, numerator: IntOrFQ, denominator: IntOrFQ, n_bytes: int) -> Tuple[int, int]:
def constant_divmod(
self, numerator: IntOrFQ, denominator: IntOrFQ, n_bytes: int
) -> Tuple[int, int]:
quotient, remainder = divmod(FQ(numerator).n, FQ(denominator).n)
quotient, remainder = FQ(quotient), FQ(remainder)
self.bytes_range_lookup(quotient, n_bytes)
Expand Down Expand Up @@ -272,7 +280,9 @@ def rlc_to_le_bytes(self, rlc: RLC) -> bytes:

def rlc_to_int_unchecked(self, rlc: RLC, n_bytes: int) -> int:
rlc_le_bytes = self.rlc_to_le_bytes(rlc)
return self.bytes_to_int(rlc_le_bytes[:n_bytes]), self.is_zero(self.sum(rlc_le_bytes[n_bytes:]))
return self.bytes_to_int(rlc_le_bytes[:n_bytes]), self.is_zero(
self.sum(rlc_le_bytes[n_bytes:])
)

def rlc_to_int_exact(self, rlc: RLC, n_bytes: int) -> int:
rlc_le_bytes = self.rlc_to_le_bytes(rlc)
Expand Down Expand Up @@ -349,7 +359,9 @@ def opcode_lookup_at(self, index: int, is_code: bool) -> int:
else:
return self.bytecode_lookup(self.curr.code_source, index, is_code)

def rw_lookup(self, rw: RW, tag: RWTableTag, inputs: Sequence[int], rw_counter: Optional[int] = None) -> Array10:
def rw_lookup(
self, rw: RW, tag: RWTableTag, inputs: Sequence[int], rw_counter: Optional[int] = None
) -> Array10:
if rw_counter is None:
rw_counter = self.curr.rw_counter + self.rw_counter_offset
self.rw_counter_offset += 1
Expand Down Expand Up @@ -478,7 +490,11 @@ def add_balance_with_reversion(
state_write_counter: Optional[int] = None,
) -> Tuple[FQ, FQ]:
balance, balance_prev = self.account_write_with_reversion(
account_address, AccountFieldTag.Balance, is_persistent, rw_counter_end_of_reversion, state_write_counter
account_address,
AccountFieldTag.Balance,
is_persistent,
rw_counter_end_of_reversion,
state_write_counter,
)
result, carry = self.add_words([balance_prev, *values])
self.constrain_equal(balance, result)
Expand All @@ -501,7 +517,11 @@ def sub_balance_with_reversion(
state_write_counter: Optional[int] = None,
) -> Tuple[FQ, FQ]:
balance, balance_prev = self.account_write_with_reversion(
account_address, AccountFieldTag.Balance, is_persistent, rw_counter_end_of_reversion, state_write_counter
account_address,
AccountFieldTag.Balance,
is_persistent,
rw_counter_end_of_reversion,
state_write_counter,
)
result, carry = self.add_words([balance, *values])
self.constrain_equal(balance_prev, result)
Expand Down Expand Up @@ -649,11 +669,15 @@ def memory_expansion_dynamic_length(
rd_offset: Optional[int] = None,
rd_length: Optional[int] = None,
) -> Tuple[int, int]:
cd_memory_size, _ = self.constant_divmod(cd_offset + cd_length + 31, 32, N_BYTES_MEMORY_SIZE)
cd_memory_size, _ = self.constant_divmod(
cd_offset + cd_length + 31, 32, N_BYTES_MEMORY_SIZE
)
next_memory_size = self.max(self.curr.memory_size, cd_memory_size, N_BYTES_MEMORY_SIZE)

if rd_offset is not None:
rd_memory_size, _ = self.constant_divmod(rd_offset + rd_length + 31, 32, N_BYTES_MEMORY_SIZE)
rd_memory_size, _ = self.constant_divmod(
rd_offset + rd_length + 31, 32, N_BYTES_MEMORY_SIZE
)
next_memory_size = self.max(next_memory_size, rd_memory_size, N_BYTES_MEMORY_SIZE)

memory_gas_cost = self.memory_gas_cost(self.curr.memory_size)
Expand Down
13 changes: 10 additions & 3 deletions src/zkevm_specs/evm/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,14 @@ def table_assignments(self) -> Sequence[Array4]:
elif self == FixedTableTag.StateWriteOpcode:
return [(self, opcode, 0, 0) for opcode in state_write_opcodes()]
elif self == FixedTableTag.StackOverflow:
return [(self, opcode, stack_pointer, 0) for opcode, stack_pointer in stack_underflow_pairs()]
return [
(self, opcode, stack_pointer, 0)
for opcode, stack_pointer in stack_underflow_pairs()
]
elif self == FixedTableTag.StackUnderflow:
return [(self, opcode, stack_pointer, 0) for opcode, stack_pointer in stack_overflow_pairs()]
return [
(self, opcode, stack_pointer, 0) for opcode, stack_pointer in stack_overflow_pairs()
]
else:
ValueError("Unreacheable")

Expand Down Expand Up @@ -234,7 +239,9 @@ def __init__(self, table_name: str, inputs: Tuple[int, ...]) -> None:


class LookupAmbiguousFailure(Exception):
def __init__(self, table_name: str, inputs: Tuple[int, ...], matched_rows: Sequence[Tuple[int, ...]]) -> None:
def __init__(
self, table_name: str, inputs: Tuple[int, ...], matched_rows: Sequence[Tuple[int, ...]]
) -> None:
self.inputs = inputs
self.message = f"Lookup {table_name} is ambiguous on inputs {inputs}, ${len(matched_rows)} matched rows found: {matched_rows}"

Expand Down
12 changes: 10 additions & 2 deletions src/zkevm_specs/evm/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,12 @@ def __init__(
def call_data_gas_cost(self) -> int:
return reduce(
lambda acc, byte: (
acc + (GAS_COST_TX_CALL_DATA_PER_ZERO_BYTE if byte == 0 else GAS_COST_TX_CALL_DATA_PER_NON_ZERO_BYTE)
acc
+ (
GAS_COST_TX_CALL_DATA_PER_ZERO_BYTE
if byte == 0
else GAS_COST_TX_CALL_DATA_PER_NON_ZERO_BYTE
)
),
self.call_data,
0,
Expand All @@ -122,7 +127,10 @@ def table_assignments(self, randomness: int) -> Iterator[Array4]:
(self.id, TxContextFieldTag.CallDataLength, 0, len(self.call_data)),
(self.id, TxContextFieldTag.CallDataGasCost, 0, self.call_data_gas_cost()),
],
map(lambda item: (self.id, TxContextFieldTag.CallData, item[0], item[1]), enumerate(self.call_data)),
map(
lambda item: (self.id, TxContextFieldTag.CallData, item[0], item[1]),
enumerate(self.call_data),
),
)


Expand Down
8 changes: 6 additions & 2 deletions src/zkevm_specs/opcode/mload_mstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ def memory_expansion(

# Calculate the gas cost for the memory expansion
# This gas cost is the difference between the next and current memory costs
memory_gas_cost = (next_memory_size - curr_memory_size) * G_MEM + (next_quad_memory_cost - curr_quad_memory_cost)
memory_gas_cost = (next_memory_size - curr_memory_size) * G_MEM + (
next_quad_memory_cost - curr_quad_memory_cost
)

# Return the new memory size and the memory expansion gas cost
return (next_memory_size, memory_gas_cost)
Expand Down Expand Up @@ -124,7 +126,9 @@ def check_memory_ops(
address = address_low(address8s)

# Calculate the next memory size and the gas cost for this memory access
(next_memory_size, memory_cost) = memory_expansion(curr_memory_size, address + 1 + is_not_mstore8 * 31)
(next_memory_size, memory_cost) = memory_expansion(
curr_memory_size, address + 1 + is_not_mstore8 * 31
)
assert next_memory_size == expected_next_memory_size
assert memory_cost == expected_memory_cost

Expand Down
4 changes: 3 additions & 1 deletion src/zkevm_specs/opcode/signextend.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,6 @@ def test_check_byte():
r8s[j] = sign_byte
selectors[j - 1] = 1

check_signextend(value, i8s, r8s, sign_byte if i < 31 else 0, selectors, sign_byte_table)
check_signextend(
value, i8s, r8s, sign_byte if i < 31 else 0, selectors, sign_byte_table
)
20 changes: 15 additions & 5 deletions src/zkevm_specs/util/arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,28 @@ class RLC:
le_bytes: bytes
value: FQ

def __init__(self, int_or_bytes: Union[IntOrFQ, bytes], randomness: int, n_bytes: int = 32) -> None:
def __init__(
self, int_or_bytes: Union[IntOrFQ, bytes], randomness: int, n_bytes: int = 32
) -> None:
if isinstance(int_or_bytes, int):
assert 0 <= int_or_bytes < 256**n_bytes, f"Value {int_or_bytes} too large to fit {n_bytes} bytes"
assert (
0 <= int_or_bytes < 256**n_bytes
), f"Value {int_or_bytes} too large to fit {n_bytes} bytes"
self.le_bytes = int_or_bytes.to_bytes(n_bytes, "little")
elif isinstance(int_or_bytes, FQ):
assert int_or_bytes.n < 256**n_bytes, f"Value {int_or_bytes} too large to fit {n_bytes} bytes"
assert (
int_or_bytes.n < 256**n_bytes
), f"Value {int_or_bytes} too large to fit {n_bytes} bytes"
self.le_bytes = int_or_bytes.n.to_bytes(n_bytes, "little")
elif isinstance(int_or_bytes, bytes):
assert len(int_or_bytes) <= n_bytes, f"Expected bytes with length less or equal than {n_bytes}"
assert (
len(int_or_bytes) <= n_bytes
), f"Expected bytes with length less or equal than {n_bytes}"
self.le_bytes = int_or_bytes.ljust(n_bytes, b"\x00")
else:
raise TypeError(f"Expected an int or bytes, but got object of type {type(int_or_bytes)}")
raise TypeError(
f"Expected an int or bytes, but got object of type {type(int_or_bytes)}"
)

self.value = fp_linear_combine(self.le_bytes, randomness)

Expand Down
6 changes: 5 additions & 1 deletion tests/evm/test_add.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@
def test_add(opcode: Opcode, a: int, b: int, c: Optional[int]):
randomness = rand_fp()

c = RLC(c, randomness) if c is not None else RLC((a + b if opcode == Opcode.ADD else a - b) % 2**256, randomness)
c = (
RLC(c, randomness)
if c is not None
else RLC((a + b if opcode == Opcode.ADD else a - b) % 2**256, randomness)
)
a = RLC(a, randomness)
b = RLC(b, randomness)

Expand Down
Loading