Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support eip-1559 #3

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@ type: ## Check the typing of the Python code
test: ## Run tests
pytest --doctest-modules

test-eip1559:
pytest tests/evm/test_end_tx.py

.PHONY: help install fmt lint test
6 changes: 3 additions & 3 deletions src/zkevm_specs/evm_circuit/execution/begin_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ def begin_tx(instruction: Instruction):
# bump the account nonce if the tx is valid
instruction.constrain_equal(nonce, nonce_prev.expr() + 1 - is_tx_invalid.expr())

# TODO: Implement EIP 1559 (currently it supports legacy transaction format)
# Calculate gas fee
# Subtract the gas fee from the sender balance: gas * gas_fee_cap
tx_gas = instruction.tx_context_lookup(tx_id, TxContextFieldTag.Gas)
tx_gas_price = instruction.tx_gas_price(tx_id)
gas_fee, carry = instruction.mul_word_by_u64(tx_gas_price, tx_gas)
tx_gas_fee_cap = instruction.tx_gas_fee_cap(tx_id)
gas_fee, carry = instruction.mul_word_by_u64(tx_gas_fee_cap, tx_gas)
instruction.constrain_zero(carry)

# intrinsic gas
Expand Down
15 changes: 11 additions & 4 deletions src/zkevm_specs/evm_circuit/execution/end_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,29 @@ def end_tx(instruction: Instruction):
instruction.constrain_zero(effective_refund)

# Add effective_refund * gas_price back to caller's balance
tx_gas_price = instruction.tx_gas_price(tx_id)
tx_gas_fee_cap = instruction.tx_gas_fee_cap(tx_id)
value, carry = instruction.mul_word_by_u64(
tx_gas_price, instruction.curr.gas_left + effective_refund
tx_gas_fee_cap, 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])

# Add gas_used * effective_tip to coinbase's balance
base_fee = instruction.block_context_lookup_word(BlockContextFieldTag.BaseFee)
effective_tip, _ = instruction.sub_word(tx_gas_price, base_fee)
max_tip, _ = instruction.sub_word(tx_gas_fee_cap, base_fee)
tx_gas_tip_cap = instruction.tx_gas_tip_cap(tx_id)
# min(gas_tip_cap, gas_fee_cap - base_fee)
effective_tip = instruction.min_word(tx_gas_tip_cap, max_tip)
reward, carry = instruction.mul_word_by_u64(effective_tip, gas_used)
instruction.constrain_zero(carry)
coinbase = instruction.block_context_lookup(BlockContextFieldTag.Coinbase)
instruction.add_balance(coinbase, [reward])

# send base_fee to treasury
burned, carry = instruction.mul_word_by_u64(base_fee, gas_used)
instruction.constrain_zero(carry)
treasury = instruction.block_context_lookup(BlockContextFieldTag.Treasury)
instruction.add_balance(treasury, [burned])
# constrain tx status matches with `PostStateOrStatus` of TxReceipt tag in RW
instruction.constrain_equal(
(1 - is_tx_invalid.expr()) * is_persistent,
Expand Down
2 changes: 1 addition & 1 deletion src/zkevm_specs/evm_circuit/execution/gasprice.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def gasprice(instruction: Instruction):
# fetch gasPrice from rw table
# fetch from the Tx context table the gasPrice
instruction.constrain_equal_word(
instruction.tx_context_lookup_word(tx_id, TxContextFieldTag.GasPrice),
instruction.tx_context_lookup_word(tx_id, TxContextFieldTag.GasFeeCap),
instruction.stack_push(),
)

Expand Down
11 changes: 9 additions & 2 deletions src/zkevm_specs/evm_circuit/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,10 @@ def precompile(self, address: Expression) -> FQ:
except ValueError:
return FQ(0)

def min_word(self, lhs: Word, rhs: Word) -> Word:
lt, _ = self.compare_word(lhs, rhs)
return self.select_word(lt, lhs, rhs)

def min(self, lhs: Expression, rhs: Expression, n_bytes: int) -> FQ:
lt, _ = self.compare(lhs, rhs, n_bytes)
return cast_expr(self.select(lt, lhs, rhs), FQ)
Expand Down Expand Up @@ -756,8 +760,11 @@ def bytecode_length(self, bytecode_hash: Word) -> Expression:
bytecode_hash, FQ(BytecodeFieldTag.Header), FQ(0), FQ(0)
).value

def tx_gas_price(self, tx_id: Expression) -> Word:
return self.tx_context_lookup_word(tx_id, TxContextFieldTag.GasPrice)
def tx_gas_tip_cap(self, tx_id: Expression) -> Word:
return self.tx_context_lookup_word(tx_id, TxContextFieldTag.GasTipCap)

def tx_gas_fee_cap(self, tx_id: Expression) -> Word:
return self.tx_context_lookup_word(tx_id, TxContextFieldTag.GasFeeCap)

def responsible_opcode_lookup(self, opcode: Expression, aux: Expression = FQ(0)):
self.fixed_lookup(
Expand Down
4 changes: 3 additions & 1 deletion src/zkevm_specs/evm_circuit/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ class BlockContextFieldTag(IntEnum):
"""

Coinbase = auto()
Treasury = auto()
GasLimit = auto()
Number = auto()
Timestamp = auto()
Expand All @@ -153,7 +154,8 @@ class TxContextFieldTag(IntEnum):

Nonce = auto()
Gas = auto()
GasPrice = auto()
GasTipCap = auto() # a.k.a. maxPriorityFeePerGas
GasFeeCap = auto() # a.k.a. maxFeePerGas
CallerAddress = auto()
CalleeAddress = auto()
IsCreate = auto()
Expand Down
27 changes: 21 additions & 6 deletions src/zkevm_specs/evm_circuit/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@

class Block:
coinbase: U160
treasury: U160

# Gas needs a lot arithmetic operation or comparison in EVM circuit, so we
# assume gas limit in the near futuer will not exceed U64, to reduce the
Expand All @@ -87,6 +88,7 @@ class Block:
def __init__(
self,
coinbase: U160 = U160(0x10),
treasury: U160 = U160(0x20),
gas_limit: U64 = U64(int(15e6)),
number: U64 = U64(0),
timestamp: U64 = U64(0),
Expand All @@ -98,6 +100,7 @@ def __init__(
assert len(history_hashes) <= min(256, number)

self.coinbase = coinbase
self.treasury = treasury
self.gas_limit = gas_limit
self.number = number
self.timestamp = timestamp
Expand All @@ -111,6 +114,7 @@ def table_assignments(self) -> List[BlockTableRow]:
word = lambda w: WordOrValue(Word(w))
return [
BlockTableRow(FQ(BlockContextFieldTag.Coinbase), FQ(0), value(self.coinbase)),
BlockTableRow(FQ(BlockContextFieldTag.Treasury), FQ(0), value(self.treasury)),
BlockTableRow(FQ(BlockContextFieldTag.GasLimit), FQ(0), value(self.gas_limit)),
BlockTableRow(FQ(BlockContextFieldTag.Number), FQ(0), value(self.number)),
BlockTableRow(FQ(BlockContextFieldTag.Timestamp), FQ(0), value(self.timestamp)),
Expand All @@ -136,7 +140,8 @@ class Transaction:
id: int
nonce: U64
gas: U64
gas_price: U256
gas_tip_cap: U256
gas_fee_cap: U256
caller_address: U160
callee_address: Optional[U160]
value: U256
Expand All @@ -149,7 +154,8 @@ def __init__(
id: int = 1,
nonce: U64 = U64(0),
gas: U64 = U64(21000),
gas_price: U256 = U256(int(2e9)),
gas_tip_cap: U256 = U256(int(0)),
gas_fee_cap: U256 = U256(int(2e9)),
caller_address: U160 = U160(0xCAFE),
callee_address: Optional[U160] = None,
value: U256 = U256(0),
Expand All @@ -160,7 +166,8 @@ def __init__(
self.id = id
self.nonce = nonce
self.gas = gas
self.gas_price = gas_price
self.gas_tip_cap = gas_tip_cap
self.gas_fee_cap = gas_fee_cap
self.caller_address = caller_address
self.callee_address = callee_address
self.value = value
Expand All @@ -170,7 +177,9 @@ def __init__(

@classmethod
def padding(obj, id: int):
tx = obj(id, U64(0), U64(0), U256(0), U160(0), U160(0), U256(0), bytes(), 0, list())
tx = obj(
id, U64(0), U64(0), U256(0), U256(0), U160(0), U160(0), U256(0), bytes(), 0, list()
)
return tx

def call_data_gas_cost(self) -> int:
Expand Down Expand Up @@ -204,9 +213,15 @@ def table_fixed(self) -> List[TxTableRow]:
TxTableRow(FQ(self.id), FQ(TxContextFieldTag.Gas), FQ(0), value(self.gas)),
TxTableRow(
FQ(self.id),
FQ(TxContextFieldTag.GasPrice),
FQ(TxContextFieldTag.GasTipCap),
FQ(0),
word(self.gas_tip_cap),
),
TxTableRow(
FQ(self.id),
FQ(TxContextFieldTag.GasFeeCap),
FQ(0),
word(self.gas_price),
word(self.gas_fee_cap),
),
TxTableRow(
FQ(self.id), FQ(TxContextFieldTag.CallerAddress), FQ(0), value(self.caller_address)
Expand Down
24 changes: 19 additions & 5 deletions src/zkevm_specs/tx_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from eth_keys import KeyAPI # type: ignore
import rlp # type: ignore
from eth_utils import keccak
from .evm_circuit import TxContextFieldTag as Tag
from .evm_circuit import TxContextFieldTag as Tag, AccessTuple


class Row:
Expand Down Expand Up @@ -297,11 +297,13 @@ class Transaction(NamedTuple):
"""

nonce: U64
gas_price: U256
gas_tip_cap: U256
gas_fee_cap: U256
gas: U64
to: Union[None, U160]
value: U256
data: bytes
access_list: List[AccessTuple]
sig_v: U64
sig_r: U256
sig_s: U256
Expand All @@ -316,7 +318,8 @@ def padding_tx(tx_id: int) -> List[Row]:
return [
Row(FQ(tx_id), FQ(Tag.Nonce), FQ(0), FQ(0)),
Row(FQ(tx_id), FQ(Tag.Gas), FQ(0), FQ(0)),
Row(FQ(tx_id), FQ(Tag.GasPrice), FQ(0), Word(0)),
Row(FQ(tx_id), FQ(Tag.GasTipCap), FQ(0), Word(0)),
Row(FQ(tx_id), FQ(Tag.GasFeeCap), FQ(0), Word(0)),
Row(FQ(tx_id), FQ(Tag.CallerAddress), FQ(0), FQ(0)),
Row(FQ(tx_id), FQ(Tag.CalleeAddress), FQ(0), FQ(0)),
Row(FQ(tx_id), FQ(Tag.IsCreate), FQ(0), FQ(0)),
Expand All @@ -339,7 +342,17 @@ def tx2witness(
"""

tx_sign_data = rlp.encode(
[tx.nonce, tx.gas_price, tx.gas, tx.encode_to(), tx.value, tx.data, chain_id, 0, 0]
[
chain_id,
tx.nonce,
tx.gas_tip_cap,
tx.gas_fee_cap,
tx.gas,
tx.encode_to(),
tx.value,
tx.data,
tx.access_list,
]
)
tx_sign_hash = keccak(tx_sign_data)

Expand Down Expand Up @@ -379,7 +392,8 @@ def tx2witness(
rows: List[Row] = []
rows.append(Row(tx_id, FQ(Tag.Nonce), FQ(0), FQ(tx.nonce)))
rows.append(Row(tx_id, FQ(Tag.Gas), FQ(0), FQ(tx.gas)))
rows.append(Row(tx_id, FQ(Tag.GasPrice), FQ(0), Word(tx.gas_price)))
rows.append(Row(tx_id, FQ(Tag.GasTipCap), FQ(0), Word(tx.gas_tip_cap)))
rows.append(Row(tx_id, FQ(Tag.GasFeeCap), FQ(0), Word(tx.gas_fee_cap)))
rows.append(Row(tx_id, FQ(Tag.CallerAddress), FQ(0), FQ(int.from_bytes(addr, "big"))))
rows.append(Row(tx_id, FQ(Tag.CalleeAddress), FQ(0), FQ(tx.to or 0)))
rows.append(Row(tx_id, FQ(Tag.IsCreate), FQ(0), FQ(1) if tx.to is None else FQ(0)))
Expand Down
10 changes: 5 additions & 5 deletions tests/evm/test_begin_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@
CALLEE_WITH_RETURN_BYTECODE,
True,
),
# Transfer nothing with random gas_price, successfully
# Transfer nothing with random gas_fee_cap, successfully
(
Transaction(
caller_address=rand_address(),
callee_address=CALLEE_ADDRESS,
gas_price=rand_range(42857142857143),
gas_fee_cap=rand_range(42857142857143),
),
CALLEE_WITH_RETURN_BYTECODE,
True,
Expand All @@ -70,12 +70,12 @@
CALLEE_WITH_REVERT_BYTECODE,
False,
),
# Transfer nothing with random gas_price, tx reverts
# Transfer nothing with random gas_fee_cap, tx reverts
(
Transaction(
caller_address=rand_address(),
callee_address=CALLEE_ADDRESS,
gas_price=rand_range(42857142857143),
gas_fee_cap=rand_range(42857142857143),
),
CALLEE_WITH_REVERT_BYTECODE,
False,
Expand Down Expand Up @@ -164,7 +164,7 @@ def test_begin_tx(tx: Transaction, callee: Account, is_success: bool):
caller_balance_prev = int(1e20)
callee_balance_prev = callee.balance
caller_balance = (
caller_balance_prev - (tx.value + tx.gas * tx.gas_price)
caller_balance_prev - (tx.value + tx.gas * tx.gas_fee_cap)
if is_tx_valid
else caller_balance_prev
)
Expand Down
Loading