From 9513e0f17d0fc5633240cd87a25ad8a64a199233 Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Wed, 10 Aug 2022 11:17:46 +0800 Subject: [PATCH 1/9] Add bus-mapping and circuit of opcode `BALANCE`. --- bus-mapping/src/evm/opcodes/balance.rs | 253 ++- zkevm-circuits/src/evm_circuit/execution.rs | 6 +- .../src/evm_circuit/execution/balance.rs | 272 ++++ zkevm-circuits/src/evm_circuit/witness.rs | 1388 +++++++++++++++++ 4 files changed, 1839 insertions(+), 80 deletions(-) create mode 100644 zkevm-circuits/src/evm_circuit/execution/balance.rs create mode 100644 zkevm-circuits/src/evm_circuit/witness.rs diff --git a/bus-mapping/src/evm/opcodes/balance.rs b/bus-mapping/src/evm/opcodes/balance.rs index 40b5583e92..73a36f6c9b 100644 --- a/bus-mapping/src/evm/opcodes/balance.rs +++ b/bus-mapping/src/evm/opcodes/balance.rs @@ -1,8 +1,9 @@ use crate::circuit_input_builder::{CircuitInputStateRef, ExecStep}; use crate::evm::Opcode; -use crate::operation::{TxAccessListAccountOp, RW}; +use crate::operation::{AccountField, CallContextField, TxAccessListAccountOp, RW}; +use crate::state_db::Account; use crate::Error; -use eth_types::{GethExecStep, ToAddress, ToWord}; +use eth_types::{GethExecStep, ToAddress, ToWord, U256}; #[derive(Debug, Copy, Clone)] pub(crate) struct Balance; @@ -12,16 +13,39 @@ impl Opcode for Balance { state: &mut CircuitInputStateRef, geth_steps: &[GethExecStep], ) -> Result, Error> { - // TODO: finish this, only access list part is done let geth_step = &geth_steps[0]; let mut exec_step = state.new_step(geth_step)?; - let address = geth_steps[0].stack.last()?.to_address(); + // Read account address from stack. + let address = geth_step.stack.last()?.to_address(); state.stack_read( &mut exec_step, geth_step.stack.last_filled(), address.to_word(), )?; + + // Read transaction ID, rw_counter_end_of_reversion, and is_persistent + // from call context. + state.call_context_read( + &mut exec_step, + state.call()?.call_id, + CallContextField::TxId, + U256::from(state.tx_ctx.id()), + ); + state.call_context_read( + &mut exec_step, + state.call()?.call_id, + CallContextField::RwCounterEndOfReversion, + U256::from(state.call()?.rw_counter_end_of_reversion as u64), + ); + state.call_context_read( + &mut exec_step, + state.call()?.call_id, + CallContextField::IsPersistent, + U256::from(state.call()?.is_persistent as u64), + ); + + // Update transaction access list for account address. let is_warm = state.sdb.check_account_in_access_list(&address); state.push_op_reversible( &mut exec_step, @@ -34,6 +58,17 @@ impl Opcode for Balance { }, )?; + // Read account balance. + let &Account { balance, .. } = state.sdb.get_account(&address).1; + state.account_read( + &mut exec_step, + address, + AccountField::Balance, + balance, + balance, + )?; + + // Write the BALANCE result to stack. state.stack_write( &mut exec_step, geth_steps[1].stack.nth_last_filled(0), @@ -47,73 +82,71 @@ impl Opcode for Balance { #[cfg(test)] mod balance_tests { use super::*; - use crate::{ - circuit_input_builder::ExecState, - mock::BlockData, - operation::{StackOp, RW}, - }; - use eth_types::{ - bytecode, - evm_types::{OpcodeId, StackAddress}, - geth_types::GethData, - }; - use mock::eth; - use mock::test_ctx::{helpers::*, TestContext}; + use crate::circuit_input_builder::ExecState; + use crate::mock::BlockData; + use crate::operation::{AccountOp, CallContextOp, StackOp}; + use eth_types::evm_types::{OpcodeId, StackAddress}; + use eth_types::geth_types::GethData; + use eth_types::{address, bytecode, Bytecode, Word, U256}; + use mock::TestContext; use pretty_assertions::assert_eq; - // If the given account doesn't exist, it will push 0 onto the stack instead. #[test] - fn test_balance_of_non_exists_address() { - let code = bytecode! { - BALANCE - STOP - }; - - // Get the execution steps from the external tracer - let block: GethData = TestContext::<2, 1>::new( - None, - account_0_code_account_1_no_code(code), - tx_from_1_to_0, - |block, _tx| block.number(0xcafeu64), - ) - .unwrap() - .into(); - - let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); - builder - .handle_block(&block.eth_block, &block.geth_traces) - .unwrap(); - - let _step = builder.block.txs()[0] - .steps() - .iter() - .find(|step| step.exec_state == ExecState::Op(OpcodeId::BALANCE)) - .unwrap(); + fn test_balance_of_non_existing_address() { + test_ok(false, false); + } - assert_eq!(&builder.block.container.stack.len(), &0_usize); + #[test] + fn test_balance_of_cold_address() { + test_ok(true, false); } #[test] - fn test_balance_of_exists_address() { - let (addr_a, addr_b) = (mock::MOCK_ACCOUNTS[0], mock::MOCK_ACCOUNTS[1]); + fn test_balance_of_warm_address() { + test_ok(true, true); + } - let code = bytecode! { - PUSH32(addr_a.to_word()) + fn test_ok(is_existing: bool, is_warm: bool) { + let address = address!("0xaabbccddee000000000000000000000000000000"); + + // Pop balance first for warm account. + let mut code = Bytecode::default(); + if is_warm { + code.append(&bytecode! { + PUSH20(address.to_word()) + BALANCE + POP + }); + } + code.append(&bytecode! { + PUSH20(address.to_word()) BALANCE STOP + }); + + let balance = if is_existing { + Word::from(800u64) + } else { + Word::zero() }; - // Get the execution steps from the external tracer - let block: GethData = TestContext::<2, 1>::new( + // Get the execution steps from the external tracer. + let block: GethData = TestContext::<3, 1>::new( None, |accs| { - accs[0].address(addr_a).balance(eth(10)).code(code); - accs[1].address(addr_b).balance(eth(10)); + accs[0] + .address(address!("0x0000000000000000000000000000000000000010")) + .balance(Word::from(1u64 << 20)) + .code(code.clone()); + accs[1].address(address).balance(balance); + accs[2] + .address(address!("0x0000000000000000000000000000000000cafe01")) + .balance(Word::from(1u64 << 20)); }, |mut txs, accs| { - txs[0].from(accs[1].address).to(accs[0].address); + txs[0].to(accs[0].address).from(accs[2].address); }, - |block, _tx| block, + |block, _tx| block.number(0xcafeu64), ) .unwrap() .into(); @@ -123,37 +156,101 @@ mod balance_tests { .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); - let step = builder.block.txs()[0] + // Check if account address is in access list as a result of bus mapping. + assert!(builder.sdb.add_account_to_access_list(address)); + + let tx_id = 1; + let transaction = &builder.block.txs()[tx_id - 1]; + let call_id = transaction.calls()[0].call_id; + + let indices = transaction .steps() .iter() - .find(|step| step.exec_state == ExecState::Op(OpcodeId::BALANCE)) - .unwrap(); + .filter(|step| step.exec_state == ExecState::Op(OpcodeId::BALANCE)) + .last() + .unwrap() + .bus_mapping_instance + .clone(); - let call_id = builder.block.txs()[0].calls()[0].call_id; - let balance_a = builder.sdb.get_account(&addr_a).1.balance; + let container = builder.block.container; - assert_eq!(addr_a, block.eth_block.transactions[0].to.unwrap()); + let operation = &container.stack[indices[0].as_usize()]; + assert_eq!(operation.rw(), RW::READ); assert_eq!( - { - let operation = - &builder.block.container.stack[step.bus_mapping_instance[0].as_usize()]; - (operation.rw(), operation.op()) - }, - ( - RW::READ, - &StackOp::new(call_id, StackAddress::from(1023), addr_a.to_word()) - ) + operation.op(), + &StackOp { + call_id, + address: StackAddress::from(1023u32), + value: address.to_word() + } ); + + let operation = &container.call_context[indices[1].as_usize()]; + assert_eq!(operation.rw(), RW::READ); assert_eq!( - { - let operation = - &builder.block.container.stack[step.bus_mapping_instance[1].as_usize()]; - (operation.rw(), operation.op()) - }, - ( - RW::WRITE, - &StackOp::new(call_id, StackAddress::from(1023), balance_a) - ) + operation.op(), + &CallContextOp { + call_id, + field: CallContextField::TxId, + value: tx_id.into() + } + ); + + let operation = &container.call_context[indices[2].as_usize()]; + assert_eq!(operation.rw(), RW::READ); + assert_eq!( + operation.op(), + &CallContextOp { + call_id, + field: CallContextField::RwCounterEndOfReversion, + value: U256::zero() + } + ); + + let operation = &container.call_context[indices[3].as_usize()]; + assert_eq!(operation.rw(), RW::READ); + assert_eq!( + operation.op(), + &CallContextOp { + call_id, + field: CallContextField::IsPersistent, + value: U256::one() + } + ); + + let operation = &container.tx_access_list_account[indices[4].as_usize()]; + assert_eq!(operation.rw(), RW::WRITE); + assert_eq!( + operation.op(), + &TxAccessListAccountOp { + tx_id, + address, + is_warm: true, + is_warm_prev: is_warm + } + ); + + let operation = &container.account[indices[5].as_usize()]; + assert_eq!(operation.rw(), RW::READ); + assert_eq!( + operation.op(), + &AccountOp { + address, + field: AccountField::Balance, + value: balance, + value_prev: balance, + } + ); + + let operation = &container.stack[indices[6].as_usize()]; + assert_eq!(operation.rw(), RW::WRITE); + assert_eq!( + operation.op(), + &StackOp { + call_id, + address: 1023u32.into(), + value: balance, + } ); } } diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 99b7c85013..44463f3ff5 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -30,6 +30,7 @@ use strum::IntoEnumIterator; mod add_sub; mod addmod; mod address; +mod balance; mod begin_tx; mod bitwise; mod block_ctx; @@ -90,6 +91,7 @@ use self::sha3::Sha3Gadget; use add_sub::AddSubGadget; use addmod::AddModGadget; use address::AddressGadget; +use balance::BalanceGadget; use begin_tx::BeginTxGadget; use bitwise::BitwiseGadget; use block_ctx::{BlockCtxU160Gadget, BlockCtxU256Gadget, BlockCtxU64Gadget}; @@ -190,6 +192,7 @@ pub(crate) struct ExecutionConfig { add_sub_gadget: AddSubGadget, addmod_gadget: AddModGadget, address_gadget: AddressGadget, + balance_gadget: BalanceGadget, bitwise_gadget: BitwiseGadget, byte_gadget: ByteGadget, call_op_gadget: CallOpGadget, @@ -226,7 +229,6 @@ pub(crate) struct ExecutionConfig { selfbalance_gadget: SelfbalanceGadget, sha3_gadget: Sha3Gadget, shl_shr_gadget: ShlShrGadget, - balance_gadget: DummyGadget, sar_gadget: DummyGadget, extcodesize_gadget: DummyGadget, extcodecopy_gadget: DummyGadget, @@ -972,6 +974,7 @@ impl ExecutionConfig { ExecutionState::ADD_SUB => assign_exec_step!(self.add_sub_gadget), ExecutionState::ADDMOD => assign_exec_step!(self.addmod_gadget), ExecutionState::ADDRESS => assign_exec_step!(self.address_gadget), + ExecutionState::BALANCE => assign_exec_step!(self.balance_gadget), ExecutionState::BITWISE => assign_exec_step!(self.bitwise_gadget), ExecutionState::BYTE => assign_exec_step!(self.byte_gadget), ExecutionState::CALL_OP => assign_exec_step!(self.call_op_gadget), @@ -1014,7 +1017,6 @@ impl ExecutionConfig { ExecutionState::BLOCKHASH => assign_exec_step!(self.blockhash_gadget), ExecutionState::SELFBALANCE => assign_exec_step!(self.selfbalance_gadget), // dummy gadgets - ExecutionState::BALANCE => assign_exec_step!(self.balance_gadget), ExecutionState::SAR => assign_exec_step!(self.sar_gadget), ExecutionState::EXTCODESIZE => assign_exec_step!(self.extcodesize_gadget), ExecutionState::EXTCODECOPY => assign_exec_step!(self.extcodecopy_gadget), diff --git a/zkevm-circuits/src/evm_circuit/execution/balance.rs b/zkevm-circuits/src/evm_circuit/execution/balance.rs new file mode 100644 index 0000000000..61555ccfab --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/balance.rs @@ -0,0 +1,272 @@ +use crate::evm_circuit::execution::ExecutionGadget; +use crate::evm_circuit::param::N_BYTES_ACCOUNT_ADDRESS; +use crate::evm_circuit::step::ExecutionState; +use crate::evm_circuit::util::common_gadget::SameContextGadget; +use crate::evm_circuit::util::constraint_builder::Transition::Delta; +use crate::evm_circuit::util::constraint_builder::{ + ConstraintBuilder, ReversionInfo, StepStateTransition, +}; +use crate::evm_circuit::util::{from_bytes, CachedRegion, Cell, RandomLinearCombination}; +use crate::evm_circuit::witness::{Block, Call, ExecStep, Transaction}; +use crate::table::{AccountFieldTag, CallContextFieldTag}; +use crate::util::Expr; +use eth_types::evm_types::GasCost; +use eth_types::{Field, ToAddress}; +use halo2_proofs::circuit::Value; +use halo2_proofs::plonk::Error; + +#[derive(Clone, Debug)] +pub(crate) struct BalanceGadget { + same_context: SameContextGadget, + address: RandomLinearCombination, + reversion_info: ReversionInfo, + tx_id: Cell, + is_warm: Cell, + balance: Cell, +} + +impl ExecutionGadget for BalanceGadget { + const NAME: &'static str = "BALANCE"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::BALANCE; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let address = cb.query_rlc(); + cb.stack_pop(address.expr()); + + let tx_id = cb.call_context(None, CallContextFieldTag::TxId); + let mut reversion_info = cb.reversion_info_read(None); + let is_warm = cb.query_bool(); + cb.account_access_list_write( + tx_id.expr(), + from_bytes::expr(&address.cells), + 1.expr(), + is_warm.expr(), + Some(&mut reversion_info), + ); + + let balance = cb.query_cell(); + cb.account_read( + from_bytes::expr(&address.cells), + AccountFieldTag::Balance, + balance.expr(), + ); + cb.stack_push(balance.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()), + stack_pointer: Delta(0.expr()), + gas_left: Delta(-gas_cost), + reversible_write_counter: Delta(1.expr()), + ..Default::default() + }; + + let opcode = cb.query_cell(); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); + + Self { + same_context, + address, + reversion_info, + tx_id, + is_warm, + balance, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + tx: &Transaction, + call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + self.same_context.assign_exec_step(region, offset, step)?; + + let mut address_bytes = block.rws[step.rw_indices[0]].stack_value().to_address().0; + address_bytes.reverse(); + self.address.assign(region, offset, Some(address_bytes))?; + + self.tx_id + .assign(region, offset, Value::known(F::from(tx.id as u64)))?; + + self.reversion_info.assign( + region, + offset, + call.rw_counter_end_of_reversion, + call.is_persistent, + )?; + + let (_, is_warm) = block.rws[step.rw_indices[4]].tx_access_list_value_pair(); + self.is_warm + .assign(region, offset, Value::known(F::from(is_warm)))?; + + let balance = block.rws[step.rw_indices[5]] + .table_assignment_aux(block.randomness) + .value; + self.balance.assign(region, offset, Value::known(balance))?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::evm_circuit::test::rand_bytes; + use crate::test_util::run_test_circuits; + use eth_types::geth_types::Account; + use eth_types::{address, bytecode, Address, Bytecode, ToWord, Word, U256}; + use lazy_static::lazy_static; + use mock::TestContext; + + lazy_static! { + static ref TEST_ADDRESS: Address = address!("0xaabbccddee000000000000000000000000000000"); + } + + #[test] + fn balance_gadget_non_existing_account() { + test_root_ok(&None, false); + test_internal_ok(0x20, 0x00, &None, false); + test_internal_ok(0x1010, 0xff, &None, false); + } + + #[test] + fn balance_gadget_cold_account() { + let account = Some(Account { + address: *TEST_ADDRESS, + balance: U256::from(900), + ..Default::default() + }); + + test_root_ok(&account, false); + test_internal_ok(0x20, 0x00, &account, false); + test_internal_ok(0x1010, 0xff, &account, false); + } + + #[test] + fn balance_gadget_warm_account() { + let account = Some(Account { + address: *TEST_ADDRESS, + balance: U256::from(900), + ..Default::default() + }); + + test_root_ok(&account, true); + test_internal_ok(0x20, 0x00, &account, true); + test_internal_ok(0x1010, 0xff, &account, true); + } + + fn test_root_ok(account: &Option, is_warm: bool) { + let address = account.as_ref().map(|a| a.address).unwrap_or(*TEST_ADDRESS); + + let mut code = Bytecode::default(); + if is_warm { + code.append(&bytecode! { + PUSH20(address.to_word()) + BALANCE + POP + }); + } + code.append(&bytecode! { + PUSH20(address.to_word()) + BALANCE + STOP + }); + + let ctx = TestContext::<3, 1>::new( + None, + |accs| { + accs[0] + .address(address!("0x000000000000000000000000000000000000cafe")) + .balance(Word::from(1_u64 << 20)) + .code(code); + accs[1].address(address); + if let Some(account) = account { + accs[1].balance(account.balance); + } + accs[2] + .address(address!("0x0000000000000000000000000000000000000010")) + .balance(Word::from(1_u64 << 20)); + }, + |mut txs, accs| { + txs[0].to(accs[0].address).from(accs[2].address); + }, + |block, _tx| block, + ) + .unwrap(); + + assert_eq!(run_test_circuits(ctx, None), Ok(())); + } + + fn test_internal_ok( + call_data_offset: usize, + call_data_length: usize, + account: &Option, + is_warm: bool, + ) { + let address = account.as_ref().map(|a| a.address).unwrap_or(*TEST_ADDRESS); + let (addr_a, addr_b) = (mock::MOCK_ACCOUNTS[0], mock::MOCK_ACCOUNTS[1]); + + // code B gets called by code A, so the call is an internal call. + let mut code_b = Bytecode::default(); + if is_warm { + code_b.append(&bytecode! { + PUSH20(address.to_word()) + BALANCE + POP + }); + } + code_b.append(&bytecode! { + PUSH20(address.to_word()) + BALANCE + STOP + }); + + // code A calls code B. + let pushdata = rand_bytes(8); + let code_a = bytecode! { + // populate memory in A's context. + PUSH8(Word::from_big_endian(&pushdata)) + PUSH1(0x00) // offset + MSTORE + // call ADDR_B. + PUSH1(0x00) // retLength + PUSH1(0x00) // retOffset + PUSH32(call_data_length) // argsLength + PUSH32(call_data_offset) // argsOffset + PUSH1(0x00) // value + PUSH32(addr_b.to_word()) // addr + PUSH32(0x1_0000) // gas + CALL + STOP + }; + + let ctx = TestContext::<4, 1>::new( + None, + |accs| { + accs[0].address(addr_b).code(code_b); + accs[1].address(addr_a).code(code_a); + accs[2].address(address); + if let Some(account) = account { + accs[2].balance(account.balance); + } + accs[3] + .address(mock::MOCK_ACCOUNTS[2]) + .balance(Word::from(1_u64 << 20)); + }, + |mut txs, accs| { + txs[0].to(accs[1].address).from(accs[3].address); + }, + |block, _tx| block, + ) + .unwrap(); + + assert_eq!(run_test_circuits(ctx, None), Ok(())); + } +} diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs new file mode 100644 index 0000000000..fd238be491 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -0,0 +1,1388 @@ +#![allow(missing_docs)] +use crate::{ + evm_circuit::{ + param::{N_BYTES_WORD, STACK_CAPACITY}, + step::ExecutionState, + util::RandomLinearCombination, + }, + table::{ + AccountFieldTag, BlockContextFieldTag, BytecodeFieldTag, CallContextFieldTag, RwTableTag, + TxContextFieldTag, TxLogFieldTag, TxReceiptFieldTag, + }, +}; + +use bus_mapping::{ + circuit_input_builder::{self, CopyEvent}, + error::{ExecError, OogError}, + operation::{self, AccountField, CallContextField, TxLogField, TxReceiptField}, +}; + +use eth_types::{evm_types::OpcodeId, ToWord}; +use eth_types::{Address, Field, ToLittleEndian, ToScalar, Word}; +use eth_types::{ToAddress, U256}; +use halo2_proofs::arithmetic::{BaseExt, FieldExt}; +use halo2_proofs::pairing::bn256::Fr; +use itertools::Itertools; +use sha3::{Digest, Keccak256}; +use std::{collections::HashMap, iter}; + +#[derive(Debug, Default, Clone)] +pub struct Block { + /// The randomness for random linear combination + pub randomness: F, + /// Transactions in the block + pub txs: Vec, + /// Read write events in the RwTable + pub rws: RwMap, + /// Bytecode used in the block + pub bytecodes: HashMap, + /// The block context + pub context: BlockContext, + /// Copy events for the EVM circuit's copy table. + pub copy_events: Vec, +} + +#[derive(Debug, Default, Clone)] +pub struct BlockContext { + /// The address of the miner for the block + pub coinbase: Address, + /// The gas limit of the block + pub gas_limit: u64, + /// The number of the block + pub number: Word, + /// The timestamp of the block + pub timestamp: Word, + /// The difficulty of the blcok + pub difficulty: Word, + /// The base fee, the minimum amount of gas fee for a transaction + pub base_fee: Word, + /// The hash of previous blocks + pub history_hashes: Vec, + /// The chain id + pub chain_id: Word, +} + +impl BlockContext { + pub fn table_assignments(&self, randomness: F) -> Vec<[F; 3]> { + [ + vec![ + [ + F::from(BlockContextFieldTag::Coinbase as u64), + F::zero(), + self.coinbase.to_scalar().unwrap(), + ], + [ + F::from(BlockContextFieldTag::Timestamp as u64), + F::zero(), + self.timestamp.to_scalar().unwrap(), + ], + [ + F::from(BlockContextFieldTag::Number as u64), + F::zero(), + self.number.to_scalar().unwrap(), + ], + [ + F::from(BlockContextFieldTag::Difficulty as u64), + F::zero(), + RandomLinearCombination::random_linear_combine( + self.difficulty.to_le_bytes(), + randomness, + ), + ], + [ + F::from(BlockContextFieldTag::GasLimit as u64), + F::zero(), + F::from(self.gas_limit), + ], + [ + F::from(BlockContextFieldTag::BaseFee as u64), + F::zero(), + RandomLinearCombination::random_linear_combine( + self.base_fee.to_le_bytes(), + randomness, + ), + ], + [ + F::from(BlockContextFieldTag::ChainId as u64), + F::zero(), + RandomLinearCombination::random_linear_combine( + self.chain_id.to_le_bytes(), + randomness, + ), + ], + ], + self.history_hashes + .iter() + .enumerate() + .map(|(idx, hash)| { + [ + F::from(BlockContextFieldTag::BlockHash as u64), + (self.number - idx - 1).to_scalar().unwrap(), + RandomLinearCombination::random_linear_combine( + hash.to_le_bytes(), + randomness, + ), + ] + }) + .collect(), + ] + .concat() + } +} + +#[derive(Debug, Default, Clone)] +pub struct Transaction { + /// The transaction identifier in the block + pub id: usize, + /// The sender account nonce of the transaction + pub nonce: u64, + /// The gas limit of the transaction + pub gas: u64, + /// The gas price + pub gas_price: Word, + /// The caller address + pub caller_address: Address, + /// The callee address + pub callee_address: Address, + /// Whether it's a create transaction + pub is_create: bool, + /// The ether amount of the transaction + pub value: Word, + /// The call data + pub call_data: Vec, + /// The call data length + pub call_data_length: usize, + /// The gas cost for transaction call data + pub call_data_gas_cost: u64, + /// The calls made in the transaction + pub calls: Vec, + /// The steps executioned in the transaction + pub steps: Vec, +} + +impl Transaction { + pub fn table_assignments(&self, randomness: F) -> Vec<[F; 4]> { + [ + vec![ + [ + F::from(self.id as u64), + F::from(TxContextFieldTag::Nonce as u64), + F::zero(), + F::from(self.nonce), + ], + [ + F::from(self.id as u64), + F::from(TxContextFieldTag::Gas as u64), + F::zero(), + F::from(self.gas), + ], + [ + F::from(self.id as u64), + F::from(TxContextFieldTag::GasPrice as u64), + F::zero(), + RandomLinearCombination::random_linear_combine( + self.gas_price.to_le_bytes(), + randomness, + ), + ], + [ + F::from(self.id as u64), + F::from(TxContextFieldTag::CallerAddress as u64), + F::zero(), + self.caller_address.to_scalar().unwrap(), + ], + [ + F::from(self.id as u64), + F::from(TxContextFieldTag::CalleeAddress as u64), + F::zero(), + self.callee_address.to_scalar().unwrap(), + ], + [ + F::from(self.id as u64), + F::from(TxContextFieldTag::IsCreate as u64), + F::zero(), + F::from(self.is_create as u64), + ], + [ + F::from(self.id as u64), + F::from(TxContextFieldTag::Value as u64), + F::zero(), + RandomLinearCombination::random_linear_combine( + self.value.to_le_bytes(), + randomness, + ), + ], + [ + F::from(self.id as u64), + F::from(TxContextFieldTag::CallDataLength as u64), + F::zero(), + F::from(self.call_data_length as u64), + ], + [ + F::from(self.id as u64), + F::from(TxContextFieldTag::CallDataGasCost as u64), + F::zero(), + F::from(self.call_data_gas_cost), + ], + ], + self.call_data + .iter() + .enumerate() + .map(|(idx, byte)| { + [ + F::from(self.id as u64), + F::from(TxContextFieldTag::CallData as u64), + F::from(idx as u64), + F::from(*byte as u64), + ] + }) + .collect(), + ] + .concat() + } +} + +#[derive(Debug, Default, Clone)] +pub struct Call { + /// The unique identifier of call in the whole proof, using the + /// `rw_counter` at the call step. + pub id: usize, + /// Indicate if the call is the root call + pub is_root: bool, + /// Indicate if the call is a create call + pub is_create: bool, + /// The identifier of current executed bytecode + pub code_hash: Word, + /// The `rw_counter` at the end of reversion of a call if it has + /// `is_persistent == false` + pub rw_counter_end_of_reversion: usize, + /// The call index of caller + pub caller_id: usize, + /// The depth in the call stack + pub depth: usize, + /// The caller address + pub caller_address: Address, + /// The callee address + pub callee_address: Address, + /// The call data offset in the memory + pub call_data_offset: u64, + /// The length of call data + pub call_data_length: u64, + /// The return data offset in the memory + pub return_data_offset: u64, + /// The length of return data + pub return_data_length: u64, + /// The ether amount of the transaction + pub value: Word, + /// Indicate if this call halts successfully. + pub is_success: bool, + /// Indicate if this call and all its caller have `is_success == true` + pub is_persistent: bool, + /// Indicate if it's a static call + pub is_static: bool, +} + +#[derive(Clone, Debug, Default)] +pub struct ExecStep { + /// The index in the Transaction calls + pub call_index: usize, + /// The indices in the RW trace incurred in this step + pub rw_indices: Vec<(RwTableTag, usize)>, + /// The execution state for the step + pub execution_state: ExecutionState, + /// The Read/Write counter before the step + pub rw_counter: usize, + /// The program counter + pub program_counter: u64, + /// The stack pointer + pub stack_pointer: usize, + /// The amount of gas left + pub gas_left: u64, + /// The gas cost in this step + pub gas_cost: u64, + /// The memory size in bytes + pub memory_size: u64, + /// The counter for reversible writes + pub reversible_write_counter: usize, + /// The counter for log index within tx + pub log_id: usize, + /// The opcode corresponds to the step + pub opcode: Option, +} + +impl ExecStep { + pub fn memory_word_size(&self) -> u64 { + // EVM always pads the memory size to word size + // https://github.com/ethereum/go-ethereum/blob/master/core/vm/interpreter.go#L212-L216 + // Thus, the memory size must be a multiple of 32 bytes. + assert_eq!(self.memory_size % N_BYTES_WORD as u64, 0); + self.memory_size / N_BYTES_WORD as u64 + } +} + +#[derive(Clone, Debug)] +pub struct Bytecode { + pub hash: Word, + pub bytes: Vec, +} + +impl Bytecode { + pub fn new(bytes: Vec) -> Self { + let hash = Word::from_big_endian(Keccak256::digest(&bytes).as_slice()); + Self { hash, bytes } + } + + pub fn table_assignments(&self, randomness: F) -> Vec<[F; 5]> { + let n = 1 + self.bytes.len(); + let mut rows = Vec::with_capacity(n); + let hash = + RandomLinearCombination::random_linear_combine(self.hash.to_le_bytes(), randomness); + + rows.push([ + hash, + F::from(BytecodeFieldTag::Length as u64), + F::zero(), + F::zero(), + F::from(self.bytes.len() as u64), + ]); + + let mut push_data_left = 0; + for (idx, byte) in self.bytes.iter().enumerate() { + let mut is_code = true; + if push_data_left > 0 { + is_code = false; + push_data_left -= 1; + } else if (OpcodeId::PUSH1.as_u8()..=OpcodeId::PUSH32.as_u8()).contains(byte) { + push_data_left = *byte as usize - (OpcodeId::PUSH1.as_u8() - 1) as usize; + } + rows.push([ + hash, + F::from(BytecodeFieldTag::Byte as u64), + F::from(idx as u64), + F::from(is_code as u64), + F::from(*byte as u64), + ]) + } + rows + } +} + +#[derive(Debug, Default, Clone)] +pub struct RwMap(pub HashMap>); + +impl std::ops::Index<(RwTableTag, usize)> for RwMap { + type Output = Rw; + + fn index(&self, (tag, idx): (RwTableTag, usize)) -> &Self::Output { + &self.0.get(&tag).unwrap()[idx] + } +} + +#[derive(Clone, Copy, Debug)] +pub enum Rw { + Start { + rw_counter: usize, + }, + TxAccessListAccount { + rw_counter: usize, + is_write: bool, + tx_id: usize, + account_address: Address, + is_warm: bool, + is_warm_prev: bool, + }, + TxAccessListAccountStorage { + rw_counter: usize, + is_write: bool, + tx_id: usize, + account_address: Address, + storage_key: Word, + is_warm: bool, + is_warm_prev: bool, + }, + TxRefund { + rw_counter: usize, + is_write: bool, + tx_id: usize, + value: u64, + value_prev: u64, + }, + Account { + rw_counter: usize, + is_write: bool, + account_address: Address, + field_tag: AccountFieldTag, + value: Word, + value_prev: Word, + }, + AccountStorage { + rw_counter: usize, + is_write: bool, + account_address: Address, + storage_key: Word, + value: Word, + value_prev: Word, + tx_id: usize, + committed_value: Word, + }, + AccountDestructed { + rw_counter: usize, + is_write: bool, + tx_id: usize, + account_address: Address, + is_destructed: bool, + is_destructed_prev: bool, + }, + CallContext { + rw_counter: usize, + is_write: bool, + call_id: usize, + field_tag: CallContextFieldTag, + value: Word, + }, + Stack { + rw_counter: usize, + is_write: bool, + call_id: usize, + stack_pointer: usize, + value: Word, + }, + Memory { + rw_counter: usize, + is_write: bool, + call_id: usize, + memory_address: u64, + byte: u8, + }, + TxLog { + rw_counter: usize, + is_write: bool, + tx_id: usize, + log_id: u64, // pack this can index together into address? + field_tag: TxLogFieldTag, + // topic index (0..4) if field_tag is TxLogFieldTag:Topic + // byte index if field_tag is TxLogFieldTag:Data + // 0 for other field tags + index: usize, + + // when it is topic field, value can be word type + value: Word, + }, + TxReceipt { + rw_counter: usize, + is_write: bool, + tx_id: usize, + field_tag: TxReceiptFieldTag, + value: u64, + }, +} +#[derive(Default, Clone, Copy)] +pub struct RwRow { + pub rw_counter: F, + pub is_write: F, + pub tag: F, + pub key1: F, + pub key2: F, + pub key3: F, + pub key4: F, + pub value: F, + pub value_prev: F, + pub aux1: F, + pub aux2: F, +} + +impl From<[F; 11]> for RwRow { + fn from(row: [F; 11]) -> Self { + Self { + rw_counter: row[0], + is_write: row[1], + tag: row[2], + key1: row[3], + key2: row[4], + key3: row[5], + key4: row[6], + value: row[7], + value_prev: row[8], + aux1: row[9], + aux2: row[10], + } + } +} + +impl Rw { + pub fn tx_access_list_value_pair(&self) -> (bool, bool) { + match self { + Self::TxAccessListAccount { + is_warm, + is_warm_prev, + .. + } => (*is_warm, *is_warm_prev), + Self::TxAccessListAccountStorage { + is_warm, + is_warm_prev, + .. + } => (*is_warm, *is_warm_prev), + _ => unreachable!(), + } + } + + pub fn tx_refund_value_pair(&self) -> (u64, u64) { + match self { + Self::TxRefund { + value, value_prev, .. + } => (*value, *value_prev), + _ => unreachable!(), + } + } + + pub fn account_value_pair(&self) -> (Word, Word) { + match self { + Self::Account { + value, value_prev, .. + } => (*value, *value_prev), + _ => unreachable!(), + } + } + + pub fn aux_pair(&self) -> (usize, Word) { + match self { + Self::AccountStorage { + tx_id, + committed_value, + .. + } => (*tx_id, *committed_value), + _ => unreachable!(), + } + } + + pub fn storage_value_aux(&self) -> (Word, Word, usize, Word) { + match self { + Self::AccountStorage { + value, + value_prev, + tx_id, + committed_value, + .. + } => (*value, *value_prev, *tx_id, *committed_value), + _ => unreachable!(), + } + } + + pub fn call_context_value(&self) -> Word { + match self { + Self::CallContext { value, .. } => *value, + _ => unreachable!(), + } + } + + pub fn stack_value(&self) -> Word { + match self { + Self::Stack { value, .. } => *value, + _ => unreachable!(), + } + } + + pub fn log_value(&self) -> Word { + match self { + Self::TxLog { value, .. } => *value, + _ => unreachable!(), + } + } + + pub fn receipt_value(&self) -> u64 { + match self { + Self::TxReceipt { value, .. } => *value, + _ => unreachable!(), + } + } + + pub fn memory_value(&self) -> u8 { + match self { + Self::Memory { byte, .. } => *byte, + _ => unreachable!(), + } + } + + pub fn table_assignment(&self, randomness: F) -> RwRow { + RwRow { + rw_counter: F::from(self.rw_counter() as u64), + is_write: F::from(self.is_write() as u64), + tag: F::from(self.tag() as u64), + key1: F::from(self.id().unwrap_or_default() as u64), + key2: self.address().unwrap_or_default().to_scalar().unwrap(), + key3: F::from(self.field_tag().unwrap_or_default() as u64), + key4: RandomLinearCombination::random_linear_combine( + self.storage_key().unwrap_or_default().to_le_bytes(), + randomness, + ), + value: self.value_assignment(randomness), + value_prev: self.value_prev_assignment(randomness).unwrap_or_default(), + aux1: F::zero(), // only used for AccountStorage::tx_id, which moved to key1. + aux2: self + .committed_value_assignment(randomness) + .unwrap_or_default(), + } + } + + pub fn rw_counter(&self) -> usize { + match self { + Self::Start { rw_counter } + | Self::Memory { rw_counter, .. } + | Self::Stack { rw_counter, .. } + | Self::AccountStorage { rw_counter, .. } + | Self::TxAccessListAccount { rw_counter, .. } + | Self::TxAccessListAccountStorage { rw_counter, .. } + | Self::TxRefund { rw_counter, .. } + | Self::Account { rw_counter, .. } + | Self::AccountDestructed { rw_counter, .. } + | Self::CallContext { rw_counter, .. } + | Self::TxLog { rw_counter, .. } + | Self::TxReceipt { rw_counter, .. } => *rw_counter, + } + } + + pub fn is_write(&self) -> bool { + match self { + Self::Start { .. } => false, + Self::Memory { is_write, .. } + | Self::Stack { is_write, .. } + | Self::AccountStorage { is_write, .. } + | Self::TxAccessListAccount { is_write, .. } + | Self::TxAccessListAccountStorage { is_write, .. } + | Self::TxRefund { is_write, .. } + | Self::Account { is_write, .. } + | Self::AccountDestructed { is_write, .. } + | Self::CallContext { is_write, .. } + | Self::TxLog { is_write, .. } + | Self::TxReceipt { is_write, .. } => *is_write, + } + } + + pub fn tag(&self) -> RwTableTag { + match self { + Self::Start { .. } => RwTableTag::Start, + Self::Memory { .. } => RwTableTag::Memory, + Self::Stack { .. } => RwTableTag::Stack, + Self::AccountStorage { .. } => RwTableTag::AccountStorage, + Self::TxAccessListAccount { .. } => RwTableTag::TxAccessListAccount, + Self::TxAccessListAccountStorage { .. } => RwTableTag::TxAccessListAccountStorage, + Self::TxRefund { .. } => RwTableTag::TxRefund, + Self::Account { .. } => RwTableTag::Account, + Self::AccountDestructed { .. } => RwTableTag::AccountDestructed, + Self::CallContext { .. } => RwTableTag::CallContext, + Self::TxLog { .. } => RwTableTag::TxLog, + Self::TxReceipt { .. } => RwTableTag::TxReceipt, + } + } + + pub fn id(&self) -> Option { + match self { + Self::AccountStorage { tx_id, .. } + | Self::TxAccessListAccount { tx_id, .. } + | Self::TxAccessListAccountStorage { tx_id, .. } + | Self::TxRefund { tx_id, .. } + | Self::TxLog { tx_id, .. } + | Self::TxReceipt { tx_id, .. } => Some(*tx_id), + Self::CallContext { call_id, .. } + | Self::Stack { call_id, .. } + | Self::Memory { call_id, .. } => Some(*call_id), + Self::Start { .. } | Self::Account { .. } | Self::AccountDestructed { .. } => None, + } + } + + pub fn address(&self) -> Option
{ + match self { + Self::TxAccessListAccount { + account_address, .. + } + | Self::TxAccessListAccountStorage { + account_address, .. + } + | Self::Account { + account_address, .. + } + | Self::AccountStorage { + account_address, .. + } + | Self::AccountDestructed { + account_address, .. + } => Some(*account_address), + Self::Memory { memory_address, .. } => Some(U256::from(*memory_address).to_address()), + Self::Stack { stack_pointer, .. } => { + Some(U256::from(*stack_pointer as u64).to_address()) + } + Self::TxLog { + log_id, + field_tag, + index, + .. + } => { + // make field_tag fit into one limb (16 bits) + Some( + (U256::from(*index as u64) + + (U256::from(*field_tag as u64) << 32) + + (U256::from(*log_id) << 48)) + .to_address(), + ) + } + Self::Start { .. } + | Self::CallContext { .. } + | Self::TxRefund { .. } + | Self::TxReceipt { .. } => None, + } + } + + pub fn field_tag(&self) -> Option { + match self { + Self::Account { field_tag, .. } => Some(*field_tag as u64), + Self::CallContext { field_tag, .. } => Some(*field_tag as u64), + Self::TxReceipt { field_tag, .. } => Some(*field_tag as u64), + Self::Start { .. } + | Self::Memory { .. } + | Self::Stack { .. } + | Self::AccountStorage { .. } + | Self::TxAccessListAccount { .. } + | Self::TxAccessListAccountStorage { .. } + | Self::TxRefund { .. } + | Self::TxLog { .. } + | Self::AccountDestructed { .. } => None, + } + } + + pub fn storage_key(&self) -> Option { + match self { + Self::AccountStorage { storage_key, .. } + | Self::TxAccessListAccountStorage { storage_key, .. } => Some(*storage_key), + Self::Start { .. } + | Self::CallContext { .. } + | Self::Stack { .. } + | Self::Memory { .. } + | Self::TxRefund { .. } + | Self::Account { .. } + | Self::TxAccessListAccount { .. } + | Self::AccountDestructed { .. } + | Self::TxLog { .. } + | Self::TxReceipt { .. } => None, + } + } + + pub fn value_assignment(&self, randomness: F) -> F { + match self { + Self::Start { .. } => F::zero(), + Self::CallContext { + field_tag, value, .. + } => { + match field_tag { + // Only these two tags have values that may not fit into a scalar, so we need to + // RLC. + CallContextFieldTag::CodeHash | CallContextFieldTag::Value => { + RandomLinearCombination::random_linear_combine( + value.to_le_bytes(), + randomness, + ) + } + _ => value.to_scalar().unwrap(), + } + } + Self::Account { + value, field_tag, .. + } => match field_tag { + AccountFieldTag::CodeHash | AccountFieldTag::Balance => { + RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness) + } + AccountFieldTag::Nonce => value.to_scalar().unwrap(), + }, + Self::AccountStorage { value, .. } | Self::Stack { value, .. } => { + RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness) + } + + Self::TxLog { + field_tag, value, .. + } => match field_tag { + TxLogFieldTag::Topic => { + RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness) + } + _ => value.to_scalar().unwrap(), + }, + + Self::TxAccessListAccount { is_warm, .. } + | Self::TxAccessListAccountStorage { is_warm, .. } => F::from(*is_warm as u64), + Self::AccountDestructed { is_destructed, .. } => F::from(*is_destructed as u64), + Self::Memory { byte, .. } => F::from(u64::from(*byte)), + Self::TxRefund { value, .. } | Self::TxReceipt { value, .. } => F::from(*value), + } + } + + pub fn value_prev_assignment(&self, randomness: F) -> Option { + match self { + Self::Account { + value_prev, + field_tag, + .. + } => Some(match field_tag { + AccountFieldTag::CodeHash | AccountFieldTag::Balance => { + RandomLinearCombination::random_linear_combine( + value_prev.to_le_bytes(), + randomness, + ) + } + AccountFieldTag::Nonce => value_prev.to_scalar().unwrap(), + }), + Self::AccountStorage { value_prev, .. } => { + Some(RandomLinearCombination::random_linear_combine( + value_prev.to_le_bytes(), + randomness, + )) + } + Self::TxAccessListAccount { is_warm_prev, .. } + | Self::TxAccessListAccountStorage { is_warm_prev, .. } => { + Some(F::from(*is_warm_prev as u64)) + } + Self::AccountDestructed { + is_destructed_prev, .. + } => Some(F::from(*is_destructed_prev as u64)), + Self::TxRefund { value_prev, .. } => Some(F::from(*value_prev)), + Self::Start { .. } + | Self::Stack { .. } + | Self::Memory { .. } + | Self::CallContext { .. } + | Self::TxLog { .. } + | Self::TxReceipt { .. } => None, + } + } + + fn committed_value_assignment(&self, randomness: F) -> Option { + match self { + Self::AccountStorage { + committed_value, .. + } => Some(RandomLinearCombination::random_linear_combine( + committed_value.to_le_bytes(), + randomness, + )), + _ => None, + } + } +} + +impl From<&circuit_input_builder::Block> for BlockContext { + fn from(block: &circuit_input_builder::Block) -> Self { + Self { + coinbase: block.coinbase, + gas_limit: block.gas_limit, + number: block.number, + timestamp: block.timestamp, + difficulty: block.difficulty, + base_fee: block.base_fee, + history_hashes: block.history_hashes.clone(), + chain_id: block.chain_id, + } + } +} + +impl From<&operation::OperationContainer> for RwMap { + fn from(container: &operation::OperationContainer) -> Self { + let mut rws = HashMap::default(); + + rws.insert( + RwTableTag::TxAccessListAccount, + container + .tx_access_list_account + .iter() + .map(|op| Rw::TxAccessListAccount { + rw_counter: op.rwc().into(), + is_write: true, + tx_id: op.op().tx_id, + account_address: op.op().address, + is_warm: op.op().is_warm, + is_warm_prev: op.op().is_warm_prev, + }) + .collect(), + ); + rws.insert( + RwTableTag::TxAccessListAccountStorage, + container + .tx_access_list_account_storage + .iter() + .map(|op| Rw::TxAccessListAccountStorage { + rw_counter: op.rwc().into(), + is_write: true, + tx_id: op.op().tx_id, + account_address: op.op().address, + storage_key: op.op().key, + is_warm: op.op().is_warm, + is_warm_prev: op.op().is_warm_prev, + }) + .collect(), + ); + rws.insert( + RwTableTag::TxRefund, + container + .tx_refund + .iter() + .map(|op| Rw::TxRefund { + rw_counter: op.rwc().into(), + is_write: op.rw().is_write(), + tx_id: op.op().tx_id, + value: op.op().value, + value_prev: op.op().value_prev, + }) + .collect(), + ); + rws.insert( + RwTableTag::Account, + container + .account + .iter() + .map(|op| Rw::Account { + rw_counter: op.rwc().into(), + is_write: op.rw().is_write(), + account_address: op.op().address, + field_tag: match op.op().field { + AccountField::Nonce => AccountFieldTag::Nonce, + AccountField::Balance => AccountFieldTag::Balance, + AccountField::CodeHash => AccountFieldTag::CodeHash, + }, + value: op.op().value, + value_prev: op.op().value_prev, + }) + .collect(), + ); + rws.insert( + RwTableTag::AccountStorage, + container + .storage + .iter() + .map(|op| Rw::AccountStorage { + rw_counter: op.rwc().into(), + is_write: op.rw().is_write(), + account_address: op.op().address, + storage_key: op.op().key, + value: op.op().value, + value_prev: op.op().value_prev, + tx_id: op.op().tx_id, + committed_value: op.op().committed_value, + }) + .collect(), + ); + rws.insert( + RwTableTag::AccountDestructed, + container + .account_destructed + .iter() + .map(|op| Rw::AccountDestructed { + rw_counter: op.rwc().into(), + is_write: true, + tx_id: op.op().tx_id, + account_address: op.op().address, + is_destructed: op.op().is_destructed, + is_destructed_prev: op.op().is_destructed_prev, + }) + .collect(), + ); + rws.insert( + RwTableTag::CallContext, + container + .call_context + .iter() + .map(|op| Rw::CallContext { + rw_counter: op.rwc().into(), + is_write: op.rw().is_write(), + call_id: op.op().call_id, + field_tag: match op.op().field { + CallContextField::RwCounterEndOfReversion => { + CallContextFieldTag::RwCounterEndOfReversion + } + CallContextField::CallerId => CallContextFieldTag::CallerId, + CallContextField::TxId => CallContextFieldTag::TxId, + CallContextField::Depth => CallContextFieldTag::Depth, + CallContextField::CallerAddress => CallContextFieldTag::CallerAddress, + CallContextField::CalleeAddress => CallContextFieldTag::CalleeAddress, + CallContextField::CallDataOffset => CallContextFieldTag::CallDataOffset, + CallContextField::CallDataLength => CallContextFieldTag::CallDataLength, + CallContextField::ReturnDataOffset => CallContextFieldTag::ReturnDataOffset, + CallContextField::ReturnDataLength => CallContextFieldTag::ReturnDataLength, + CallContextField::Value => CallContextFieldTag::Value, + CallContextField::IsSuccess => CallContextFieldTag::IsSuccess, + CallContextField::IsPersistent => CallContextFieldTag::IsPersistent, + CallContextField::IsStatic => CallContextFieldTag::IsStatic, + CallContextField::LastCalleeId => CallContextFieldTag::LastCalleeId, + CallContextField::LastCalleeReturnDataOffset => { + CallContextFieldTag::LastCalleeReturnDataOffset + } + CallContextField::LastCalleeReturnDataLength => { + CallContextFieldTag::LastCalleeReturnDataLength + } + CallContextField::IsRoot => CallContextFieldTag::IsRoot, + CallContextField::IsCreate => CallContextFieldTag::IsCreate, + CallContextField::CodeHash => CallContextFieldTag::CodeHash, + CallContextField::ProgramCounter => CallContextFieldTag::ProgramCounter, + CallContextField::StackPointer => CallContextFieldTag::StackPointer, + CallContextField::GasLeft => CallContextFieldTag::GasLeft, + CallContextField::MemorySize => CallContextFieldTag::MemorySize, + CallContextField::ReversibleWriteCounter => { + CallContextFieldTag::ReversibleWriteCounter + } + }, + value: op.op().value, + }) + .collect(), + ); + rws.insert( + RwTableTag::Stack, + container + .stack + .iter() + .map(|op| Rw::Stack { + rw_counter: op.rwc().into(), + is_write: op.rw().is_write(), + call_id: op.op().call_id(), + stack_pointer: usize::from(*op.op().address()), + value: *op.op().value(), + }) + .collect(), + ); + rws.insert( + RwTableTag::Memory, + container + .memory + .iter() + .map(|op| Rw::Memory { + rw_counter: op.rwc().into(), + is_write: op.rw().is_write(), + call_id: op.op().call_id(), + memory_address: u64::from_le_bytes( + op.op().address().to_le_bytes()[..8].try_into().unwrap(), + ), + byte: op.op().value(), + }) + .collect(), + ); + rws.insert( + RwTableTag::TxLog, + container + .tx_log + .iter() + .map(|op| Rw::TxLog { + rw_counter: op.rwc().into(), + is_write: op.rw().is_write(), + tx_id: op.op().tx_id, + log_id: op.op().log_id as u64, + field_tag: match op.op().field { + TxLogField::Address => TxLogFieldTag::Address, + TxLogField::Topic => TxLogFieldTag::Topic, + TxLogField::Data => TxLogFieldTag::Data, + }, + index: op.op().index, + value: op.op().value, + }) + .collect(), + ); + rws.insert( + RwTableTag::TxReceipt, + container + .tx_receipt + .iter() + .map(|op| Rw::TxReceipt { + rw_counter: op.rwc().into(), + is_write: op.rw().is_write(), + tx_id: op.op().tx_id, + field_tag: match op.op().field { + TxReceiptField::PostStateOrStatus => TxReceiptFieldTag::PostStateOrStatus, + TxReceiptField::LogLength => TxReceiptFieldTag::LogLength, + TxReceiptField::CumulativeGasUsed => TxReceiptFieldTag::CumulativeGasUsed, + }, + value: op.op().value, + }) + .collect(), + ); + + Self(rws) + } +} + +impl From<&ExecError> for ExecutionState { + fn from(error: &ExecError) -> Self { + match error { + ExecError::InvalidOpcode => ExecutionState::ErrorInvalidOpcode, + ExecError::StackOverflow => ExecutionState::ErrorStackOverflow, + ExecError::StackUnderflow => ExecutionState::ErrorStackUnderflow, + ExecError::WriteProtection => ExecutionState::ErrorWriteProtection, + ExecError::Depth => ExecutionState::ErrorDepth, + ExecError::InsufficientBalance => ExecutionState::ErrorInsufficientBalance, + ExecError::ContractAddressCollision => ExecutionState::ErrorContractAddressCollision, + ExecError::InvalidCreationCode => ExecutionState::ErrorInvalidCreationCode, + ExecError::InvalidJump => ExecutionState::ErrorInvalidJump, + ExecError::ReturnDataOutOfBounds => ExecutionState::ErrorReturnDataOutOfBound, + ExecError::CodeStoreOutOfGas => ExecutionState::ErrorOutOfGasCodeStore, + ExecError::MaxCodeSizeExceeded => ExecutionState::ErrorMaxCodeSizeExceeded, + ExecError::OutOfGas(oog_error) => match oog_error { + OogError::Constant => ExecutionState::ErrorOutOfGasConstant, + OogError::StaticMemoryExpansion => { + ExecutionState::ErrorOutOfGasStaticMemoryExpansion + } + OogError::DynamicMemoryExpansion => { + ExecutionState::ErrorOutOfGasDynamicMemoryExpansion + } + OogError::MemoryCopy => ExecutionState::ErrorOutOfGasMemoryCopy, + OogError::AccountAccess => ExecutionState::ErrorOutOfGasAccountAccess, + OogError::CodeStore => ExecutionState::ErrorOutOfGasCodeStore, + OogError::Log => ExecutionState::ErrorOutOfGasLOG, + OogError::Exp => ExecutionState::ErrorOutOfGasEXP, + OogError::Sha3 => ExecutionState::ErrorOutOfGasSHA3, + OogError::ExtCodeCopy => ExecutionState::ErrorOutOfGasEXTCODECOPY, + OogError::Sload => ExecutionState::ErrorOutOfGasSLOAD, + OogError::Sstore => ExecutionState::ErrorOutOfGasSSTORE, + OogError::Call => ExecutionState::ErrorOutOfGasCALL, + OogError::CallCode => ExecutionState::ErrorOutOfGasCALLCODE, + OogError::DelegateCall => ExecutionState::ErrorOutOfGasDELEGATECALL, + OogError::Create2 => ExecutionState::ErrorOutOfGasCREATE2, + OogError::StaticCall => ExecutionState::ErrorOutOfGasSTATICCALL, + OogError::SelfDestruct => ExecutionState::ErrorOutOfGasSELFDESTRUCT, + }, + } + } +} + +impl From<&circuit_input_builder::ExecStep> for ExecutionState { + fn from(step: &circuit_input_builder::ExecStep) -> Self { + if let Some(error) = step.error.as_ref() { + return error.into(); + } + match step.exec_state { + circuit_input_builder::ExecState::Op(op) => { + if op.is_dup() { + return ExecutionState::DUP; + } + if op.is_push() { + return ExecutionState::PUSH; + } + if op.is_swap() { + return ExecutionState::SWAP; + } + if op.is_log() { + return ExecutionState::LOG; + } + + macro_rules! dummy { + ($name:expr) => {{ + log::warn!("{:?} is implemented with DummyGadget", $name); + $name + }}; + } + + match op { + OpcodeId::ADD | OpcodeId::SUB => ExecutionState::ADD_SUB, + OpcodeId::ADDMOD => ExecutionState::ADDMOD, + OpcodeId::BALANCE => ExecutionState::BALANCE, + OpcodeId::MUL | OpcodeId::DIV | OpcodeId::MOD => ExecutionState::MUL_DIV_MOD, + OpcodeId::MULMOD => ExecutionState::MULMOD, + OpcodeId::SDIV | OpcodeId::SMOD => ExecutionState::SDIV_SMOD, + OpcodeId::EQ | OpcodeId::LT | OpcodeId::GT => ExecutionState::CMP, + OpcodeId::SLT | OpcodeId::SGT => ExecutionState::SCMP, + OpcodeId::SIGNEXTEND => ExecutionState::SIGNEXTEND, + OpcodeId::STOP => ExecutionState::STOP, + OpcodeId::AND => ExecutionState::BITWISE, + OpcodeId::XOR => ExecutionState::BITWISE, + OpcodeId::OR => ExecutionState::BITWISE, + OpcodeId::NOT => ExecutionState::NOT, + OpcodeId::POP => ExecutionState::POP, + OpcodeId::PUSH32 => ExecutionState::PUSH, + OpcodeId::BYTE => ExecutionState::BYTE, + OpcodeId::MLOAD => ExecutionState::MEMORY, + OpcodeId::MSTORE => ExecutionState::MEMORY, + OpcodeId::MSTORE8 => ExecutionState::MEMORY, + OpcodeId::JUMPDEST => ExecutionState::JUMPDEST, + OpcodeId::JUMP => ExecutionState::JUMP, + OpcodeId::JUMPI => ExecutionState::JUMPI, + OpcodeId::GASPRICE => ExecutionState::GASPRICE, + OpcodeId::PC => ExecutionState::PC, + OpcodeId::MSIZE => ExecutionState::MSIZE, + OpcodeId::CALLER => ExecutionState::CALLER, + OpcodeId::CALLVALUE => ExecutionState::CALLVALUE, + OpcodeId::EXTCODEHASH => ExecutionState::EXTCODEHASH, + OpcodeId::TIMESTAMP | OpcodeId::NUMBER | OpcodeId::GASLIMIT => { + ExecutionState::BLOCKCTXU64 + } + OpcodeId::COINBASE => ExecutionState::BLOCKCTXU160, + OpcodeId::DIFFICULTY | OpcodeId::BASEFEE => ExecutionState::BLOCKCTXU256, + OpcodeId::GAS => ExecutionState::GAS, + OpcodeId::SELFBALANCE => ExecutionState::SELFBALANCE, + OpcodeId::SHA3 => ExecutionState::SHA3, + OpcodeId::SHR => ExecutionState::SHR, + OpcodeId::SLOAD => ExecutionState::SLOAD, + OpcodeId::SSTORE => ExecutionState::SSTORE, + OpcodeId::CALLDATASIZE => ExecutionState::CALLDATASIZE, + OpcodeId::CALLDATACOPY => ExecutionState::CALLDATACOPY, + OpcodeId::CHAINID => ExecutionState::CHAINID, + OpcodeId::ISZERO => ExecutionState::ISZERO, + OpcodeId::CALL => ExecutionState::CALL, + OpcodeId::ORIGIN => ExecutionState::ORIGIN, + OpcodeId::CODECOPY => ExecutionState::CODECOPY, + OpcodeId::CALLDATALOAD => ExecutionState::CALLDATALOAD, + OpcodeId::CODESIZE => ExecutionState::CODESIZE, + OpcodeId::RETURN | OpcodeId::REVERT => ExecutionState::RETURN, + // dummy ops + OpcodeId::ADDRESS => dummy!(ExecutionState::ADDRESS), + OpcodeId::BLOCKHASH => dummy!(ExecutionState::BLOCKHASH), + OpcodeId::EXP => dummy!(ExecutionState::EXP), + OpcodeId::SHL => dummy!(ExecutionState::SHL), + OpcodeId::SAR => dummy!(ExecutionState::SAR), + OpcodeId::EXTCODESIZE => dummy!(ExecutionState::EXTCODESIZE), + OpcodeId::EXTCODECOPY => dummy!(ExecutionState::EXTCODECOPY), + OpcodeId::RETURNDATASIZE => dummy!(ExecutionState::RETURNDATASIZE), + OpcodeId::RETURNDATACOPY => dummy!(ExecutionState::RETURNDATACOPY), + OpcodeId::CREATE => dummy!(ExecutionState::CREATE), + OpcodeId::CALLCODE => dummy!(ExecutionState::CALLCODE), + OpcodeId::DELEGATECALL => dummy!(ExecutionState::DELEGATECALL), + OpcodeId::CREATE2 => dummy!(ExecutionState::CREATE2), + OpcodeId::STATICCALL => dummy!(ExecutionState::STATICCALL), + OpcodeId::SELFDESTRUCT => dummy!(ExecutionState::SELFDESTRUCT), + _ => unimplemented!("unimplemented opcode {:?}", op), + } + } + circuit_input_builder::ExecState::BeginTx => ExecutionState::BeginTx, + circuit_input_builder::ExecState::EndTx => ExecutionState::EndTx, + } + } +} + +impl From<ð_types::bytecode::Bytecode> for Bytecode { + fn from(b: ð_types::bytecode::Bytecode) -> Self { + Bytecode::new(b.to_vec()) + } +} + +fn step_convert(step: &circuit_input_builder::ExecStep) -> ExecStep { + ExecStep { + call_index: step.call_index, + rw_indices: step + .bus_mapping_instance + .iter() + .map(|x| { + let tag = match x.target() { + operation::Target::Memory => RwTableTag::Memory, + operation::Target::Stack => RwTableTag::Stack, + operation::Target::Storage => RwTableTag::AccountStorage, + operation::Target::TxAccessListAccount => RwTableTag::TxAccessListAccount, + operation::Target::TxAccessListAccountStorage => { + RwTableTag::TxAccessListAccountStorage + } + operation::Target::TxRefund => RwTableTag::TxRefund, + operation::Target::Account => RwTableTag::Account, + operation::Target::AccountDestructed => RwTableTag::AccountDestructed, + operation::Target::CallContext => RwTableTag::CallContext, + operation::Target::TxReceipt => RwTableTag::TxReceipt, + operation::Target::TxLog => RwTableTag::TxLog, + }; + (tag, x.as_usize()) + }) + .collect(), + execution_state: ExecutionState::from(step), + rw_counter: usize::from(step.rwc), + program_counter: usize::from(step.pc) as u64, + stack_pointer: STACK_CAPACITY - step.stack_size, + gas_left: step.gas_left.0, + gas_cost: step.gas_cost.as_u64(), + opcode: match step.exec_state { + circuit_input_builder::ExecState::Op(op) => Some(op), + _ => None, + }, + memory_size: step.memory_size as u64, + reversible_write_counter: step.reversible_write_counter, + log_id: step.log_id, + } +} + +fn tx_convert(tx: &circuit_input_builder::Transaction, id: usize, is_last_tx: bool) -> Transaction { + Transaction { + id, + nonce: tx.nonce, + gas: tx.gas, + gas_price: tx.gas_price, + caller_address: tx.from, + callee_address: tx.to, + is_create: tx.is_create(), + value: tx.value, + call_data: tx.input.clone(), + call_data_length: tx.input.len(), + call_data_gas_cost: tx + .input + .iter() + .fold(0, |acc, byte| acc + if *byte == 0 { 4 } else { 16 }), + calls: tx + .calls() + .iter() + .map(|call| Call { + id: call.call_id, + is_root: call.is_root, + is_create: call.is_create(), + code_hash: call.code_hash.to_word(), + rw_counter_end_of_reversion: call.rw_counter_end_of_reversion, + caller_id: call.caller_id, + depth: call.depth, + caller_address: call.caller_address, + callee_address: call.address, + call_data_offset: call.call_data_offset, + call_data_length: call.call_data_length, + return_data_offset: call.return_data_offset, + return_data_length: call.return_data_length, + value: call.value, + is_success: call.is_success, + is_persistent: call.is_persistent, + is_static: call.is_static, + }) + .collect(), + steps: tx + .steps() + .iter() + .map(step_convert) + .chain( + (if is_last_tx { + Some(iter::once(ExecStep { + // if it is the first tx, less 1 rw lookup, refer to end_tx gadget + rw_counter: tx.steps().last().unwrap().rwc.0 + 9 - (id == 1) as usize, + execution_state: ExecutionState::EndBlock, + ..Default::default() + })) + } else { + None + }) + .into_iter() + .flatten(), + ) + .collect(), + } +} + +pub fn block_convert( + block: &circuit_input_builder::Block, + code_db: &bus_mapping::state_db::CodeDB, +) -> Block { + Block { + randomness: Fr::rand(), + context: block.into(), + rws: RwMap::from(&block.container), + txs: block + .txs() + .iter() + .enumerate() + .map(|(idx, tx)| tx_convert(tx, idx + 1, idx + 1 == block.txs().len())) + .collect(), + bytecodes: block + .txs() + .iter() + .flat_map(|tx| { + tx.calls() + .iter() + .map(|call| call.code_hash) + .unique() + .into_iter() + .map(|code_hash| { + let bytecode = Bytecode::new(code_db.0.get(&code_hash).unwrap().to_vec()); + (bytecode.hash, bytecode) + }) + }) + .collect(), + copy_events: block.copy_events.clone(), + } +} From 2fd71d5720134b04ed43f6e6584b1d33a8f3079e Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Tue, 13 Dec 2022 05:08:15 +0800 Subject: [PATCH 2/9] Update `BALANCE` circuit with non-existing proofs. --- .../src/circuit_input_builder/execution.rs | 12 +- .../circuit_input_builder/input_state_ref.rs | 3 + bus-mapping/src/evm/opcodes/balance.rs | 28 +- .../src/evm_circuit/execution/balance.rs | 46 +- zkevm-circuits/src/evm_circuit/witness.rs | 1388 ----------------- zkevm-circuits/src/witness/step.rs | 30 +- 6 files changed, 83 insertions(+), 1424 deletions(-) delete mode 100644 zkevm-circuits/src/evm_circuit/witness.rs diff --git a/bus-mapping/src/circuit_input_builder/execution.rs b/bus-mapping/src/circuit_input_builder/execution.rs index 875e5b936f..86e67b4e5d 100644 --- a/bus-mapping/src/circuit_input_builder/execution.rs +++ b/bus-mapping/src/circuit_input_builder/execution.rs @@ -6,10 +6,11 @@ use crate::{ }; use eth_types::{ evm_types::{Gas, GasCost, OpcodeId, ProgramCounter}, - GethExecStep, Word, H256, + Address, GethExecStep, Word, H256, }; use gadgets::impl_expr; use halo2_proofs::plonk::Expression; +use std::collections::HashSet; use strum_macros::EnumIter; /// An execution step of the EVM. @@ -44,6 +45,8 @@ pub struct ExecStep { pub bus_mapping_instance: Vec, /// Error generated by this step pub error: Option, + /// Non existent account addresses + pub non_existent_accounts: HashSet
, } impl ExecStep { @@ -69,6 +72,7 @@ impl ExecStep { log_id, bus_mapping_instance: Vec::new(), error: None, + non_existent_accounts: HashSet::new(), } } @@ -79,6 +83,11 @@ impl ExecStep { Some(ExecError::OutOfGas(_) | ExecError::StackOverflow | ExecError::StackUnderflow) ) } + + /// Add a non existent account address. + pub fn add_non_existent_account(&mut self, address: Address) { + self.non_existent_accounts.insert(address); + } } impl Default for ExecStep { @@ -97,6 +106,7 @@ impl Default for ExecStep { log_id: 0, bus_mapping_instance: Vec::new(), error: None, + non_existent_accounts: HashSet::new(), } } } diff --git a/bus-mapping/src/circuit_input_builder/input_state_ref.rs b/bus-mapping/src/circuit_input_builder/input_state_ref.rs index 6fb41bf800..79c093bdbe 100644 --- a/bus-mapping/src/circuit_input_builder/input_state_ref.rs +++ b/bus-mapping/src/circuit_input_builder/input_state_ref.rs @@ -276,6 +276,9 @@ impl<'a> CircuitInputStateRef<'a> { value: Word, value_prev: Word, ) -> Result<(), Error> { + if field == AccountField::NonExisting { + step.add_non_existent_account(address); + } self.push_op( step, RW::READ, diff --git a/bus-mapping/src/evm/opcodes/balance.rs b/bus-mapping/src/evm/opcodes/balance.rs index 73a36f6c9b..c05cd47fbe 100644 --- a/bus-mapping/src/evm/opcodes/balance.rs +++ b/bus-mapping/src/evm/opcodes/balance.rs @@ -3,7 +3,7 @@ use crate::evm::Opcode; use crate::operation::{AccountField, CallContextField, TxAccessListAccountOp, RW}; use crate::state_db::Account; use crate::Error; -use eth_types::{GethExecStep, ToAddress, ToWord, U256}; +use eth_types::{GethExecStep, ToAddress, ToWord, Word, U256}; #[derive(Debug, Copy, Clone)] pub(crate) struct Balance; @@ -59,14 +59,24 @@ impl Opcode for Balance { )?; // Read account balance. - let &Account { balance, .. } = state.sdb.get_account(&address).1; - state.account_read( - &mut exec_step, - address, - AccountField::Balance, - balance, - balance, - )?; + let (exists, &Account { balance, .. }) = state.sdb.get_account(&address); + if exists { + state.account_read( + &mut exec_step, + address, + AccountField::Balance, + balance, + balance, + )?; + } else { + state.account_read( + &mut exec_step, + address, + AccountField::NonExisting, + Word::zero(), + Word::zero(), + )?; + }; // Write the BALANCE result to stack. state.stack_write( diff --git a/zkevm-circuits/src/evm_circuit/execution/balance.rs b/zkevm-circuits/src/evm_circuit/execution/balance.rs index 61555ccfab..4dfa311dc0 100644 --- a/zkevm-circuits/src/evm_circuit/execution/balance.rs +++ b/zkevm-circuits/src/evm_circuit/execution/balance.rs @@ -6,7 +6,7 @@ use crate::evm_circuit::util::constraint_builder::Transition::Delta; use crate::evm_circuit::util::constraint_builder::{ ConstraintBuilder, ReversionInfo, StepStateTransition, }; -use crate::evm_circuit::util::{from_bytes, CachedRegion, Cell, RandomLinearCombination}; +use crate::evm_circuit::util::{from_bytes, select, CachedRegion, Cell, RandomLinearCombination}; use crate::evm_circuit::witness::{Block, Call, ExecStep, Transaction}; use crate::table::{AccountFieldTag, CallContextFieldTag}; use crate::util::Expr; @@ -23,6 +23,7 @@ pub(crate) struct BalanceGadget { tx_id: Cell, is_warm: Cell, balance: Cell, + exists: Cell, } impl ExecutionGadget for BalanceGadget { @@ -45,13 +46,25 @@ impl ExecutionGadget for BalanceGadget { Some(&mut reversion_info), ); - let balance = cb.query_cell(); - cb.account_read( - from_bytes::expr(&address.cells), - AccountFieldTag::Balance, - balance.expr(), - ); - cb.stack_push(balance.expr()); + let exists = cb.query_bool(); + let balance = cb.condition(exists.expr(), |cb| { + let balance = cb.query_cell(); + cb.account_read( + from_bytes::expr(&address.cells), + AccountFieldTag::Balance, + balance.expr(), + ); + balance + }); + cb.condition(1.expr() - exists.expr(), |cb| { + cb.account_read( + from_bytes::expr(&address.cells), + AccountFieldTag::NonExisting, + 0.expr(), + ); + }); + + cb.stack_push(select::expr(exists.expr(), balance.expr(), 0.expr())); let gas_cost = is_warm.expr() * GasCost::WARM_ACCESS.expr() + (1.expr() - is_warm.expr()) * GasCost::COLD_ACCOUNT_ACCESS.expr(); @@ -75,6 +88,7 @@ impl ExecutionGadget for BalanceGadget { tx_id, is_warm, balance, + exists, } } @@ -89,7 +103,8 @@ impl ExecutionGadget for BalanceGadget { ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; - let mut address_bytes = block.rws[step.rw_indices[0]].stack_value().to_address().0; + let address = block.rws[step.rw_indices[0]].stack_value().to_address(); + let mut address_bytes = address.0; address_bytes.reverse(); self.address.assign(region, offset, Some(address_bytes))?; @@ -107,10 +122,17 @@ impl ExecutionGadget for BalanceGadget { self.is_warm .assign(region, offset, Value::known(F::from(is_warm)))?; - let balance = block.rws[step.rw_indices[5]] - .table_assignment_aux(block.randomness) - .value; + let exists = step.account_exists(&address); + let balance = if exists { + block.rws[step.rw_indices[5]] + .table_assignment_aux(block.randomness) + .value + } else { + F::zero() + }; self.balance.assign(region, offset, Value::known(balance))?; + self.exists + .assign(region, offset, Value::known(F::from(exists)))?; Ok(()) } diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs deleted file mode 100644 index fd238be491..0000000000 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ /dev/null @@ -1,1388 +0,0 @@ -#![allow(missing_docs)] -use crate::{ - evm_circuit::{ - param::{N_BYTES_WORD, STACK_CAPACITY}, - step::ExecutionState, - util::RandomLinearCombination, - }, - table::{ - AccountFieldTag, BlockContextFieldTag, BytecodeFieldTag, CallContextFieldTag, RwTableTag, - TxContextFieldTag, TxLogFieldTag, TxReceiptFieldTag, - }, -}; - -use bus_mapping::{ - circuit_input_builder::{self, CopyEvent}, - error::{ExecError, OogError}, - operation::{self, AccountField, CallContextField, TxLogField, TxReceiptField}, -}; - -use eth_types::{evm_types::OpcodeId, ToWord}; -use eth_types::{Address, Field, ToLittleEndian, ToScalar, Word}; -use eth_types::{ToAddress, U256}; -use halo2_proofs::arithmetic::{BaseExt, FieldExt}; -use halo2_proofs::pairing::bn256::Fr; -use itertools::Itertools; -use sha3::{Digest, Keccak256}; -use std::{collections::HashMap, iter}; - -#[derive(Debug, Default, Clone)] -pub struct Block { - /// The randomness for random linear combination - pub randomness: F, - /// Transactions in the block - pub txs: Vec, - /// Read write events in the RwTable - pub rws: RwMap, - /// Bytecode used in the block - pub bytecodes: HashMap, - /// The block context - pub context: BlockContext, - /// Copy events for the EVM circuit's copy table. - pub copy_events: Vec, -} - -#[derive(Debug, Default, Clone)] -pub struct BlockContext { - /// The address of the miner for the block - pub coinbase: Address, - /// The gas limit of the block - pub gas_limit: u64, - /// The number of the block - pub number: Word, - /// The timestamp of the block - pub timestamp: Word, - /// The difficulty of the blcok - pub difficulty: Word, - /// The base fee, the minimum amount of gas fee for a transaction - pub base_fee: Word, - /// The hash of previous blocks - pub history_hashes: Vec, - /// The chain id - pub chain_id: Word, -} - -impl BlockContext { - pub fn table_assignments(&self, randomness: F) -> Vec<[F; 3]> { - [ - vec![ - [ - F::from(BlockContextFieldTag::Coinbase as u64), - F::zero(), - self.coinbase.to_scalar().unwrap(), - ], - [ - F::from(BlockContextFieldTag::Timestamp as u64), - F::zero(), - self.timestamp.to_scalar().unwrap(), - ], - [ - F::from(BlockContextFieldTag::Number as u64), - F::zero(), - self.number.to_scalar().unwrap(), - ], - [ - F::from(BlockContextFieldTag::Difficulty as u64), - F::zero(), - RandomLinearCombination::random_linear_combine( - self.difficulty.to_le_bytes(), - randomness, - ), - ], - [ - F::from(BlockContextFieldTag::GasLimit as u64), - F::zero(), - F::from(self.gas_limit), - ], - [ - F::from(BlockContextFieldTag::BaseFee as u64), - F::zero(), - RandomLinearCombination::random_linear_combine( - self.base_fee.to_le_bytes(), - randomness, - ), - ], - [ - F::from(BlockContextFieldTag::ChainId as u64), - F::zero(), - RandomLinearCombination::random_linear_combine( - self.chain_id.to_le_bytes(), - randomness, - ), - ], - ], - self.history_hashes - .iter() - .enumerate() - .map(|(idx, hash)| { - [ - F::from(BlockContextFieldTag::BlockHash as u64), - (self.number - idx - 1).to_scalar().unwrap(), - RandomLinearCombination::random_linear_combine( - hash.to_le_bytes(), - randomness, - ), - ] - }) - .collect(), - ] - .concat() - } -} - -#[derive(Debug, Default, Clone)] -pub struct Transaction { - /// The transaction identifier in the block - pub id: usize, - /// The sender account nonce of the transaction - pub nonce: u64, - /// The gas limit of the transaction - pub gas: u64, - /// The gas price - pub gas_price: Word, - /// The caller address - pub caller_address: Address, - /// The callee address - pub callee_address: Address, - /// Whether it's a create transaction - pub is_create: bool, - /// The ether amount of the transaction - pub value: Word, - /// The call data - pub call_data: Vec, - /// The call data length - pub call_data_length: usize, - /// The gas cost for transaction call data - pub call_data_gas_cost: u64, - /// The calls made in the transaction - pub calls: Vec, - /// The steps executioned in the transaction - pub steps: Vec, -} - -impl Transaction { - pub fn table_assignments(&self, randomness: F) -> Vec<[F; 4]> { - [ - vec![ - [ - F::from(self.id as u64), - F::from(TxContextFieldTag::Nonce as u64), - F::zero(), - F::from(self.nonce), - ], - [ - F::from(self.id as u64), - F::from(TxContextFieldTag::Gas as u64), - F::zero(), - F::from(self.gas), - ], - [ - F::from(self.id as u64), - F::from(TxContextFieldTag::GasPrice as u64), - F::zero(), - RandomLinearCombination::random_linear_combine( - self.gas_price.to_le_bytes(), - randomness, - ), - ], - [ - F::from(self.id as u64), - F::from(TxContextFieldTag::CallerAddress as u64), - F::zero(), - self.caller_address.to_scalar().unwrap(), - ], - [ - F::from(self.id as u64), - F::from(TxContextFieldTag::CalleeAddress as u64), - F::zero(), - self.callee_address.to_scalar().unwrap(), - ], - [ - F::from(self.id as u64), - F::from(TxContextFieldTag::IsCreate as u64), - F::zero(), - F::from(self.is_create as u64), - ], - [ - F::from(self.id as u64), - F::from(TxContextFieldTag::Value as u64), - F::zero(), - RandomLinearCombination::random_linear_combine( - self.value.to_le_bytes(), - randomness, - ), - ], - [ - F::from(self.id as u64), - F::from(TxContextFieldTag::CallDataLength as u64), - F::zero(), - F::from(self.call_data_length as u64), - ], - [ - F::from(self.id as u64), - F::from(TxContextFieldTag::CallDataGasCost as u64), - F::zero(), - F::from(self.call_data_gas_cost), - ], - ], - self.call_data - .iter() - .enumerate() - .map(|(idx, byte)| { - [ - F::from(self.id as u64), - F::from(TxContextFieldTag::CallData as u64), - F::from(idx as u64), - F::from(*byte as u64), - ] - }) - .collect(), - ] - .concat() - } -} - -#[derive(Debug, Default, Clone)] -pub struct Call { - /// The unique identifier of call in the whole proof, using the - /// `rw_counter` at the call step. - pub id: usize, - /// Indicate if the call is the root call - pub is_root: bool, - /// Indicate if the call is a create call - pub is_create: bool, - /// The identifier of current executed bytecode - pub code_hash: Word, - /// The `rw_counter` at the end of reversion of a call if it has - /// `is_persistent == false` - pub rw_counter_end_of_reversion: usize, - /// The call index of caller - pub caller_id: usize, - /// The depth in the call stack - pub depth: usize, - /// The caller address - pub caller_address: Address, - /// The callee address - pub callee_address: Address, - /// The call data offset in the memory - pub call_data_offset: u64, - /// The length of call data - pub call_data_length: u64, - /// The return data offset in the memory - pub return_data_offset: u64, - /// The length of return data - pub return_data_length: u64, - /// The ether amount of the transaction - pub value: Word, - /// Indicate if this call halts successfully. - pub is_success: bool, - /// Indicate if this call and all its caller have `is_success == true` - pub is_persistent: bool, - /// Indicate if it's a static call - pub is_static: bool, -} - -#[derive(Clone, Debug, Default)] -pub struct ExecStep { - /// The index in the Transaction calls - pub call_index: usize, - /// The indices in the RW trace incurred in this step - pub rw_indices: Vec<(RwTableTag, usize)>, - /// The execution state for the step - pub execution_state: ExecutionState, - /// The Read/Write counter before the step - pub rw_counter: usize, - /// The program counter - pub program_counter: u64, - /// The stack pointer - pub stack_pointer: usize, - /// The amount of gas left - pub gas_left: u64, - /// The gas cost in this step - pub gas_cost: u64, - /// The memory size in bytes - pub memory_size: u64, - /// The counter for reversible writes - pub reversible_write_counter: usize, - /// The counter for log index within tx - pub log_id: usize, - /// The opcode corresponds to the step - pub opcode: Option, -} - -impl ExecStep { - pub fn memory_word_size(&self) -> u64 { - // EVM always pads the memory size to word size - // https://github.com/ethereum/go-ethereum/blob/master/core/vm/interpreter.go#L212-L216 - // Thus, the memory size must be a multiple of 32 bytes. - assert_eq!(self.memory_size % N_BYTES_WORD as u64, 0); - self.memory_size / N_BYTES_WORD as u64 - } -} - -#[derive(Clone, Debug)] -pub struct Bytecode { - pub hash: Word, - pub bytes: Vec, -} - -impl Bytecode { - pub fn new(bytes: Vec) -> Self { - let hash = Word::from_big_endian(Keccak256::digest(&bytes).as_slice()); - Self { hash, bytes } - } - - pub fn table_assignments(&self, randomness: F) -> Vec<[F; 5]> { - let n = 1 + self.bytes.len(); - let mut rows = Vec::with_capacity(n); - let hash = - RandomLinearCombination::random_linear_combine(self.hash.to_le_bytes(), randomness); - - rows.push([ - hash, - F::from(BytecodeFieldTag::Length as u64), - F::zero(), - F::zero(), - F::from(self.bytes.len() as u64), - ]); - - let mut push_data_left = 0; - for (idx, byte) in self.bytes.iter().enumerate() { - let mut is_code = true; - if push_data_left > 0 { - is_code = false; - push_data_left -= 1; - } else if (OpcodeId::PUSH1.as_u8()..=OpcodeId::PUSH32.as_u8()).contains(byte) { - push_data_left = *byte as usize - (OpcodeId::PUSH1.as_u8() - 1) as usize; - } - rows.push([ - hash, - F::from(BytecodeFieldTag::Byte as u64), - F::from(idx as u64), - F::from(is_code as u64), - F::from(*byte as u64), - ]) - } - rows - } -} - -#[derive(Debug, Default, Clone)] -pub struct RwMap(pub HashMap>); - -impl std::ops::Index<(RwTableTag, usize)> for RwMap { - type Output = Rw; - - fn index(&self, (tag, idx): (RwTableTag, usize)) -> &Self::Output { - &self.0.get(&tag).unwrap()[idx] - } -} - -#[derive(Clone, Copy, Debug)] -pub enum Rw { - Start { - rw_counter: usize, - }, - TxAccessListAccount { - rw_counter: usize, - is_write: bool, - tx_id: usize, - account_address: Address, - is_warm: bool, - is_warm_prev: bool, - }, - TxAccessListAccountStorage { - rw_counter: usize, - is_write: bool, - tx_id: usize, - account_address: Address, - storage_key: Word, - is_warm: bool, - is_warm_prev: bool, - }, - TxRefund { - rw_counter: usize, - is_write: bool, - tx_id: usize, - value: u64, - value_prev: u64, - }, - Account { - rw_counter: usize, - is_write: bool, - account_address: Address, - field_tag: AccountFieldTag, - value: Word, - value_prev: Word, - }, - AccountStorage { - rw_counter: usize, - is_write: bool, - account_address: Address, - storage_key: Word, - value: Word, - value_prev: Word, - tx_id: usize, - committed_value: Word, - }, - AccountDestructed { - rw_counter: usize, - is_write: bool, - tx_id: usize, - account_address: Address, - is_destructed: bool, - is_destructed_prev: bool, - }, - CallContext { - rw_counter: usize, - is_write: bool, - call_id: usize, - field_tag: CallContextFieldTag, - value: Word, - }, - Stack { - rw_counter: usize, - is_write: bool, - call_id: usize, - stack_pointer: usize, - value: Word, - }, - Memory { - rw_counter: usize, - is_write: bool, - call_id: usize, - memory_address: u64, - byte: u8, - }, - TxLog { - rw_counter: usize, - is_write: bool, - tx_id: usize, - log_id: u64, // pack this can index together into address? - field_tag: TxLogFieldTag, - // topic index (0..4) if field_tag is TxLogFieldTag:Topic - // byte index if field_tag is TxLogFieldTag:Data - // 0 for other field tags - index: usize, - - // when it is topic field, value can be word type - value: Word, - }, - TxReceipt { - rw_counter: usize, - is_write: bool, - tx_id: usize, - field_tag: TxReceiptFieldTag, - value: u64, - }, -} -#[derive(Default, Clone, Copy)] -pub struct RwRow { - pub rw_counter: F, - pub is_write: F, - pub tag: F, - pub key1: F, - pub key2: F, - pub key3: F, - pub key4: F, - pub value: F, - pub value_prev: F, - pub aux1: F, - pub aux2: F, -} - -impl From<[F; 11]> for RwRow { - fn from(row: [F; 11]) -> Self { - Self { - rw_counter: row[0], - is_write: row[1], - tag: row[2], - key1: row[3], - key2: row[4], - key3: row[5], - key4: row[6], - value: row[7], - value_prev: row[8], - aux1: row[9], - aux2: row[10], - } - } -} - -impl Rw { - pub fn tx_access_list_value_pair(&self) -> (bool, bool) { - match self { - Self::TxAccessListAccount { - is_warm, - is_warm_prev, - .. - } => (*is_warm, *is_warm_prev), - Self::TxAccessListAccountStorage { - is_warm, - is_warm_prev, - .. - } => (*is_warm, *is_warm_prev), - _ => unreachable!(), - } - } - - pub fn tx_refund_value_pair(&self) -> (u64, u64) { - match self { - Self::TxRefund { - value, value_prev, .. - } => (*value, *value_prev), - _ => unreachable!(), - } - } - - pub fn account_value_pair(&self) -> (Word, Word) { - match self { - Self::Account { - value, value_prev, .. - } => (*value, *value_prev), - _ => unreachable!(), - } - } - - pub fn aux_pair(&self) -> (usize, Word) { - match self { - Self::AccountStorage { - tx_id, - committed_value, - .. - } => (*tx_id, *committed_value), - _ => unreachable!(), - } - } - - pub fn storage_value_aux(&self) -> (Word, Word, usize, Word) { - match self { - Self::AccountStorage { - value, - value_prev, - tx_id, - committed_value, - .. - } => (*value, *value_prev, *tx_id, *committed_value), - _ => unreachable!(), - } - } - - pub fn call_context_value(&self) -> Word { - match self { - Self::CallContext { value, .. } => *value, - _ => unreachable!(), - } - } - - pub fn stack_value(&self) -> Word { - match self { - Self::Stack { value, .. } => *value, - _ => unreachable!(), - } - } - - pub fn log_value(&self) -> Word { - match self { - Self::TxLog { value, .. } => *value, - _ => unreachable!(), - } - } - - pub fn receipt_value(&self) -> u64 { - match self { - Self::TxReceipt { value, .. } => *value, - _ => unreachable!(), - } - } - - pub fn memory_value(&self) -> u8 { - match self { - Self::Memory { byte, .. } => *byte, - _ => unreachable!(), - } - } - - pub fn table_assignment(&self, randomness: F) -> RwRow { - RwRow { - rw_counter: F::from(self.rw_counter() as u64), - is_write: F::from(self.is_write() as u64), - tag: F::from(self.tag() as u64), - key1: F::from(self.id().unwrap_or_default() as u64), - key2: self.address().unwrap_or_default().to_scalar().unwrap(), - key3: F::from(self.field_tag().unwrap_or_default() as u64), - key4: RandomLinearCombination::random_linear_combine( - self.storage_key().unwrap_or_default().to_le_bytes(), - randomness, - ), - value: self.value_assignment(randomness), - value_prev: self.value_prev_assignment(randomness).unwrap_or_default(), - aux1: F::zero(), // only used for AccountStorage::tx_id, which moved to key1. - aux2: self - .committed_value_assignment(randomness) - .unwrap_or_default(), - } - } - - pub fn rw_counter(&self) -> usize { - match self { - Self::Start { rw_counter } - | Self::Memory { rw_counter, .. } - | Self::Stack { rw_counter, .. } - | Self::AccountStorage { rw_counter, .. } - | Self::TxAccessListAccount { rw_counter, .. } - | Self::TxAccessListAccountStorage { rw_counter, .. } - | Self::TxRefund { rw_counter, .. } - | Self::Account { rw_counter, .. } - | Self::AccountDestructed { rw_counter, .. } - | Self::CallContext { rw_counter, .. } - | Self::TxLog { rw_counter, .. } - | Self::TxReceipt { rw_counter, .. } => *rw_counter, - } - } - - pub fn is_write(&self) -> bool { - match self { - Self::Start { .. } => false, - Self::Memory { is_write, .. } - | Self::Stack { is_write, .. } - | Self::AccountStorage { is_write, .. } - | Self::TxAccessListAccount { is_write, .. } - | Self::TxAccessListAccountStorage { is_write, .. } - | Self::TxRefund { is_write, .. } - | Self::Account { is_write, .. } - | Self::AccountDestructed { is_write, .. } - | Self::CallContext { is_write, .. } - | Self::TxLog { is_write, .. } - | Self::TxReceipt { is_write, .. } => *is_write, - } - } - - pub fn tag(&self) -> RwTableTag { - match self { - Self::Start { .. } => RwTableTag::Start, - Self::Memory { .. } => RwTableTag::Memory, - Self::Stack { .. } => RwTableTag::Stack, - Self::AccountStorage { .. } => RwTableTag::AccountStorage, - Self::TxAccessListAccount { .. } => RwTableTag::TxAccessListAccount, - Self::TxAccessListAccountStorage { .. } => RwTableTag::TxAccessListAccountStorage, - Self::TxRefund { .. } => RwTableTag::TxRefund, - Self::Account { .. } => RwTableTag::Account, - Self::AccountDestructed { .. } => RwTableTag::AccountDestructed, - Self::CallContext { .. } => RwTableTag::CallContext, - Self::TxLog { .. } => RwTableTag::TxLog, - Self::TxReceipt { .. } => RwTableTag::TxReceipt, - } - } - - pub fn id(&self) -> Option { - match self { - Self::AccountStorage { tx_id, .. } - | Self::TxAccessListAccount { tx_id, .. } - | Self::TxAccessListAccountStorage { tx_id, .. } - | Self::TxRefund { tx_id, .. } - | Self::TxLog { tx_id, .. } - | Self::TxReceipt { tx_id, .. } => Some(*tx_id), - Self::CallContext { call_id, .. } - | Self::Stack { call_id, .. } - | Self::Memory { call_id, .. } => Some(*call_id), - Self::Start { .. } | Self::Account { .. } | Self::AccountDestructed { .. } => None, - } - } - - pub fn address(&self) -> Option
{ - match self { - Self::TxAccessListAccount { - account_address, .. - } - | Self::TxAccessListAccountStorage { - account_address, .. - } - | Self::Account { - account_address, .. - } - | Self::AccountStorage { - account_address, .. - } - | Self::AccountDestructed { - account_address, .. - } => Some(*account_address), - Self::Memory { memory_address, .. } => Some(U256::from(*memory_address).to_address()), - Self::Stack { stack_pointer, .. } => { - Some(U256::from(*stack_pointer as u64).to_address()) - } - Self::TxLog { - log_id, - field_tag, - index, - .. - } => { - // make field_tag fit into one limb (16 bits) - Some( - (U256::from(*index as u64) - + (U256::from(*field_tag as u64) << 32) - + (U256::from(*log_id) << 48)) - .to_address(), - ) - } - Self::Start { .. } - | Self::CallContext { .. } - | Self::TxRefund { .. } - | Self::TxReceipt { .. } => None, - } - } - - pub fn field_tag(&self) -> Option { - match self { - Self::Account { field_tag, .. } => Some(*field_tag as u64), - Self::CallContext { field_tag, .. } => Some(*field_tag as u64), - Self::TxReceipt { field_tag, .. } => Some(*field_tag as u64), - Self::Start { .. } - | Self::Memory { .. } - | Self::Stack { .. } - | Self::AccountStorage { .. } - | Self::TxAccessListAccount { .. } - | Self::TxAccessListAccountStorage { .. } - | Self::TxRefund { .. } - | Self::TxLog { .. } - | Self::AccountDestructed { .. } => None, - } - } - - pub fn storage_key(&self) -> Option { - match self { - Self::AccountStorage { storage_key, .. } - | Self::TxAccessListAccountStorage { storage_key, .. } => Some(*storage_key), - Self::Start { .. } - | Self::CallContext { .. } - | Self::Stack { .. } - | Self::Memory { .. } - | Self::TxRefund { .. } - | Self::Account { .. } - | Self::TxAccessListAccount { .. } - | Self::AccountDestructed { .. } - | Self::TxLog { .. } - | Self::TxReceipt { .. } => None, - } - } - - pub fn value_assignment(&self, randomness: F) -> F { - match self { - Self::Start { .. } => F::zero(), - Self::CallContext { - field_tag, value, .. - } => { - match field_tag { - // Only these two tags have values that may not fit into a scalar, so we need to - // RLC. - CallContextFieldTag::CodeHash | CallContextFieldTag::Value => { - RandomLinearCombination::random_linear_combine( - value.to_le_bytes(), - randomness, - ) - } - _ => value.to_scalar().unwrap(), - } - } - Self::Account { - value, field_tag, .. - } => match field_tag { - AccountFieldTag::CodeHash | AccountFieldTag::Balance => { - RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness) - } - AccountFieldTag::Nonce => value.to_scalar().unwrap(), - }, - Self::AccountStorage { value, .. } | Self::Stack { value, .. } => { - RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness) - } - - Self::TxLog { - field_tag, value, .. - } => match field_tag { - TxLogFieldTag::Topic => { - RandomLinearCombination::random_linear_combine(value.to_le_bytes(), randomness) - } - _ => value.to_scalar().unwrap(), - }, - - Self::TxAccessListAccount { is_warm, .. } - | Self::TxAccessListAccountStorage { is_warm, .. } => F::from(*is_warm as u64), - Self::AccountDestructed { is_destructed, .. } => F::from(*is_destructed as u64), - Self::Memory { byte, .. } => F::from(u64::from(*byte)), - Self::TxRefund { value, .. } | Self::TxReceipt { value, .. } => F::from(*value), - } - } - - pub fn value_prev_assignment(&self, randomness: F) -> Option { - match self { - Self::Account { - value_prev, - field_tag, - .. - } => Some(match field_tag { - AccountFieldTag::CodeHash | AccountFieldTag::Balance => { - RandomLinearCombination::random_linear_combine( - value_prev.to_le_bytes(), - randomness, - ) - } - AccountFieldTag::Nonce => value_prev.to_scalar().unwrap(), - }), - Self::AccountStorage { value_prev, .. } => { - Some(RandomLinearCombination::random_linear_combine( - value_prev.to_le_bytes(), - randomness, - )) - } - Self::TxAccessListAccount { is_warm_prev, .. } - | Self::TxAccessListAccountStorage { is_warm_prev, .. } => { - Some(F::from(*is_warm_prev as u64)) - } - Self::AccountDestructed { - is_destructed_prev, .. - } => Some(F::from(*is_destructed_prev as u64)), - Self::TxRefund { value_prev, .. } => Some(F::from(*value_prev)), - Self::Start { .. } - | Self::Stack { .. } - | Self::Memory { .. } - | Self::CallContext { .. } - | Self::TxLog { .. } - | Self::TxReceipt { .. } => None, - } - } - - fn committed_value_assignment(&self, randomness: F) -> Option { - match self { - Self::AccountStorage { - committed_value, .. - } => Some(RandomLinearCombination::random_linear_combine( - committed_value.to_le_bytes(), - randomness, - )), - _ => None, - } - } -} - -impl From<&circuit_input_builder::Block> for BlockContext { - fn from(block: &circuit_input_builder::Block) -> Self { - Self { - coinbase: block.coinbase, - gas_limit: block.gas_limit, - number: block.number, - timestamp: block.timestamp, - difficulty: block.difficulty, - base_fee: block.base_fee, - history_hashes: block.history_hashes.clone(), - chain_id: block.chain_id, - } - } -} - -impl From<&operation::OperationContainer> for RwMap { - fn from(container: &operation::OperationContainer) -> Self { - let mut rws = HashMap::default(); - - rws.insert( - RwTableTag::TxAccessListAccount, - container - .tx_access_list_account - .iter() - .map(|op| Rw::TxAccessListAccount { - rw_counter: op.rwc().into(), - is_write: true, - tx_id: op.op().tx_id, - account_address: op.op().address, - is_warm: op.op().is_warm, - is_warm_prev: op.op().is_warm_prev, - }) - .collect(), - ); - rws.insert( - RwTableTag::TxAccessListAccountStorage, - container - .tx_access_list_account_storage - .iter() - .map(|op| Rw::TxAccessListAccountStorage { - rw_counter: op.rwc().into(), - is_write: true, - tx_id: op.op().tx_id, - account_address: op.op().address, - storage_key: op.op().key, - is_warm: op.op().is_warm, - is_warm_prev: op.op().is_warm_prev, - }) - .collect(), - ); - rws.insert( - RwTableTag::TxRefund, - container - .tx_refund - .iter() - .map(|op| Rw::TxRefund { - rw_counter: op.rwc().into(), - is_write: op.rw().is_write(), - tx_id: op.op().tx_id, - value: op.op().value, - value_prev: op.op().value_prev, - }) - .collect(), - ); - rws.insert( - RwTableTag::Account, - container - .account - .iter() - .map(|op| Rw::Account { - rw_counter: op.rwc().into(), - is_write: op.rw().is_write(), - account_address: op.op().address, - field_tag: match op.op().field { - AccountField::Nonce => AccountFieldTag::Nonce, - AccountField::Balance => AccountFieldTag::Balance, - AccountField::CodeHash => AccountFieldTag::CodeHash, - }, - value: op.op().value, - value_prev: op.op().value_prev, - }) - .collect(), - ); - rws.insert( - RwTableTag::AccountStorage, - container - .storage - .iter() - .map(|op| Rw::AccountStorage { - rw_counter: op.rwc().into(), - is_write: op.rw().is_write(), - account_address: op.op().address, - storage_key: op.op().key, - value: op.op().value, - value_prev: op.op().value_prev, - tx_id: op.op().tx_id, - committed_value: op.op().committed_value, - }) - .collect(), - ); - rws.insert( - RwTableTag::AccountDestructed, - container - .account_destructed - .iter() - .map(|op| Rw::AccountDestructed { - rw_counter: op.rwc().into(), - is_write: true, - tx_id: op.op().tx_id, - account_address: op.op().address, - is_destructed: op.op().is_destructed, - is_destructed_prev: op.op().is_destructed_prev, - }) - .collect(), - ); - rws.insert( - RwTableTag::CallContext, - container - .call_context - .iter() - .map(|op| Rw::CallContext { - rw_counter: op.rwc().into(), - is_write: op.rw().is_write(), - call_id: op.op().call_id, - field_tag: match op.op().field { - CallContextField::RwCounterEndOfReversion => { - CallContextFieldTag::RwCounterEndOfReversion - } - CallContextField::CallerId => CallContextFieldTag::CallerId, - CallContextField::TxId => CallContextFieldTag::TxId, - CallContextField::Depth => CallContextFieldTag::Depth, - CallContextField::CallerAddress => CallContextFieldTag::CallerAddress, - CallContextField::CalleeAddress => CallContextFieldTag::CalleeAddress, - CallContextField::CallDataOffset => CallContextFieldTag::CallDataOffset, - CallContextField::CallDataLength => CallContextFieldTag::CallDataLength, - CallContextField::ReturnDataOffset => CallContextFieldTag::ReturnDataOffset, - CallContextField::ReturnDataLength => CallContextFieldTag::ReturnDataLength, - CallContextField::Value => CallContextFieldTag::Value, - CallContextField::IsSuccess => CallContextFieldTag::IsSuccess, - CallContextField::IsPersistent => CallContextFieldTag::IsPersistent, - CallContextField::IsStatic => CallContextFieldTag::IsStatic, - CallContextField::LastCalleeId => CallContextFieldTag::LastCalleeId, - CallContextField::LastCalleeReturnDataOffset => { - CallContextFieldTag::LastCalleeReturnDataOffset - } - CallContextField::LastCalleeReturnDataLength => { - CallContextFieldTag::LastCalleeReturnDataLength - } - CallContextField::IsRoot => CallContextFieldTag::IsRoot, - CallContextField::IsCreate => CallContextFieldTag::IsCreate, - CallContextField::CodeHash => CallContextFieldTag::CodeHash, - CallContextField::ProgramCounter => CallContextFieldTag::ProgramCounter, - CallContextField::StackPointer => CallContextFieldTag::StackPointer, - CallContextField::GasLeft => CallContextFieldTag::GasLeft, - CallContextField::MemorySize => CallContextFieldTag::MemorySize, - CallContextField::ReversibleWriteCounter => { - CallContextFieldTag::ReversibleWriteCounter - } - }, - value: op.op().value, - }) - .collect(), - ); - rws.insert( - RwTableTag::Stack, - container - .stack - .iter() - .map(|op| Rw::Stack { - rw_counter: op.rwc().into(), - is_write: op.rw().is_write(), - call_id: op.op().call_id(), - stack_pointer: usize::from(*op.op().address()), - value: *op.op().value(), - }) - .collect(), - ); - rws.insert( - RwTableTag::Memory, - container - .memory - .iter() - .map(|op| Rw::Memory { - rw_counter: op.rwc().into(), - is_write: op.rw().is_write(), - call_id: op.op().call_id(), - memory_address: u64::from_le_bytes( - op.op().address().to_le_bytes()[..8].try_into().unwrap(), - ), - byte: op.op().value(), - }) - .collect(), - ); - rws.insert( - RwTableTag::TxLog, - container - .tx_log - .iter() - .map(|op| Rw::TxLog { - rw_counter: op.rwc().into(), - is_write: op.rw().is_write(), - tx_id: op.op().tx_id, - log_id: op.op().log_id as u64, - field_tag: match op.op().field { - TxLogField::Address => TxLogFieldTag::Address, - TxLogField::Topic => TxLogFieldTag::Topic, - TxLogField::Data => TxLogFieldTag::Data, - }, - index: op.op().index, - value: op.op().value, - }) - .collect(), - ); - rws.insert( - RwTableTag::TxReceipt, - container - .tx_receipt - .iter() - .map(|op| Rw::TxReceipt { - rw_counter: op.rwc().into(), - is_write: op.rw().is_write(), - tx_id: op.op().tx_id, - field_tag: match op.op().field { - TxReceiptField::PostStateOrStatus => TxReceiptFieldTag::PostStateOrStatus, - TxReceiptField::LogLength => TxReceiptFieldTag::LogLength, - TxReceiptField::CumulativeGasUsed => TxReceiptFieldTag::CumulativeGasUsed, - }, - value: op.op().value, - }) - .collect(), - ); - - Self(rws) - } -} - -impl From<&ExecError> for ExecutionState { - fn from(error: &ExecError) -> Self { - match error { - ExecError::InvalidOpcode => ExecutionState::ErrorInvalidOpcode, - ExecError::StackOverflow => ExecutionState::ErrorStackOverflow, - ExecError::StackUnderflow => ExecutionState::ErrorStackUnderflow, - ExecError::WriteProtection => ExecutionState::ErrorWriteProtection, - ExecError::Depth => ExecutionState::ErrorDepth, - ExecError::InsufficientBalance => ExecutionState::ErrorInsufficientBalance, - ExecError::ContractAddressCollision => ExecutionState::ErrorContractAddressCollision, - ExecError::InvalidCreationCode => ExecutionState::ErrorInvalidCreationCode, - ExecError::InvalidJump => ExecutionState::ErrorInvalidJump, - ExecError::ReturnDataOutOfBounds => ExecutionState::ErrorReturnDataOutOfBound, - ExecError::CodeStoreOutOfGas => ExecutionState::ErrorOutOfGasCodeStore, - ExecError::MaxCodeSizeExceeded => ExecutionState::ErrorMaxCodeSizeExceeded, - ExecError::OutOfGas(oog_error) => match oog_error { - OogError::Constant => ExecutionState::ErrorOutOfGasConstant, - OogError::StaticMemoryExpansion => { - ExecutionState::ErrorOutOfGasStaticMemoryExpansion - } - OogError::DynamicMemoryExpansion => { - ExecutionState::ErrorOutOfGasDynamicMemoryExpansion - } - OogError::MemoryCopy => ExecutionState::ErrorOutOfGasMemoryCopy, - OogError::AccountAccess => ExecutionState::ErrorOutOfGasAccountAccess, - OogError::CodeStore => ExecutionState::ErrorOutOfGasCodeStore, - OogError::Log => ExecutionState::ErrorOutOfGasLOG, - OogError::Exp => ExecutionState::ErrorOutOfGasEXP, - OogError::Sha3 => ExecutionState::ErrorOutOfGasSHA3, - OogError::ExtCodeCopy => ExecutionState::ErrorOutOfGasEXTCODECOPY, - OogError::Sload => ExecutionState::ErrorOutOfGasSLOAD, - OogError::Sstore => ExecutionState::ErrorOutOfGasSSTORE, - OogError::Call => ExecutionState::ErrorOutOfGasCALL, - OogError::CallCode => ExecutionState::ErrorOutOfGasCALLCODE, - OogError::DelegateCall => ExecutionState::ErrorOutOfGasDELEGATECALL, - OogError::Create2 => ExecutionState::ErrorOutOfGasCREATE2, - OogError::StaticCall => ExecutionState::ErrorOutOfGasSTATICCALL, - OogError::SelfDestruct => ExecutionState::ErrorOutOfGasSELFDESTRUCT, - }, - } - } -} - -impl From<&circuit_input_builder::ExecStep> for ExecutionState { - fn from(step: &circuit_input_builder::ExecStep) -> Self { - if let Some(error) = step.error.as_ref() { - return error.into(); - } - match step.exec_state { - circuit_input_builder::ExecState::Op(op) => { - if op.is_dup() { - return ExecutionState::DUP; - } - if op.is_push() { - return ExecutionState::PUSH; - } - if op.is_swap() { - return ExecutionState::SWAP; - } - if op.is_log() { - return ExecutionState::LOG; - } - - macro_rules! dummy { - ($name:expr) => {{ - log::warn!("{:?} is implemented with DummyGadget", $name); - $name - }}; - } - - match op { - OpcodeId::ADD | OpcodeId::SUB => ExecutionState::ADD_SUB, - OpcodeId::ADDMOD => ExecutionState::ADDMOD, - OpcodeId::BALANCE => ExecutionState::BALANCE, - OpcodeId::MUL | OpcodeId::DIV | OpcodeId::MOD => ExecutionState::MUL_DIV_MOD, - OpcodeId::MULMOD => ExecutionState::MULMOD, - OpcodeId::SDIV | OpcodeId::SMOD => ExecutionState::SDIV_SMOD, - OpcodeId::EQ | OpcodeId::LT | OpcodeId::GT => ExecutionState::CMP, - OpcodeId::SLT | OpcodeId::SGT => ExecutionState::SCMP, - OpcodeId::SIGNEXTEND => ExecutionState::SIGNEXTEND, - OpcodeId::STOP => ExecutionState::STOP, - OpcodeId::AND => ExecutionState::BITWISE, - OpcodeId::XOR => ExecutionState::BITWISE, - OpcodeId::OR => ExecutionState::BITWISE, - OpcodeId::NOT => ExecutionState::NOT, - OpcodeId::POP => ExecutionState::POP, - OpcodeId::PUSH32 => ExecutionState::PUSH, - OpcodeId::BYTE => ExecutionState::BYTE, - OpcodeId::MLOAD => ExecutionState::MEMORY, - OpcodeId::MSTORE => ExecutionState::MEMORY, - OpcodeId::MSTORE8 => ExecutionState::MEMORY, - OpcodeId::JUMPDEST => ExecutionState::JUMPDEST, - OpcodeId::JUMP => ExecutionState::JUMP, - OpcodeId::JUMPI => ExecutionState::JUMPI, - OpcodeId::GASPRICE => ExecutionState::GASPRICE, - OpcodeId::PC => ExecutionState::PC, - OpcodeId::MSIZE => ExecutionState::MSIZE, - OpcodeId::CALLER => ExecutionState::CALLER, - OpcodeId::CALLVALUE => ExecutionState::CALLVALUE, - OpcodeId::EXTCODEHASH => ExecutionState::EXTCODEHASH, - OpcodeId::TIMESTAMP | OpcodeId::NUMBER | OpcodeId::GASLIMIT => { - ExecutionState::BLOCKCTXU64 - } - OpcodeId::COINBASE => ExecutionState::BLOCKCTXU160, - OpcodeId::DIFFICULTY | OpcodeId::BASEFEE => ExecutionState::BLOCKCTXU256, - OpcodeId::GAS => ExecutionState::GAS, - OpcodeId::SELFBALANCE => ExecutionState::SELFBALANCE, - OpcodeId::SHA3 => ExecutionState::SHA3, - OpcodeId::SHR => ExecutionState::SHR, - OpcodeId::SLOAD => ExecutionState::SLOAD, - OpcodeId::SSTORE => ExecutionState::SSTORE, - OpcodeId::CALLDATASIZE => ExecutionState::CALLDATASIZE, - OpcodeId::CALLDATACOPY => ExecutionState::CALLDATACOPY, - OpcodeId::CHAINID => ExecutionState::CHAINID, - OpcodeId::ISZERO => ExecutionState::ISZERO, - OpcodeId::CALL => ExecutionState::CALL, - OpcodeId::ORIGIN => ExecutionState::ORIGIN, - OpcodeId::CODECOPY => ExecutionState::CODECOPY, - OpcodeId::CALLDATALOAD => ExecutionState::CALLDATALOAD, - OpcodeId::CODESIZE => ExecutionState::CODESIZE, - OpcodeId::RETURN | OpcodeId::REVERT => ExecutionState::RETURN, - // dummy ops - OpcodeId::ADDRESS => dummy!(ExecutionState::ADDRESS), - OpcodeId::BLOCKHASH => dummy!(ExecutionState::BLOCKHASH), - OpcodeId::EXP => dummy!(ExecutionState::EXP), - OpcodeId::SHL => dummy!(ExecutionState::SHL), - OpcodeId::SAR => dummy!(ExecutionState::SAR), - OpcodeId::EXTCODESIZE => dummy!(ExecutionState::EXTCODESIZE), - OpcodeId::EXTCODECOPY => dummy!(ExecutionState::EXTCODECOPY), - OpcodeId::RETURNDATASIZE => dummy!(ExecutionState::RETURNDATASIZE), - OpcodeId::RETURNDATACOPY => dummy!(ExecutionState::RETURNDATACOPY), - OpcodeId::CREATE => dummy!(ExecutionState::CREATE), - OpcodeId::CALLCODE => dummy!(ExecutionState::CALLCODE), - OpcodeId::DELEGATECALL => dummy!(ExecutionState::DELEGATECALL), - OpcodeId::CREATE2 => dummy!(ExecutionState::CREATE2), - OpcodeId::STATICCALL => dummy!(ExecutionState::STATICCALL), - OpcodeId::SELFDESTRUCT => dummy!(ExecutionState::SELFDESTRUCT), - _ => unimplemented!("unimplemented opcode {:?}", op), - } - } - circuit_input_builder::ExecState::BeginTx => ExecutionState::BeginTx, - circuit_input_builder::ExecState::EndTx => ExecutionState::EndTx, - } - } -} - -impl From<ð_types::bytecode::Bytecode> for Bytecode { - fn from(b: ð_types::bytecode::Bytecode) -> Self { - Bytecode::new(b.to_vec()) - } -} - -fn step_convert(step: &circuit_input_builder::ExecStep) -> ExecStep { - ExecStep { - call_index: step.call_index, - rw_indices: step - .bus_mapping_instance - .iter() - .map(|x| { - let tag = match x.target() { - operation::Target::Memory => RwTableTag::Memory, - operation::Target::Stack => RwTableTag::Stack, - operation::Target::Storage => RwTableTag::AccountStorage, - operation::Target::TxAccessListAccount => RwTableTag::TxAccessListAccount, - operation::Target::TxAccessListAccountStorage => { - RwTableTag::TxAccessListAccountStorage - } - operation::Target::TxRefund => RwTableTag::TxRefund, - operation::Target::Account => RwTableTag::Account, - operation::Target::AccountDestructed => RwTableTag::AccountDestructed, - operation::Target::CallContext => RwTableTag::CallContext, - operation::Target::TxReceipt => RwTableTag::TxReceipt, - operation::Target::TxLog => RwTableTag::TxLog, - }; - (tag, x.as_usize()) - }) - .collect(), - execution_state: ExecutionState::from(step), - rw_counter: usize::from(step.rwc), - program_counter: usize::from(step.pc) as u64, - stack_pointer: STACK_CAPACITY - step.stack_size, - gas_left: step.gas_left.0, - gas_cost: step.gas_cost.as_u64(), - opcode: match step.exec_state { - circuit_input_builder::ExecState::Op(op) => Some(op), - _ => None, - }, - memory_size: step.memory_size as u64, - reversible_write_counter: step.reversible_write_counter, - log_id: step.log_id, - } -} - -fn tx_convert(tx: &circuit_input_builder::Transaction, id: usize, is_last_tx: bool) -> Transaction { - Transaction { - id, - nonce: tx.nonce, - gas: tx.gas, - gas_price: tx.gas_price, - caller_address: tx.from, - callee_address: tx.to, - is_create: tx.is_create(), - value: tx.value, - call_data: tx.input.clone(), - call_data_length: tx.input.len(), - call_data_gas_cost: tx - .input - .iter() - .fold(0, |acc, byte| acc + if *byte == 0 { 4 } else { 16 }), - calls: tx - .calls() - .iter() - .map(|call| Call { - id: call.call_id, - is_root: call.is_root, - is_create: call.is_create(), - code_hash: call.code_hash.to_word(), - rw_counter_end_of_reversion: call.rw_counter_end_of_reversion, - caller_id: call.caller_id, - depth: call.depth, - caller_address: call.caller_address, - callee_address: call.address, - call_data_offset: call.call_data_offset, - call_data_length: call.call_data_length, - return_data_offset: call.return_data_offset, - return_data_length: call.return_data_length, - value: call.value, - is_success: call.is_success, - is_persistent: call.is_persistent, - is_static: call.is_static, - }) - .collect(), - steps: tx - .steps() - .iter() - .map(step_convert) - .chain( - (if is_last_tx { - Some(iter::once(ExecStep { - // if it is the first tx, less 1 rw lookup, refer to end_tx gadget - rw_counter: tx.steps().last().unwrap().rwc.0 + 9 - (id == 1) as usize, - execution_state: ExecutionState::EndBlock, - ..Default::default() - })) - } else { - None - }) - .into_iter() - .flatten(), - ) - .collect(), - } -} - -pub fn block_convert( - block: &circuit_input_builder::Block, - code_db: &bus_mapping::state_db::CodeDB, -) -> Block { - Block { - randomness: Fr::rand(), - context: block.into(), - rws: RwMap::from(&block.container), - txs: block - .txs() - .iter() - .enumerate() - .map(|(idx, tx)| tx_convert(tx, idx + 1, idx + 1 == block.txs().len())) - .collect(), - bytecodes: block - .txs() - .iter() - .flat_map(|tx| { - tx.calls() - .iter() - .map(|call| call.code_hash) - .unique() - .into_iter() - .map(|code_hash| { - let bytecode = Bytecode::new(code_db.0.get(&code_hash).unwrap().to_vec()); - (bytecode.hash, bytecode) - }) - }) - .collect(), - copy_events: block.copy_events.clone(), - } -} diff --git a/zkevm-circuits/src/witness/step.rs b/zkevm-circuits/src/witness/step.rs index 9d59a9f5ce..77c7c5286f 100644 --- a/zkevm-circuits/src/witness/step.rs +++ b/zkevm-circuits/src/witness/step.rs @@ -1,17 +1,11 @@ -use bus_mapping::{ - circuit_input_builder, - error::{ExecError, OogError}, - evm::OpcodeId, - operation, -}; - -use crate::{ - evm_circuit::{ - param::{N_BYTES_WORD, STACK_CAPACITY}, - step::ExecutionState, - }, - table::RwTableTag, -}; +use crate::evm_circuit::param::{N_BYTES_WORD, STACK_CAPACITY}; +use crate::evm_circuit::step::ExecutionState; +use crate::table::RwTableTag; +use bus_mapping::error::{ExecError, OogError}; +use bus_mapping::evm::OpcodeId; +use bus_mapping::{circuit_input_builder, operation}; +use eth_types::Address; +use std::collections::HashSet; /// Step executed in a transaction #[derive(Clone, Debug, Default, PartialEq, Eq)] @@ -40,6 +34,8 @@ pub struct ExecStep { pub log_id: usize, /// The opcode corresponds to the step pub opcode: Option, + /// Non existent account addresses + pub non_existent_accounts: HashSet
, } impl ExecStep { @@ -51,6 +47,11 @@ impl ExecStep { assert_eq!(self.memory_size % N_BYTES_WORD as u64, 0); self.memory_size / N_BYTES_WORD as u64 } + + /// Check if account address exists + pub fn account_exists(&self, address: &Address) -> bool { + !self.non_existent_accounts.contains(address) + } } impl From<&ExecError> for ExecutionState { @@ -238,5 +239,6 @@ pub(super) fn step_convert(step: &circuit_input_builder::ExecStep) -> ExecStep { memory_size: step.memory_size as u64, reversible_write_counter: step.reversible_write_counter, log_id: step.log_id, + non_existent_accounts: step.non_existent_accounts.clone(), } } From 6c50cf9aafc35113bc5817cf565db77bc8f0d3c9 Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Tue, 13 Dec 2022 06:49:48 +0800 Subject: [PATCH 3/9] Update tests. --- bus-mapping/src/evm/opcodes/balance.rs | 18 ++++++++++++++---- .../src/evm_circuit/execution/balance.rs | 18 ++++++++++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/bus-mapping/src/evm/opcodes/balance.rs b/bus-mapping/src/evm/opcodes/balance.rs index c05cd47fbe..e563a1bb13 100644 --- a/bus-mapping/src/evm/opcodes/balance.rs +++ b/bus-mapping/src/evm/opcodes/balance.rs @@ -116,7 +116,7 @@ mod balance_tests { test_ok(true, true); } - fn test_ok(is_existing: bool, is_warm: bool) { + fn test_ok(exists: bool, is_warm: bool) { let address = address!("0xaabbccddee000000000000000000000000000000"); // Pop balance first for warm account. @@ -134,7 +134,7 @@ mod balance_tests { STOP }); - let balance = if is_existing { + let balance = if exists { Word::from(800u64) } else { Word::zero() @@ -148,7 +148,13 @@ mod balance_tests { .address(address!("0x0000000000000000000000000000000000000010")) .balance(Word::from(1u64 << 20)) .code(code.clone()); - accs[1].address(address).balance(balance); + if exists { + accs[1].address(address).balance(balance); + } else { + accs[1] + .address(address!("0x0000000000000000000000000000000000000020")) + .balance(Word::from(1u64 << 20)); + } accs[2] .address(address!("0x0000000000000000000000000000000000cafe01")) .balance(Word::from(1u64 << 20)); @@ -246,7 +252,11 @@ mod balance_tests { operation.op(), &AccountOp { address, - field: AccountField::Balance, + field: if exists { + AccountField::Balance + } else { + AccountField::NonExisting + }, value: balance, value_prev: balance, } diff --git a/zkevm-circuits/src/evm_circuit/execution/balance.rs b/zkevm-circuits/src/evm_circuit/execution/balance.rs index 4dfa311dc0..008f5b425b 100644 --- a/zkevm-circuits/src/evm_circuit/execution/balance.rs +++ b/zkevm-circuits/src/evm_circuit/execution/balance.rs @@ -208,12 +208,15 @@ mod test { .address(address!("0x000000000000000000000000000000000000cafe")) .balance(Word::from(1_u64 << 20)) .code(code); - accs[1].address(address); if let Some(account) = account { - accs[1].balance(account.balance); + accs[1].address(address).balance(account.balance); + } else { + accs[1] + .address(address!("0x0000000000000000000000000000000000000010")) + .balance(Word::from(1_u64 << 20)); } accs[2] - .address(address!("0x0000000000000000000000000000000000000010")) + .address(address!("0x0000000000000000000000000000000000000020")) .balance(Word::from(1_u64 << 20)); }, |mut txs, accs| { @@ -274,12 +277,15 @@ mod test { |accs| { accs[0].address(addr_b).code(code_b); accs[1].address(addr_a).code(code_a); - accs[2].address(address); if let Some(account) = account { - accs[2].balance(account.balance); + accs[2].address(address).balance(account.balance); + } else { + accs[2] + .address(mock::MOCK_ACCOUNTS[2]) + .balance(Word::from(1_u64 << 20)); } accs[3] - .address(mock::MOCK_ACCOUNTS[2]) + .address(mock::MOCK_ACCOUNTS[3]) .balance(Word::from(1_u64 << 20)); }, |mut txs, accs| { From 1ee1d4c3c1e30d544f783589325950a595b9ab21 Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Tue, 13 Dec 2022 06:56:41 +0800 Subject: [PATCH 4/9] Update test comments. --- zkevm-circuits/src/evm_circuit/execution/balance.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zkevm-circuits/src/evm_circuit/execution/balance.rs b/zkevm-circuits/src/evm_circuit/execution/balance.rs index 008f5b425b..e6ad4c3721 100644 --- a/zkevm-circuits/src/evm_circuit/execution/balance.rs +++ b/zkevm-circuits/src/evm_circuit/execution/balance.rs @@ -208,6 +208,7 @@ mod test { .address(address!("0x000000000000000000000000000000000000cafe")) .balance(Word::from(1_u64 << 20)) .code(code); + // Set balance if account exists. if let Some(account) = account { accs[1].address(address).balance(account.balance); } else { @@ -277,6 +278,7 @@ mod test { |accs| { accs[0].address(addr_b).code(code_b); accs[1].address(addr_a).code(code_a); + // Set balance if account exists. if let Some(account) = account { accs[2].address(address).balance(account.balance); } else { From 129d531231a721dae552cd2d281dd98bd8d9f075 Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Sat, 17 Dec 2022 10:01:20 +0800 Subject: [PATCH 5/9] Replace `table_assignment_aux` invocation with `value_assignment`. --- zkevm-circuits/src/evm_circuit/execution/balance.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/zkevm-circuits/src/evm_circuit/execution/balance.rs b/zkevm-circuits/src/evm_circuit/execution/balance.rs index e6ad4c3721..a8966edea2 100644 --- a/zkevm-circuits/src/evm_circuit/execution/balance.rs +++ b/zkevm-circuits/src/evm_circuit/execution/balance.rs @@ -124,9 +124,7 @@ impl ExecutionGadget for BalanceGadget { let exists = step.account_exists(&address); let balance = if exists { - block.rws[step.rw_indices[5]] - .table_assignment_aux(block.randomness) - .value + block.rws[step.rw_indices[5]].value_assignment(block.randomness) } else { F::zero() }; From 9c960f8b516340be0f300037718056e0430b0e02 Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Tue, 20 Dec 2022 17:03:25 +0800 Subject: [PATCH 6/9] Replace `cb.rw_counter_offset()` with `7.expr()`. --- zkevm-circuits/src/evm_circuit/execution/balance.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zkevm-circuits/src/evm_circuit/execution/balance.rs b/zkevm-circuits/src/evm_circuit/execution/balance.rs index a8966edea2..8b5d259ff3 100644 --- a/zkevm-circuits/src/evm_circuit/execution/balance.rs +++ b/zkevm-circuits/src/evm_circuit/execution/balance.rs @@ -70,7 +70,7 @@ impl ExecutionGadget for BalanceGadget { + (1.expr() - is_warm.expr()) * GasCost::COLD_ACCOUNT_ACCESS.expr(); let step_state_transition = StepStateTransition { - rw_counter: Delta(cb.rw_counter_offset()), + rw_counter: Delta(7.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta(0.expr()), gas_left: Delta(-gas_cost), From 065b3f99882fc269f90c5869125e9b20192cea3b Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Tue, 20 Dec 2022 17:35:23 +0800 Subject: [PATCH 7/9] Delete `non_existent_accounts` hash set, and fix to match `Rw` type for account existence. --- .../src/circuit_input_builder/execution.rs | 12 +-------- .../circuit_input_builder/input_state_ref.rs | 3 --- .../src/evm_circuit/execution/balance.rs | 27 ++++++++++++++----- zkevm-circuits/src/witness/step.rs | 10 ------- 4 files changed, 21 insertions(+), 31 deletions(-) diff --git a/bus-mapping/src/circuit_input_builder/execution.rs b/bus-mapping/src/circuit_input_builder/execution.rs index 86e67b4e5d..875e5b936f 100644 --- a/bus-mapping/src/circuit_input_builder/execution.rs +++ b/bus-mapping/src/circuit_input_builder/execution.rs @@ -6,11 +6,10 @@ use crate::{ }; use eth_types::{ evm_types::{Gas, GasCost, OpcodeId, ProgramCounter}, - Address, GethExecStep, Word, H256, + GethExecStep, Word, H256, }; use gadgets::impl_expr; use halo2_proofs::plonk::Expression; -use std::collections::HashSet; use strum_macros::EnumIter; /// An execution step of the EVM. @@ -45,8 +44,6 @@ pub struct ExecStep { pub bus_mapping_instance: Vec, /// Error generated by this step pub error: Option, - /// Non existent account addresses - pub non_existent_accounts: HashSet
, } impl ExecStep { @@ -72,7 +69,6 @@ impl ExecStep { log_id, bus_mapping_instance: Vec::new(), error: None, - non_existent_accounts: HashSet::new(), } } @@ -83,11 +79,6 @@ impl ExecStep { Some(ExecError::OutOfGas(_) | ExecError::StackOverflow | ExecError::StackUnderflow) ) } - - /// Add a non existent account address. - pub fn add_non_existent_account(&mut self, address: Address) { - self.non_existent_accounts.insert(address); - } } impl Default for ExecStep { @@ -106,7 +97,6 @@ impl Default for ExecStep { log_id: 0, bus_mapping_instance: Vec::new(), error: None, - non_existent_accounts: HashSet::new(), } } } diff --git a/bus-mapping/src/circuit_input_builder/input_state_ref.rs b/bus-mapping/src/circuit_input_builder/input_state_ref.rs index 79c093bdbe..6fb41bf800 100644 --- a/bus-mapping/src/circuit_input_builder/input_state_ref.rs +++ b/bus-mapping/src/circuit_input_builder/input_state_ref.rs @@ -276,9 +276,6 @@ impl<'a> CircuitInputStateRef<'a> { value: Word, value_prev: Word, ) -> Result<(), Error> { - if field == AccountField::NonExisting { - step.add_non_existent_account(address); - } self.push_op( step, RW::READ, diff --git a/zkevm-circuits/src/evm_circuit/execution/balance.rs b/zkevm-circuits/src/evm_circuit/execution/balance.rs index 8b5d259ff3..c270630d7b 100644 --- a/zkevm-circuits/src/evm_circuit/execution/balance.rs +++ b/zkevm-circuits/src/evm_circuit/execution/balance.rs @@ -7,11 +7,11 @@ use crate::evm_circuit::util::constraint_builder::{ ConstraintBuilder, ReversionInfo, StepStateTransition, }; use crate::evm_circuit::util::{from_bytes, select, CachedRegion, Cell, RandomLinearCombination}; -use crate::evm_circuit::witness::{Block, Call, ExecStep, Transaction}; +use crate::evm_circuit::witness::{Block, Call, ExecStep, Rw, Transaction}; use crate::table::{AccountFieldTag, CallContextFieldTag}; use crate::util::Expr; use eth_types::evm_types::GasCost; -use eth_types::{Field, ToAddress}; +use eth_types::{Field, ToAddress, ToLittleEndian}; use halo2_proofs::circuit::Value; use halo2_proofs::plonk::Error; @@ -122,12 +122,25 @@ impl ExecutionGadget for BalanceGadget { self.is_warm .assign(region, offset, Value::known(F::from(is_warm)))?; - let exists = step.account_exists(&address); - let balance = if exists { - block.rws[step.rw_indices[5]].value_assignment(block.randomness) - } else { - F::zero() + let (balance, exists) = match block.rws[step.rw_indices[5]] { + Rw::Account { + field_tag: AccountFieldTag::Balance, + value, + .. + } => ( + RandomLinearCombination::random_linear_combine( + value.to_le_bytes(), + block.randomness, + ), + true, + ), + Rw::Account { + field_tag: AccountFieldTag::NonExisting, + .. + } => (F::zero(), false), + _ => unreachable!(), }; + self.balance.assign(region, offset, Value::known(balance))?; self.exists .assign(region, offset, Value::known(F::from(exists)))?; diff --git a/zkevm-circuits/src/witness/step.rs b/zkevm-circuits/src/witness/step.rs index 78fedabc40..32736a98e5 100644 --- a/zkevm-circuits/src/witness/step.rs +++ b/zkevm-circuits/src/witness/step.rs @@ -4,8 +4,6 @@ use crate::table::RwTableTag; use bus_mapping::error::{ExecError, OogError}; use bus_mapping::evm::OpcodeId; use bus_mapping::{circuit_input_builder, operation}; -use eth_types::Address; -use std::collections::HashSet; /// Step executed in a transaction #[derive(Clone, Debug, Default, PartialEq, Eq)] @@ -34,8 +32,6 @@ pub struct ExecStep { pub log_id: usize, /// The opcode corresponds to the step pub opcode: Option, - /// Non existent account addresses - pub non_existent_accounts: HashSet
, } impl ExecStep { @@ -47,11 +43,6 @@ impl ExecStep { assert_eq!(self.memory_size % N_BYTES_WORD as u64, 0); self.memory_size / N_BYTES_WORD as u64 } - - /// Check if account address exists - pub fn account_exists(&self, address: &Address) -> bool { - !self.non_existent_accounts.contains(address) - } } impl From<&ExecError> for ExecutionState { @@ -238,6 +229,5 @@ pub(super) fn step_convert(step: &circuit_input_builder::ExecStep) -> ExecStep { memory_size: step.memory_size as u64, reversible_write_counter: step.reversible_write_counter, log_id: step.log_id, - non_existent_accounts: step.non_existent_accounts.clone(), } } From c01d3055c7968a6f9baa6671d7e550b8e0bdc1ff Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Tue, 20 Dec 2022 18:00:15 +0800 Subject: [PATCH 8/9] Revert useless format. --- zkevm-circuits/src/witness/step.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/zkevm-circuits/src/witness/step.rs b/zkevm-circuits/src/witness/step.rs index 32736a98e5..848944d6e8 100644 --- a/zkevm-circuits/src/witness/step.rs +++ b/zkevm-circuits/src/witness/step.rs @@ -1,9 +1,17 @@ -use crate::evm_circuit::param::{N_BYTES_WORD, STACK_CAPACITY}; -use crate::evm_circuit::step::ExecutionState; -use crate::table::RwTableTag; -use bus_mapping::error::{ExecError, OogError}; -use bus_mapping::evm::OpcodeId; -use bus_mapping::{circuit_input_builder, operation}; +use bus_mapping::{ + circuit_input_builder, + error::{ExecError, OogError}, + evm::OpcodeId, + operation, +}; + +use crate::{ + evm_circuit::{ + param::{N_BYTES_WORD, STACK_CAPACITY}, + step::ExecutionState, + }, + table::RwTableTag, +}; /// Step executed in a transaction #[derive(Clone, Debug, Default, PartialEq, Eq)] From 2adef8a310c2c46edb20a2409ac2a07a921db40a Mon Sep 17 00:00:00 2001 From: Steven Gu Date: Wed, 21 Dec 2022 14:03:27 +0800 Subject: [PATCH 9/9] Fix wrong revert in `step.rs` and others according to code review. --- zkevm-circuits/src/evm_circuit/execution/balance.rs | 12 +++++++----- zkevm-circuits/src/witness/step.rs | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/zkevm-circuits/src/evm_circuit/execution/balance.rs b/zkevm-circuits/src/evm_circuit/execution/balance.rs index c270630d7b..8dc3054f89 100644 --- a/zkevm-circuits/src/evm_circuit/execution/balance.rs +++ b/zkevm-circuits/src/evm_circuit/execution/balance.rs @@ -46,15 +46,14 @@ impl ExecutionGadget for BalanceGadget { Some(&mut reversion_info), ); + let balance = cb.query_cell(); let exists = cb.query_bool(); - let balance = cb.condition(exists.expr(), |cb| { - let balance = cb.query_cell(); + cb.condition(exists.expr(), |cb| { cb.account_read( from_bytes::expr(&address.cells), AccountFieldTag::Balance, balance.expr(), ); - balance }); cb.condition(1.expr() - exists.expr(), |cb| { cb.account_read( @@ -66,8 +65,11 @@ impl ExecutionGadget for BalanceGadget { cb.stack_push(select::expr(exists.expr(), balance.expr(), 0.expr())); - let gas_cost = is_warm.expr() * GasCost::WARM_ACCESS.expr() - + (1.expr() - is_warm.expr()) * GasCost::COLD_ACCOUNT_ACCESS.expr(); + let gas_cost = select::expr( + is_warm.expr(), + GasCost::WARM_ACCESS.expr(), + GasCost::COLD_ACCOUNT_ACCESS.expr(), + ); let step_state_transition = StepStateTransition { rw_counter: Delta(7.expr()), diff --git a/zkevm-circuits/src/witness/step.rs b/zkevm-circuits/src/witness/step.rs index 848944d6e8..f342ed0ecf 100644 --- a/zkevm-circuits/src/witness/step.rs +++ b/zkevm-circuits/src/witness/step.rs @@ -126,6 +126,7 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::ADD | OpcodeId::SUB => ExecutionState::ADD_SUB, OpcodeId::ADDMOD => ExecutionState::ADDMOD, OpcodeId::ADDRESS => ExecutionState::ADDRESS, + OpcodeId::BALANCE => ExecutionState::BALANCE, OpcodeId::MUL | OpcodeId::DIV | OpcodeId::MOD => ExecutionState::MUL_DIV_MOD, OpcodeId::MULMOD => ExecutionState::MULMOD, OpcodeId::SDIV | OpcodeId::SMOD => ExecutionState::SDIV_SMOD, @@ -181,7 +182,6 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::RETURNDATASIZE => ExecutionState::RETURNDATASIZE, OpcodeId::RETURNDATACOPY => ExecutionState::RETURNDATACOPY, // dummy ops - OpcodeId::BALANCE => dummy!(ExecutionState::BALANCE), OpcodeId::SAR => dummy!(ExecutionState::SAR), OpcodeId::EXTCODESIZE => dummy!(ExecutionState::EXTCODESIZE), OpcodeId::EXTCODECOPY => dummy!(ExecutionState::EXTCODECOPY),