From 28b2ef4bfcbbc94748f4ee0508a6502c61e18630 Mon Sep 17 00:00:00 2001 From: han0110 Date: Sat, 12 Feb 2022 12:07:38 +0800 Subject: [PATCH 1/5] feat: implement CALL --- bus-mapping/src/circuit_input_builder.rs | 4 - bus-mapping/src/evm/opcodes.rs | 4 +- bus-mapping/src/evm/opcodes/call.rs | 272 +++++++ bus-mapping/src/evm/opcodes/extcodehash.rs | 4 +- eth-types/src/evm_types.rs | 63 +- eth-types/src/evm_types/memory.rs | 5 + eth-types/src/evm_types/opcode_ids.rs | 16 +- zkevm-circuits/Cargo.toml | 1 + zkevm-circuits/src/evm_circuit.rs | 28 +- zkevm-circuits/src/evm_circuit/execution.rs | 7 + .../src/evm_circuit/execution/begin_tx.rs | 30 +- .../src/evm_circuit/execution/call.rs | 696 ++++++++++++++++++ .../src/evm_circuit/execution/calldatacopy.rs | 6 +- .../src/evm_circuit/execution/extcodehash.rs | 41 +- .../src/evm_circuit/execution/sload.rs | 47 +- .../src/evm_circuit/execution/sstore.rs | 91 +-- zkevm-circuits/src/evm_circuit/param.rs | 6 + zkevm-circuits/src/evm_circuit/table.rs | 10 + .../src/evm_circuit/util/common_gadget.rs | 85 ++- .../evm_circuit/util/constraint_builder.rs | 113 ++- .../src/evm_circuit/util/math_gadget.rs | 22 +- .../src/evm_circuit/util/memory_gadget.rs | 15 +- zkevm-circuits/src/evm_circuit/witness.rs | 1 + 23 files changed, 1321 insertions(+), 246 deletions(-) create mode 100644 bus-mapping/src/evm/opcodes/call.rs create mode 100644 zkevm-circuits/src/evm_circuit/execution/call.rs diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index 213ad9c8e4..043fe80d62 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -91,10 +91,6 @@ pub enum ExecError { InvalidJump, /// For RETURNDATACOPY ReturnDataOutOfBounds, - // NOTE: We don't use the GasUintOverflow in favour of always reporting an - // OutOfGas error. - // /// Internal calculation of gas overflow - // GasUintOverflow, /// For RETURN in a CREATE, CREATE2 CodeStoreOutOfGas, /// For RETURN in a CREATE, CREATE2 diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index bf8e1cc2c1..e0c33ba59f 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -15,6 +15,7 @@ use eth_types::{ }; use log::warn; +mod call; mod calldatacopy; mod calldatasize; mod caller; @@ -30,6 +31,7 @@ mod stackonlyop; mod stop; mod swap; +use call::Call; use calldatacopy::Calldatacopy; use calldatasize::Calldatasize; use caller::Caller; @@ -207,7 +209,7 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { // OpcodeId::LOG3 => {}, // OpcodeId::LOG4 => {}, // OpcodeId::CREATE => {}, - // OpcodeId::CALL => {}, + OpcodeId::CALL => Call::gen_associated_ops, // OpcodeId::CALLCODE => {}, // TODO: Handle RETURN by its own gen_associated_ops. OpcodeId::RETURN => Stop::gen_associated_ops, diff --git a/bus-mapping/src/evm/opcodes/call.rs b/bus-mapping/src/evm/opcodes/call.rs new file mode 100644 index 0000000000..d2c79ece71 --- /dev/null +++ b/bus-mapping/src/evm/opcodes/call.rs @@ -0,0 +1,272 @@ +use super::Opcode; +use crate::{ + circuit_input_builder::{CircuitInputStateRef, ExecStep}, + operation::{ + AccountField, AccountOp, CallContextField, CallContextOp, StackOp, TxAccessListAccountOp, + RW, + }, + Error, +}; +use eth_types::{ + evm_types::{eip150_gas, memory_expansion_gas_cost, GasCost}, + GethExecStep, ToWord, +}; + +/// Placeholder structure used to implement [`Opcode`] trait over it +/// corresponding to the `OpcodeId::DUP*` `OpcodeId`. +#[derive(Debug, Copy, Clone)] +pub(crate) struct Call; + +impl Opcode for Call { + fn gen_associated_ops( + state: &mut CircuitInputStateRef, + geth_steps: &[GethExecStep], + ) -> Result, Error> { + let geth_step = &geth_steps[0]; + let mut exec_step = state.new_step(geth_step)?; + + let tx_id = state.tx_ctx.id(); + let call = state.call()?.clone(); + let callee = state.parse_call(geth_step)?; + + for (field, value) in [ + (CallContextField::TxId, tx_id.into()), + (CallContextField::RwCounterEndOfReversion, 0.into()), + ( + CallContextField::IsPersistent, + (call.is_persistent as u64).into(), + ), + (CallContextField::CallerAddress, call.address.to_word()), + (CallContextField::IsStatic, (call.is_static as u64).into()), + (CallContextField::Depth, call.depth.into()), + ] { + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: call.call_id, + field, + value, + }, + ); + } + + for i in 0..7 { + state.push_op( + &mut exec_step, + RW::READ, + StackOp { + call_id: call.call_id, + address: geth_step.stack.nth_last_filled(i), + value: geth_step.stack.nth_last(i)?, + }, + ); + } + state.push_op( + &mut exec_step, + RW::WRITE, + StackOp { + call_id: call.call_id, + address: geth_step.stack.nth_last_filled(6), + value: (callee.is_success as u64).into(), + }, + ); + + let is_warm_access = !state.sdb.add_account_to_access_list(callee.address); + state.push_op_reversible( + &mut exec_step, + RW::WRITE, + TxAccessListAccountOp { + tx_id, + address: callee.address, + value: true, + value_prev: is_warm_access, + }, + )?; + + // Switch to callee's call context + state.push_call(callee.clone()); + + for (field, value) in [ + (CallContextField::RwCounterEndOfReversion, 0.into()), + ( + CallContextField::IsPersistent, + (callee.is_persistent as u64).into(), + ), + ] { + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: callee.call_id, + field, + value, + }, + ); + } + + let (found, caller_account) = state.sdb.get_account_mut(&callee.caller_address); + if !found { + return Err(Error::AccountNotFound(callee.caller_address)); + } + let caller_balance_prev = caller_account.balance; + let caller_balance = caller_account.balance - callee.value; + caller_account.balance = caller_balance; + state.push_op_reversible( + &mut exec_step, + RW::WRITE, + AccountOp { + address: callee.caller_address, + field: AccountField::Balance, + value: caller_balance, + value_prev: caller_balance_prev, + }, + )?; + + let (found, callee_account) = state.sdb.get_account_mut(&callee.address); + if !found { + return Err(Error::AccountNotFound(callee.address)); + } + let is_account_empty = callee_account.is_empty(); + let callee_balance_prev = callee_account.balance; + let callee_balance = callee_account.balance + callee.value; + callee_account.balance = callee_balance; + state.push_op_reversible( + &mut exec_step, + RW::WRITE, + AccountOp { + address: callee.address, + field: AccountField::Balance, + value: callee_balance, + value_prev: callee_balance_prev, + }, + )?; + + let (_, account) = state.sdb.get_account_mut(&callee.address); + for (field, value) in [ + (AccountField::Nonce, account.nonce), + (AccountField::CodeHash, account.code_hash.to_word()), + ] { + state.push_op( + &mut exec_step, + RW::READ, + AccountOp { + address: callee.address, + field, + value, + value_prev: value, + }, + ); + } + + // Calculate next_memory_word_size and callee_gas_left manually in case + // there isn't next geth_step (e.g. callee doesn't have code). + let next_memory_word_size = [ + geth_step.memory.word_size() as u64, + (callee.call_data_offset + callee.call_data_length + 31) / 32, + (callee.return_data_offset + callee.return_data_length + 31) / 32, + ] + .into_iter() + .max() + .unwrap(); + let has_value = !callee.value.is_zero(); + let gas_cost = if is_warm_access { + GasCost::WARM_ACCESS.0 + } else { + GasCost::COLD_ACCOUNT_ACCESS.0 + } + if is_account_empty { + GasCost::CALL_EMPTY_ACCOUNT.0 + } else { + 0 + } + if has_value { + GasCost::CALL_WITH_VALUE.0 + } else { + 0 + } + memory_expansion_gas_cost( + geth_step.memory.word_size() as u64, + next_memory_word_size, + ); + let callee_gas_left = eip150_gas(geth_step.gas.0 - gas_cost, geth_step.stack.last()?); + + for (field, value) in [ + (CallContextField::IsRoot, (call.is_root as u64).into()), + (CallContextField::IsCreate, (call.is_create() as u64).into()), + (CallContextField::CodeSource, call.code_hash.to_word()), + ( + CallContextField::ProgramCounter, + (geth_step.pc.0 + 1).into(), + ), + ( + CallContextField::StackPointer, + (geth_step.stack.stack_pointer().0 + 6).into(), + ), + ( + CallContextField::GasLeft, + (geth_step.gas.0 - gas_cost - callee_gas_left).into(), + ), + (CallContextField::MemorySize, next_memory_word_size.into()), + ( + CallContextField::StateWriteCounter, + (exec_step.swc + 1).into(), + ), + ] { + state.push_op( + &mut exec_step, + RW::WRITE, + CallContextOp { + call_id: call.call_id, + field, + value, + }, + ); + } + + for (field, value) in [ + (CallContextField::CallerId, call.call_id.into()), + (CallContextField::TxId, tx_id.into()), + (CallContextField::Depth, callee.depth.into()), + ( + CallContextField::CallerAddress, + callee.caller_address.to_word(), + ), + (CallContextField::CalleeAddress, callee.address.to_word()), + ( + CallContextField::CallDataOffset, + callee.call_data_offset.into(), + ), + ( + CallContextField::CallDataLength, + callee.call_data_length.into(), + ), + ( + CallContextField::ReturnDataOffset, + callee.return_data_offset.into(), + ), + ( + CallContextField::ReturnDataLength, + callee.return_data_length.into(), + ), + (CallContextField::Value, callee.value), + ( + CallContextField::IsSuccess, + (callee.is_success as u64).into(), + ), + (CallContextField::IsStatic, (callee.is_static as u64).into()), + (CallContextField::LastCalleeId, 0.into()), + (CallContextField::LastCalleeReturnDataOffset, 0.into()), + (CallContextField::LastCalleeReturnDataLength, 0.into()), + ] { + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: callee.call_id, + field, + value, + }, + ); + } + + Ok(vec![exec_step]) + } +} diff --git a/bus-mapping/src/evm/opcodes/extcodehash.rs b/bus-mapping/src/evm/opcodes/extcodehash.rs index 33354f538a..c4b118a00f 100644 --- a/bus-mapping/src/evm/opcodes/extcodehash.rs +++ b/bus-mapping/src/evm/opcodes/extcodehash.rs @@ -59,8 +59,8 @@ impl Opcode for Extcodehash { // Update transaction access list for external_address let is_warm = match step.gas_cost { - GasCost::WARM_STORAGE_READ_COST => true, - GasCost::COLD_ACCOUNT_ACCESS_COST => false, + GasCost::WARM_ACCESS => true, + GasCost::COLD_ACCOUNT_ACCESS => false, _ => unreachable!(), }; state.sdb.add_account_to_access_list(external_address); diff --git a/eth-types/src/evm_types.rs b/eth-types/src/evm_types.rs index c77564880a..f340baf421 100644 --- a/eth-types/src/evm_types.rs +++ b/eth-types/src/evm_types.rs @@ -1,5 +1,6 @@ //! Evm types needed for parsing instruction sets as well +use crate::Word; use serde::{Deserialize, Serialize}; use std::fmt; @@ -63,6 +64,8 @@ impl fmt::Debug for Gas { /// Quotient for max refund of gas used pub const MAX_REFUND_QUOTIENT_OF_GAS_USED: usize = 5; +/// Gas stipend when CALL or CALLCODE is attached with value. +pub const GAS_STIPEND_CALL_WITH_VALUE: u64 = 2300; /// Defines the gas consumption. #[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] @@ -97,28 +100,28 @@ impl GasCost { pub const SELFDESTRUCT: Self = Self(5000); /// Constant cost for CREATE pub const CREATE: Self = Self(32000); - /// Constant cost for every additional word when expanding memory - pub const MEMORY: Self = Self(3); /// Constant cost for copying every word pub const COPY: Self = Self(3); + /// Constant cost for accessing account or storage key + pub const WARM_ACCESS: Self = Self(100); /// Constant cost for a cold SLOAD - pub const COLD_SLOAD_COST: Self = Self(2100); + pub const COLD_SLOAD: Self = Self(2100); /// Constant cost for a cold account access - pub const COLD_ACCOUNT_ACCESS_COST: Self = Self(2600); - /// Constant cost for a warm storage read - pub const WARM_STORAGE_READ_COST: Self = Self(100); - /// Constant cost for a basic storage operation - pub const SLOAD_GAS: Self = Self(100); + pub const COLD_ACCOUNT_ACCESS: Self = Self(2600); /// Constant cost for a storage set - pub const SSTORE_SET_GAS: Self = Self(20000); + pub const SSTORE_SET: Self = Self(20000); /// Constant cost for a storage reset - pub const SSTORE_RESET_GAS: Self = Self(2900); + pub const SSTORE_RESET: Self = Self(2900); /// Constant cost for a storage clear pub const SSTORE_CLEARS_SCHEDULE: Self = Self(15000); /// Constant cost for a non-creation transaction pub const TX: Self = Self(21000); - /// Constant cost for creation transaction + /// Constant cost for a creation transaction pub const CREATION_TX: Self = Self(53000); + /// Constant cost for calling with non-zero value + pub const CALL_WITH_VALUE: Self = Self(9000); + /// Constant cost for calling empty account + pub const CALL_EMPTY_ACCOUNT: Self = Self(25000); /// Denominator of quadratic part of memory expansion gas cost pub const MEMORY_EXPANSION_QUAD_DENOMINATOR: Self = Self(512); /// Coefficient of linear part of memory expansion gas cost @@ -150,3 +153,41 @@ impl From for GasCost { GasCost(cost) } } + +/// Calculate memory expansion gas cost by current and next memory word size. +pub fn memory_expansion_gas_cost(curr_memory_word_size: u64, next_memory_word_size: u64) -> u64 { + if next_memory_word_size == curr_memory_word_size { + 0 + } else { + GasCost::MEMORY_EXPANSION_LINEAR_COEFF.0 * (next_memory_word_size - curr_memory_word_size) + + (next_memory_word_size * next_memory_word_size + - curr_memory_word_size * curr_memory_word_size) + / GasCost::MEMORY_EXPANSION_QUAD_DENOMINATOR.0 + } +} + +/// Calculate memory copier gas cost by current and next memory word size, and +/// number of bytes to copy. +pub fn memory_copier_gas_cost( + curr_memory_word_size: u64, + next_memory_word_size: u64, + num_copy_bytes: u64, +) -> u64 { + let num_words = (num_copy_bytes + 31) / 32; + num_words * GasCost::COPY.as_u64() + + memory_expansion_gas_cost(curr_memory_word_size, next_memory_word_size) +} + +/// Calculate EIP 150 gas passed to callee. +pub fn eip150_gas(gas_left: u64, gas_specified: Word) -> u64 { + let capped_gas = gas_left - gas_left / 64; + + if gas_specified.bits() <= 64 { + let gas_specified = gas_specified.low_u64(); + if gas_specified < capped_gas { + return gas_specified; + } + } + + capped_gas +} diff --git a/eth-types/src/evm_types/memory.rs b/eth-types/src/evm_types/memory.rs index ddb68e3c28..4418f24e34 100644 --- a/eth-types/src/evm_types/memory.rs +++ b/eth-types/src/evm_types/memory.rs @@ -271,6 +271,11 @@ impl Memory { &self[addr..addr + MemoryAddress::from(32)], )) } + + /// Returns the size of memroy in word. + pub fn word_size(&self) -> usize { + self.0.len() / 32 + } } #[cfg(test)] diff --git a/eth-types/src/evm_types/opcode_ids.rs b/eth-types/src/evm_types/opcode_ids.rs index 9c85f0d1b0..e8be148578 100644 --- a/eth-types/src/evm_types/opcode_ids.rs +++ b/eth-types/src/evm_types/opcode_ids.rs @@ -516,7 +516,7 @@ impl OpcodeId { OpcodeId::SAR => GasCost::FASTEST, OpcodeId::SHA3 => GasCost::SHA3, OpcodeId::ADDRESS => GasCost::QUICK, - OpcodeId::BALANCE => GasCost::WARM_STORAGE_READ_COST, + OpcodeId::BALANCE => GasCost::WARM_ACCESS, OpcodeId::ORIGIN => GasCost::QUICK, OpcodeId::CALLER => GasCost::QUICK, OpcodeId::CALLVALUE => GasCost::QUICK, @@ -526,11 +526,11 @@ impl OpcodeId { OpcodeId::CODESIZE => GasCost::QUICK, OpcodeId::CODECOPY => GasCost::FASTEST, OpcodeId::GASPRICE => GasCost::QUICK, - OpcodeId::EXTCODESIZE => GasCost::WARM_STORAGE_READ_COST, - OpcodeId::EXTCODECOPY => GasCost::WARM_STORAGE_READ_COST, + OpcodeId::EXTCODESIZE => GasCost::WARM_ACCESS, + OpcodeId::EXTCODECOPY => GasCost::WARM_ACCESS, OpcodeId::RETURNDATASIZE => GasCost::QUICK, OpcodeId::RETURNDATACOPY => GasCost::FASTEST, - OpcodeId::EXTCODEHASH => GasCost::WARM_STORAGE_READ_COST, + OpcodeId::EXTCODEHASH => GasCost::WARM_ACCESS, OpcodeId::BLOCKHASH => GasCost::EXT, OpcodeId::COINBASE => GasCost::QUICK, OpcodeId::TIMESTAMP => GasCost::QUICK, @@ -622,12 +622,12 @@ impl OpcodeId { OpcodeId::LOG3 => GasCost::ZERO, OpcodeId::LOG4 => GasCost::ZERO, OpcodeId::CREATE => GasCost::CREATE, - OpcodeId::CALL => GasCost::WARM_STORAGE_READ_COST, - OpcodeId::CALLCODE => GasCost::WARM_STORAGE_READ_COST, + OpcodeId::CALL => GasCost::WARM_ACCESS, + OpcodeId::CALLCODE => GasCost::WARM_ACCESS, OpcodeId::RETURN => GasCost::ZERO, - OpcodeId::DELEGATECALL => GasCost::WARM_STORAGE_READ_COST, + OpcodeId::DELEGATECALL => GasCost::WARM_ACCESS, OpcodeId::CREATE2 => GasCost::CREATE, - OpcodeId::STATICCALL => GasCost::WARM_STORAGE_READ_COST, + OpcodeId::STATICCALL => GasCost::WARM_ACCESS, OpcodeId::REVERT => GasCost::ZERO, OpcodeId::INVALID(_) => GasCost::ZERO, OpcodeId::SELFDESTRUCT => GasCost::SELFDESTRUCT, diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index c7d247f35e..90ed6faeb4 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -30,6 +30,7 @@ keccak256 = { path = "../keccak256"} criterion = "0.3" hex = "0.4.3" mock = { path = "../mock" } +itertools = "0.10.1" [[bench]] name = "binary_value" diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index eaa29a080f..107f5ddf4d 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -124,7 +124,7 @@ pub mod test { rw_table::RwTable, util::Expr, }; - use eth_types::{evm_types::GasCost, Field, Word}; + use eth_types::{Field, Word}; use halo2_proofs::{ arithmetic::BaseExt, circuit::{Layouter, SimpleFloorPlanner}, @@ -433,8 +433,10 @@ pub mod test { FixedTableTag::Range5, FixedTableTag::Range16, FixedTableTag::Range32, + FixedTableTag::Range64, FixedTableTag::Range256, FixedTableTag::Range512, + FixedTableTag::Range1024, FixedTableTag::SignByte, FixedTableTag::ResponsibleOpcode, ], @@ -446,28 +448,4 @@ pub mod test { ) -> Result<(), Vec> { run_test_circuit(block, FixedTableTag::iterator().collect()) } - - pub(crate) fn calc_memory_expension_gas_cost( - curr_memory_word_size: u64, - next_memory_word_size: u64, - ) -> u64 { - if next_memory_word_size <= curr_memory_word_size { - 0 - } else { - let total_cost = |mem_word_size| { - mem_word_size * GasCost::MEMORY.as_u64() + mem_word_size * mem_word_size / 512 - }; - total_cost(next_memory_word_size) - total_cost(curr_memory_word_size) - } - } - - pub(crate) fn calc_memory_copier_gas_cost( - curr_memory_word_size: u64, - next_memory_word_size: u64, - num_copy_bytes: u64, - ) -> u64 { - let num_words = (num_copy_bytes + 31) / 32; - num_words * GasCost::COPY.as_u64() - + calc_memory_expension_gas_cost(curr_memory_word_size, next_memory_word_size) - } } diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 8a846097b9..2ce1123da4 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -21,6 +21,7 @@ mod add; mod begin_tx; mod bitwise; mod byte; +mod call; mod calldatacopy; mod calldataload; mod calldatasize; @@ -59,6 +60,7 @@ use add::AddGadget; use begin_tx::BeginTxGadget; use bitwise::BitwiseGadget; use byte::ByteGadget; +use call::CallGadget; use calldatacopy::CallDataCopyGadget; use calldataload::CallDataLoadGadget; use calldatasize::CallDataSizeGadget; @@ -128,6 +130,7 @@ pub(crate) struct ExecutionConfig { calldatasize_gadget: CallDataSizeGadget, caller_gadget: CallerGadget, call_value_gadget: CallValueGadget, + call_gadget: CallGadget, comparator_gadget: ComparatorGadget, dup_gadget: DupGadget, end_block_gadget: EndBlockGadget, @@ -345,6 +348,7 @@ impl ExecutionConfig { calldatasize_gadget: configure_gadget!(), caller_gadget: configure_gadget!(), call_value_gadget: configure_gadget!(), + call_gadget: configure_gadget!(), comparator_gadget: configure_gadget!(), dup_gadget: configure_gadget!(), end_block_gadget: configure_gadget!(), @@ -661,6 +665,9 @@ impl ExecutionConfig { assign_exec_step!(self.calldatasize_gadget) } ExecutionState::ISZERO => assign_exec_step!(self.iszero_gadget), + ExecutionState::CALL => { + assign_exec_step!(self.call_gadget) + } _ => unimplemented!(), } diff --git a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs index c72509f742..474179729d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs @@ -7,7 +7,7 @@ use crate::{ util::{ common_gadget::TransferWithGasFeeGadget, constraint_builder::{ - ConstraintBuilder, StepStateTransition, + ConstraintBuilder, ReversionInfo, StepStateTransition, Transition::{Delta, To}, }, math_gadget::{MulWordByU64Gadget, RangeCheckGadget}, @@ -17,9 +17,7 @@ use crate::{ }, util::Expr, }; -use eth_types::evm_types::GasCost; -use eth_types::Field; -use eth_types::{ToLittleEndian, ToScalar}; +use eth_types::{evm_types::GasCost, Field, ToLittleEndian, ToScalar}; use halo2_proofs::{circuit::Region, plonk::Error}; #[derive(Clone, Debug)] @@ -35,8 +33,7 @@ pub(crate) struct BeginTxGadget { tx_value: Word, tx_call_data_length: Cell, tx_call_data_gas_cost: Cell, - rw_counter_end_of_reversion: Cell, - is_persistent: Cell, + reversion_info: ReversionInfo, sufficient_gas_left: RangeCheckGadget, transfer_with_gas_fee: TransferWithGasFeeGadget, code_hash: Cell, @@ -51,12 +48,8 @@ impl ExecutionGadget for BeginTxGadget { // Use rw_counter of the step which triggers next call as its call_id. let call_id = cb.curr.state.rw_counter.clone(); - let [tx_id, rw_counter_end_of_reversion, is_persistent] = [ - CallContextFieldTag::TxId, - CallContextFieldTag::RwCounterEndOfReversion, - CallContextFieldTag::IsPersistent, - ] - .map(|field_tag| cb.call_context(Some(call_id.expr()), field_tag)); + let tx_id = cb.call_context(Some(call_id.expr()), CallContextFieldTag::TxId); + let mut reversion_info = cb.reversion_info(None); let [tx_nonce, tx_gas, tx_caller_address, tx_callee_address, tx_is_create, tx_call_data_length, tx_call_data_gas_cost] = [ @@ -130,8 +123,7 @@ impl ExecutionGadget for BeginTxGadget { tx_callee_address.expr(), tx_value.clone(), mul_gas_fee_by_gas.product().clone(), - is_persistent.expr(), - rw_counter_end_of_reversion.expr(), + &mut reversion_info, ); // TODO: Handle creation transaction @@ -207,8 +199,7 @@ impl ExecutionGadget for BeginTxGadget { tx_value, tx_call_data_length, tx_call_data_gas_cost, - rw_counter_end_of_reversion, - is_persistent, + reversion_info, sufficient_gas_left, transfer_with_gas_fee, code_hash, @@ -251,13 +242,12 @@ impl ExecutionGadget for BeginTxGadget { )?; self.tx_call_data_gas_cost .assign(region, offset, Some(F::from(tx.call_data_gas_cost)))?; - self.rw_counter_end_of_reversion.assign( + self.reversion_info.assign( region, offset, - Some(F::from(call.rw_counter_end_of_reversion as u64)), + call.rw_counter_end_of_reversion, + call.is_persistent, )?; - self.is_persistent - .assign(region, offset, Some(F::from(call.is_persistent as u64)))?; self.sufficient_gas_left .assign(region, offset, F::from(tx.gas - step.gas_cost))?; self.transfer_with_gas_fee.assign( diff --git a/zkevm-circuits/src/evm_circuit/execution/call.rs b/zkevm-circuits/src/evm_circuit/execution/call.rs new file mode 100644 index 0000000000..456d6bf7d0 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/call.rs @@ -0,0 +1,696 @@ +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + param::{EMPTY_HASH, N_BYTES_ACCOUNT_ADDRESS, N_BYTES_GAS, N_BYTES_MEMORY_WORD_SIZE}, + step::ExecutionState, + table::{AccountFieldTag, CallContextFieldTag, FixedTableTag, Lookup}, + util::{ + common_gadget::TransferGadget, + constraint_builder::{ + ConstraintBuilder, ReversionInfo, StepStateTransition, + Transition::{Delta, To}, + }, + from_bytes, + math_gadget::{ConstantDivisionGadget, IsEqualGadget, IsZeroGadget, MinMaxGadget}, + memory_gadget::{MemoryAddressGadget, MemoryExpansionGadget}, + select, sum, Cell, Word, + }, + witness::{Block, Call, ExecStep, Transaction}, + }, + util::Expr, +}; +use eth_types::{ + evm_types::{GasCost, GAS_STIPEND_CALL_WITH_VALUE}, + Field, ToLittleEndian, ToScalar, +}; +use halo2_proofs::{circuit::Region, plonk::Error}; + +#[derive(Clone, Debug)] +pub(crate) struct CallGadget { + opcode: Cell, + tx_id: Cell, + reversion_info: ReversionInfo, + caller_address: Cell, + is_static: Cell, + depth: Cell, + gas: Word, + callee_address: Word, + value: Word, + is_success: Cell, + gas_is_u64: IsZeroGadget, + is_warm_access: Cell, + is_warm_access_prev: Cell, + callee_reversion_info: ReversionInfo, + value_is_zero: IsZeroGadget, + cd_address: MemoryAddressGadget, + rd_address: MemoryAddressGadget, + memory_expansion: MemoryExpansionGadget, + transfer: TransferGadget, + callee_nonce: Cell, + callee_code_hash: Cell, + callee_nonce_is_zero: IsZeroGadget, + callee_balance_is_zero: IsZeroGadget, + callee_code_hash_is_empty: IsEqualGadget, + is_account_empty: Cell, + memory_expansion_gas_cost: Cell, + next_memory_word_size: Cell, + one_64th_gas: ConstantDivisionGadget, + capped_callee_gas_left: MinMaxGadget, +} + +impl ExecutionGadget for CallGadget { + const NAME: &'static str = "CALL"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::CALL; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let opcode = cb.query_cell(); + cb.opcode_lookup(opcode.expr(), 1.expr()); + cb.add_lookup( + "Responsible opcode lookup", + Lookup::Fixed { + tag: FixedTableTag::ResponsibleOpcode.expr(), + values: [ + cb.execution_state().as_u64().expr(), + opcode.expr(), + 0.expr(), + ], + }, + ); + + let gas_word = cb.query_word(); + let callee_address_word = cb.query_word(); + let value = cb.query_word(); + let cd_offset = cb.query_cell(); + let cd_length = cb.query_rlc(); + let rd_offset = cb.query_cell(); + let rd_length = cb.query_rlc(); + let is_success = cb.query_bool(); + + // Use rw_counter of the step which triggers next call as its call_id. + let callee_call_id = cb.curr.state.rw_counter.clone(); + + let tx_id = cb.call_context(None, CallContextFieldTag::TxId); + let mut reversion_info = cb.reversion_info(None); + let [caller_address, is_static, depth] = [ + CallContextFieldTag::CallerAddress, + CallContextFieldTag::IsStatic, + CallContextFieldTag::Depth, + ] + .map(|field_tag| cb.call_context(None, field_tag)); + + cb.range_lookup(depth.expr(), 1024); + + // Lookup values from stack + cb.stack_pop(gas_word.expr()); + cb.stack_pop(callee_address_word.expr()); + cb.stack_pop(value.expr()); + cb.stack_pop(cd_offset.expr()); + cb.stack_pop(cd_length.expr()); + cb.stack_pop(rd_offset.expr()); + cb.stack_pop(rd_length.expr()); + cb.stack_push(is_success.expr()); + + // Recomposition of random linear combination to integer + let callee_address = + from_bytes::expr(&callee_address_word.cells[..N_BYTES_ACCOUNT_ADDRESS]); + let gas = from_bytes::expr(&gas_word.cells[..N_BYTES_GAS]); + let gas_is_u64 = IsZeroGadget::construct(cb, sum::expr(&gas_word.cells[N_BYTES_GAS..])); + let cd_address = MemoryAddressGadget::construct(cb, cd_offset, cd_length); + let rd_address = MemoryAddressGadget::construct(cb, rd_offset, rd_length); + let memory_expansion = MemoryExpansionGadget::construct( + cb, + cb.curr.state.memory_word_size.expr(), + [cd_address.address(), rd_address.address()], + ); + + // Add callee to access list + let is_warm_access = cb.query_bool(); + let is_warm_access_prev = cb.query_bool(); + cb.account_access_list_write( + tx_id.expr(), + callee_address.clone(), + is_warm_access.expr(), + is_warm_access_prev.expr(), + Some(&mut reversion_info), + ); + + // Propagate rw_counter_end_of_reversion and is_persistent + let mut callee_reversion_info = cb.reversion_info(Some(callee_call_id.expr())); + cb.require_equal( + "callee_is_persistent == is_persistent ⋅ is_success", + callee_reversion_info.is_persistent(), + reversion_info.is_persistent() * is_success.expr(), + ); + cb.condition(is_success.expr() * (1.expr() - reversion_info.is_persistent()), |cb| { + cb.require_equal( + "callee_rw_counter_end_of_reversion == rw_counter_end_of_reversion - (state_write_counter + 1)", + callee_reversion_info.rw_counter_end_of_reversion(), + reversion_info.rw_counter_of_reversion(), + ); + }); + + // Verify transfer + let value_is_zero = IsZeroGadget::construct(cb, sum::expr(&value.cells)); + let has_value = 1.expr() - value_is_zero.expr(); + cb.condition(has_value.clone(), |cb| { + cb.require_zero( + "CALL with value must not be in static call stack", + is_static.expr(), + ); + }); + let transfer = TransferGadget::construct( + cb, + caller_address.expr(), + callee_address.clone(), + value.clone(), + &mut callee_reversion_info, + ); + + // Verify gas cost + let [callee_nonce, callee_code_hash] = [AccountFieldTag::Nonce, AccountFieldTag::CodeHash] + .map(|field_tag| { + let value = cb.query_cell(); + cb.account_read(callee_address.clone(), field_tag, value.expr()); + value + }); + let callee_nonce_is_zero = + IsZeroGadget::construct(cb, sum::expr(&transfer.receiver().balance_prev().cells)); + let callee_balance_is_zero = IsZeroGadget::construct(cb, callee_nonce.expr()); + let callee_code_hash_is_empty = IsEqualGadget::construct( + cb, + callee_code_hash.expr(), + Word::random_linear_combine_expr( + EMPTY_HASH.map(|byte| byte.expr()), + cb.power_of_randomness(), + ), + ); + // Use copy for is_account_empty, memory_expansion_gas_cost, + // next_memory_word_size to avoid degree too high. + let is_account_empty = cb.copy( + callee_nonce_is_zero.expr() + * callee_balance_is_zero.expr() + * callee_code_hash_is_empty.expr(), + ); + let memory_expansion_gas_cost = cb.copy(memory_expansion.gas_cost()); + let next_memory_word_size = cb.copy(memory_expansion.next_memory_word_size()); + // Sum up gas cost + let gas_cost = select::expr( + is_warm_access_prev.expr(), + GasCost::WARM_ACCESS.expr(), + GasCost::COLD_ACCOUNT_ACCESS.expr(), + ) + is_account_empty.expr() * GasCost::CALL_EMPTY_ACCOUNT.expr() + + has_value.clone() * GasCost::CALL_WITH_VALUE.expr() + + memory_expansion_gas_cost.expr(); + + // Apply EIP 150 + let gas_available = cb.curr.state.gas_left.expr() - gas_cost.clone(); + let one_64th_gas = ConstantDivisionGadget::construct(cb, gas_available.clone(), 64); + let all_but_one_64th_gas = gas_available - one_64th_gas.quotient(); + let capped_callee_gas_left = MinMaxGadget::construct(cb, gas, all_but_one_64th_gas.clone()); + let callee_gas_left = select::expr( + gas_is_u64.expr(), + capped_callee_gas_left.min(), + all_but_one_64th_gas, + ); + + // TODO: Handle precompiled + + // Save caller's call state + for (field_tag, value) in [ + (CallContextFieldTag::IsRoot, cb.curr.state.is_root.expr()), + ( + CallContextFieldTag::IsCreate, + cb.curr.state.is_create.expr(), + ), + ( + CallContextFieldTag::CodeSource, + cb.curr.state.code_source.expr(), + ), + ( + CallContextFieldTag::ProgramCounter, + cb.curr.state.program_counter.expr() + 1.expr(), + ), + ( + CallContextFieldTag::StackPointer, + cb.curr.state.stack_pointer.expr() + 6.expr(), + ), + ( + CallContextFieldTag::GasLeft, + cb.curr.state.gas_left.expr() - gas_cost - callee_gas_left.clone(), + ), + ( + CallContextFieldTag::MemorySize, + next_memory_word_size.expr(), + ), + ( + CallContextFieldTag::StateWriteCounter, + cb.curr.state.state_write_counter.expr() + 1.expr(), + ), + ] { + cb.call_context_lookup(true.expr(), None, field_tag, value); + } + + // Setup next call's context. + for (field_tag, value) in [ + (CallContextFieldTag::CallerId, cb.curr.state.call_id.expr()), + (CallContextFieldTag::TxId, tx_id.expr()), + (CallContextFieldTag::Depth, depth.expr() + 1.expr()), + (CallContextFieldTag::CallerAddress, caller_address.expr()), + (CallContextFieldTag::CalleeAddress, callee_address), + (CallContextFieldTag::CallDataOffset, cd_address.offset()), + (CallContextFieldTag::CallDataLength, cd_address.length()), + (CallContextFieldTag::ReturnDataOffset, rd_address.offset()), + (CallContextFieldTag::ReturnDataLength, rd_address.length()), + (CallContextFieldTag::Value, value.expr()), + (CallContextFieldTag::IsSuccess, is_success.expr()), + (CallContextFieldTag::IsStatic, is_static.expr()), + (CallContextFieldTag::LastCalleeId, 0.expr()), + (CallContextFieldTag::LastCalleeReturnDataOffset, 0.expr()), + (CallContextFieldTag::LastCalleeReturnDataLength, 0.expr()), + ] { + cb.call_context_lookup(false.expr(), Some(callee_call_id.expr()), field_tag, value); + } + + // Give gas stipend if value is not zero + let callee_gas_left = callee_gas_left + has_value * GAS_STIPEND_CALL_WITH_VALUE.expr(); + + cb.require_step_state_transition(StepStateTransition { + rw_counter: Delta(44.expr()), + call_id: To(callee_call_id.expr()), + is_root: To(false.expr()), + is_create: To(false.expr()), + code_source: To(callee_code_hash.expr()), + gas_left: To(callee_gas_left), + state_write_counter: To(2.expr()), + ..StepStateTransition::new_context() + }); + + Self { + opcode, + tx_id, + reversion_info, + caller_address, + is_static, + depth, + gas: gas_word, + callee_address: callee_address_word, + value, + is_success, + gas_is_u64, + is_warm_access, + is_warm_access_prev, + callee_reversion_info, + value_is_zero, + cd_address, + rd_address, + memory_expansion, + transfer, + callee_nonce, + callee_code_hash, + callee_nonce_is_zero, + callee_balance_is_zero, + callee_code_hash_is_empty, + is_account_empty, + memory_expansion_gas_cost, + next_memory_word_size, + one_64th_gas, + capped_callee_gas_left, + } + } + + fn assign_exec_step( + &self, + region: &mut Region<'_, F>, + offset: usize, + block: &Block, + _: &Transaction, + call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + let [tx_id, caller_address, is_static, depth, callee_rw_counter_end_of_reversion, callee_is_persistent] = + [ + step.rw_indices[0], + step.rw_indices[3], + step.rw_indices[4], + step.rw_indices[5], + step.rw_indices[15], + step.rw_indices[16], + ] + .map(|idx| block.rws[idx].call_context_value()); + let [gas, callee_address, value, cd_offset, cd_length, rd_offset, rd_length, is_success] = + [ + step.rw_indices[6], + step.rw_indices[7], + step.rw_indices[8], + step.rw_indices[9], + step.rw_indices[10], + step.rw_indices[11], + step.rw_indices[12], + step.rw_indices[13], + ] + .map(|idx| block.rws[idx].stack_value()); + let (is_warm_access, is_warm_access_prev) = + block.rws[step.rw_indices[14]].tx_access_list_value_pair(); + let [caller_balance_pair, callee_balance_pair, (callee_nonce, _), (callee_code_hash, _)] = + [ + step.rw_indices[17], + step.rw_indices[18], + step.rw_indices[19], + step.rw_indices[20], + ] + .map(|idx| block.rws[idx].account_value_pair()); + + let opcode = step.opcode.unwrap(); + self.opcode + .assign(region, offset, Some(F::from(opcode.as_u64())))?; + + self.tx_id + .assign(region, offset, Some(F::from(tx_id.low_u64())))?; + self.reversion_info.assign( + region, + offset, + call.rw_counter_end_of_reversion, + call.is_persistent, + )?; + self.caller_address + .assign(region, offset, caller_address.to_scalar())?; + self.is_static + .assign(region, offset, Some(F::from(is_static.low_u64())))?; + self.depth + .assign(region, offset, Some(F::from(depth.low_u64())))?; + + self.gas.assign(region, offset, Some(gas.to_le_bytes()))?; + self.callee_address + .assign(region, offset, Some(callee_address.to_le_bytes()))?; + self.value + .assign(region, offset, Some(value.to_le_bytes()))?; + self.is_success + .assign(region, offset, Some(F::from(is_success.low_u64())))?; + self.gas_is_u64.assign( + region, + offset, + sum::value(&gas.to_le_bytes()[N_BYTES_GAS..]), + )?; + self.is_warm_access + .assign(region, offset, Some(F::from(is_warm_access as u64)))?; + self.is_warm_access_prev.assign( + region, + offset, + Some(F::from(is_warm_access_prev as u64)), + )?; + self.callee_reversion_info.assign( + region, + offset, + callee_rw_counter_end_of_reversion.low_u64() as usize, + callee_is_persistent.low_u64() != 0, + )?; + let value_is_zero = + self.value_is_zero + .assign(region, offset, sum::value(&value.to_le_bytes()))?; + let cd_address = + self.cd_address + .assign(region, offset, cd_offset, cd_length, block.randomness)?; + let rd_address = + self.rd_address + .assign(region, offset, rd_offset, rd_length, block.randomness)?; + let (next_memory_word_size, memory_expansion_gas_cost) = self.memory_expansion.assign( + region, + offset, + step.memory_size, + [cd_address, rd_address], + )?; + self.transfer.assign( + region, + offset, + caller_balance_pair, + callee_balance_pair, + value, + )?; + self.callee_nonce + .assign(region, offset, callee_nonce.to_scalar())?; + self.callee_code_hash.assign( + region, + offset, + Some(Word::random_linear_combine( + callee_code_hash.to_le_bytes(), + block.randomness, + )), + )?; + let callee_nonce_is_zero = + self.callee_nonce_is_zero + .assign(region, offset, F::from(callee_nonce.low_u64()))?; + let callee_balance_is_zero = self.callee_balance_is_zero.assign( + region, + offset, + sum::value(&callee_balance_pair.1.to_le_bytes()), + )?; + let callee_code_hash_is_empty = self.callee_code_hash_is_empty.assign( + region, + offset, + Word::random_linear_combine(callee_code_hash.to_le_bytes(), block.randomness), + Word::random_linear_combine(EMPTY_HASH, block.randomness), + )?; + let is_account_empty = callee_nonce_is_zero == F::one() + && callee_balance_is_zero == F::one() + && callee_code_hash_is_empty == F::one(); + let has_value = value_is_zero != F::one(); + let gas_cost = if is_warm_access_prev { + GasCost::WARM_ACCESS.as_u64() + } else { + GasCost::COLD_ACCOUNT_ACCESS.as_u64() + } + if is_account_empty { + GasCost::CALL_EMPTY_ACCOUNT.as_u64() + } else { + 0 + } + if has_value { + GasCost::CALL_WITH_VALUE.as_u64() + } else { + 0 + } + memory_expansion_gas_cost; + let gas_available = step.gas_left - gas_cost; + self.is_account_empty + .assign(region, offset, Some(F::from(is_account_empty as u64)))?; + self.memory_expansion_gas_cost.assign( + region, + offset, + Some(F::from(memory_expansion_gas_cost)), + )?; + self.next_memory_word_size + .assign(region, offset, Some(F::from(next_memory_word_size)))?; + self.one_64th_gas + .assign(region, offset, gas_available as u128)?; + self.capped_callee_gas_left.assign( + region, + offset, + F::from(gas.low_u64()), + F::from(gas_available - gas_available / 64), + )?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::evm_circuit::{ + test::{run_test_circuit_complete_fixed_table, run_test_circuit_incomplete_fixed_table}, + witness::block_convert, + }; + use eth_types::bytecode; + use eth_types::{bytecode::Bytecode, evm_types::OpcodeId, geth_types::Account}; + use eth_types::{Address, ToWord, Transaction, Word}; + use itertools::Itertools; + use std::default::Default; + + #[derive(Clone, Copy, Debug, Default)] + struct Stack { + gas: u64, + value: Word, + cd_offset: u64, + cd_length: u64, + rd_offset: u64, + rd_length: u64, + } + + fn caller(stack: Stack, caller_is_success: bool) -> Account { + let terminator = if caller_is_success { + OpcodeId::RETURN + } else { + OpcodeId::REVERT + }; + + let bytecode = bytecode! { + PUSH32(Word::from(stack.rd_length)) + PUSH32(Word::from(stack.rd_offset)) + PUSH32(Word::from(stack.cd_length)) + PUSH32(Word::from(stack.cd_offset)) + PUSH32(stack.value) + PUSH32(Address::repeat_byte(0xff).to_word()) + PUSH32(Word::from(stack.gas)) + CALL + PUSH1(0) + PUSH1(0) + .write_op(terminator) + }; + + Account { + address: Address::repeat_byte(0xfe), + balance: Word::from(10).pow(20.into()), + code: bytecode.to_vec().into(), + ..Default::default() + } + } + + fn callee(code: Bytecode) -> Account { + Account { + address: Address::repeat_byte(0xff), + code: code.to_vec().into(), + ..Default::default() + } + } + + fn test_ok(caller: Account, callee: Account, use_complete_fixed_table: bool) { + let tx = Transaction { + to: Some(caller.address), + gas: 100000.into(), + transaction_index: Some(0.into()), + ..Default::default() + }; + let block_data = bus_mapping::mock::BlockData::new_from_geth_data( + mock::new(vec![caller, callee], vec![tx]).unwrap(), + ); + let mut builder = block_data.new_circuit_input_builder(); + builder + .handle_block(&block_data.eth_block, &block_data.geth_traces) + .unwrap(); + let block = block_convert(&builder.block, &builder.code_db); + assert_eq!( + if use_complete_fixed_table { + run_test_circuit_complete_fixed_table(block) + } else { + run_test_circuit_incomplete_fixed_table(block) + }, + Ok(()) + ); + } + + #[test] + fn call_gadget_simple() { + for stack in [ + // With nothing + Stack::default(), + // With value + Stack { + value: Word::from(10).pow(18.into()), + ..Default::default() + }, + // With gas + Stack { + gas: 100, + ..Default::default() + }, + Stack { + gas: 100000, + ..Default::default() + }, + // With memory expansion + Stack { + cd_offset: 0, + cd_length: 320, + ..Default::default() + }, + Stack { + rd_offset: 0, + rd_length: 320, + ..Default::default() + }, + Stack { + cd_offset: 0, + cd_length: 320, + rd_offset: 0, + rd_length: 1000, + ..Default::default() + }, + ] { + test_ok(caller(stack, true), callee(bytecode! { STOP }), false); + } + } + + #[test] + fn call_gadget_nested() { + let callers = vec![ + caller( + Stack { + gas: 100000, + ..Default::default() + }, + true, + ), + caller( + Stack { + gas: 100000, + ..Default::default() + }, + false, + ), + ]; + let callees = vec![ + // Success + callee(bytecode! { PUSH1(0) PUSH1(0) RETURN }), + // Failure + callee(bytecode! { PUSH1(0) PUSH1(0) REVERT }), + ]; + + for (caller, callee) in callers.into_iter().cartesian_product(callees.into_iter()) { + test_ok(caller, callee, false); + } + } + + #[test] + fn call_gadget_recursive() { + test_ok( + caller( + Stack { + gas: 100000, + ..Default::default() + }, + false, + ), + // The following bytecode calls itself recursively if gas_left is greater than 100, and + // halts with REVERT if gas_left is odd, otherwise just halts with STOP. + callee(bytecode! { + GAS + PUSH1(100) + GT + PUSH1(43) + JUMPI + + PUSH1(0) + PUSH1(0) + PUSH1(0) + PUSH1(0) + PUSH1(0) + PUSH20(Address::repeat_byte(0xff).to_word()) + PUSH1(132) + GAS + SUB + CALL + + JUMPDEST // 43 + GAS + PUSH1(1) + AND + PUSH1(56) + JUMPI + + PUSH1(0) + PUSH1(0) + REVERT + + JUMPDEST // 56 + STOP + }), + true, + ); + } +} diff --git a/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs b/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs index a9d1aaaa88..dcccf9cc95 100644 --- a/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs @@ -231,13 +231,13 @@ mod test { execution::memory_copy::test::make_memory_copy_steps, step::ExecutionState, table::{CallContextFieldTag, RwTableTag}, - test::{calc_memory_copier_gas_cost, rand_bytes, run_test_circuit_incomplete_fixed_table}, + test::{rand_bytes, run_test_circuit_incomplete_fixed_table}, witness::{Block, Bytecode, Call, CodeSource, ExecStep, Rw, RwMap, Transaction}, }; use crate::test_util::{test_circuits_using_bytecode, BytecodeTestConfig}; use eth_types::{ bytecode, - evm_types::{GasCost, OpcodeId}, + evm_types::{memory_copier_gas_cost, GasCost, OpcodeId}, ToBigEndian, Word, }; use halo2_proofs::arithmetic::BaseExt; @@ -353,7 +353,7 @@ mod test { ) }; let gas_cost = GasCost::FASTEST.as_u64() - + calc_memory_copier_gas_cost( + + memory_copier_gas_cost( curr_memory_word_size, next_memory_word_size, length.as_u64(), diff --git a/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs b/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs index 34f4045be6..fec38ffe42 100644 --- a/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs +++ b/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs @@ -6,7 +6,9 @@ use crate::{ table::{AccountFieldTag, CallContextFieldTag}, util::{ common_gadget::SameContextGadget, - constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, + constraint_builder::{ + ConstraintBuilder, ReversionInfo, StepStateTransition, Transition::Delta, + }, from_bytes, Cell, RandomLinearCombination, Word, }, witness::{Block, Call, ExecStep, Transaction}, @@ -28,8 +30,7 @@ pub(crate) struct ExtcodehashGadget { same_context: SameContextGadget, external_address: RandomLinearCombination, tx_id: Cell, - is_persistent: Cell, - rw_counter_end_of_reversion: Cell, + reversion_info: ReversionInfo, is_warm: Cell, nonce: Cell, balance: Cell, @@ -49,12 +50,8 @@ impl ExecutionGadget for ExtcodehashGadget { let external_address = cb.query_rlc(); cb.stack_pop(external_address.expr()); - let [tx_id, rw_counter_end_of_reversion, is_persistent] = [ - CallContextFieldTag::TxId, - CallContextFieldTag::RwCounterEndOfReversion, - CallContextFieldTag::IsPersistent, - ] - .map(|tag| cb.call_context(None, tag)); + let tx_id = cb.call_context(None, CallContextFieldTag::TxId); + let mut reversion_info = cb.reversion_info(None); let is_warm = cb.query_bool(); cb.account_access_list_write( @@ -62,13 +59,7 @@ impl ExecutionGadget for ExtcodehashGadget { from_bytes::expr(&external_address.cells), 1.expr(), is_warm.expr(), - Some( - ( - &is_persistent, - rw_counter_end_of_reversion.expr() - cb.curr.state.state_write_counter.expr(), - ) - .into(), - ), + Some(&mut reversion_info), ); let nonce = cb.query_cell(); @@ -123,8 +114,8 @@ impl ExecutionGadget for ExtcodehashGadget { // code hash cb.stack_push((1.expr() - is_empty.expr()) * code_hash.expr()); - let gas_cost = is_warm.expr() * GasCost::WARM_STORAGE_READ_COST.expr() - + (1.expr() - is_warm.expr()) * GasCost::COLD_ACCOUNT_ACCESS_COST.expr(); + let gas_cost = is_warm.expr() * GasCost::WARM_ACCESS.expr() + + (1.expr() - is_warm.expr()) * GasCost::COLD_ACCOUNT_ACCESS.expr(); let step_state_transition = StepStateTransition { rw_counter: Delta(cb.rw_counter_offset()), program_counter: Delta(1.expr()), @@ -141,8 +132,7 @@ impl ExecutionGadget for ExtcodehashGadget { same_context, external_address, tx_id, - is_persistent, - rw_counter_end_of_reversion, + reversion_info, is_warm, nonce, balance, @@ -171,17 +161,16 @@ impl ExecutionGadget for ExtcodehashGadget { self.tx_id .assign(region, offset, U256::from(tx.id).to_scalar())?; - self.is_persistent - .assign(region, offset, Some(F::from(call.is_persistent as u64)))?; - self.rw_counter_end_of_reversion.assign( + self.reversion_info.assign( region, offset, - Some(F::from(call.rw_counter_end_of_reversion as u64)), + call.rw_counter_end_of_reversion, + call.is_persistent, )?; let is_warm = match GasCost::from(step.gas_cost) { - GasCost::COLD_ACCOUNT_ACCESS_COST => 0, - GasCost::WARM_STORAGE_READ_COST => 1, + GasCost::COLD_ACCOUNT_ACCESS => 0, + GasCost::WARM_ACCESS => 1, _ => unreachable!(), }; self.is_warm diff --git a/zkevm-circuits/src/evm_circuit/execution/sload.rs b/zkevm-circuits/src/evm_circuit/execution/sload.rs index adcb2fa76c..b1a3a609cd 100644 --- a/zkevm-circuits/src/evm_circuit/execution/sload.rs +++ b/zkevm-circuits/src/evm_circuit/execution/sload.rs @@ -5,7 +5,9 @@ use crate::{ table::CallContextFieldTag, util::{ common_gadget::SameContextGadget, - constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, + constraint_builder::{ + ConstraintBuilder, ReversionInfo, StepStateTransition, Transition::Delta, + }, select, Cell, Word, }, witness::{Block, Call, ExecStep, Transaction}, @@ -22,8 +24,7 @@ use halo2_proofs::{ pub(crate) struct SloadGadget { same_context: SameContextGadget, tx_id: Cell, - rw_counter_end_of_reversion: Cell, - is_persistent: Cell, + reversion_info: ReversionInfo, callee_address: Cell, key: Cell, value: Cell, @@ -39,13 +40,9 @@ impl ExecutionGadget for SloadGadget { fn configure(cb: &mut ConstraintBuilder) -> Self { let opcode = cb.query_cell(); - let [tx_id, rw_counter_end_of_reversion, is_persistent, callee_address] = [ - CallContextFieldTag::TxId, - CallContextFieldTag::RwCounterEndOfReversion, - CallContextFieldTag::IsPersistent, - CallContextFieldTag::CalleeAddress, - ] - .map(|field_tag| cb.call_context(None, field_tag)); + let tx_id = cb.call_context(None, CallContextFieldTag::TxId); + let mut reversion_info = cb.reversion_info(None); + let callee_address = cb.call_context(None, CallContextFieldTag::CalleeAddress); let key = cb.query_cell(); // Pop the key from the stack @@ -70,13 +67,7 @@ impl ExecutionGadget for SloadGadget { key.expr(), true.expr(), is_warm.expr(), - Some( - ( - &is_persistent, - rw_counter_end_of_reversion.expr() - cb.curr.state.state_write_counter.expr(), - ) - .into(), - ), + Some(&mut reversion_info), ); let gas_cost = SloadGasGadget::construct(cb, is_warm.expr()).expr(); @@ -92,8 +83,7 @@ impl ExecutionGadget for SloadGadget { Self { same_context, tx_id, - rw_counter_end_of_reversion, - is_persistent, + reversion_info, callee_address, key, value, @@ -115,13 +105,12 @@ impl ExecutionGadget for SloadGadget { self.tx_id .assign(region, offset, Some(F::from(tx.id as u64)))?; - self.rw_counter_end_of_reversion.assign( + self.reversion_info.assign( region, offset, - Some(F::from(call.rw_counter_end_of_reversion as u64)), + call.rw_counter_end_of_reversion, + call.is_persistent, )?; - self.is_persistent - .assign(region, offset, Some(F::from(call.is_persistent as u64)))?; self.callee_address .assign(region, offset, call.callee_address.to_scalar())?; @@ -172,8 +161,8 @@ impl SloadGasGadget { pub(crate) fn construct(_cb: &mut ConstraintBuilder, is_warm: Expression) -> Self { let gas_cost = select::expr( is_warm.expr(), - GasCost::WARM_STORAGE_READ_COST.expr(), - GasCost::COLD_SLOAD_COST.expr(), + GasCost::WARM_ACCESS.expr(), + GasCost::COLD_SLOAD.expr(), ); Self { is_warm, gas_cost } @@ -271,14 +260,14 @@ mod test { program_counter: 33, stack_pointer: STACK_CAPACITY, gas_left: if is_warm { - GasCost::WARM_STORAGE_READ_COST.as_u64() + GasCost::WARM_ACCESS.as_u64() } else { - GasCost::COLD_SLOAD_COST.as_u64() + GasCost::COLD_SLOAD.as_u64() }, gas_cost: if is_warm { - GasCost::WARM_STORAGE_READ_COST.as_u64() + GasCost::WARM_ACCESS.as_u64() } else { - GasCost::COLD_SLOAD_COST.as_u64() + GasCost::COLD_SLOAD.as_u64() }, opcode: Some(OpcodeId::SLOAD), ..Default::default() diff --git a/zkevm-circuits/src/evm_circuit/execution/sstore.rs b/zkevm-circuits/src/evm_circuit/execution/sstore.rs index 4fa4c969d1..9d4e6c4a49 100644 --- a/zkevm-circuits/src/evm_circuit/execution/sstore.rs +++ b/zkevm-circuits/src/evm_circuit/execution/sstore.rs @@ -5,7 +5,9 @@ use crate::{ table::CallContextFieldTag, util::{ common_gadget::SameContextGadget, - constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, + constraint_builder::{ + ConstraintBuilder, ReversionInfo, StepStateTransition, Transition::Delta, + }, math_gadget::{IsEqualGadget, IsZeroGadget}, not, select, Cell, Word, }, @@ -23,8 +25,7 @@ use halo2_proofs::{ pub(crate) struct SstoreGadget { same_context: SameContextGadget, tx_id: Cell, - rw_counter_end_of_reversion: Cell, - is_persistent: Cell, + reversion_info: ReversionInfo, callee_address: Cell, key: Cell, value: Cell, @@ -44,13 +45,9 @@ impl ExecutionGadget for SstoreGadget { fn configure(cb: &mut ConstraintBuilder) -> Self { let opcode = cb.query_cell(); - let [tx_id, rw_counter_end_of_reversion, is_persistent, callee_address] = [ - CallContextFieldTag::TxId, - CallContextFieldTag::RwCounterEndOfReversion, - CallContextFieldTag::IsPersistent, - CallContextFieldTag::CalleeAddress, - ] - .map(|field_tag| cb.call_context(None, field_tag)); + let tx_id = cb.call_context(None, CallContextFieldTag::TxId); + let mut reversion_info = cb.reversion_info(None); + let callee_address = cb.call_context(None, CallContextFieldTag::CalleeAddress); let key = cb.query_cell(); // Pop the key from the stack @@ -69,13 +66,7 @@ impl ExecutionGadget for SstoreGadget { value_prev.expr(), tx_id.expr(), committed_value.expr(), - Some( - ( - &is_persistent, - rw_counter_end_of_reversion.expr() - cb.curr.state.state_write_counter.expr(), - ) - .into(), - ), + Some(&mut reversion_info), ); let is_warm = cb.query_bool(); @@ -85,15 +76,7 @@ impl ExecutionGadget for SstoreGadget { key.expr(), true.expr(), is_warm.expr(), - Some( - ( - &is_persistent, - rw_counter_end_of_reversion.expr() - - 1.expr() - - cb.curr.state.state_write_counter.expr(), - ) - .into(), - ), + Some(&mut reversion_info), ); let gas_cost = SstoreGasGadget::construct( @@ -116,15 +99,7 @@ impl ExecutionGadget for SstoreGadget { tx_id.expr(), tx_refund.expr(), tx_refund_prev.expr(), - Some( - ( - &is_persistent, - rw_counter_end_of_reversion.expr() - - 2.expr() - - cb.curr.state.state_write_counter.expr(), - ) - .into(), - ), + Some(&mut reversion_info), ); let step_state_transition = StepStateTransition { @@ -140,8 +115,7 @@ impl ExecutionGadget for SstoreGadget { Self { same_context, tx_id, - rw_counter_end_of_reversion, - is_persistent, + reversion_info, callee_address, key, value, @@ -167,13 +141,12 @@ impl ExecutionGadget for SstoreGadget { self.tx_id .assign(region, offset, Some(F::from(tx.id as u64)))?; - self.rw_counter_end_of_reversion.assign( + self.reversion_info.assign( region, offset, - Some(F::from(call.rw_counter_end_of_reversion as u64)), + call.rw_counter_end_of_reversion, + call.is_persistent, )?; - self.is_persistent - .assign(region, offset, Some(F::from(call.is_persistent as u64)))?; self.callee_address .assign(region, offset, call.callee_address.to_scalar())?; @@ -272,21 +245,21 @@ impl SstoreGasGadget { let original_is_zero = IsZeroGadget::construct(cb, committed_value.expr()); let warm_case_gas = select::expr( value_eq_prev.expr(), - GasCost::SLOAD_GAS.expr(), + GasCost::WARM_ACCESS.expr(), select::expr( original_eq_prev.expr(), select::expr( original_is_zero.expr(), - GasCost::SSTORE_SET_GAS.expr(), - GasCost::SSTORE_RESET_GAS.expr(), + GasCost::SSTORE_SET.expr(), + GasCost::SSTORE_RESET.expr(), ), - GasCost::SLOAD_GAS.expr(), + GasCost::WARM_ACCESS.expr(), ), ); let gas_cost = select::expr( is_warm.expr(), warm_case_gas.expr(), - warm_case_gas + GasCost::COLD_SLOAD_COST.expr(), + warm_case_gas + GasCost::COLD_SLOAD.expr(), ); Self { @@ -413,13 +386,13 @@ impl SstoreTxRefundGadget { let nz_ne_ne_case_refund = cb.copy(select::expr( not::expr(original_eq_value.expr()), nz_allne_case_refund.expr(), - nz_allne_case_refund.expr() + GasCost::SSTORE_RESET_GAS.expr() - - GasCost::SLOAD_GAS.expr(), + nz_allne_case_refund.expr() + GasCost::SSTORE_RESET.expr() + - GasCost::WARM_ACCESS.expr(), )); // original_value!=value_prev, value_prev!=value, original_value==0 let ez_ne_ne_case_refund = cb.copy(select::expr( original_eq_value.expr(), - tx_refund_old.expr() + GasCost::SSTORE_SET_GAS.expr() - GasCost::SLOAD_GAS.expr(), + tx_refund_old.expr() + GasCost::SSTORE_SET.expr() - GasCost::WARM_ACCESS.expr(), tx_refund_old.expr(), )); // original_value!=value_prev, value_prev!=value @@ -555,13 +528,13 @@ impl SstoreTxRefundGadget { let nz_ne_ne_case_refund = if committed_value != value { nz_allne_case_refund } else { - nz_allne_case_refund + GasCost::SSTORE_RESET_GAS.as_u64() - GasCost::SLOAD_GAS.as_u64() + nz_allne_case_refund + GasCost::SSTORE_RESET.as_u64() - GasCost::WARM_ACCESS.as_u64() }; self.nz_ne_ne_case_refund .assign(region, offset, Some(F::from(nz_ne_ne_case_refund)))?; let ez_ne_ne_case_refund = if committed_value == value { - tx_refund_old + GasCost::SSTORE_SET_GAS.as_u64() - GasCost::SLOAD_GAS.as_u64() + tx_refund_old + GasCost::SSTORE_SET.as_u64() - GasCost::WARM_ACCESS.as_u64() } else { tx_refund_old }; @@ -602,20 +575,20 @@ mod test { is_warm: bool, ) -> u64 { let warm_case_gas = if value_prev == value { - GasCost::SLOAD_GAS + GasCost::WARM_ACCESS } else if committed_value == value_prev { if committed_value == Word::from(0) { - GasCost::SSTORE_SET_GAS + GasCost::SSTORE_SET } else { - GasCost::SSTORE_RESET_GAS + GasCost::SSTORE_RESET } } else { - GasCost::SLOAD_GAS + GasCost::WARM_ACCESS }; if is_warm { warm_case_gas.as_u64() } else { - warm_case_gas.as_u64() + GasCost::COLD_SLOAD_COST.as_u64() + warm_case_gas.as_u64() + GasCost::COLD_SLOAD.as_u64() } } @@ -644,10 +617,10 @@ mod test { if committed_value == value { if committed_value == Word::from(0) { tx_refund_new += - GasCost::SSTORE_SET_GAS.as_u64() - GasCost::SLOAD_GAS.as_u64(); + GasCost::SSTORE_SET.as_u64() - GasCost::WARM_ACCESS.as_u64(); } else { tx_refund_new += - GasCost::SSTORE_RESET_GAS.as_u64() - GasCost::SLOAD_GAS.as_u64(); + GasCost::SSTORE_RESET.as_u64() - GasCost::WARM_ACCESS.as_u64(); } } } @@ -666,7 +639,7 @@ mod test { result: bool, ) { let gas = calc_expected_gas_cost(value, value_prev, committed_value, is_warm); - let tx_refund_old = GasCost::SSTORE_SET_GAS.as_u64(); + let tx_refund_old = GasCost::SSTORE_SET.as_u64(); let tx_refund_new = calc_expected_tx_refund(tx_refund_old, value, value_prev, committed_value); let rw_counter_end_of_reversion = if result { 0 } else { 14 }; diff --git a/zkevm-circuits/src/evm_circuit/param.rs b/zkevm-circuits/src/evm_circuit/param.rs index 0b745b6068..869e5d3ff1 100644 --- a/zkevm-circuits/src/evm_circuit/param.rs +++ b/zkevm-circuits/src/evm_circuit/param.rs @@ -36,3 +36,9 @@ pub(crate) const N_BYTES_GAS: usize = N_BYTES_U64; // Number of bytes that will be used for call data's size. pub(crate) const N_BYTES_CALLDATASIZE: usize = N_BYTES_U64; + +// Hash output in little-endian of keccak256 with empty input. +pub(crate) const EMPTY_HASH: [u8; 32] = [ + 0x70, 0xa4, 0x85, 0x5d, 0x04, 0xd8, 0xfa, 0x7b, 0x3b, 0x27, 0x82, 0xca, 0x53, 0xb6, 0x00, 0xe5, + 0xc0, 0x03, 0xc7, 0xdc, 0xb2, 0x7d, 0x7e, 0x92, 0x3c, 0x23, 0xf7, 0x86, 0x01, 0x46, 0xd2, 0xc5, +]; diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index f067dae2e9..16394eea8a 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -26,8 +26,10 @@ pub enum FixedTableTag { Range5 = 1, Range16, Range32, + Range64, Range256, Range512, + Range1024, SignByte, BitwiseAnd, BitwiseOr, @@ -41,8 +43,10 @@ impl FixedTableTag { Self::Range5, Self::Range16, Self::Range32, + Self::Range64, Self::Range256, Self::Range512, + Self::Range1024, Self::SignByte, Self::BitwiseAnd, Self::BitwiseOr, @@ -65,12 +69,18 @@ impl FixedTableTag { Self::Range32 => { Box::new((0..32).map(move |value| [tag, F::from(value), F::zero(), F::zero()])) } + Self::Range64 => { + Box::new((0..64).map(move |value| [tag, F::from(value), F::zero(), F::zero()])) + } Self::Range256 => { Box::new((0..256).map(move |value| [tag, F::from(value), F::zero(), F::zero()])) } Self::Range512 => { Box::new((0..512).map(move |value| [tag, F::from(value), F::zero(), F::zero()])) } + Self::Range1024 => { + Box::new((0..1024).map(move |value| [tag, F::from(value), F::zero(), F::zero()])) + } Self::SignByte => Box::new((0..256).map(move |value| { [ tag, diff --git a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs index fd11371056..0eacc3e42d 100644 --- a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs @@ -90,7 +90,7 @@ impl cb: &mut ConstraintBuilder, address: Expression, updates: Vec>, - reversion_info: Option>, + reversion_info: Option<&mut ReversionInfo>, ) -> Self { debug_assert!(updates.len() == N_ADDENDS - 1); @@ -124,6 +124,22 @@ impl Self { add_words } } + pub(crate) fn balance(&self) -> &Word { + if INCREASE { + self.add_words.sum() + } else { + &self.add_words.addends()[0] + } + } + + pub(crate) fn balance_prev(&self) -> &Word { + if INCREASE { + &self.add_words.addends()[0] + } else { + self.add_words.sum() + } + } + pub(crate) fn assign( &self, region: &mut Region<'_, F>, @@ -151,21 +167,16 @@ impl TransferWithGasFeeGadget { receiver_address: Expression, value: Word, gas_fee: Word, - is_persistent: Expression, - rw_counter_end_of_reversion: Expression, + reversion_info: &mut ReversionInfo, ) -> Self { let sender = UpdateBalanceGadget::construct( cb, sender_address, vec![value.clone(), gas_fee], - Some((&is_persistent, &rw_counter_end_of_reversion).into()), - ); - let receiver = UpdateBalanceGadget::construct( - cb, - receiver_address, - vec![value], - Some((is_persistent, rw_counter_end_of_reversion - 1.expr()).into()), + Some(reversion_info), ); + let receiver = + UpdateBalanceGadget::construct(cb, receiver_address, vec![value], Some(reversion_info)); Self { sender, receiver } } @@ -194,3 +205,57 @@ impl TransferWithGasFeeGadget { Ok(()) } } + +#[derive(Clone, Debug)] +pub(crate) struct TransferGadget { + sender: UpdateBalanceGadget, + receiver: UpdateBalanceGadget, +} + +impl TransferGadget { + pub(crate) fn construct( + cb: &mut ConstraintBuilder, + sender_address: Expression, + receiver_address: Expression, + value: Word, + reversion_info: &mut ReversionInfo, + ) -> Self { + let sender = UpdateBalanceGadget::construct( + cb, + sender_address, + vec![value.clone()], + Some(reversion_info), + ); + let receiver = + UpdateBalanceGadget::construct(cb, receiver_address, vec![value], Some(reversion_info)); + + Self { sender, receiver } + } + + pub(crate) fn receiver(&self) -> &UpdateBalanceGadget { + &self.receiver + } + + pub(crate) fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + (sender_balance, sender_balance_prev): (U256, U256), + (receiver_balance, receiver_balance_prev): (U256, U256), + value: U256, + ) -> Result<(), Error> { + self.sender.assign( + region, + offset, + vec![sender_balance, value], + sender_balance_prev, + )?; + self.receiver.assign( + region, + offset, + vec![receiver_balance_prev, value], + receiver_balance, + )?; + Ok(()) + } +} diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index 500b0dcad2..7b16a14a00 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -10,7 +10,11 @@ use crate::{ }, util::Expr, }; -use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; +use halo2_proofs::{ + arithmetic::FieldExt, + circuit::Region, + plonk::{Error, Expression}, +}; use std::convert::TryInto; // Max degree allowed in all expressions passing through the ConstraintBuilder. @@ -81,18 +85,44 @@ impl StepStateTransition { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub(crate) struct ReversionInfo { - is_persistent: Expression, - rw_counter: Expression, + rw_counter_end_of_reversion: Cell, + is_persistent: Cell, + state_write_counter: Expression, } -impl, E2: Expr> From<(E1, E2)> for ReversionInfo { - fn from((is_persistent, rw_counter): (E1, E2)) -> Self { - Self { - is_persistent: is_persistent.expr(), - rw_counter: rw_counter.expr(), - } +impl ReversionInfo { + pub(crate) fn rw_counter_end_of_reversion(&self) -> Expression { + self.rw_counter_end_of_reversion.expr() + } + + pub(crate) fn is_persistent(&self) -> Expression { + self.is_persistent.expr() + } + + pub(crate) fn rw_counter_of_reversion(&mut self) -> Expression { + let rw_counter_of_reversion = + self.rw_counter_end_of_reversion.expr() - self.state_write_counter.expr(); + self.state_write_counter = self.state_write_counter.clone() + 1.expr(); + rw_counter_of_reversion + } + + pub(crate) fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + rw_counter_end_of_reversion: usize, + is_persistent: bool, + ) -> Result<(), Error> { + self.rw_counter_end_of_reversion.assign( + region, + offset, + Some(F::from(rw_counter_end_of_reversion as u64)), + )?; + self.is_persistent + .assign(region, offset, Some(F::from(is_persistent as u64)))?; + Ok(()) } } @@ -463,8 +493,10 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { 5 => ("Range5", FixedTableTag::Range5), 16 => ("Range16", FixedTableTag::Range16), 32 => ("Range32", FixedTableTag::Range32), + 64 => ("Range64", FixedTableTag::Range64), 256 => ("Range256", FixedTableTag::Range256), 512 => ("Range512", FixedTableTag::Range512), + 1024 => ("Range1024", FixedTableTag::Range1024), _ => unimplemented!(), }; self.add_lookup( @@ -617,23 +649,25 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { name: &'static str, tag: RwTableTag, mut values: [Expression; 8], - reversion_info: Option>, + reversion_info: Option<&mut ReversionInfo>, ) { debug_assert!(tag.is_reversible(), "Only reversible tags are state write"); self.rw_lookup(name, true.expr(), tag, values.clone()); - if let Some(ReversionInfo { - is_persistent, - rw_counter, - }) = reversion_info - { + if let Some(reversion_info) = reversion_info { // Revert if is_persistent is 0 - self.condition(1.expr() - is_persistent, |cb| { + self.condition(1.expr() - reversion_info.is_persistent(), |cb| { // Swap value and value_prev values.swap(4, 5); - cb.rw_lookup_with_counter(name, rw_counter, true.expr(), tag, values) + cb.rw_lookup_with_counter( + name, + reversion_info.rw_counter_of_reversion(), + true.expr(), + tag, + values, + ) }); } } @@ -646,8 +680,8 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { account_address: Expression, value: Expression, value_prev: Expression, - reversion_info: Option>, - ) -> Expression { + reversion_info: Option<&mut ReversionInfo>, + ) { self.state_write( "TxAccessListAccount write", RwTableTag::TxAccessListAccount, @@ -656,15 +690,13 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { account_address, 0.expr(), 0.expr(), - value.clone(), - value_prev.clone(), + value, + value_prev, 0.expr(), 0.expr(), ], reversion_info, ); - - value - value_prev } pub(crate) fn account_storage_access_list_write( @@ -674,8 +706,8 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { storage_key: Expression, value: Expression, value_prev: Expression, - reversion_info: Option>, - ) -> Expression { + reversion_info: Option<&mut ReversionInfo>, + ) { self.state_write( "TxAccessListAccountStorage write", RwTableTag::TxAccessListAccountStorage, @@ -684,15 +716,13 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { account_address, 0.expr(), storage_key, - value.clone(), - value_prev.clone(), + value, + value_prev, 0.expr(), 0.expr(), ], reversion_info, ); - - value - value_prev } // Tx Refund @@ -720,7 +750,7 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { tx_id: Expression, value: Expression, value_prev: Expression, - reversion_info: Option>, + reversion_info: Option<&mut ReversionInfo>, ) { self.state_write( "TxRefund write", @@ -770,7 +800,7 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { field_tag: AccountFieldTag, value: Expression, value_prev: Expression, - reversion_info: Option>, + reversion_info: Option<&mut ReversionInfo>, ) { self.state_write( "Account write with reversion", @@ -825,7 +855,7 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { value_prev: Expression, tx_id: Expression, committed_value: Expression, - reversion_info: Option>, + reversion_info: Option<&mut ReversionInfo>, ) { self.state_write( "AccountStorage write", @@ -880,6 +910,23 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { ); } + pub(crate) fn reversion_info(&mut self, call_id: Option>) -> ReversionInfo { + let [rw_counter_end_of_reversion, is_persistent] = [ + CallContextFieldTag::RwCounterEndOfReversion, + CallContextFieldTag::IsPersistent, + ] + .map(|field_tag| self.call_context(call_id.clone(), field_tag)); + ReversionInfo { + rw_counter_end_of_reversion, + is_persistent, + state_write_counter: if call_id.is_some() { + 0.expr() + } else { + self.curr.state.state_write_counter.expr() + }, + } + } + // Stack pub(crate) fn stack_pop(&mut self, value: Expression) { diff --git a/zkevm-circuits/src/evm_circuit/util/math_gadget.rs b/zkevm-circuits/src/evm_circuit/util/math_gadget.rs index bf0ba7442b..e11beec638 100644 --- a/zkevm-circuits/src/evm_circuit/util/math_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/math_gadget.rs @@ -6,8 +6,10 @@ use crate::{ util::Expr, }; use eth_types::{Field, ToLittleEndian, ToScalar, Word}; -use halo2_proofs::plonk::Error; -use halo2_proofs::{arithmetic::FieldExt, circuit::Region, plonk::Expression}; +use halo2_proofs::{ + circuit::Region, + plonk::{Error, Expression}, +}; use std::convert::TryFrom; /// Returns `1` when `value == 0`, and returns `0` otherwise. @@ -17,7 +19,7 @@ pub struct IsZeroGadget { is_zero: Expression, } -impl IsZeroGadget { +impl IsZeroGadget { pub(crate) fn construct(cb: &mut ConstraintBuilder, value: Expression) -> Self { let inverse = cb.query_cell(); @@ -61,7 +63,7 @@ pub struct IsEqualGadget { is_zero: IsZeroGadget, } -impl IsEqualGadget { +impl IsEqualGadget { pub(crate) fn construct( cb: &mut ConstraintBuilder, lhs: Expression, @@ -198,6 +200,10 @@ impl Ok(()) } + pub(crate) fn addends(&self) -> &[util::Word] { + &self.addends + } + pub(crate) fn sum(&self) -> &util::Word { &self.sum } @@ -236,7 +242,7 @@ pub(crate) struct MulWordsGadget { * t2 + t3 + = + */ } -impl MulWordsGadget { +impl MulWordsGadget { pub(crate) fn construct( cb: &mut ConstraintBuilder, a: util::Word, @@ -400,7 +406,7 @@ pub(crate) struct MulWordByU64Gadget { carry_lo: [util::Cell; 8], } -impl MulWordByU64Gadget { +impl MulWordByU64Gadget { pub(crate) fn construct( cb: &mut ConstraintBuilder, multiplicand: util::Word, @@ -630,7 +636,7 @@ pub struct PairSelectGadget { is_b: Expression, } -impl PairSelectGadget { +impl PairSelectGadget { pub(crate) fn construct( cb: &mut ConstraintBuilder, value: Expression, @@ -792,7 +798,7 @@ impl MinMaxGadget { // This function generates a Lagrange polynomial in the range [start, end) which // will be evaluated to 1 when `exp == value`, otherwise 0 pub(crate) fn generate_lagrange_base_polynomial< - F: FieldExt, + F: Field, Exp: Expr, R: Iterator, >( diff --git a/zkevm-circuits/src/evm_circuit/util/memory_gadget.rs b/zkevm-circuits/src/evm_circuit/util/memory_gadget.rs index 9c1b48ef15..c85edaa3d3 100644 --- a/zkevm-circuits/src/evm_circuit/util/memory_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/memory_gadget.rs @@ -13,7 +13,6 @@ use crate::{ use array_init::array_init; use eth_types::{evm_types::GasCost, Field, ToLittleEndian, U256}; use halo2_proofs::{ - arithmetic::FieldExt, circuit::Region, plonk::{Error, Expression}, }; @@ -25,9 +24,10 @@ pub(crate) mod address_low { param::N_BYTES_MEMORY_ADDRESS, util::{from_bytes, Word}, }; - use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; + use eth_types::Field; + use halo2_proofs::plonk::Expression; - pub(crate) fn expr(address: &Word) -> Expression { + pub(crate) fn expr(address: &Word) -> Expression { from_bytes::expr(&address.cells[..N_BYTES_MEMORY_ADDRESS]) } @@ -45,13 +45,14 @@ pub(crate) mod address_high { param::N_BYTES_MEMORY_ADDRESS, util::{sum, Word}, }; - use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; + use eth_types::Field; + use halo2_proofs::plonk::Expression; - pub(crate) fn expr(address: &Word) -> Expression { + pub(crate) fn expr(address: &Word) -> Expression { sum::expr(&address.cells[N_BYTES_MEMORY_ADDRESS..]) } - pub(crate) fn value(address: [u8; 32]) -> F { + pub(crate) fn value(address: [u8; 32]) -> F { sum::value::(&address[N_BYTES_MEMORY_ADDRESS..]) } } @@ -66,7 +67,7 @@ pub(crate) struct MemoryAddressGadget { memory_length_is_zero: IsZeroGadget, } -impl MemoryAddressGadget { +impl MemoryAddressGadget { pub(crate) fn construct( cb: &mut ConstraintBuilder, memory_offset: Cell, diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index c2558396d1..3886036089 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -1107,6 +1107,7 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::SSTORE => ExecutionState::SSTORE, OpcodeId::CALLDATACOPY => ExecutionState::CALLDATACOPY, OpcodeId::ISZERO => ExecutionState::ISZERO, + OpcodeId::CALL => ExecutionState::CALL, _ => unimplemented!("unimplemented opcode {:?}", op), } } From ea3f299a6d3c05470aaa079dd284063025d82684 Mon Sep 17 00:00:00 2001 From: han0110 Date: Wed, 23 Mar 2022 10:17:32 +0800 Subject: [PATCH 2/5] feat: support call to account with empty code and follow updated spec --- bus-mapping/src/circuit_input_builder.rs | 10 +- bus-mapping/src/evm/opcodes.rs | 54 ++- bus-mapping/src/evm/opcodes/call.rs | 61 +++- eth-types/src/bytecode.rs | 9 +- eth-types/src/evm_types.rs | 4 +- eth-types/src/lib.rs | 2 +- .../src/evm_circuit/execution/begin_tx.rs | 5 +- .../src/evm_circuit/execution/call.rs | 343 ++++++++++-------- .../src/evm_circuit/execution/end_tx.rs | 5 +- 9 files changed, 306 insertions(+), 187 deletions(-) diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index 043fe80d62..0d0d02b870 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -492,6 +492,10 @@ impl TransactionContext { } else if geth_step.depth - 1 == geth_next_step.depth { let is_success = !geth_next_step.stack.last()?.is_zero(); call_is_success_map.insert(call_indices.pop().unwrap(), is_success); + // Callee with empty code + } else if CallKind::try_from(geth_step.op).is_ok() { + let is_success = !geth_next_step.stack.last()?.is_zero(); + call_is_success_map.insert(index, is_success); } } } @@ -562,10 +566,12 @@ impl TransactionContext { op_refs: Vec::new(), }) } else if let Some(reversion_group) = self.reversion_groups.last_mut() { - let caller_swc = self.calls.last().expect("calls should not be empty").swc; + let caller_ctx = self.calls.last().expect("calls should not be empty"); + let caller_swc = caller_ctx.swc; let caller_swc_offset = reversion_group .calls - .last() + .iter() + .find(|(call_idx, _)| *call_idx == caller_ctx.index) .expect("calls should not be empty") .1; reversion_group diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index e0c33ba59f..39aee3d07a 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -11,7 +11,7 @@ use crate::{ use core::fmt::Debug; use eth_types::{ evm_types::{GasCost, MAX_REFUND_QUOTIENT_OF_GAS_USED}, - GethExecStep, ToWord, + GethExecStep, ToWord, EMPTY_HASH, }; use log::warn; @@ -339,27 +339,38 @@ pub fn gen_begin_tx_ops(state: &mut CircuitInputStateRef) -> Result { - // TODO: Implement creation transaction - } - (_, true) => { - // TODO: Implement calling to precompiled - } - _ => { - state.push_op( - &mut exec_step, - RW::READ, - AccountOp { - address: call.address, - field: AccountField::CodeHash, - value: code_hash.to_word(), - value_prev: code_hash.to_word(), - }, - ); - } + // There are 4 branches from here. + + // 1. Creation transaction. + if call.is_create() { + // TODO: + return Ok(exec_step); + } + + // 2. Call to precompiled. + if state.is_precompiled(&call.address) { + // TODO: + return Ok(exec_step); + } + + state.push_op( + &mut exec_step, + RW::READ, + AccountOp { + address: call.address, + field: AccountField::CodeHash, + value: code_hash.to_word(), + value_prev: code_hash.to_word(), + }, + ); + + // 3. Call to account with empty code. + if code_hash == EMPTY_HASH { + // TODO: + return Ok(exec_step); } + // 4. Call to account with non-empty code. for (field, value) in [ (CallContextField::Depth, call.depth.into()), ( @@ -380,6 +391,9 @@ pub fn gen_begin_tx_ops(state: &mut CircuitInputStateRef) -> Result ExecutionGadget for BeginTxGadget { (CallContextFieldTag::LastCalleeId, 0.expr()), (CallContextFieldTag::LastCalleeReturnDataOffset, 0.expr()), (CallContextFieldTag::LastCalleeReturnDataLength, 0.expr()), + (CallContextFieldTag::IsRoot, 1.expr()), + (CallContextFieldTag::IsCreate, 0.expr()), + (CallContextFieldTag::CodeSource, code_hash.expr()), ] { cb.call_context_lookup(false.expr(), Some(call_id.expr()), field_tag, value); } @@ -177,7 +180,7 @@ impl ExecutionGadget for BeginTxGadget { // - Read CallContext LastCalleeId // - Read CallContext LastCalleeReturnDataOffset // - Read CallContext LastCalleeReturnDataLength - rw_counter: Delta(19.expr()), + rw_counter: Delta(22.expr()), call_id: To(call_id.expr()), is_root: To(true.expr()), is_create: To(false.expr()), diff --git a/zkevm-circuits/src/evm_circuit/execution/call.rs b/zkevm-circuits/src/evm_circuit/execution/call.rs index 456d6bf7d0..684b69afd5 100644 --- a/zkevm-circuits/src/evm_circuit/execution/call.rs +++ b/zkevm-circuits/src/evm_circuit/execution/call.rs @@ -48,12 +48,9 @@ pub(crate) struct CallGadget { transfer: TransferGadget, callee_nonce: Cell, callee_code_hash: Cell, - callee_nonce_is_zero: IsZeroGadget, - callee_balance_is_zero: IsZeroGadget, + callee_is_empty: Cell, + callee_nonempty_witness: Cell, callee_code_hash_is_empty: IsEqualGadget, - is_account_empty: Cell, - memory_expansion_gas_cost: Cell, - next_memory_word_size: Cell, one_64th_gas: ConstantDivisionGadget, capped_callee_gas_left: MinMaxGadget, } @@ -174,9 +171,24 @@ impl ExecutionGadget for CallGadget { cb.account_read(callee_address.clone(), field_tag, value.expr()); value }); - let callee_nonce_is_zero = - IsZeroGadget::construct(cb, sum::expr(&transfer.receiver().balance_prev().cells)); - let callee_balance_is_zero = IsZeroGadget::construct(cb, callee_nonce.expr()); + let callee_is_empty = cb.query_bool(); + let callee_nonempty_witness = cb.query_cell(); + cb.require_zero( + "is_empty is 0 when nonce is non-zero", + callee_is_empty.expr() * callee_nonce.expr(), + ); + cb.require_zero( + "is_empty is 0 when balance is non-zero", + callee_is_empty.expr() * sum::expr(&transfer.receiver().balance_prev().cells), + ); + cb.require_zero( + "is_empty is 1 if nonce and balance are all zero", + (1.expr() - callee_is_empty.expr()) + * (1.expr() - callee_nonce.expr() * callee_nonempty_witness.expr()) + * (1.expr() + - sum::expr(&transfer.receiver().balance_prev().cells) + * callee_nonempty_witness.expr()), + ); let callee_code_hash_is_empty = IsEqualGadget::construct( cb, callee_code_hash.expr(), @@ -185,23 +197,15 @@ impl ExecutionGadget for CallGadget { cb.power_of_randomness(), ), ); - // Use copy for is_account_empty, memory_expansion_gas_cost, - // next_memory_word_size to avoid degree too high. - let is_account_empty = cb.copy( - callee_nonce_is_zero.expr() - * callee_balance_is_zero.expr() - * callee_code_hash_is_empty.expr(), - ); - let memory_expansion_gas_cost = cb.copy(memory_expansion.gas_cost()); - let next_memory_word_size = cb.copy(memory_expansion.next_memory_word_size()); // Sum up gas cost let gas_cost = select::expr( is_warm_access_prev.expr(), GasCost::WARM_ACCESS.expr(), GasCost::COLD_ACCOUNT_ACCESS.expr(), - ) + is_account_empty.expr() * GasCost::CALL_EMPTY_ACCOUNT.expr() - + has_value.clone() * GasCost::CALL_WITH_VALUE.expr() - + memory_expansion_gas_cost.expr(); + ) + has_value.clone() + * (GasCost::CALL_WITH_VALUE.expr() + + callee_is_empty.expr() * GasCost::NEW_ACCOUNT.expr()) + + memory_expansion.gas_cost(); // Apply EIP 150 let gas_available = cb.curr.state.gas_left.expr() - gas_cost.clone(); @@ -216,74 +220,93 @@ impl ExecutionGadget for CallGadget { // TODO: Handle precompiled - // Save caller's call state - for (field_tag, value) in [ - (CallContextFieldTag::IsRoot, cb.curr.state.is_root.expr()), - ( - CallContextFieldTag::IsCreate, - cb.curr.state.is_create.expr(), - ), - ( - CallContextFieldTag::CodeSource, - cb.curr.state.code_source.expr(), - ), - ( - CallContextFieldTag::ProgramCounter, - cb.curr.state.program_counter.expr() + 1.expr(), - ), - ( - CallContextFieldTag::StackPointer, - cb.curr.state.stack_pointer.expr() + 6.expr(), - ), - ( - CallContextFieldTag::GasLeft, - cb.curr.state.gas_left.expr() - gas_cost - callee_gas_left.clone(), - ), - ( - CallContextFieldTag::MemorySize, - next_memory_word_size.expr(), - ), - ( - CallContextFieldTag::StateWriteCounter, - cb.curr.state.state_write_counter.expr() + 1.expr(), - ), - ] { - cb.call_context_lookup(true.expr(), None, field_tag, value); - } - - // Setup next call's context. - for (field_tag, value) in [ - (CallContextFieldTag::CallerId, cb.curr.state.call_id.expr()), - (CallContextFieldTag::TxId, tx_id.expr()), - (CallContextFieldTag::Depth, depth.expr() + 1.expr()), - (CallContextFieldTag::CallerAddress, caller_address.expr()), - (CallContextFieldTag::CalleeAddress, callee_address), - (CallContextFieldTag::CallDataOffset, cd_address.offset()), - (CallContextFieldTag::CallDataLength, cd_address.length()), - (CallContextFieldTag::ReturnDataOffset, rd_address.offset()), - (CallContextFieldTag::ReturnDataLength, rd_address.length()), - (CallContextFieldTag::Value, value.expr()), - (CallContextFieldTag::IsSuccess, is_success.expr()), - (CallContextFieldTag::IsStatic, is_static.expr()), - (CallContextFieldTag::LastCalleeId, 0.expr()), - (CallContextFieldTag::LastCalleeReturnDataOffset, 0.expr()), - (CallContextFieldTag::LastCalleeReturnDataLength, 0.expr()), - ] { - cb.call_context_lookup(false.expr(), Some(callee_call_id.expr()), field_tag, value); - } + cb.condition(callee_code_hash_is_empty.expr(), |cb| { + // Save caller's call state + for field_tag in [ + CallContextFieldTag::LastCalleeId, + CallContextFieldTag::LastCalleeReturnDataOffset, + CallContextFieldTag::LastCalleeReturnDataLength, + ] { + cb.call_context_lookup(true.expr(), None, field_tag, 0.expr()); + } + + cb.require_step_state_transition(StepStateTransition { + rw_counter: Delta(24.expr()), + program_counter: Delta(1.expr()), + stack_pointer: Delta(6.expr()), + gas_left: Delta( + has_value.clone() * GAS_STIPEND_CALL_WITH_VALUE.expr() - gas_cost.clone(), + ), + memory_word_size: To(memory_expansion.next_memory_word_size()), + state_write_counter: Delta(3.expr()), + ..StepStateTransition::default() + }); + }); - // Give gas stipend if value is not zero - let callee_gas_left = callee_gas_left + has_value * GAS_STIPEND_CALL_WITH_VALUE.expr(); - - cb.require_step_state_transition(StepStateTransition { - rw_counter: Delta(44.expr()), - call_id: To(callee_call_id.expr()), - is_root: To(false.expr()), - is_create: To(false.expr()), - code_source: To(callee_code_hash.expr()), - gas_left: To(callee_gas_left), - state_write_counter: To(2.expr()), - ..StepStateTransition::new_context() + cb.condition(1.expr() - callee_code_hash_is_empty.expr(), |cb| { + // Save caller's call state + for (field_tag, value) in [ + ( + CallContextFieldTag::ProgramCounter, + cb.curr.state.program_counter.expr() + 1.expr(), + ), + ( + CallContextFieldTag::StackPointer, + cb.curr.state.stack_pointer.expr() + 6.expr(), + ), + ( + CallContextFieldTag::GasLeft, + cb.curr.state.gas_left.expr() - gas_cost - callee_gas_left.clone(), + ), + ( + CallContextFieldTag::MemorySize, + memory_expansion.next_memory_word_size(), + ), + ( + CallContextFieldTag::StateWriteCounter, + cb.curr.state.state_write_counter.expr() + 1.expr(), + ), + ] { + cb.call_context_lookup(true.expr(), None, field_tag, value); + } + + // Setup next call's context. + for (field_tag, value) in [ + (CallContextFieldTag::CallerId, cb.curr.state.call_id.expr()), + (CallContextFieldTag::TxId, tx_id.expr()), + (CallContextFieldTag::Depth, depth.expr() + 1.expr()), + (CallContextFieldTag::CallerAddress, caller_address.expr()), + (CallContextFieldTag::CalleeAddress, callee_address), + (CallContextFieldTag::CallDataOffset, cd_address.offset()), + (CallContextFieldTag::CallDataLength, cd_address.length()), + (CallContextFieldTag::ReturnDataOffset, rd_address.offset()), + (CallContextFieldTag::ReturnDataLength, rd_address.length()), + (CallContextFieldTag::Value, value.expr()), + (CallContextFieldTag::IsSuccess, is_success.expr()), + (CallContextFieldTag::IsStatic, is_static.expr()), + (CallContextFieldTag::LastCalleeId, 0.expr()), + (CallContextFieldTag::LastCalleeReturnDataOffset, 0.expr()), + (CallContextFieldTag::LastCalleeReturnDataLength, 0.expr()), + (CallContextFieldTag::IsRoot, 0.expr()), + (CallContextFieldTag::IsCreate, 0.expr()), + (CallContextFieldTag::CodeSource, callee_code_hash.expr()), + ] { + cb.call_context_lookup(false.expr(), Some(callee_call_id.expr()), field_tag, value); + } + + // Give gas stipend if value is not zero + let callee_gas_left = callee_gas_left + has_value * GAS_STIPEND_CALL_WITH_VALUE.expr(); + + cb.require_step_state_transition(StepStateTransition { + rw_counter: Delta(44.expr()), + call_id: To(callee_call_id.expr()), + is_root: To(false.expr()), + is_create: To(false.expr()), + code_source: To(callee_code_hash.expr()), + gas_left: To(callee_gas_left), + state_write_counter: To(2.expr()), + ..StepStateTransition::new_context() + }); }); Self { @@ -308,12 +331,9 @@ impl ExecutionGadget for CallGadget { transfer, callee_nonce, callee_code_hash, - callee_nonce_is_zero, - callee_balance_is_zero, + callee_is_empty, + callee_nonempty_witness, callee_code_hash_is_empty, - is_account_empty, - memory_expansion_gas_cost, - next_memory_word_size, one_64th_gas, capped_callee_gas_left, } @@ -405,19 +425,18 @@ impl ExecutionGadget for CallGadget { callee_rw_counter_end_of_reversion.low_u64() as usize, callee_is_persistent.low_u64() != 0, )?; - let value_is_zero = - self.value_is_zero - .assign(region, offset, sum::value(&value.to_le_bytes()))?; + self.value_is_zero + .assign(region, offset, sum::value(&value.to_le_bytes()))?; let cd_address = self.cd_address .assign(region, offset, cd_offset, cd_length, block.randomness)?; let rd_address = self.rd_address .assign(region, offset, rd_offset, rd_length, block.randomness)?; - let (next_memory_word_size, memory_expansion_gas_cost) = self.memory_expansion.assign( + let (_, memory_expansion_gas_cost) = self.memory_expansion.assign( region, offset, - step.memory_size, + step.memory_word_size(), [cd_address, rd_address], )?; self.transfer.assign( @@ -437,47 +456,44 @@ impl ExecutionGadget for CallGadget { block.randomness, )), )?; - let callee_nonce_is_zero = - self.callee_nonce_is_zero - .assign(region, offset, F::from(callee_nonce.low_u64()))?; - let callee_balance_is_zero = self.callee_balance_is_zero.assign( - region, - offset, - sum::value(&callee_balance_pair.1.to_le_bytes()), - )?; - let callee_code_hash_is_empty = self.callee_code_hash_is_empty.assign( + let callee_nonce_is_zero = callee_nonce.is_zero(); + let callee_balance_is_zero = callee_balance_pair.1.is_zero(); + let callee_is_empty = callee_nonce_is_zero && callee_balance_is_zero; + let callee_nonempty_witness = if !callee_nonce_is_zero { + F::from(callee_nonce.low_u64()).invert().unwrap() + } else if !callee_balance_is_zero { + sum::value::(&callee_balance_pair.1.to_le_bytes()) + .invert() + .unwrap() + } else { + 0.into() + }; + self.callee_is_empty + .assign(region, offset, Some(F::from(callee_is_empty as u64)))?; + self.callee_nonempty_witness + .assign(region, offset, Some(callee_nonempty_witness))?; + self.callee_code_hash_is_empty.assign( region, offset, Word::random_linear_combine(callee_code_hash.to_le_bytes(), block.randomness), Word::random_linear_combine(EMPTY_HASH, block.randomness), )?; - let is_account_empty = callee_nonce_is_zero == F::one() - && callee_balance_is_zero == F::one() - && callee_code_hash_is_empty == F::one(); - let has_value = value_is_zero != F::one(); + let has_value = !value.is_zero(); let gas_cost = if is_warm_access_prev { GasCost::WARM_ACCESS.as_u64() } else { GasCost::COLD_ACCOUNT_ACCESS.as_u64() - } + if is_account_empty { - GasCost::CALL_EMPTY_ACCOUNT.as_u64() - } else { - 0 } + if has_value { GasCost::CALL_WITH_VALUE.as_u64() + + if callee_is_empty { + GasCost::NEW_ACCOUNT.as_u64() + } else { + 0 + } } else { 0 } + memory_expansion_gas_cost; let gas_available = step.gas_left - gas_cost; - self.is_account_empty - .assign(region, offset, Some(F::from(is_account_empty as u64)))?; - self.memory_expansion_gas_cost.assign( - region, - offset, - Some(F::from(memory_expansion_gas_cost)), - )?; - self.next_memory_word_size - .assign(region, offset, Some(F::from(next_memory_word_size)))?; self.one_64th_gas .assign(region, offset, gas_available as u128)?; self.capped_callee_gas_left.assign( @@ -496,10 +512,11 @@ mod test { test::{run_test_circuit_complete_fixed_table, run_test_circuit_incomplete_fixed_table}, witness::block_convert, }; - use eth_types::bytecode; + use eth_types::{address, bytecode}; use eth_types::{bytecode::Bytecode, evm_types::OpcodeId, geth_types::Account}; - use eth_types::{Address, ToWord, Transaction, Word}; + use eth_types::{Address, ToWord, Word}; use itertools::Itertools; + use mock::TestContext; use std::default::Default; #[derive(Clone, Copy, Debug, Default)] @@ -519,7 +536,16 @@ mod test { OpcodeId::REVERT }; + // Call twice for testing both cold and warm access let bytecode = bytecode! { + PUSH32(Word::from(stack.rd_length)) + PUSH32(Word::from(stack.rd_offset)) + PUSH32(Word::from(stack.cd_length)) + PUSH32(Word::from(stack.cd_offset)) + PUSH32(stack.value) + PUSH32(Address::repeat_byte(0xff).to_word()) + PUSH32(Word::from(stack.gas)) + CALL PUSH32(Word::from(stack.rd_length)) PUSH32(Word::from(stack.rd_offset)) PUSH32(Word::from(stack.cd_length)) @@ -542,23 +568,46 @@ mod test { } fn callee(code: Bytecode) -> Account { + let code = code.to_vec(); + let is_empty = code.is_empty(); Account { address: Address::repeat_byte(0xff), - code: code.to_vec().into(), + code: code.into(), + nonce: if is_empty { 0 } else { 1 }.into(), + balance: if is_empty { 0 } else { 0xdeadbeefu64 }.into(), ..Default::default() } } fn test_ok(caller: Account, callee: Account, use_complete_fixed_table: bool) { - let tx = Transaction { - to: Some(caller.address), - gas: 100000.into(), - transaction_index: Some(0.into()), - ..Default::default() - }; - let block_data = bus_mapping::mock::BlockData::new_from_geth_data( - mock::new(vec![caller, callee], vec![tx]).unwrap(), - ); + let block = TestContext::<3, 1>::new( + None, + |accs| { + accs[0] + .address(address!("0x000000000000000000000000000000000000cafe")) + .balance(Word::from(10u64.pow(19))); + accs[1] + .address(caller.address) + .code(caller.code) + .nonce(caller.nonce) + .balance(caller.balance); + accs[2] + .address(callee.address) + .code(callee.code) + .nonce(callee.nonce) + .balance(callee.balance); + }, + |mut txs, accs| { + txs[0] + .from(accs[0].address) + .to(accs[1].address) + .gas(100000.into()); + }, + |block, _tx| block.number(0xcafeu64), + ) + .unwrap() + .into(); + let block_data = bus_mapping::mock::BlockData::new_from_geth_data(block); let mut builder = block_data.new_circuit_input_builder(); builder .handle_block(&block_data.eth_block, &block_data.geth_traces) @@ -576,7 +625,7 @@ mod test { #[test] fn call_gadget_simple() { - for stack in [ + let stacks = vec![ // With nothing Stack::default(), // With value @@ -595,24 +644,30 @@ mod test { }, // With memory expansion Stack { - cd_offset: 0, + cd_offset: 64, cd_length: 320, + rd_offset: 0, + rd_length: 32, ..Default::default() }, Stack { - rd_offset: 0, + cd_offset: 0, + cd_length: 32, + rd_offset: 64, rd_length: 320, ..Default::default() }, Stack { - cd_offset: 0, - cd_length: 320, - rd_offset: 0, - rd_length: 1000, + cd_offset: 0xFFFFFF, + cd_length: 0, + rd_offset: 0xFFFFFF, + rd_length: 0, ..Default::default() }, - ] { - test_ok(caller(stack, true), callee(bytecode! { STOP }), false); + ]; + let callees = vec![callee(bytecode! {}), callee(bytecode! { STOP })]; + for (stack, callee) in stacks.into_iter().cartesian_product(callees.into_iter()) { + test_ok(caller(stack, true), callee, false); } } diff --git a/zkevm-circuits/src/evm_circuit/execution/end_tx.rs b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs index ac3de7f995..474c2c58de 100644 --- a/zkevm-circuits/src/evm_circuit/execution/end_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs @@ -210,7 +210,7 @@ mod test { use crate::evm_circuit::{ test::run_test_circuit_incomplete_fixed_table, witness::block_convert, }; - use eth_types::{self, address, geth_types::GethData, Word}; + use eth_types::{self, address, bytecode, geth_types::GethData, Word}; use mock::TestContext; fn test_ok(block: GethData) { @@ -248,7 +248,8 @@ mod test { |accs| { accs[0] .address(address!("0x00000000000000000000000000000000000000fe")) - .balance(Word::from(10u64.pow(19))); + .balance(Word::from(10u64.pow(19))) + .code(bytecode! { STOP }); accs[1] .address(address!("0x00000000000000000000000000000000000000fd")) .balance(Word::from(10u64.pow(19))); From f3cd70065e096bda8ce4994922536cfbcbf2eff7 Mon Sep 17 00:00:00 2001 From: han0110 Date: Wed, 30 Mar 2022 10:05:56 +0800 Subject: [PATCH 3/5] fix: typo and docs for reversion_info --- eth-types/src/evm_types/memory.rs | 2 +- .../src/evm_circuit/util/constraint_builder.rs | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/eth-types/src/evm_types/memory.rs b/eth-types/src/evm_types/memory.rs index 4418f24e34..841863e325 100644 --- a/eth-types/src/evm_types/memory.rs +++ b/eth-types/src/evm_types/memory.rs @@ -272,7 +272,7 @@ impl Memory { )) } - /// Returns the size of memroy in word. + /// Returns the size of memory in word. pub fn word_size(&self) -> usize { self.0.len() / 32 } diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index 7b16a14a00..ea8ec919fd 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -85,10 +85,19 @@ impl StepStateTransition { } } +/// ReversionInfo counts `rw_counter` of reversion for gadgets, by tracking how +/// many reversions that have been used. Gadgets should call +/// [`ConstraintBuilder::reversion_info`] to get [`ReversionInfo`] with +/// `state_write_counter` initialized at current tracking one if no `call_id` is +/// specified, then pass it as mutable reference when doing state write. #[derive(Clone, Debug)] pub(crate) struct ReversionInfo { + /// Field [`CallContextFieldTag::RwCounterEndOfReversion`] read from call + /// context. rw_counter_end_of_reversion: Cell, + /// Field [`CallContextFieldTag::IsPersistent`] read from call context. is_persistent: Cell, + /// Current cumulative state_write_counter. state_write_counter: Expression, } @@ -101,6 +110,8 @@ impl ReversionInfo { self.is_persistent.expr() } + /// Returns `rw_counter_end_of_reversion - state_write_counter` and + /// increases `state_write_counter` by `1`. pub(crate) fn rw_counter_of_reversion(&mut self) -> Expression { let rw_counter_of_reversion = self.rw_counter_end_of_reversion.expr() - self.state_write_counter.expr(); From 00bc02156a240c3248ebec6c9986991ac886f3cb Mon Sep 17 00:00:00 2001 From: han0110 Date: Thu, 31 Mar 2022 01:28:34 +0800 Subject: [PATCH 4/5] fix: apply suggestions of PR review --- bus-mapping/src/evm/opcodes.rs | 6 +- bus-mapping/src/evm/opcodes/call.rs | 5 +- eth-types/src/bytecode.rs | 7 +- eth-types/src/lib.rs | 2 +- .../src/evm_circuit/execution/call.rs | 87 ++++++++----------- .../src/evm_circuit/execution/end_tx.rs | 12 +-- .../src/evm_circuit/execution/extcodehash.rs | 71 +++++---------- zkevm-circuits/src/evm_circuit/param.rs | 6 -- .../src/evm_circuit/util/common_gadget.rs | 28 ++++-- .../src/evm_circuit/util/math_gadget.rs | 55 ++++++++++++ 10 files changed, 155 insertions(+), 124 deletions(-) diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index 39aee3d07a..b3f29c27bf 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -343,13 +343,13 @@ pub fn gen_begin_tx_ops(state: &mut CircuitInputStateRef) -> Result Result { transfer: TransferGadget, callee_nonce: Cell, callee_code_hash: Cell, - callee_is_empty: Cell, - callee_nonempty_witness: Cell, - callee_code_hash_is_empty: IsEqualGadget, + is_account_empty: BatchedIsZeroGadget, + is_empty_code_hash: IsEqualGadget, one_64th_gas: ConstantDivisionGadget, capped_callee_gas_left: MinMaxGadget, } @@ -63,6 +65,9 @@ impl ExecutionGadget for CallGadget { fn configure(cb: &mut ConstraintBuilder) -> Self { let opcode = cb.query_cell(); cb.opcode_lookup(opcode.expr(), 1.expr()); + + // We do the `ResponsibleOpcode` lookup explicitly here because we're not using + // the `SameContextGadget` for `CALL`. cb.add_lookup( "Responsible opcode lookup", Lookup::Fixed { @@ -171,29 +176,18 @@ impl ExecutionGadget for CallGadget { cb.account_read(callee_address.clone(), field_tag, value.expr()); value }); - let callee_is_empty = cb.query_bool(); - let callee_nonempty_witness = cb.query_cell(); - cb.require_zero( - "is_empty is 0 when nonce is non-zero", - callee_is_empty.expr() * callee_nonce.expr(), - ); - cb.require_zero( - "is_empty is 0 when balance is non-zero", - callee_is_empty.expr() * sum::expr(&transfer.receiver().balance_prev().cells), - ); - cb.require_zero( - "is_empty is 1 if nonce and balance are all zero", - (1.expr() - callee_is_empty.expr()) - * (1.expr() - callee_nonce.expr() * callee_nonempty_witness.expr()) - * (1.expr() - - sum::expr(&transfer.receiver().balance_prev().cells) - * callee_nonempty_witness.expr()), + let is_account_empty = BatchedIsZeroGadget::construct( + cb, + [ + callee_nonce.expr(), + transfer.receiver().balance_prev().expr(), + ], ); - let callee_code_hash_is_empty = IsEqualGadget::construct( + let is_empty_code_hash = IsEqualGadget::construct( cb, callee_code_hash.expr(), Word::random_linear_combine_expr( - EMPTY_HASH.map(|byte| byte.expr()), + EMPTY_HASH_LE.to_fixed_bytes().map(|byte| byte.expr()), cb.power_of_randomness(), ), ); @@ -204,7 +198,9 @@ impl ExecutionGadget for CallGadget { GasCost::COLD_ACCOUNT_ACCESS.expr(), ) + has_value.clone() * (GasCost::CALL_WITH_VALUE.expr() - + callee_is_empty.expr() * GasCost::NEW_ACCOUNT.expr()) + + is_account_empty.expr() + * is_empty_code_hash.expr() + * GasCost::NEW_ACCOUNT.expr()) + memory_expansion.gas_cost(); // Apply EIP 150 @@ -220,7 +216,7 @@ impl ExecutionGadget for CallGadget { // TODO: Handle precompiled - cb.condition(callee_code_hash_is_empty.expr(), |cb| { + cb.condition(is_empty_code_hash.expr(), |cb| { // Save caller's call state for field_tag in [ CallContextFieldTag::LastCalleeId, @@ -243,7 +239,7 @@ impl ExecutionGadget for CallGadget { }); }); - cb.condition(1.expr() - callee_code_hash_is_empty.expr(), |cb| { + cb.condition(1.expr() - is_empty_code_hash.expr(), |cb| { // Save caller's call state for (field_tag, value) in [ ( @@ -331,9 +327,8 @@ impl ExecutionGadget for CallGadget { transfer, callee_nonce, callee_code_hash, - callee_is_empty, - callee_nonempty_witness, - callee_code_hash_is_empty, + is_account_empty, + is_empty_code_hash, one_64th_gas, capped_callee_gas_left, } @@ -456,27 +451,19 @@ impl ExecutionGadget for CallGadget { block.randomness, )), )?; - let callee_nonce_is_zero = callee_nonce.is_zero(); - let callee_balance_is_zero = callee_balance_pair.1.is_zero(); - let callee_is_empty = callee_nonce_is_zero && callee_balance_is_zero; - let callee_nonempty_witness = if !callee_nonce_is_zero { - F::from(callee_nonce.low_u64()).invert().unwrap() - } else if !callee_balance_is_zero { - sum::value::(&callee_balance_pair.1.to_le_bytes()) - .invert() - .unwrap() - } else { - 0.into() - }; - self.callee_is_empty - .assign(region, offset, Some(F::from(callee_is_empty as u64)))?; - self.callee_nonempty_witness - .assign(region, offset, Some(callee_nonempty_witness))?; - self.callee_code_hash_is_empty.assign( + let is_account_empty = self.is_account_empty.assign( + region, + offset, + [ + F::from(callee_nonce.low_u64()), + Word::random_linear_combine(callee_balance_pair.1.to_le_bytes(), block.randomness), + ], + )?; + self.is_empty_code_hash.assign( region, offset, Word::random_linear_combine(callee_code_hash.to_le_bytes(), block.randomness), - Word::random_linear_combine(EMPTY_HASH, block.randomness), + Word::random_linear_combine(EMPTY_HASH_LE.to_fixed_bytes(), block.randomness), )?; let has_value = !value.is_zero(); let gas_cost = if is_warm_access_prev { @@ -485,7 +472,7 @@ impl ExecutionGadget for CallGadget { GasCost::COLD_ACCOUNT_ACCESS.as_u64() } + if has_value { GasCost::CALL_WITH_VALUE.as_u64() - + if callee_is_empty { + + if is_account_empty == F::one() { GasCost::NEW_ACCOUNT.as_u64() } else { 0 diff --git a/zkevm-circuits/src/evm_circuit/execution/end_tx.rs b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs index 474c2c58de..42ee6ab39f 100644 --- a/zkevm-circuits/src/evm_circuit/execution/end_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs @@ -147,7 +147,7 @@ impl ExecutionGadget for EndTxGadget { ) -> Result<(), Error> { let gas_used = tx.gas - step.gas_left; let (refund, _) = block.rws[step.rw_indices[1]].tx_refund_value_pair(); - let [caller_balance_pair, coinbase_balance_pair] = + let [(caller_balance, caller_balance_prev), (coinbase_balance, coinbase_balance_prev)] = [step.rw_indices[2], step.rw_indices[3]].map(|idx| block.rws[idx].account_value_pair()); self.tx_id @@ -175,8 +175,9 @@ impl ExecutionGadget for EndTxGadget { self.gas_fee_refund.assign( region, offset, - vec![caller_balance_pair.1, gas_fee_refund], - caller_balance_pair.0, + caller_balance_prev, + vec![gas_fee_refund], + caller_balance, )?; let effective_tip = tx.gas_price - block.context.base_fee; self.sub_gas_price_by_base_fee.assign( @@ -197,8 +198,9 @@ impl ExecutionGadget for EndTxGadget { self.coinbase_reward.assign( region, offset, - vec![coinbase_balance_pair.1, effective_tip * gas_used], - coinbase_balance_pair.0, + coinbase_balance_prev, + vec![effective_tip * gas_used], + coinbase_balance, )?; Ok(()) diff --git a/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs b/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs index fec38ffe42..0ab10376c3 100644 --- a/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs +++ b/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs @@ -9,21 +9,16 @@ use crate::{ constraint_builder::{ ConstraintBuilder, ReversionInfo, StepStateTransition, Transition::Delta, }, - from_bytes, Cell, RandomLinearCombination, Word, + from_bytes, + math_gadget::BatchedIsZeroGadget, + Cell, RandomLinearCombination, Word, }, witness::{Block, Call, ExecStep, Transaction}, }, util::Expr, }; -use eth_types::ToLittleEndian; -use eth_types::{evm_types::GasCost, Field, ToAddress, ToScalar, U256}; -use ethers_core::utils::keccak256; +use eth_types::{evm_types::GasCost, Field, ToAddress, ToScalar, EMPTY_HASH_LE, U256}; use halo2_proofs::{circuit::Region, plonk::Error}; -use lazy_static::lazy_static; - -lazy_static! { - static ref EMPTY_CODE_HASH_LE_BYTES: [u8; 32] = U256::from(keccak256(&[])).to_le_bytes(); -} #[derive(Clone, Debug)] pub(crate) struct ExtcodehashGadget { @@ -35,10 +30,7 @@ pub(crate) struct ExtcodehashGadget { nonce: Cell, balance: Cell, code_hash: Cell, - is_empty: Cell, // boolean for if the external account is empty - nonempty_witness: Cell, /* if the account isn't empty, will witness that by holding - * inverse of one of balance, nonce, or RLC(code hash) - - * RLC(EMPTY_CODE_HASH_LE_BYTES) */ + is_empty: BatchedIsZeroGadget, // boolean for if the external account is empty } impl ExecutionGadget for ExtcodehashGadget { @@ -81,33 +73,19 @@ impl ExecutionGadget for ExtcodehashGadget { code_hash.expr(), ); - let is_empty = cb.query_bool(); - cb.require_zero( - "is_empty is 0 when nonce is non-zero", - is_empty.expr() * nonce.expr(), - ); - // Note that balance is RLC encoded, but RLC(x) = 0 iff x = 0, so we don't need - // go to the work of writing out the RLC expression - cb.require_zero( - "is_empty is 0 when balance is non-zero", - is_empty.expr() * balance.expr(), - ); let empty_code_hash_rlc = Word::random_linear_combine_expr( - EMPTY_CODE_HASH_LE_BYTES.map(|x| x.expr()), + EMPTY_HASH_LE.to_fixed_bytes().map(|x| x.expr()), cb.power_of_randomness(), ); - cb.require_zero( - "is_empty is 0 when code_hash is non-zero", - is_empty.expr() * (code_hash.expr() - empty_code_hash_rlc.clone()), - ); - - let nonempty_witness = cb.query_cell(); - cb.require_zero( - "is_empty is 1 if nonce, balance, and (code_hash - empty_code_hash_rlc) are all zero", - (1.expr() - is_empty.expr()) - * (1.expr() - nonce.expr() * nonempty_witness.expr()) - * (1.expr() - balance.expr() * nonempty_witness.expr()) - * (1.expr() - (code_hash.expr() - empty_code_hash_rlc) * nonempty_witness.expr()), + // Note that balance is RLC encoded, but RLC(x) = 0 iff x = 0, so we don't need + // go to the work of writing out the RLC expression + let is_empty = BatchedIsZeroGadget::construct( + cb, + [ + nonce.expr(), + balance.expr(), + code_hash.expr() - empty_code_hash_rlc, + ], ); // The stack push is 0 if the external account is empty. Otherwise, it's the @@ -138,7 +116,6 @@ impl ExecutionGadget for ExtcodehashGadget { balance, code_hash, is_empty, - nonempty_witness, } } @@ -181,22 +158,18 @@ impl ExecutionGadget for ExtcodehashGadget { .table_assignment(block.randomness) .value }); + self.nonce.assign(region, offset, Some(nonce))?; self.balance.assign(region, offset, Some(balance))?; self.code_hash.assign(region, offset, Some(code_hash))?; let empty_code_hash_rlc = - Word::random_linear_combine(*EMPTY_CODE_HASH_LE_BYTES, block.randomness); - - if let Some(inverse) = Option::from(nonce.invert()) - .or_else(|| balance.invert().into()) - .or_else(|| (code_hash - empty_code_hash_rlc).invert().into()) - { - self.nonempty_witness - .assign(region, offset, Some(inverse))?; - } else { - self.is_empty.assign(region, offset, Some(F::one()))?; - } + Word::random_linear_combine(EMPTY_HASH_LE.to_fixed_bytes(), block.randomness); + self.is_empty.assign( + region, + offset, + [nonce, balance, code_hash - empty_code_hash_rlc], + )?; Ok(()) } diff --git a/zkevm-circuits/src/evm_circuit/param.rs b/zkevm-circuits/src/evm_circuit/param.rs index 869e5d3ff1..0b745b6068 100644 --- a/zkevm-circuits/src/evm_circuit/param.rs +++ b/zkevm-circuits/src/evm_circuit/param.rs @@ -36,9 +36,3 @@ pub(crate) const N_BYTES_GAS: usize = N_BYTES_U64; // Number of bytes that will be used for call data's size. pub(crate) const N_BYTES_CALLDATASIZE: usize = N_BYTES_U64; - -// Hash output in little-endian of keccak256 with empty input. -pub(crate) const EMPTY_HASH: [u8; 32] = [ - 0x70, 0xa4, 0x85, 0x5d, 0x04, 0xd8, 0xfa, 0x7b, 0x3b, 0x27, 0x82, 0xca, 0x53, 0xb6, 0x00, 0xe5, - 0xc0, 0x03, 0xc7, 0xdc, 0xb2, 0x7d, 0x7e, 0x92, 0x3c, 0x23, 0xf7, 0x86, 0x01, 0x46, 0xd2, 0xc5, -]; diff --git a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs index 0eacc3e42d..68b839ed37 100644 --- a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs @@ -144,13 +144,21 @@ impl &self, region: &mut Region<'_, F>, offset: usize, - addends: Vec, - sum: U256, + value_prev: U256, + updates: Vec, + value: U256, ) -> Result<(), Error> { - debug_assert!(addends.len() == N_ADDENDS); + debug_assert!(updates.len() + 1 == N_ADDENDS); + let [value, value_prev] = if INCREASE { + [value, value_prev] + } else { + [value_prev, value] + }; + let mut addends = vec![value_prev]; + addends.extend(updates); self.add_words - .assign(region, offset, addends.try_into().unwrap(), sum) + .assign(region, offset, addends.try_into().unwrap(), value) } } @@ -193,13 +201,15 @@ impl TransferWithGasFeeGadget { self.sender.assign( region, offset, - vec![sender_balance, value, gas_fee], sender_balance_prev, + vec![value, gas_fee], + sender_balance, )?; self.receiver.assign( region, offset, - vec![receiver_balance_prev, value], + receiver_balance_prev, + vec![value], receiver_balance, )?; Ok(()) @@ -247,13 +257,15 @@ impl TransferGadget { self.sender.assign( region, offset, - vec![sender_balance, value], sender_balance_prev, + vec![value], + sender_balance, )?; self.receiver.assign( region, offset, - vec![receiver_balance_prev, value], + receiver_balance_prev, + vec![value], receiver_balance, )?; Ok(()) diff --git a/zkevm-circuits/src/evm_circuit/util/math_gadget.rs b/zkevm-circuits/src/evm_circuit/util/math_gadget.rs index e11beec638..d454b3cbd3 100644 --- a/zkevm-circuits/src/evm_circuit/util/math_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/math_gadget.rs @@ -89,6 +89,61 @@ impl IsEqualGadget { } } +#[derive(Clone, Debug)] +pub struct BatchedIsZeroGadget { + is_zero: Cell, + nonempty_witness: Cell, +} + +impl BatchedIsZeroGadget { + pub(crate) fn construct(cb: &mut ConstraintBuilder, values: [Expression; N]) -> Self { + let is_zero = cb.query_bool(); + let nonempty_witness = cb.query_cell(); + + for value in values.iter() { + cb.require_zero( + "is_zero is 0 if there is any non-zero value", + is_zero.expr() * value.clone(), + ); + } + + cb.require_zero( + "is_zero is 1 if values are all zero", + values.iter().fold(1.expr() - is_zero.expr(), |acc, value| { + acc * (1.expr() - value.expr() * nonempty_witness.clone().expr()) + }), + ); + + Self { + is_zero, + nonempty_witness, + } + } + + pub(crate) fn expr(&self) -> Expression { + self.is_zero.expr() + } + + pub(crate) fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + values: [F; N], + ) -> Result { + let is_zero = + if let Some(inverse) = values.iter().find_map(|value| Option::from(value.invert())) { + self.nonempty_witness + .assign(region, offset, Some(inverse))?; + F::zero() + } else { + F::one() + }; + self.is_zero.assign(region, offset, Some(is_zero))?; + + Ok(is_zero) + } +} + /// Construction of 2 256-bit words addition and result, which is useful for /// opcode ADD, SUB and balance operation #[derive(Clone, Debug)] From 11f6e13662aae9ef8e1718b02559ce97dc4b18fd Mon Sep 17 00:00:00 2001 From: han0110 Date: Thu, 31 Mar 2022 16:34:25 +0800 Subject: [PATCH 5/5] fix: apply more suggestions --- Cargo.lock | 2 + bus-mapping/Cargo.toml | 1 + bus-mapping/src/evm/opcodes.rs | 134 ++++++----- bus-mapping/src/evm/opcodes/call.rs | 223 +++++++++--------- eth-types/src/bytecode.rs | 14 +- eth-types/src/evm_types.rs | 40 +--- eth-types/src/evm_types/gas_utils.rs | 42 ++++ eth-types/src/lib.rs | 2 +- keccak256/Cargo.toml | 1 + keccak256/src/lib.rs | 16 ++ .../src/evm_circuit/execution/call.rs | 7 +- .../src/evm_circuit/execution/calldatacopy.rs | 2 +- .../src/evm_circuit/execution/extcodehash.rs | 8 +- 13 files changed, 260 insertions(+), 232 deletions(-) create mode 100644 eth-types/src/evm_types/gas_utils.rs diff --git a/Cargo.lock b/Cargo.lock index d58644902c..f35ee46129 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -339,6 +339,7 @@ dependencies = [ "ethers-core", "ethers-providers", "itertools", + "keccak256", "lazy_static", "log", "mock", @@ -2078,6 +2079,7 @@ dependencies = [ "eth-types", "halo2_proofs", "itertools", + "lazy_static", "num-bigint", "num-traits", "pairing_bn256", diff --git a/bus-mapping/Cargo.toml b/bus-mapping/Cargo.toml index 20ee9bd021..0acfa70ee2 100644 --- a/bus-mapping/Cargo.toml +++ b/bus-mapping/Cargo.toml @@ -6,6 +6,7 @@ authors = ["CPerezz "] [dependencies] eth-types = { path = "../eth-types" } +keccak256 = { path = "../keccak256" } ethers-core = "0.6" ethers-providers = "0.6" itertools = "0.10" diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index b3f29c27bf..c034b8440d 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -11,8 +11,9 @@ use crate::{ use core::fmt::Debug; use eth_types::{ evm_types::{GasCost, MAX_REFUND_QUOTIENT_OF_GAS_USED}, - GethExecStep, ToWord, EMPTY_HASH, + GethExecStep, ToWord, }; +use keccak256::EMPTY_HASH; use log::warn; mod call; @@ -340,73 +341,78 @@ pub fn gen_begin_tx_ops(state: &mut CircuitInputStateRef) -> Result { + warn!("Creation transaction is left unimplemented"); + Ok(exec_step) + } + // 2. Call to precompiled. + (_, true, _) => { + warn!("Call to precompiled is left unimplemented"); + Ok(exec_step) + } + (_, _, is_empty_code_hash) => { + state.push_op( + &mut exec_step, + RW::READ, + AccountOp { + address: call.address, + field: AccountField::CodeHash, + value: code_hash.to_word(), + value_prev: code_hash.to_word(), + }, + ); - // 1. Creation transaction. - if call.is_create() { - warn!("Creation transaction is left unimplemented"); - return Ok(exec_step); - } + // 3. Call to account with empty code. + if is_empty_code_hash { + warn!("Call to account with empty code is left unimplemented"); + return Ok(exec_step); + } - // 2. Call to precompiled. - if state.is_precompiled(&call.address) { - warn!("Call to precompiled is left unimplemented"); - return Ok(exec_step); - } + // 4. Call to account with non-empty code. + for (field, value) in [ + (CallContextField::Depth, call.depth.into()), + ( + CallContextField::CallerAddress, + call.caller_address.to_word(), + ), + (CallContextField::CalleeAddress, call.address.to_word()), + ( + CallContextField::CallDataOffset, + call.call_data_offset.into(), + ), + ( + CallContextField::CallDataLength, + call.call_data_length.into(), + ), + (CallContextField::Value, call.value), + (CallContextField::IsStatic, (call.is_static as usize).into()), + (CallContextField::LastCalleeId, 0.into()), + (CallContextField::LastCalleeReturnDataOffset, 0.into()), + (CallContextField::LastCalleeReturnDataLength, 0.into()), + (CallContextField::IsRoot, 1.into()), + (CallContextField::IsCreate, 0.into()), + (CallContextField::CodeSource, code_hash.to_word()), + ] { + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: call.call_id, + field, + value, + }, + ); + } - state.push_op( - &mut exec_step, - RW::READ, - AccountOp { - address: call.address, - field: AccountField::CodeHash, - value: code_hash.to_word(), - value_prev: code_hash.to_word(), - }, - ); - - // 3. Call to account with empty code. - if code_hash == EMPTY_HASH { - warn!("Call to account with empty code is left unimplemented"); - return Ok(exec_step); - } - - // 4. Call to account with non-empty code. - for (field, value) in [ - (CallContextField::Depth, call.depth.into()), - ( - CallContextField::CallerAddress, - call.caller_address.to_word(), - ), - (CallContextField::CalleeAddress, call.address.to_word()), - ( - CallContextField::CallDataOffset, - call.call_data_offset.into(), - ), - ( - CallContextField::CallDataLength, - call.call_data_length.into(), - ), - (CallContextField::Value, call.value), - (CallContextField::IsStatic, (call.is_static as usize).into()), - (CallContextField::LastCalleeId, 0.into()), - (CallContextField::LastCalleeReturnDataOffset, 0.into()), - (CallContextField::LastCalleeReturnDataLength, 0.into()), - (CallContextField::IsRoot, 1.into()), - (CallContextField::IsCreate, 0.into()), - (CallContextField::CodeSource, code_hash.to_word()), - ] { - state.push_op( - &mut exec_step, - RW::READ, - CallContextOp { - call_id: call.call_id, - field, - value, - }, - ); + Ok(exec_step) + } } - - Ok(exec_step) } pub fn gen_end_tx_ops(state: &mut CircuitInputStateRef) -> Result { diff --git a/bus-mapping/src/evm/opcodes/call.rs b/bus-mapping/src/evm/opcodes/call.rs index 46592f9e93..a8471d8a8c 100644 --- a/bus-mapping/src/evm/opcodes/call.rs +++ b/bus-mapping/src/evm/opcodes/call.rs @@ -8,9 +8,14 @@ use crate::{ Error, }; use eth_types::{ - evm_types::{eip150_gas, memory_expansion_gas_cost, GasCost}, - GethExecStep, ToWord, EMPTY_HASH, + evm_types::{ + gas_utils::{eip150_gas, memory_expansion_gas_cost}, + GasCost, + }, + GethExecStep, ToWord, }; +use keccak256::EMPTY_HASH; +use log::warn; /// Placeholder structure used to implement [`Opcode`] trait over it /// corresponding to the `OpcodeId::CALL` `OpcodeId`. @@ -195,114 +200,118 @@ impl Opcode for Call { let callee_gas_left = eip150_gas(geth_step.gas.0 - gas_cost, geth_step.stack.last()?); // There are 3 branches from here. - - // 1. Call to precompiled. - if state.is_precompiled(&call.address) { - // TODO: - return Ok(vec![exec_step]); - } - - // 2. Call to account with empty code. - if callee_code_hash == EMPTY_HASH { - for (field, value) in [ - (CallContextField::LastCalleeId, 0.into()), - (CallContextField::LastCalleeReturnDataOffset, 0.into()), - (CallContextField::LastCalleeReturnDataLength, 0.into()), - ] { - state.push_op( - &mut exec_step, - RW::WRITE, - CallContextOp { - call_id: call.call_id, - field, - value, - }, - ); + match ( + state.is_precompiled(&call.address), + callee_code_hash.to_fixed_bytes() == *EMPTY_HASH, + ) { + // 1. Call to precompiled. + (true, _) => { + warn!("Call to precompiled is left unimplemented"); + Ok(vec![exec_step]) } - state.handle_return()?; - return Ok(vec![exec_step]); - } + // 2. Call to account with empty code. + (_, true) => { + for (field, value) in [ + (CallContextField::LastCalleeId, 0.into()), + (CallContextField::LastCalleeReturnDataOffset, 0.into()), + (CallContextField::LastCalleeReturnDataLength, 0.into()), + ] { + state.push_op( + &mut exec_step, + RW::WRITE, + CallContextOp { + call_id: call.call_id, + field, + value, + }, + ); + } + state.handle_return()?; + Ok(vec![exec_step]) + } + // 3. Call to account with non-empty code. + (_, false) => { + for (field, value) in [ + ( + CallContextField::ProgramCounter, + (geth_step.pc.0 + 1).into(), + ), + ( + CallContextField::StackPointer, + (geth_step.stack.stack_pointer().0 + 6).into(), + ), + ( + CallContextField::GasLeft, + (geth_step.gas.0 - gas_cost - callee_gas_left).into(), + ), + (CallContextField::MemorySize, next_memory_word_size.into()), + ( + CallContextField::StateWriteCounter, + (exec_step.swc + 1).into(), + ), + ] { + state.push_op( + &mut exec_step, + RW::WRITE, + CallContextOp { + call_id: call.call_id, + field, + value, + }, + ); + } - // 3. Call to account with non-empty code. - for (field, value) in [ - ( - CallContextField::ProgramCounter, - (geth_step.pc.0 + 1).into(), - ), - ( - CallContextField::StackPointer, - (geth_step.stack.stack_pointer().0 + 6).into(), - ), - ( - CallContextField::GasLeft, - (geth_step.gas.0 - gas_cost - callee_gas_left).into(), - ), - (CallContextField::MemorySize, next_memory_word_size.into()), - ( - CallContextField::StateWriteCounter, - (exec_step.swc + 1).into(), - ), - ] { - state.push_op( - &mut exec_step, - RW::WRITE, - CallContextOp { - call_id: call.call_id, - field, - value, - }, - ); - } + for (field, value) in [ + (CallContextField::CallerId, call.call_id.into()), + (CallContextField::TxId, tx_id.into()), + (CallContextField::Depth, callee.depth.into()), + ( + CallContextField::CallerAddress, + callee.caller_address.to_word(), + ), + (CallContextField::CalleeAddress, callee.address.to_word()), + ( + CallContextField::CallDataOffset, + callee.call_data_offset.into(), + ), + ( + CallContextField::CallDataLength, + callee.call_data_length.into(), + ), + ( + CallContextField::ReturnDataOffset, + callee.return_data_offset.into(), + ), + ( + CallContextField::ReturnDataLength, + callee.return_data_length.into(), + ), + (CallContextField::Value, callee.value), + ( + CallContextField::IsSuccess, + (callee.is_success as u64).into(), + ), + (CallContextField::IsStatic, (callee.is_static as u64).into()), + (CallContextField::LastCalleeId, 0.into()), + (CallContextField::LastCalleeReturnDataOffset, 0.into()), + (CallContextField::LastCalleeReturnDataLength, 0.into()), + (CallContextField::IsRoot, 0.into()), + (CallContextField::IsCreate, 0.into()), + (CallContextField::CodeSource, callee.code_hash.to_word()), + ] { + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: callee.call_id, + field, + value, + }, + ); + } - for (field, value) in [ - (CallContextField::CallerId, call.call_id.into()), - (CallContextField::TxId, tx_id.into()), - (CallContextField::Depth, callee.depth.into()), - ( - CallContextField::CallerAddress, - callee.caller_address.to_word(), - ), - (CallContextField::CalleeAddress, callee.address.to_word()), - ( - CallContextField::CallDataOffset, - callee.call_data_offset.into(), - ), - ( - CallContextField::CallDataLength, - callee.call_data_length.into(), - ), - ( - CallContextField::ReturnDataOffset, - callee.return_data_offset.into(), - ), - ( - CallContextField::ReturnDataLength, - callee.return_data_length.into(), - ), - (CallContextField::Value, callee.value), - ( - CallContextField::IsSuccess, - (callee.is_success as u64).into(), - ), - (CallContextField::IsStatic, (callee.is_static as u64).into()), - (CallContextField::LastCalleeId, 0.into()), - (CallContextField::LastCalleeReturnDataOffset, 0.into()), - (CallContextField::LastCalleeReturnDataLength, 0.into()), - (CallContextField::IsRoot, 0.into()), - (CallContextField::IsCreate, 0.into()), - (CallContextField::CodeSource, callee.code_hash.to_word()), - ] { - state.push_op( - &mut exec_step, - RW::READ, - CallContextOp { - call_id: callee.call_id, - field, - value, - }, - ); + Ok(vec![exec_step]) + } } - - Ok(vec![exec_step]) } } diff --git a/eth-types/src/bytecode.rs b/eth-types/src/bytecode.rs index df0cc38090..a6afa4aea4 100644 --- a/eth-types/src/bytecode.rs +++ b/eth-types/src/bytecode.rs @@ -1,20 +1,8 @@ //! EVM byte code generator -use crate::{evm_types::OpcodeId, Bytes, Hash, Word}; -use ethers_core::types::H256; +use crate::{evm_types::OpcodeId, Bytes, Word}; use std::collections::HashMap; -/// Keccak256 of empty input in big-endian -pub const EMPTY_HASH: Hash = H256([ - 0xc5, 0xd2, 0x46, 0x01, 0x86, 0xf7, 0x23, 0x3c, 0x92, 0x7e, 0x7d, 0xb2, 0xdc, 0xc7, 0x03, 0xc0, - 0xe5, 0x00, 0xb6, 0x53, 0xca, 0x82, 0x27, 0x3b, 0x7b, 0xfa, 0xd8, 0x04, 0x5d, 0x85, 0xa4, 0x70, -]); -/// Keccak256 of empty input in little-endian -pub const EMPTY_HASH_LE: Hash = H256([ - 0x70, 0xa4, 0x85, 0x5d, 0x04, 0xd8, 0xfa, 0x7b, 0x3b, 0x27, 0x82, 0xca, 0x53, 0xb6, 0x00, 0xe5, - 0xc0, 0x03, 0xc7, 0xdc, 0xb2, 0x7d, 0x7e, 0x92, 0x3c, 0x23, 0xf7, 0x86, 0x01, 0x46, 0xd2, 0xc5, -]); - /// EVM Bytecode #[derive(Debug, Default, Clone)] pub struct Bytecode { diff --git a/eth-types/src/evm_types.rs b/eth-types/src/evm_types.rs index abdf64a176..4f61229bee 100644 --- a/eth-types/src/evm_types.rs +++ b/eth-types/src/evm_types.rs @@ -1,9 +1,9 @@ //! Evm types needed for parsing instruction sets as well -use crate::Word; use serde::{Deserialize, Serialize}; use std::fmt; +pub mod gas_utils; pub mod memory; pub mod opcode_ids; pub mod stack; @@ -153,41 +153,3 @@ impl From for GasCost { GasCost(cost) } } - -/// Calculate memory expansion gas cost by current and next memory word size. -pub fn memory_expansion_gas_cost(curr_memory_word_size: u64, next_memory_word_size: u64) -> u64 { - if next_memory_word_size == curr_memory_word_size { - 0 - } else { - GasCost::MEMORY_EXPANSION_LINEAR_COEFF.0 * (next_memory_word_size - curr_memory_word_size) - + (next_memory_word_size * next_memory_word_size - - curr_memory_word_size * curr_memory_word_size) - / GasCost::MEMORY_EXPANSION_QUAD_DENOMINATOR.0 - } -} - -/// Calculate memory copier gas cost by current and next memory word size, and -/// number of bytes to copy. -pub fn memory_copier_gas_cost( - curr_memory_word_size: u64, - next_memory_word_size: u64, - num_copy_bytes: u64, -) -> u64 { - let num_words = (num_copy_bytes + 31) / 32; - num_words * GasCost::COPY.as_u64() - + memory_expansion_gas_cost(curr_memory_word_size, next_memory_word_size) -} - -/// Calculate EIP 150 gas passed to callee. -pub fn eip150_gas(gas_left: u64, gas_specified: Word) -> u64 { - let capped_gas = gas_left - gas_left / 64; - - if gas_specified.bits() <= 64 { - let gas_specified = gas_specified.low_u64(); - if gas_specified < capped_gas { - return gas_specified; - } - } - - capped_gas -} diff --git a/eth-types/src/evm_types/gas_utils.rs b/eth-types/src/evm_types/gas_utils.rs new file mode 100644 index 0000000000..34c4480c88 --- /dev/null +++ b/eth-types/src/evm_types/gas_utils.rs @@ -0,0 +1,42 @@ +//! Utility functions to help calculate gas + +use super::GasCost; +use crate::Word; + +/// Calculate memory expansion gas cost by current and next memory word size. +pub fn memory_expansion_gas_cost(curr_memory_word_size: u64, next_memory_word_size: u64) -> u64 { + if next_memory_word_size == curr_memory_word_size { + 0 + } else { + GasCost::MEMORY_EXPANSION_LINEAR_COEFF.0 * (next_memory_word_size - curr_memory_word_size) + + (next_memory_word_size * next_memory_word_size + - curr_memory_word_size * curr_memory_word_size) + / GasCost::MEMORY_EXPANSION_QUAD_DENOMINATOR.0 + } +} + +/// Calculate memory copier gas cost by current and next memory word size, and +/// number of bytes to copy. +pub fn memory_copier_gas_cost( + curr_memory_word_size: u64, + next_memory_word_size: u64, + num_copy_bytes: u64, +) -> u64 { + let num_words = (num_copy_bytes + 31) / 32; + num_words * GasCost::COPY.as_u64() + + memory_expansion_gas_cost(curr_memory_word_size, next_memory_word_size) +} + +/// Calculate EIP 150 gas passed to callee. +pub fn eip150_gas(gas_left: u64, gas_specified: Word) -> u64 { + let capped_gas = gas_left - gas_left / 64; + + if gas_specified.bits() <= 64 { + let gas_specified = gas_specified.low_u64(); + if gas_specified < capped_gas { + return gas_specified; + } + } + + capped_gas +} diff --git a/eth-types/src/lib.rs b/eth-types/src/lib.rs index 4e7a32d8ce..915f54c632 100644 --- a/eth-types/src/lib.rs +++ b/eth-types/src/lib.rs @@ -21,7 +21,7 @@ pub mod bytecode; pub mod evm_types; pub mod geth_types; -pub use bytecode::{Bytecode, EMPTY_HASH, EMPTY_HASH_LE}; +pub use bytecode::Bytecode; pub use error::Error; use pairing::group::ff::PrimeField; diff --git a/keccak256/Cargo.toml b/keccak256/Cargo.toml index 34c1da447b..20f2862178 100644 --- a/keccak256/Cargo.toml +++ b/keccak256/Cargo.toml @@ -14,6 +14,7 @@ num-traits = "0.2.14" pairing = { git = 'https://github.com/appliedzkp/pairing', package = "pairing_bn256" } plotters = { version = "0.3.0", optional = true } eth-types = { path = "../eth-types" } +lazy_static = "1.4" [dev-dependencies] pretty_assertions = "1.0" diff --git a/keccak256/src/lib.rs b/keccak256/src/lib.rs index cde425add8..0191d9df48 100644 --- a/keccak256/src/lib.rs +++ b/keccak256/src/lib.rs @@ -10,3 +10,19 @@ pub mod permutation; pub mod keccak_arith; // We build plain module for the purpose of reviewing the circuit pub mod plain; + +lazy_static::lazy_static! { + pub static ref EMPTY_HASH: [u8; 32] = { + use std::convert::TryInto; + + let mut keccak = plain::Keccak::default(); + keccak.update(&[]); + keccak.digest().try_into().unwrap() + }; + pub static ref EMPTY_HASH_LE: [u8; 32] = { + use std::convert::TryInto; + use itertools::Itertools; + + EMPTY_HASH.iter().rev().cloned().collect_vec().try_into().unwrap() + }; +} diff --git a/zkevm-circuits/src/evm_circuit/execution/call.rs b/zkevm-circuits/src/evm_circuit/execution/call.rs index da3e57e2f0..d93b69fd9c 100644 --- a/zkevm-circuits/src/evm_circuit/execution/call.rs +++ b/zkevm-circuits/src/evm_circuit/execution/call.rs @@ -24,9 +24,10 @@ use crate::{ }; use eth_types::{ evm_types::{GasCost, GAS_STIPEND_CALL_WITH_VALUE}, - Field, ToLittleEndian, ToScalar, EMPTY_HASH_LE, + Field, ToLittleEndian, ToScalar, }; use halo2_proofs::{circuit::Region, plonk::Error}; +use keccak256::EMPTY_HASH_LE; #[derive(Clone, Debug)] pub(crate) struct CallGadget { @@ -187,7 +188,7 @@ impl ExecutionGadget for CallGadget { cb, callee_code_hash.expr(), Word::random_linear_combine_expr( - EMPTY_HASH_LE.to_fixed_bytes().map(|byte| byte.expr()), + (*EMPTY_HASH_LE).map(|byte| byte.expr()), cb.power_of_randomness(), ), ); @@ -463,7 +464,7 @@ impl ExecutionGadget for CallGadget { region, offset, Word::random_linear_combine(callee_code_hash.to_le_bytes(), block.randomness), - Word::random_linear_combine(EMPTY_HASH_LE.to_fixed_bytes(), block.randomness), + Word::random_linear_combine(*EMPTY_HASH_LE, block.randomness), )?; let has_value = !value.is_zero(); let gas_cost = if is_warm_access_prev { diff --git a/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs b/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs index dcccf9cc95..b1d1ac9967 100644 --- a/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs @@ -237,7 +237,7 @@ mod test { use crate::test_util::{test_circuits_using_bytecode, BytecodeTestConfig}; use eth_types::{ bytecode, - evm_types::{memory_copier_gas_cost, GasCost, OpcodeId}, + evm_types::{gas_utils::memory_copier_gas_cost, GasCost, OpcodeId}, ToBigEndian, Word, }; use halo2_proofs::arithmetic::BaseExt; diff --git a/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs b/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs index 0ab10376c3..9828638a84 100644 --- a/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs +++ b/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs @@ -17,8 +17,9 @@ use crate::{ }, util::Expr, }; -use eth_types::{evm_types::GasCost, Field, ToAddress, ToScalar, EMPTY_HASH_LE, U256}; +use eth_types::{evm_types::GasCost, Field, ToAddress, ToScalar, U256}; use halo2_proofs::{circuit::Region, plonk::Error}; +use keccak256::EMPTY_HASH_LE; #[derive(Clone, Debug)] pub(crate) struct ExtcodehashGadget { @@ -74,7 +75,7 @@ impl ExecutionGadget for ExtcodehashGadget { ); let empty_code_hash_rlc = Word::random_linear_combine_expr( - EMPTY_HASH_LE.to_fixed_bytes().map(|x| x.expr()), + (*EMPTY_HASH_LE).map(|byte| byte.expr()), cb.power_of_randomness(), ); // Note that balance is RLC encoded, but RLC(x) = 0 iff x = 0, so we don't need @@ -163,8 +164,7 @@ impl ExecutionGadget for ExtcodehashGadget { self.balance.assign(region, offset, Some(balance))?; self.code_hash.assign(region, offset, Some(code_hash))?; - let empty_code_hash_rlc = - Word::random_linear_combine(EMPTY_HASH_LE.to_fixed_bytes(), block.randomness); + let empty_code_hash_rlc = Word::random_linear_combine(*EMPTY_HASH_LE, block.randomness); self.is_empty.assign( region, offset,