diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index 7e7eb090c3..5cc41561b0 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -4,17 +4,16 @@ use crate::evm::opcodes::gen_associated_ops; use crate::exec_trace::OperationRef; use crate::geth_errors::*; use crate::operation::container::OperationContainer; -use crate::operation::{MemoryOp, Op, Operation, RWCounter, StackOp, RW}; +use crate::operation::{ + AccountField, CallContextField, MemoryOp, Op, OpEnum, Operation, RWCounter, StackOp, Target, RW, +}; use crate::state_db::{self, CodeDB, StateDB}; use crate::Error; use core::fmt::Debug; use eth_types::evm_types::{Gas, GasCost, MemoryAddress, OpcodeId, ProgramCounter, StackAddress}; -use eth_types::geth_types::BlockConstants; -use eth_types::{ - self, Address, ChainConstants, GethExecStep, GethExecTrace, Hash, ToAddress, ToBigEndian, Word, -}; +use eth_types::{self, Address, GethExecStep, GethExecTrace, Hash, ToAddress, ToBigEndian, Word}; use ethers_core::utils::{get_contract_address, get_create2_address}; -use std::collections::{hash_map::Entry, HashMap, HashSet}; +use std::collections::{hash_map::Entry, BTreeMap, HashMap, HashSet}; use crate::rpc::GethClient; use ethers_providers::JsonRpcClient; @@ -118,8 +117,6 @@ pub struct ExecStep { pub bus_mapping_instance: Vec, /// Error generated by this step pub error: Option, - /// The step has been reverted - pub reverted: bool, } impl ExecStep { @@ -142,7 +139,6 @@ impl ExecStep { swc, bus_mapping_instance: Vec::new(), error: None, - reverted: false, } } } @@ -153,6 +149,10 @@ pub struct BlockContext { /// Used to track the global counter in every operation in the block. /// Contains the next available value. pub rwc: RWCounter, + /// Map call_id to (tx_index, call_index) (where tx_index is the index used + /// in Block.txs and call_index is the index used in Transaction. + /// calls). + call_map: HashMap, } impl Default for BlockContext { @@ -166,6 +166,7 @@ impl BlockContext { pub fn new() -> Self { Self { rwc: RWCounter::new(), + call_map: HashMap::new(), } } } @@ -173,10 +174,23 @@ impl BlockContext { /// Circuit Input related to a block. #[derive(Debug)] pub struct Block { - /// Constants associated to the chain. - pub chain_const: ChainConstants, - /// Constants associated to the block. - pub block_const: BlockConstants, + /// chain id + pub chain_id: Word, + /// history hashes contains most recent 256 block hashes in history, where + /// the lastest one is at history_hashes[history_hashes.len() - 1]. + pub history_hashes: Vec, + /// coinbase + pub coinbase: Address, + /// time + pub gas_limit: u64, + /// number + pub number: Word, + /// difficulty + pub timestamp: Word, + /// gas limit + pub difficulty: Word, + /// base fee + pub base_fee: Word, /// Container of operations done in this block. pub container: OperationContainer, txs: Vec, @@ -186,17 +200,29 @@ pub struct Block { impl Block { /// Create a new block. pub fn new( - _eth_block: ð_types::Block, - chain_const: ChainConstants, - block_const: BlockConstants, - ) -> Self { - Self { - chain_const, - block_const, + chain_id: Word, + history_hashes: Vec, + eth_block: ð_types::Block, + ) -> Result { + Ok(Self { + chain_id, + history_hashes, + coinbase: eth_block.author, + gas_limit: eth_block.gas_limit.low_u64(), + number: eth_block + .number + .ok_or(Error::EthTypeError(eth_types::Error::IncompleteBlock))? + .low_u64() + .into(), + timestamp: eth_block.timestamp, + difficulty: eth_block.difficulty, + base_fee: eth_block + .base_fee_per_gas + .ok_or(Error::EthTypeError(eth_types::Error::IncompleteBlock))?, container: OperationContainer::new(), txs: Vec::new(), code: HashMap::new(), - } + }) } /// Return the list of transactions of this block. @@ -211,7 +237,7 @@ impl Block { } /// Type of a *CALL*/CREATE* Function. -#[derive(Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum CallKind { /// CALL Call, @@ -250,22 +276,44 @@ impl TryFrom for CallKind { } /// Circuit Input related to an Ethereum Call -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Call { /// Unique call identifier within the Block. pub call_id: usize, + /// Caller's id. + pub caller_id: usize, /// Type of call pub kind: CallKind, /// This call is being executed without write access (STATIC) pub is_static: bool, /// This call generated implicity by a Transaction. pub is_root: bool, + /// This call is persistent or call stack reverts at some point + pub is_persistent: bool, + /// This call ends successfully or not + pub is_success: bool, + /// This rw_counter at the end of reversion + pub rw_counter_end_of_reversion: usize, + /// Address of caller + pub caller_address: Address, /// Address where this call is being executed pub address: Address, /// Code Source pub code_source: CodeSource, /// Code Hash pub code_hash: Hash, + /// Depth + pub depth: usize, + /// Value + pub value: Word, + /// Call data offset + pub call_data_offset: u64, + /// Call data length + pub call_data_length: u64, + /// Return data offset + pub return_data_offset: u64, + /// Return data length + pub return_data_length: u64, } impl Call { @@ -277,56 +325,141 @@ impl Call { } /// Context of a [`Call`]. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct CallContext { + // Index of call + index: usize, /// State Write Counter tracks the count of state write operations in the - /// call. When a subcall in this call succeeds, the `swc` increases by the + /// call. When a subcall in this call succeeds, the `swc` increases by the /// number of successful state writes in the subcall. - pub swc: usize, + swc: usize, +} + +/// A reversion group is the collection of calls and the operations which are +/// [`Operation::reversible`] that happened in them, that will be reverted at +/// once when the call that initiated this reversion group eventually ends with +/// failure (and thus reverts). +#[derive(Debug, Default)] +pub struct ReversionGroup { + /// List of `index` and `swc_offset` of calls belong to this group. + /// `swc_offset` is the number of reversible operations that have + /// happened before the call within the same reversion group. + calls: Vec<(usize, usize)>, + /// List of `step_index` and `OperationRef` that have been done in this + /// group. + op_refs: Vec<(usize, OperationRef)>, } #[derive(Debug)] /// Context of a [`Transaction`] which can mutate in an [`ExecStep`]. pub struct TransactionContext { - /// Call Stack by indices with CallContext. - /// The call_stack will always have a fixed element at index 0 which - /// corresponds to the call implicitly created by the transaction. - call_stack: Vec<(usize, CallContext)>, + /// Unique identifier of transaction of the block. The value is `index + 1`. + id: usize, + /// Call stack. + calls: Vec, + /// Call `is_success` indexed by `call_index`. + call_is_success: Vec, + /// Reversion groups by failure calls. We keep the reversion groups in a + /// stack because it's possible to encounter a revert within a revert, + /// and in such case, we must only process the reverted operation once: + /// in the inner most revert (which we track with the last element in + /// the reversion groups stack), and skip it in the outer revert. + reversion_groups: Vec, } impl TransactionContext { /// Create a new Self. - pub fn new(_eth_tx: ð_types::Transaction) -> Self { - Self { - call_stack: vec![(0, CallContext { swc: 0 })], - } + pub fn new(eth_tx: ð_types::Transaction, geth_trace: &GethExecTrace) -> Result { + // Iterate over geth_trace to inspect and collect each call's is_success, which + // is at the top of stack at the step after a call. + let call_is_success = { + let mut call_is_success_map = BTreeMap::new(); + let mut call_indices = Vec::new(); + for (index, geth_step) in geth_trace.struct_logs.iter().enumerate() { + if let Some(geth_next_step) = geth_trace.struct_logs.get(index + 1) { + // Dive into call + if geth_step.depth + 1 == geth_next_step.depth { + call_indices.push(index); + // Emerge from call + } else if geth_step.depth - 1 == geth_next_step.depth { + let is_success = !geth_next_step.stack.last()?.is_zero(); + call_is_success_map.insert(call_indices.pop().unwrap(), is_success); + } + } + } + + std::iter::once(!geth_trace.failed) + .chain(call_is_success_map.into_values()) + .collect() + }; + + let mut tx_ctx = Self { + id: eth_tx + .transaction_index + .ok_or(Error::EthTypeError(eth_types::Error::IncompleteBlock))? + .as_u64() as usize + + 1, + call_is_success, + calls: Vec::new(), + reversion_groups: Vec::new(), + }; + tx_ctx.push_call_ctx(0); + + Ok(tx_ctx) } - /// Return the index and context of the current call (the last call in the - /// call stack). + /// Return id of the this transaction. + pub fn id(&self) -> usize { + self.id + } + + /// Return the index of the current call (the last call in the call stack). fn call_index(&self) -> usize { - let (index, _) = self.call_stack.last().expect("call_stack is empty"); - *index + self.calls.last().expect("calls should not be empty").index } fn call_ctx(&self) -> &CallContext { - let (_, call_ctx) = self.call_stack.last().expect("call_stack is empty"); - call_ctx + self.calls.last().expect("calls should not be empty") } fn call_ctx_mut(&mut self) -> &mut CallContext { - let (_, ref mut call_ctx) = self.call_stack.last_mut().expect("call_stack is empty"); - call_ctx - } + self.calls.last_mut().expect("calls should not be empty") + } + + /// Push a new call context and its index into the call stack. + fn push_call_ctx(&mut self, call_idx: usize) { + if !self.call_is_success[call_idx] { + self.reversion_groups.push(ReversionGroup { + calls: vec![(call_idx, 0)], + op_refs: Vec::new(), + }) + } else if let Some(reversion_group) = self.reversion_groups.last_mut() { + let caller_swc = self.calls.last().expect("calls should not be empty").swc; + let caller_swc_offset = reversion_group + .calls + .last() + .expect("calls should not be empty") + .1; + reversion_group + .calls + .push((call_idx, caller_swc + caller_swc_offset)); + } - /// Push a new call index and context into the call stack. - fn push_call_index_ctx(&mut self, index: usize, call_ctx: CallContext) { - self.call_stack.push((index, call_ctx)); + self.calls.push(CallContext { + index: call_idx, + swc: 0, + }); } /// Pop the last entry in the call stack. - fn pop_call_index_ctx(&mut self) -> Option<(usize, CallContext)> { - self.call_stack.pop() + fn pop_call_ctx(&mut self) { + let call = self.calls.pop().expect("calls should not be empty"); + // Accumulate state_write_counter if call is success + if self.call_is_success[call.index] { + if let Some(caller) = self.calls.last_mut() { + caller.swc += call.swc; + } + } } } @@ -337,6 +470,8 @@ pub struct Transaction { pub nonce: u64, /// Gas pub gas: u64, + /// Gas price + pub gas_price: Word, /// From / Caller Address pub from: Address, // caller_address /// To / Callee Address @@ -356,46 +491,74 @@ impl Transaction { sdb: &StateDB, code_db: &mut CodeDB, eth_tx: ð_types::Transaction, + is_success: bool, ) -> Result { - let mut calls = Vec::new(); - if let Some(address) = eth_tx.to { + let (found, _) = sdb.get_account(ð_tx.from); + if !found { + return Err(Error::AccountNotFound(eth_tx.from)); + } + + let call = if let Some(address) = eth_tx.to { // Contract Call / Transfer let (found, account) = sdb.get_account(&address); if !found { return Err(Error::AccountNotFound(address)); } let code_hash = account.code_hash; - calls.push(Call { + Call { call_id, + caller_id: 0, kind: CallKind::Call, is_static: false, is_root: true, + is_persistent: is_success, + is_success, + rw_counter_end_of_reversion: 0, + caller_address: eth_tx.from, address, code_source: CodeSource::Address(address), code_hash, - }); + depth: 1, + value: eth_tx.value, + call_data_offset: 0, + call_data_length: eth_tx.input.as_ref().len() as u64, + return_data_offset: 0, + return_data_length: 0, + } } else { // Contract creation let code_hash = code_db.insert(eth_tx.input.to_vec()); - calls.push(Call { + Call { call_id, + caller_id: 0, kind: CallKind::Create, is_static: false, is_root: true, + is_persistent: is_success, + is_success, + rw_counter_end_of_reversion: 0, + caller_address: eth_tx.from, address: get_contract_address(eth_tx.from, eth_tx.nonce), code_source: CodeSource::Tx, code_hash, - }); - } + depth: 1, + value: eth_tx.value, + call_data_offset: 0, + call_data_length: 0, + return_data_offset: 0, + return_data_length: 0, + } + }; + Ok(Self { nonce: eth_tx.nonce.as_u64(), gas: eth_tx.gas.as_u64(), + gas_price: eth_tx.gas_price.unwrap_or_default(), from: eth_tx.from, to: eth_tx.to.unwrap_or_default(), value: eth_tx.value, input: eth_tx.input.to_vec(), - - calls, + calls: vec![call], steps: Vec::new(), }) } @@ -420,26 +583,8 @@ impl Transaction { &self.calls } - fn push_call( - &mut self, - parent_index: usize, - call_id: usize, - kind: CallKind, - address: Address, - code_source: CodeSource, - code_hash: Hash, - ) -> usize { - let is_static = kind == CallKind::StaticCall || self.calls[parent_index].is_static; - self.calls.push(Call { - call_id, - kind, - is_static, - is_root: false, - address, - code_source, - code_hash, - }); - self.calls.len() - 1 + fn push_call(&mut self, call: Call) { + self.calls.push(call); } } @@ -467,14 +612,44 @@ impl<'a> CircuitInputStateRef<'a> { /// [`RWCounter`] and then adds a reference to the stored operation /// ([`OperationRef`]) inside the bus-mapping instance of the current /// [`ExecStep`]. Then increase the block_ctx [`RWCounter`] by one. - pub fn push_op(&mut self, op: T) { - let op_ref = self - .block - .container - .insert(Operation::new(self.block_ctx.rwc.inc_pre(), op)); + pub fn push_op(&mut self, rw: RW, op: T) { + let op_ref = + self.block + .container + .insert(Operation::new(self.block_ctx.rwc.inc_pre(), rw, op)); self.step.bus_mapping_instance.push(op_ref); } + /// Push an [`Operation`] with reversible to be true into the + /// [`OperationContainer`] with the next [`RWCounter`] and then adds a + /// reference to the stored operation ([`OperationRef`]) inside the + /// bus-mapping instance of the current [`ExecStep`]. Then increase the + /// block_ctx [`RWCounter`] by one. + /// This method should be used in `Opcode::gen_associated_ops` instead of + /// `push_op` when the operation is `RW::WRITE` and it can be reverted (for + /// example, a write `StorageOp`). + pub fn push_op_reversible(&mut self, rw: RW, op: T) { + let op_ref = self.block.container.insert(Operation::new_reversible( + self.block_ctx.rwc.inc_pre(), + rw, + op, + )); + self.step.bus_mapping_instance.push(op_ref); + + // Increase state_write_counter + self.call_ctx_mut().swc += 1; + + // Add the operation into reversible_ops if this call is not persistent + if !self.call().is_persistent { + self.tx_ctx + .reversion_groups + .last_mut() + .expect("reversion_groups should not be empty for non-persistent call") + .op_refs + .push((self.tx.steps.len(), op_ref)); + } + } + /// Push a [`MemoryOp`] into the [`OperationContainer`] with the next /// [`RWCounter`] and `call_id`, and then adds a reference to /// the stored operation ([`OperationRef`]) inside the bus-mapping @@ -482,12 +657,14 @@ impl<'a> CircuitInputStateRef<'a> { /// [`RWCounter`] by one. pub fn push_memory_op(&mut self, rw: RW, address: MemoryAddress, value: u8) { let call_id = self.call().call_id; - self.push_op(MemoryOp { + self.push_op( rw, - call_id, - address, - value, - }); + MemoryOp { + call_id, + address, + value, + }, + ); } /// Push a [`StackOp`] into the [`OperationContainer`] with the next @@ -497,12 +674,14 @@ impl<'a> CircuitInputStateRef<'a> { /// [`RWCounter`] by one. pub fn push_stack_op(&mut self, rw: RW, address: StackAddress, value: Word) { let call_id = self.call().call_id; - self.push_op(StackOp { + self.push_op( rw, - call_id, - address, - value, - }); + StackOp { + call_id, + address, + value, + }, + ); } /// Reference to the current Call @@ -510,32 +689,32 @@ impl<'a> CircuitInputStateRef<'a> { &self.tx.calls[self.tx_ctx.call_index()] } + /// Mutable reference to the current Call + pub fn call_mut(&mut self) -> &mut Call { + &mut self.tx.calls[self.tx_ctx.call_index()] + } + /// Reference to the current CallContext pub fn call_ctx(&self) -> &CallContext { self.tx_ctx.call_ctx() } - /// Mutable reference to the current Call - pub fn call_mut(&mut self) -> &mut Call { - &mut self.tx.calls[self.tx_ctx.call_index()] + /// Mutable reference to the call CallContext + pub fn call_ctx_mut(&mut self) -> &mut CallContext { + self.tx_ctx.call_ctx_mut() } /// Push a new [`Call`] into the [`Transaction`], and add its index and /// [`CallContext`] in the `call_stack` of the [`TransactionContext`] - pub fn push_call( - &mut self, - kind: CallKind, - address: Address, - code_source: CodeSource, - code_hash: Hash, - ) { - let parent_index = self.tx_ctx.call_index(); - let call_id = self.block_ctx.rwc.0; - let index = self - .tx - .push_call(parent_index, call_id, kind, address, code_source, code_hash); - self.tx_ctx - .push_call_index_ctx(index, CallContext { swc: 0 }); + pub fn push_call(&mut self, call: Call) { + let call_id = call.call_id; + + self.tx_ctx.push_call_ctx(self.tx.calls.len()); + self.tx.push_call(call); + + self.block_ctx + .call_map + .insert(call_id, (self.block.txs.len(), self.tx_ctx.call_index())); } /// Return the contract address of a CREATE step. This is calculated by @@ -561,22 +740,44 @@ impl<'a> CircuitInputStateRef<'a> { )) } - /// Return the contract address of a *CALL*/CREATE* step. - fn call_address(&self, step: &GethExecStep) -> Result { - Ok(match step.op { - OpcodeId::CALL | OpcodeId::CALLCODE | OpcodeId::DELEGATECALL | OpcodeId::STATICCALL => { - step.stack.nth_last(1)?.to_address() - } - OpcodeId::CREATE => self.create_address()?, - OpcodeId::CREATE2 => self.create2_address(step)?, - _ => return Err(Error::OpcodeIdNotCallType), - }) - } - - /// Handle a *CALL*/CREATE* step. - fn handle_call_create(&mut self, step: &GethExecStep) -> Result<(), Error> { + /// Parse [`Call`] from a *CALL*/CREATE* step. + pub fn parse_call(&mut self, step: &GethExecStep) -> Result { + let is_success = *self + .tx_ctx + .call_is_success + .get(self.tx.calls().len()) + .unwrap(); let kind = CallKind::try_from(step.op)?; - let address = self.call_address(step)?; + + let (caller_address, address, value) = match kind { + CallKind::Call => ( + self.call().address, + step.stack.nth_last(1)?.to_address(), + step.stack.nth_last(2)?, + ), + CallKind::CallCode => ( + self.call().address, + self.call().address, + step.stack.nth_last(2)?, + ), + CallKind::DelegateCall => (self.call().caller_address, self.call().address, 0.into()), + CallKind::StaticCall => ( + self.call().address, + step.stack.nth_last(1)?.to_address(), + 0.into(), + ), + CallKind::Create => ( + self.call().address, + self.create_address()?, + step.stack.last()?, + ), + CallKind::Create2 => ( + self.call().address, + self.create2_address(step)?, + step.stack.last()?, + ), + }; + let (code_source, code_hash) = match kind { CallKind::Create | CallKind::Create2 => { let init_code = get_create_init_code(step)?; @@ -584,39 +785,213 @@ impl<'a> CircuitInputStateRef<'a> { (CodeSource::Memory, code_hash) } _ => { - let (found, account) = self.sdb.get_account(&address); + let code_address = match kind { + CallKind::CallCode | CallKind::DelegateCall => { + step.stack.nth_last(1)?.to_address() + } + _ => address, + }; + let (found, account) = self.sdb.get_account(&code_address); if !found { - return Err(Error::AccountNotFound(address)); + return Err(Error::AccountNotFound(code_address)); } - (CodeSource::Address(address), (account.code_hash)) + (CodeSource::Address(code_address), account.code_hash) } }; - self.push_call(kind, address, code_source, code_hash); - Ok(()) + + let get_memory_offset_length = |nth: usize| -> Result<_, Error> { + let offset = step.stack.nth_last(nth)?; + let length = step.stack.nth_last(nth + 1)?; + if length.is_zero() { + return Ok((0, 0)); + } + Ok((offset.low_u64(), length.low_u64())) + }; + let (call_data_offset, call_data_length, return_data_offset, return_data_length) = + match kind { + CallKind::Call | CallKind::CallCode => { + let call_data = get_memory_offset_length(3)?; + let return_data = get_memory_offset_length(5)?; + (call_data.0, call_data.1, return_data.0, return_data.1) + } + CallKind::DelegateCall | CallKind::StaticCall => { + let call_data = get_memory_offset_length(2)?; + let return_data = get_memory_offset_length(4)?; + (call_data.0, call_data.1, return_data.0, return_data.1) + } + CallKind::Create | CallKind::Create2 => (0, 0, 0, 0), + }; + + let caller = self.call(); + let call = Call { + call_id: self.block_ctx.rwc.0, + caller_id: caller.call_id, + kind, + is_static: kind == CallKind::StaticCall || caller.is_static, + is_root: false, + is_persistent: caller.is_persistent && is_success, + is_success, + rw_counter_end_of_reversion: 0, + caller_address, + address, + code_source, + code_hash, + depth: caller.depth + 1, + value, + call_data_offset, + call_data_length, + return_data_offset, + return_data_length, + }; + + Ok(call) } - /// Handle a return step caused by any opcode that causes a return to the - /// previous call context. - fn handle_return( - &mut self, - step: &GethExecStep, - next_step: &GethExecStep, - ) -> Result<(), Error> { - if self.tx_ctx.call_stack.len() == 1 { - return Err(Error::InvalidGethExecStep( - "handle_tx: call stack will be empty", - Box::new(step.clone()), - )); + /// Return the reverted version of an op by op_ref only if the original op + /// was reversible. + fn get_rev_op_by_ref(&self, op_ref: &OperationRef) -> Option { + match op_ref { + OperationRef(Target::Storage, idx) => { + let operation = &self.block.container.storage[*idx]; + if operation.rw().is_write() && operation.reversible() { + Some(OpEnum::Storage(operation.op().reverse())) + } else { + None + } + } + OperationRef(Target::TxAccessListAccount, idx) => { + let operation = &self.block.container.tx_access_list_account[*idx]; + if operation.rw().is_write() && operation.reversible() { + Some(OpEnum::TxAccessListAccount(operation.op().reverse())) + } else { + None + } + } + OperationRef(Target::TxAccessListAccountStorage, idx) => { + let operation = &self.block.container.tx_access_list_account_storage[*idx]; + if operation.rw().is_write() && operation.reversible() { + Some(OpEnum::TxAccessListAccountStorage(operation.op().reverse())) + } else { + None + } + } + OperationRef(Target::TxRefund, idx) => { + let operation = &self.block.container.tx_refund[*idx]; + if operation.rw().is_write() && operation.reversible() { + Some(OpEnum::TxRefund(operation.op().reverse())) + } else { + None + } + } + OperationRef(Target::Account, idx) => { + let operation = &self.block.container.account[*idx]; + if operation.rw().is_write() && operation.reversible() { + Some(OpEnum::Account(operation.op().reverse())) + } else { + None + } + } + OperationRef(Target::AccountDestructed, idx) => { + let operation = &self.block.container.account_destructed[*idx]; + if operation.rw().is_write() && operation.reversible() { + Some(OpEnum::AccountDestructed(operation.op().reverse())) + } else { + None + } + } + _ => None, } - let (_, call_ctx) = self + } + + /// Apply reverted op to state and push to container. + fn apply_reverted_op(&mut self, op: OpEnum) -> OperationRef { + match op { + OpEnum::Storage(op) => { + let (_, account) = self.sdb.get_storage_mut(&op.address, &op.key); + *account = op.value; + self.block.container.insert(Operation::new( + self.block_ctx.rwc.inc_pre(), + RW::WRITE, + op, + )) + } + OpEnum::TxAccessListAccount(op) => { + if !op.value { + self.sdb.remove_account_from_access_list(&op.address); + } + self.block.container.insert(Operation::new( + self.block_ctx.rwc.inc_pre(), + RW::WRITE, + op, + )) + } + OpEnum::TxAccessListAccountStorage(op) => { + if !op.value { + self.sdb + .remove_account_storage_from_access_list(&(op.address, op.key)); + } + self.block.container.insert(Operation::new( + self.block_ctx.rwc.inc_pre(), + RW::WRITE, + op, + )) + } + OpEnum::Account(op) => { + let (_, account) = self.sdb.get_account_mut(&op.address); + match op.field { + AccountField::Nonce => account.nonce = op.value, + AccountField::Balance => account.balance = op.value, + AccountField::CodeHash => { + account.code_hash = op.value.to_be_bytes().into(); + } + } + self.block.container.insert(Operation::new( + self.block_ctx.rwc.inc_pre(), + RW::WRITE, + op, + )) + } + OpEnum::TxRefund(_) => unimplemented!(), + OpEnum::AccountDestructed(_) => unimplemented!(), + _ => unreachable!(), + } + } + + /// Handle a reversion group + fn handle_reversion(&mut self) { + let reversion_group = self .tx_ctx - .pop_call_index_ctx() - .expect("call stack is empty"); - // If the return was successful, accumulate the swc from the - // subcall. - if !next_step.stack.last()?.is_zero() { - self.tx_ctx.call_ctx_mut().swc += call_ctx.swc; + .reversion_groups + .pop() + .expect("reversion_groups should not be empty for non-persistent call"); + + // Apply reversions + for (step_index, op_ref) in reversion_group.op_refs.into_iter().rev() { + if let Some(op) = self.get_rev_op_by_ref(&op_ref) { + let rev_op_ref = self.apply_reverted_op(op); + self.tx.steps[step_index] + .bus_mapping_instance + .push(rev_op_ref); + } } + + // Set calls' `rw_counter_end_of_reversion` + let rwc = self.block_ctx.rwc.0 - 1; + for (call_idx, swc_offset) in reversion_group.calls { + self.tx.calls[call_idx].rw_counter_end_of_reversion = rwc - swc_offset; + } + } + + /// Handle a return step caused by any opcode that causes a return to the + /// previous call context. + pub fn handle_return(&mut self) -> Result<(), Error> { + // Handle reversion if this call doens't end successfully + if !self.call().is_success { + self.handle_reversion(); + } + + self.tx_ctx.pop_call_ctx(); + Ok(()) } @@ -629,6 +1004,10 @@ impl<'a> CircuitInputStateRef<'a> { return Ok(Some(get_step_reported_error(&step.op, error))); } + if matches!(step.op, OpcodeId::INVALID(_)) { + return Ok(Some(ExecError::InvalidOpcode)); + } + // When last step is RETURN or STOP there's no error. if matches!(next_step, None) && matches!(step.op, OpcodeId::RETURN | OpcodeId::STOP) { return Ok(None); @@ -647,10 +1026,25 @@ impl<'a> CircuitInputStateRef<'a> { OpcodeId::REVERT => ExecError::ExecutionReverted, OpcodeId::JUMP | OpcodeId::JUMPI => ExecError::InvalidJump, OpcodeId::RETURNDATACOPY => ExecError::ReturnDataOutOfBounds, + // Break write protection (CALL with value will be handled + // below) + OpcodeId::SSTORE + | OpcodeId::CREATE + | OpcodeId::CREATE2 + | OpcodeId::SELFDESTRUCT + | OpcodeId::LOG0 + | OpcodeId::LOG1 + | OpcodeId::LOG2 + | OpcodeId::LOG3 + | OpcodeId::LOG4 + if self.call().is_static => + { + ExecError::WriteProtection + } _ => { return Err(Error::UnexpectedExecStepError( "call failure without return", - Box::new(step.clone()), + step.clone(), )); } })); @@ -674,13 +1068,13 @@ impl<'a> CircuitInputStateRef<'a> { } else { return Err(Error::UnexpectedExecStepError( "failure in RETURN from {CREATE, CREATE2}", - Box::new(step.clone()), + step.clone(), )); } } else { return Err(Error::UnexpectedExecStepError( "failure in RETURN", - Box::new(step.clone()), + step.clone(), )); } } @@ -697,7 +1091,7 @@ impl<'a> CircuitInputStateRef<'a> { { return Err(Error::UnexpectedExecStepError( "success result without {RETURN, STOP}", - Box::new(step.clone()), + step.clone(), )); } @@ -724,6 +1118,12 @@ impl<'a> CircuitInputStateRef<'a> { OpcodeId::CREATE | OpcodeId::CREATE2 => step.stack.nth_last(0)?, _ => Word::zero(), }; + + // CALL with value + if matches!(step.op, OpcodeId::CALL) && !value.is_zero() && self.call().is_static { + return Ok(Some(ExecError::WriteProtection)); + } + let sender = self.call().address; let (found, account) = self.sdb.get_account(&sender); if !found { @@ -748,7 +1148,7 @@ impl<'a> CircuitInputStateRef<'a> { return Err(Error::UnexpectedExecStepError( "*CALL*/CREATE* code not executed", - Box::new(step.clone()), + step.clone(), )); } @@ -789,17 +1189,11 @@ pub struct CircuitInputBuilder { impl<'a> CircuitInputBuilder { /// Create a new CircuitInputBuilder from the given `eth_block` and /// `constants`. - pub fn new( - sdb: StateDB, - code_db: CodeDB, - eth_block: ð_types::Block, - chain_const: ChainConstants, - block_const: BlockConstants, - ) -> Self { + pub fn new(sdb: StateDB, code_db: CodeDB, block: Block) -> Self { Self { sdb, code_db, - block: Block::new(eth_block, chain_const, block_const), + block, block_ctx: BlockContext::new(), } } @@ -825,9 +1219,61 @@ impl<'a> CircuitInputBuilder { } /// Create a new Transaction from a [`eth_types::Transaction`]. - pub fn new_tx(&mut self, eth_tx: ð_types::Transaction) -> Result { + pub fn new_tx( + &mut self, + eth_tx: ð_types::Transaction, + is_success: bool, + ) -> Result { let call_id = self.block_ctx.rwc.0; - Transaction::new(call_id, &self.sdb, &mut self.code_db, eth_tx) + + self.block_ctx.call_map.insert( + call_id, + ( + eth_tx + .transaction_index + .ok_or(Error::EthTypeError(eth_types::Error::IncompleteBlock))? + .as_u64() as usize, + 0, + ), + ); + + Transaction::new(call_id, &self.sdb, &mut self.code_db, eth_tx, is_success) + } + + /// Iterate over all generated CallContext RwCounterEndOfReversion + /// operations and set the correct value. This is required because when we + /// generate the RwCounterEndOfReversion operation in + /// `gen_associated_ops` we don't know yet which value it will take, + /// so we put a placeholder; so we do it here after the values are known. + pub fn set_value_ops_call_context_rwc_eor(&mut self) { + for oper in self.block.container.call_context.iter_mut() { + let op = oper.op_mut(); + if matches!(op.field, CallContextField::RwCounterEndOfReversion) { + let (tx_idx, call_idx) = self + .block_ctx + .call_map + .get(&op.call_id) + .expect("call_id not found in call_map"); + op.value = self.block.txs[*tx_idx].calls[*call_idx] + .rw_counter_end_of_reversion + .into(); + } + } + } + + /// Handle a block by handling each transaction to generate all the + /// associated operations. + pub fn handle_block( + &mut self, + eth_block: &EthBlock, + geth_traces: &[eth_types::GethExecTrace], + ) -> Result<(), Error> { + for (tx_index, tx) in eth_block.transactions.iter().enumerate() { + let geth_trace = &geth_traces[tx_index]; + self.handle_tx(tx, geth_trace)?; + } + self.set_value_ops_call_context_rwc_eor(); + Ok(()) } /// Handle a transaction with its corresponding execution trace to generate @@ -839,8 +1285,9 @@ impl<'a> CircuitInputBuilder { eth_tx: ð_types::Transaction, geth_trace: &GethExecTrace, ) -> Result<(), Error> { - let mut tx = self.new_tx(eth_tx)?; - let mut tx_ctx = TransactionContext::new(eth_tx); + let mut tx = self.new_tx(eth_tx, !geth_trace.failed)?; + let mut tx_ctx = TransactionContext::new(eth_tx, geth_trace)?; + for (index, geth_step) in geth_trace.struct_logs.iter().enumerate() { let mut step = ExecStep::new( geth_step, @@ -849,32 +1296,24 @@ impl<'a> CircuitInputBuilder { tx_ctx.call_ctx().swc, ); let mut state_ref = self.state_ref(&mut tx, &mut tx_ctx, &mut step); + gen_associated_ops( &geth_step.op, &mut state_ref, &geth_trace.struct_logs[index..], )?; - if let Some(geth_next_step) = geth_trace.struct_logs.get(index + 1) { - if geth_step.depth + 1 == geth_next_step.depth { - // Handle *CALL*/CREATE* - state_ref.handle_call_create(geth_step)?; - } else if geth_step.depth - 1 == geth_next_step.depth { - // Handle return - state_ref.handle_return(geth_step, geth_next_step)?; - } - } tx.steps.push(step); } + self.block.txs.push(tx); + Ok(()) } } fn get_step_reported_error(op: &OpcodeId, error: &str) -> ExecError { - if error == GETH_ERR_WRITE_PROTECTION { - ExecError::WriteProtection - } else if error == GETH_ERR_OUT_OF_GAS || error == GETH_ERR_GAS_UINT_OVERFLOW { + if error == GETH_ERR_OUT_OF_GAS || error == GETH_ERR_GAS_UINT_OVERFLOW { // NOTE: We report a GasUintOverflow error as an OutOfGas error let oog_err = match op { OpcodeId::SHA3 => OogError::Sha3, @@ -901,8 +1340,6 @@ fn get_step_reported_error(op: &OpcodeId, error: &str) -> ExecError { ExecError::StackOverflow } else if error.starts_with(GETH_ERR_STACK_UNDERFLOW) { ExecError::StackUnderflow - } else if error.starts_with(GETH_ERR_INVALID_OPCODE) { - ExecError::InvalidOpcode } else { panic!("Unknown GethExecStep.error: {}", error); } @@ -1147,7 +1584,7 @@ pub fn gen_state_access_trace( if call_stack.len() == 1 { return Err(Error::InvalidGethExecStep( "gen_state_access_trace: call stack will be empty", - Box::new(step.clone()), + step.clone(), )); } call_stack.pop().expect("call stack is empty"); @@ -1164,18 +1601,20 @@ type EthBlock = eth_types::Block; /// the necessary information and using the CircuitInputBuilder. pub struct BuilderClient { cli: GethClient

, - constants: ChainConstants, + chain_id: Word, + history_hashes: Vec, } impl BuilderClient

{ /// Create a new BuilderClient pub async fn new(client: GethClient

) -> Result { - let constants = ChainConstants { - chain_id: client.get_chain_id().await?, - }; + let chain_id = client.get_chain_id().await?; + Ok(Self { cli: client, - constants, + chain_id: chain_id.into(), + // TODO: Get history hashes + history_hashes: Vec::new(), }) } @@ -1280,17 +1719,9 @@ impl BuilderClient

{ eth_block: &EthBlock, geth_traces: &[eth_types::GethExecTrace], ) -> Result { - let mut builder = CircuitInputBuilder::new( - sdb, - code_db, - eth_block, - self.constants.clone(), - BlockConstants::from_eth_block(eth_block, &Word::from(self.constants.chain_id)), - ); - for (tx_index, tx) in eth_block.transactions.iter().enumerate() { - let geth_trace = &geth_traces[tx_index]; - builder.handle_tx(tx, geth_trace)?; - } + let block = Block::new(self.chain_id, self.history_hashes.clone(), eth_block)?; + let mut builder = CircuitInputBuilder::new(sdb, code_db, block); + builder.handle_block(eth_block, geth_traces)?; Ok(builder) } @@ -1307,11 +1738,13 @@ impl BuilderClient

{ #[cfg(test)] mod tracer_tests { - use super::*; use crate::state_db::Account; + + use super::*; use eth_types::evm_types::{stack::Stack, Gas, OpcodeId}; - use eth_types::{address, bytecode, word, Bytecode, ToWord, Word}; + use eth_types::{address, bytecode, geth_types::GethData, word, Bytecode, ToWord, Word}; use lazy_static::lazy_static; + use mock; use pretty_assertions::assert_eq; use std::iter::FromIterator; @@ -1327,13 +1760,23 @@ mod tracer_tests { } impl CircuitInputBuilderTx { - fn new(block: &crate::mock::BlockData, geth_step: &GethExecStep) -> Self { + fn new(geth_data: &GethData, geth_step: &GethExecStep) -> Self { + let block = crate::mock::BlockData::new_from_geth_data(geth_data.clone()); let mut builder = block.new_circuit_input_builder(); - let tx = builder.new_tx(&block.eth_tx).unwrap(); + let tx = builder.new_tx(&block.eth_tx, true).unwrap(); + let tx_ctx = TransactionContext::new( + &block.eth_tx, + &GethExecTrace { + gas: Gas(0), + failed: false, + struct_logs: vec![geth_step.clone()], + }, + ) + .unwrap(); Self { builder, tx, - tx_ctx: TransactionContext::new(&block.eth_tx), + tx_ctx, step: ExecStep::new(geth_step, 0, RWCounter::new(), 0), } } @@ -1351,6 +1794,29 @@ mod tracer_tests { static ref WORD_ADDR_B: Word = ADDR_B.to_word(); } + fn mock_internal_create() -> Call { + Call { + call_id: 0, + caller_id: 0, + kind: CallKind::Create, + is_static: false, + is_root: false, + is_persistent: false, + is_success: false, + rw_counter_end_of_reversion: 0, + caller_address: *ADDR_A, + address: *ADDR_B, + code_source: CodeSource::Memory, + code_hash: Hash::zero(), + depth: 2, + value: 0.into(), + call_data_offset: 0, + call_data_length: 0, + return_data_offset: 0, + return_data_length: 0, + } + } + // // Geth Errors ignored // @@ -1387,9 +1853,8 @@ mod tracer_tests { PUSH2(0xab) STOP }; - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_gas(&code, Gas(1_000_000_000_000_000u64)).unwrap(), - ); + let block = + mock::new_single_tx_trace_code_gas(&code, Gas(1_000_000_000_000_000u64)).unwrap(); let struct_logs = &block.geth_trace.struct_logs; // get last CALL @@ -1442,9 +1907,7 @@ mod tracer_tests { PUSH3(0xbb) }; - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(), - ); + let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get last CALL let (index, step) = block @@ -1535,9 +1998,7 @@ mod tracer_tests { PUSH3(0xbb) }; code_b.append(&code_b_end); - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(), - ); + let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get last CREATE2 let (index, step) = block @@ -1566,9 +2027,8 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); // Set up call context at CREATE2 - builder - .state_ref() - .push_call(CallKind::Create, *ADDR_B, CodeSource::Memory, Hash::zero()); + builder.tx_ctx.call_is_success.push(false); + builder.state_ref().push_call(mock_internal_create()); // Set up account and contract that exist during the second CREATE2 builder.builder.sdb.set_account( &ADDR_B, @@ -1641,7 +2101,7 @@ mod tracer_tests { .code() .iter() .cloned() - .chain(0u8..((32 - len % 32) as u8)) + .chain(0..(32 - len % 32) as u8) .collect(); for (index, word) in code_creator.chunks(32).enumerate() { code_b.push(32, Word::from_big_endian(word)); @@ -1657,9 +2117,7 @@ mod tracer_tests { PUSH3(0xbb) }; code_b.append(&code_b_end); - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(), - ); + let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get last RETURN let (index, step) = block @@ -1675,9 +2133,8 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); // Set up call context at CREATE - builder - .state_ref() - .push_call(CallKind::Create, *ADDR_B, CodeSource::Memory, Hash::zero()); + builder.tx_ctx.call_is_success.push(false); + builder.state_ref().push_call(mock_internal_create()); assert_eq!( builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::CodeStoreOutOfGas) @@ -1745,9 +2202,7 @@ mod tracer_tests { PUSH3(0xbb) }; code_b.append(&code_b_end); - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(), - ); + let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get last RETURN let (index, step) = block @@ -1763,9 +2218,8 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); // Set up call context at RETURN - builder - .state_ref() - .push_call(CallKind::Create, *ADDR_B, CodeSource::Memory, Hash::zero()); + builder.tx_ctx.call_is_success.push(false); + builder.state_ref().push_call(mock_internal_create()); assert_eq!( builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::InvalidCode) @@ -1834,9 +2288,7 @@ mod tracer_tests { PUSH3(0xbb) }; code_b.append(&code_b_end); - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(), - ); + let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get last RETURN let (index, step) = block @@ -1852,9 +2304,8 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); // Set up call context at RETURN - builder - .state_ref() - .push_call(CallKind::Create, *ADDR_B, CodeSource::Memory, Hash::zero()); + builder.tx_ctx.call_is_success.push(false); + builder.state_ref().push_call(mock_internal_create()); assert_eq!( builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::MaxCodeSizeExceeded) @@ -1910,9 +2361,7 @@ mod tracer_tests { PUSH3(0xbb) }; code_b.append(&code_b_end); - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(), - ); + let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get first STOP let (index, step) = block @@ -1926,9 +2375,8 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); // Set up call context at STOP - builder - .state_ref() - .push_call(CallKind::Create, *ADDR_B, CodeSource::Memory, Hash::zero()); + builder.tx_ctx.call_is_success.push(false); + builder.state_ref().push_call(mock_internal_create()); assert_eq!( builder.state_ref().get_step_err(step, next_step).unwrap(), None @@ -1966,9 +2414,7 @@ mod tracer_tests { STOP }; let index = 1; // JUMP - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code(&code).unwrap(), - ); + let block = mock::new_single_tx_trace_code(&code).unwrap(); assert_eq!(block.geth_trace.struct_logs.len(), 2); let step = &block.geth_trace.struct_logs[index]; let next_step = block.geth_trace.struct_logs.get(index + 1); @@ -1995,9 +2441,7 @@ mod tracer_tests { PUSH2(0xaa) }; let index = 8; // JUMP - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_2(&code_a, &code).unwrap(), - ); + let block = mock::new_single_tx_trace_code_2(&code_a, &code).unwrap(); let step = &block.geth_trace.struct_logs[index]; let next_step = block.geth_trace.struct_logs.get(index + 1); assert!(check_err_invalid_jump(step, next_step)); @@ -2028,9 +2472,7 @@ mod tracer_tests { STOP }; let index = 2; // REVERT - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code(&code).unwrap(), - ); + let block = mock::new_single_tx_trace_code(&code).unwrap(); assert_eq!(block.geth_trace.struct_logs.len(), 3); let step = &block.geth_trace.struct_logs[index]; let next_step = block.geth_trace.struct_logs.get(index + 1); @@ -2058,9 +2500,7 @@ mod tracer_tests { PUSH2(0xaa) }; let index = 10; // REVERT - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_2(&code_a, &code).unwrap(), - ); + let block = mock::new_single_tx_trace_code_2(&code_a, &code).unwrap(); let step = &block.geth_trace.struct_logs[index]; let next_step = block.geth_trace.struct_logs.get(index + 1); assert!(check_err_execution_reverted(step, next_step)); @@ -2097,9 +2537,7 @@ mod tracer_tests { PUSH2(0xaa) }; let index = 10; // STOP - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_2(&code_a, &code).unwrap(), - ); + let block = mock::new_single_tx_trace_code_2(&code_a, &code).unwrap(); let step = &block.geth_trace.struct_logs[index]; let next_step = block.geth_trace.struct_logs.get(index + 1); @@ -2150,9 +2588,7 @@ mod tracer_tests { PUSH1(0x00) // offset RETURN }; - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(), - ); + let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get last RETURNDATACOPY let (index, step) = block @@ -2187,9 +2623,7 @@ mod tracer_tests { PUSH32(0x100_0000_0000_0000_0000_u128) // offset MSTORE }; - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code(&code).unwrap(), - ); + let block = mock::new_single_tx_trace_code(&code).unwrap(); let index = 2; // MSTORE let step = &block.geth_trace.struct_logs[index]; @@ -2210,21 +2644,12 @@ mod tracer_tests { let mut code = bytecode::Bytecode::default(); code.write_op(OpcodeId::PC); code.write(0x0f); - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code(&code).unwrap(), - ); + let block = mock::new_single_tx_trace_code(&code).unwrap(); let index = block.geth_trace.struct_logs.len() - 1; // 0x0f let step = &block.geth_trace.struct_logs[index]; let next_step = block.geth_trace.struct_logs.get(index + 1); assert_eq!(step.op, OpcodeId::INVALID(0x0f)); - assert_eq!( - step.error, - Some(format!( - "{}: opcode 0xf not defined", - GETH_ERR_INVALID_OPCODE - )) - ); let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( @@ -2254,17 +2679,36 @@ mod tracer_tests { PUSH3(0xbb) }; - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(), - ); + let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); let index = 9; // SSTORE let step = &block.geth_trace.struct_logs[index]; let next_step = block.geth_trace.struct_logs.get(index + 1); assert_eq!(step.op, OpcodeId::SSTORE); - assert_eq!(step.error, Some(GETH_ERR_WRITE_PROTECTION.to_string())); let mut builder = CircuitInputBuilderTx::new(&block, step); + builder.tx_ctx.call_is_success.push(false); + builder.state_ref().push_call(Call { + call_id: 0, + caller_id: 0, + kind: CallKind::StaticCall, + is_static: true, + is_root: false, + is_persistent: false, + is_success: false, + rw_counter_end_of_reversion: 0, + caller_address: *ADDR_A, + address: *ADDR_B, + code_source: CodeSource::Address(*ADDR_B), + code_hash: Hash::zero(), + depth: 2, + value: 0.into(), + call_data_offset: 0, + call_data_length: 0, + return_data_offset: 0, + return_data_length: 0, + }); + assert_eq!( builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::WriteProtection) @@ -2279,9 +2723,7 @@ mod tracer_tests { PUSH1(0x1) PUSH1(0x2) }; - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_gas(&code, Gas(4)).unwrap(), - ); + let block = mock::new_single_tx_trace_code_gas(&code, Gas(21004)).unwrap(); let struct_logs = block.geth_trace.struct_logs; assert_eq!(struct_logs[1].error, Some(GETH_ERR_OUT_OF_GAS.to_string())); @@ -2294,9 +2736,7 @@ mod tracer_tests { for i in 0..1025 { code.push(2, Word::from(i)); } - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code(&code).unwrap(), - ); + let block = mock::new_single_tx_trace_code(&code).unwrap(); let index = block.geth_trace.struct_logs.len() - 1; // PUSH2 let step = &block.geth_trace.struct_logs[index]; @@ -2319,9 +2759,7 @@ mod tracer_tests { let code = bytecode! { SWAP5 }; - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code(&code).unwrap(), - ); + let block = mock::new_single_tx_trace_code(&code).unwrap(); let index = 0; // SWAP5 let step = &block.geth_trace.struct_logs[index]; @@ -2392,9 +2830,7 @@ mod tracer_tests { PUSH3(0xbb) }; code_b.append(&code_b_end); - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(), - ); + let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get RETURN let (index_return, _) = block @@ -2416,9 +2852,8 @@ mod tracer_tests { .unwrap(); let mut builder = CircuitInputBuilderTx::new(&block, step_create2); // Set up call context at CREATE2 - builder - .state_ref() - .push_call(CallKind::Create, *ADDR_B, CodeSource::Memory, Hash::zero()); + builder.tx_ctx.call_is_success.push(false); + builder.state_ref().push_call(mock_internal_create()); let addr = builder.state_ref().create2_address(step_create2).unwrap(); assert_eq!(addr.to_word(), addr_expect); @@ -2479,9 +2914,7 @@ mod tracer_tests { PUSH3(0xbb) }; code_b.append(&code_b_end); - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(), - ); + let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get last RETURN let (index_return, _) = block @@ -2505,9 +2938,8 @@ mod tracer_tests { .unwrap(); let mut builder = CircuitInputBuilderTx::new(&block, step_create); // Set up call context at CREATE - builder - .state_ref() - .push_call(CallKind::Create, *ADDR_B, CodeSource::Memory, Hash::zero()); + builder.tx_ctx.call_is_success.push(false); + builder.state_ref().push_call(mock_internal_create()); builder.builder.sdb.set_account( &ADDR_B, Account { @@ -2553,9 +2985,7 @@ mod tracer_tests { PUSH3(0xbb) }; - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(), - ); + let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); let access_trace = gen_state_access_trace(&block.eth_block, &block.eth_tx, &block.geth_trace).unwrap(); diff --git a/bus-mapping/src/error.rs b/bus-mapping/src/error.rs index caca49eaea..7386f1e02d 100644 --- a/bus-mapping/src/error.rs +++ b/bus-mapping/src/error.rs @@ -10,8 +10,6 @@ use std::error::Error as StdError; pub enum Error { /// Serde de/serialization error. SerdeError(serde_json::error::Error), - /// Error while generating a trace. - TracingError, /// JSON-RPC related error. JSONRpcError(ProviderError), /// OpcodeId is not a call type. @@ -21,9 +19,9 @@ pub enum Error { /// Storage key not found in the StateDB StorageKeyNotFound(Address, Word), /// Unable to figure out error at a [`GethExecStep`] - UnexpectedExecStepError(&'static str, Box), + UnexpectedExecStepError(&'static str, GethExecStep), /// Invalid [`GethExecStep`] due to an invalid/unexpected value in it. - InvalidGethExecStep(&'static str, Box), + InvalidGethExecStep(&'static str, GethExecStep), /// Eth type related error. EthTypeError(eth_types::Error), } diff --git a/bus-mapping/src/evm/opcodes/coinbase.rs b/bus-mapping/src/evm/opcodes/coinbase.rs index 9e4b0ea314..94ada0e232 100644 --- a/bus-mapping/src/evm/opcodes/coinbase.rs +++ b/bus-mapping/src/evm/opcodes/coinbase.rs @@ -27,8 +27,10 @@ mod coinbase_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate step corresponding to COINBASE let mut step = ExecStep::new( @@ -43,7 +45,7 @@ mod coinbase_tests { state_ref.push_stack_op( RW::WRITE, StackAddress::from(1024 - 1), - block.b_constant.coinbase.to_word(), + block.eth_block.author.to_word(), ); tx.steps_mut().push(step); diff --git a/bus-mapping/src/evm/opcodes/dup.rs b/bus-mapping/src/evm/opcodes/dup.rs index 578365c9ce..61c04e1cd6 100644 --- a/bus-mapping/src/evm/opcodes/dup.rs +++ b/bus-mapping/src/evm/opcodes/dup.rs @@ -59,8 +59,10 @@ mod dup_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate steps corresponding to DUP1, DUP3, DUP5 for (i, word) in [word!("0x3"), word!("0x2"), word!("0x1")] diff --git a/bus-mapping/src/evm/opcodes/gas.rs b/bus-mapping/src/evm/opcodes/gas.rs index 7cf8e384ed..85e1b1c6cd 100644 --- a/bus-mapping/src/evm/opcodes/gas.rs +++ b/bus-mapping/src/evm/opcodes/gas.rs @@ -27,7 +27,7 @@ mod gas_tests { mock::BlockData, operation::StackAddress, }; - use eth_types::{bytecode, bytecode::Bytecode, Word}; + use eth_types::{bytecode, bytecode::Bytecode, evm_types::GasCost, Word}; use mock::new_single_tx_trace_code_at_start; use super::*; @@ -39,8 +39,8 @@ mod gas_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace)?; let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx)?; - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder.new_tx(&block.eth_tx, !block.geth_trace.failed)?; + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace)?; let mut step = ExecStep::new( &block.geth_trace.struct_logs[0], @@ -69,7 +69,8 @@ mod gas_tests { fn gas_opcode_impl() -> Result<(), Error> { const GAS_LIMIT: u64 = 1_000_000; - const GAS_COST: u64 = OpcodeId::PUSH1.constant_gas_cost().as_u64() + const GAS_COST: u64 = GasCost::TX.as_u64() + + OpcodeId::PUSH1.constant_gas_cost().as_u64() + OpcodeId::PUSH1.constant_gas_cost().as_u64() + OpcodeId::GAS.constant_gas_cost().as_u64(); diff --git a/bus-mapping/src/evm/opcodes/jump.rs b/bus-mapping/src/evm/opcodes/jump.rs index 59d3e7f6b2..4b4af4a321 100644 --- a/bus-mapping/src/evm/opcodes/jump.rs +++ b/bus-mapping/src/evm/opcodes/jump.rs @@ -61,8 +61,10 @@ mod jump_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate step corresponding to JUMP let mut step = ExecStep::new( diff --git a/bus-mapping/src/evm/opcodes/jumpi.rs b/bus-mapping/src/evm/opcodes/jumpi.rs index 2979b53b6d..12051952ab 100644 --- a/bus-mapping/src/evm/opcodes/jumpi.rs +++ b/bus-mapping/src/evm/opcodes/jumpi.rs @@ -68,8 +68,10 @@ mod jumpi_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate step corresponding to JUMP let mut step = ExecStep::new( diff --git a/bus-mapping/src/evm/opcodes/mload.rs b/bus-mapping/src/evm/opcodes/mload.rs index b5b8df2157..65a6dd7d47 100644 --- a/bus-mapping/src/evm/opcodes/mload.rs +++ b/bus-mapping/src/evm/opcodes/mload.rs @@ -85,8 +85,10 @@ mod mload_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate step corresponding to MLOAD let mut step = ExecStep::new( diff --git a/bus-mapping/src/evm/opcodes/msize.rs b/bus-mapping/src/evm/opcodes/msize.rs index bb9b279594..ba16bf7846 100644 --- a/bus-mapping/src/evm/opcodes/msize.rs +++ b/bus-mapping/src/evm/opcodes/msize.rs @@ -51,8 +51,10 @@ mod msize_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); let mut step = ExecStep::new( &block.geth_trace.struct_logs[0], diff --git a/bus-mapping/src/evm/opcodes/mstore.rs b/bus-mapping/src/evm/opcodes/mstore.rs index 0cf660c0b5..bfdd6caa16 100644 --- a/bus-mapping/src/evm/opcodes/mstore.rs +++ b/bus-mapping/src/evm/opcodes/mstore.rs @@ -80,8 +80,10 @@ mod mstore_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate step corresponding to MSTORE let mut step = ExecStep::new( @@ -138,8 +140,10 @@ mod mstore_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate step corresponding to MSTORE let mut step = ExecStep::new( diff --git a/bus-mapping/src/evm/opcodes/pc.rs b/bus-mapping/src/evm/opcodes/pc.rs index 9f5f7f172a..761574826b 100644 --- a/bus-mapping/src/evm/opcodes/pc.rs +++ b/bus-mapping/src/evm/opcodes/pc.rs @@ -49,8 +49,10 @@ mod pc_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate step corresponding to MLOAD let mut step = ExecStep::new( diff --git a/bus-mapping/src/evm/opcodes/pop.rs b/bus-mapping/src/evm/opcodes/pop.rs index 2aa3f20096..5f6a078b6f 100644 --- a/bus-mapping/src/evm/opcodes/pop.rs +++ b/bus-mapping/src/evm/opcodes/pop.rs @@ -51,8 +51,10 @@ mod pop_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate step corresponding to POP let mut step = ExecStep::new( diff --git a/bus-mapping/src/evm/opcodes/push.rs b/bus-mapping/src/evm/opcodes/push.rs index 0bb95e91b8..ef8adabd04 100644 --- a/bus-mapping/src/evm/opcodes/push.rs +++ b/bus-mapping/src/evm/opcodes/push.rs @@ -56,8 +56,10 @@ mod push_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate steps corresponding to PUSH1 80, PUSH2 1234, // PUSH16 0x00112233445566778899aabbccddeeff diff --git a/bus-mapping/src/evm/opcodes/sload.rs b/bus-mapping/src/evm/opcodes/sload.rs index 9411f18817..aa04230f93 100644 --- a/bus-mapping/src/evm/opcodes/sload.rs +++ b/bus-mapping/src/evm/opcodes/sload.rs @@ -28,13 +28,15 @@ impl Opcode for Sload { // Storage read let storage_value_read = step.storage.get_or_err(&stack_value_read)?; - state.push_op(StorageOp::new( + state.push_op( RW::READ, - state.call().address, - stack_value_read, - storage_value_read, - storage_value_read, - )); + StorageOp::new( + state.call().address, + stack_value_read, + storage_value_read, + storage_value_read, + ), + ); // First stack write state.push_stack_op(RW::WRITE, stack_position, storage_value_read); @@ -75,8 +77,10 @@ mod sload_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate step corresponding to SLOAD let mut step = ExecStep::new( @@ -89,13 +93,15 @@ mod sload_tests { // Add StackOp associated to the stack pop. state_ref.push_stack_op(RW::READ, StackAddress::from(1023), Word::from(0x0u32)); // Add StorageOp associated to the storage read. - state_ref.push_op(StorageOp::new( + state_ref.push_op( RW::READ, - Address::from([0u8; 20]), - Word::from(0x0u32), - Word::from(0x6fu32), - Word::from(0x6fu32), - )); + StorageOp::new( + Address::from([0u8; 20]), + Word::from(0x0u32), + Word::from(0x6fu32), + Word::from(0x6fu32), + ), + ); // Add StackOp associated to the stack push. state_ref.push_stack_op(RW::WRITE, StackAddress::from(1023), Word::from(0x6fu32)); tx.steps_mut().push(step); diff --git a/bus-mapping/src/evm/opcodes/stackonlyop.rs b/bus-mapping/src/evm/opcodes/stackonlyop.rs index 710b8fc378..741e1a58ef 100644 --- a/bus-mapping/src/evm/opcodes/stackonlyop.rs +++ b/bus-mapping/src/evm/opcodes/stackonlyop.rs @@ -65,8 +65,10 @@ mod stackonlyop_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate step corresponding to NOT let mut step = ExecStep::new( @@ -125,8 +127,10 @@ mod stackonlyop_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate step corresponding to ADD let mut step = ExecStep::new( @@ -187,8 +191,10 @@ mod stackonlyop_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate step corresponding to ADDMOD let mut step = ExecStep::new( diff --git a/bus-mapping/src/evm/opcodes/stop.rs b/bus-mapping/src/evm/opcodes/stop.rs index c1c6354db1..0f41a40b4c 100644 --- a/bus-mapping/src/evm/opcodes/stop.rs +++ b/bus-mapping/src/evm/opcodes/stop.rs @@ -14,10 +14,11 @@ pub(crate) struct Stop; impl Opcode for Stop { fn gen_associated_ops( - _state: &mut CircuitInputStateRef, + state: &mut CircuitInputStateRef, _steps: &[GethExecStep], ) -> Result<(), Error> { - // Stop does not generate any operations + state.handle_return()?; + Ok(()) } } diff --git a/bus-mapping/src/evm/opcodes/swap.rs b/bus-mapping/src/evm/opcodes/swap.rs index 73b38c90b1..b02f733b49 100644 --- a/bus-mapping/src/evm/opcodes/swap.rs +++ b/bus-mapping/src/evm/opcodes/swap.rs @@ -64,8 +64,10 @@ mod swap_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate steps corresponding to DUP1, DUP3, DUP5 for (i, (a, b)) in [(6, 5), (5, 3), (3, 1)].iter().enumerate() { diff --git a/bus-mapping/src/evm/opcodes/timestamp.rs b/bus-mapping/src/evm/opcodes/timestamp.rs index 9483f1485c..69a0ebce1d 100644 --- a/bus-mapping/src/evm/opcodes/timestamp.rs +++ b/bus-mapping/src/evm/opcodes/timestamp.rs @@ -26,8 +26,10 @@ mod timestamp_tests { builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx).unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx); + let mut tx = test_builder + .new_tx(&block.eth_tx, !block.geth_trace.failed) + .unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate step corresponding to TIMESTAMP let mut step = ExecStep::new( @@ -42,7 +44,7 @@ mod timestamp_tests { state_ref.push_stack_op( RW::WRITE, StackAddress::from(1024 - 1), - block.b_constant.timestamp, + block.eth_block.timestamp, ); tx.steps_mut().push(step); diff --git a/bus-mapping/src/exec_trace.rs b/bus-mapping/src/exec_trace.rs index 8be4737f64..a20b5c34eb 100644 --- a/bus-mapping/src/exec_trace.rs +++ b/bus-mapping/src/exec_trace.rs @@ -6,7 +6,7 @@ use std::fmt; #[derive(Clone, Copy, PartialEq, Eq)] /// The target and index of an `Operation` in the context of an /// `ExecutionTrace`. -pub struct OperationRef(Target, usize); +pub struct OperationRef(pub Target, pub usize); impl fmt::Debug for OperationRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -21,6 +21,7 @@ impl fmt::Debug for OperationRef { Target::TxRefund => "TxRefund", Target::Account => "Account", Target::AccountDestructed => "AccountDestructed", + Target::CallContext => "CallContext", }, self.1 )) @@ -40,6 +41,7 @@ impl From<(Target, usize)> for OperationRef { Target::TxRefund => Self(Target::TxRefund, op_ref_data.1), Target::Account => Self(Target::Account, op_ref_data.1), Target::AccountDestructed => Self(Target::AccountDestructed, op_ref_data.1), + Target::CallContext => Self(Target::CallContext, op_ref_data.1), } } } diff --git a/bus-mapping/src/geth_errors.rs b/bus-mapping/src/geth_errors.rs index 5f6e4247e4..4a31619107 100644 --- a/bus-mapping/src/geth_errors.rs +++ b/bus-mapping/src/geth_errors.rs @@ -1,12 +1,8 @@ /// Geth error message for stack overflow pub const GETH_ERR_STACK_OVERFLOW: &str = "stack limit reached"; -/// Geth error message for invalid opcode -pub const GETH_ERR_INVALID_OPCODE: &str = "invalid opcode"; /// Geth error message for stack underflow pub const GETH_ERR_STACK_UNDERFLOW: &str = "stack underflow"; /// Geth error message for out of gas pub const GETH_ERR_OUT_OF_GAS: &str = "out of gas"; /// Geth error message for gas uint64 overflow pub const GETH_ERR_GAS_UINT_OVERFLOW: &str = "gas uint64 overflow"; -/// Geth error message for write protection -pub const GETH_ERR_WRITE_PROTECTION: &str = "write protection"; diff --git a/bus-mapping/src/lib.rs b/bus-mapping/src/lib.rs index b415bc8c47..458b7ad0b0 100644 --- a/bus-mapping/src/lib.rs +++ b/bus-mapping/src/lib.rs @@ -51,11 +51,10 @@ //! use bus_mapping::Error; //! use bus_mapping::state_db::{self, StateDB, CodeDB}; //! use eth_types::{ -//! self, Address, Word, Hash, U64, GethExecTrace, GethExecStep, ChainConstants +//! self, Address, Word, Hash, U64, GethExecTrace, GethExecStep //! }; //! use eth_types::evm_types::Gas; -//! use eth_types::geth_types::BlockConstants; -//! use bus_mapping::circuit_input_builder::CircuitInputBuilder; +//! use bus_mapping::circuit_input_builder::{Block, CircuitInputBuilder}; //! use pairing::arithmetic::FieldExt; //! //! let input_trace = r#" @@ -106,25 +105,17 @@ //! ] //! "#; //! -//! let ctants = ChainConstants{ -//! chain_id: 0, -//! }; -//! //! // We use some mock data as context for the trace //! let eth_block = mock::new_block(); //! let eth_tx = mock::new_tx(ð_block); //! let mut sdb = StateDB::new(); +//! sdb.set_account(ð_tx.from, state_db::Account::zero()); //! sdb.set_account(&Address::zero(), state_db::Account::zero()); //! //! let mut builder = CircuitInputBuilder::new( //! sdb, //! CodeDB::new(), -//! ð_block, -//! ctants.clone(), -//! BlockConstants::from_eth_block( -//! ð_block, -//! &Word::from(ctants.chain_id), -//! ), +//! Block::new::<()>(0.into(), Vec::new(), ð_block).unwrap(), //! ); //! //! let geth_steps: Vec = serde_json::from_str(input_trace).unwrap(); diff --git a/bus-mapping/src/mock.rs b/bus-mapping/src/mock.rs index 224cc92e12..81d8eda56f 100644 --- a/bus-mapping/src/mock.rs +++ b/bus-mapping/src/mock.rs @@ -1,10 +1,10 @@ //! Mock types and functions to generate mock data useful for tests -use crate::circuit_input_builder::CircuitInputBuilder; -use crate::state_db::{self, CodeDB, StateDB}; -use eth_types::geth_types::{BlockConstants, GethData}; -use eth_types::{ChainConstants, Word}; -use std::collections::HashMap; +use crate::{ + circuit_input_builder::{Block, CircuitInputBuilder}, + state_db::{self, CodeDB, StateDB}, +}; +use eth_types::{geth_types::GethData, Word}; /// BlockData is a type that contains all the information from a block required /// to build the circuit inputs. @@ -14,14 +14,15 @@ pub struct BlockData { pub sdb: StateDB, /// CodeDB pub code_db: CodeDB, + /// chain id + pub chain_id: Word, + /// history hashes contains most recent 256 block hashes in history, where + /// the lastest one is at history_hashes[history_hashes.len() - 1]. + pub history_hashes: Vec, /// Block from geth pub eth_block: eth_types::Block<()>, /// Transaction from geth pub eth_tx: eth_types::Transaction, - /// Constants - pub c_constant: ChainConstants, - /// Constants - pub b_constant: BlockConstants, /// Execution Trace from geth pub geth_trace: eth_types::GethExecTrace, } @@ -33,9 +34,7 @@ impl BlockData { CircuitInputBuilder::new( self.sdb.clone(), self.code_db.clone(), - &self.eth_block, - self.c_constant.clone(), - self.b_constant.clone(), + Block::new(self.chain_id, self.history_hashes.clone(), &self.eth_block).unwrap(), ) } @@ -43,14 +42,18 @@ impl BlockData { pub fn new_from_geth_data(geth_data: GethData) -> Self { let mut sdb = StateDB::new(); let mut code_db = CodeDB::new(); + + sdb.set_account(&geth_data.eth_block.author, state_db::Account::zero()); + sdb.set_account(&geth_data.eth_tx.from, state_db::Account::zero()); + for account in geth_data.accounts { let code_hash = code_db.insert(account.code.to_vec()); sdb.set_account( &account.address, state_db::Account { - nonce: Word::zero(), + nonce: account.nonce, balance: account.balance, - storage: HashMap::new(), + storage: account.storage, code_hash, }, ); @@ -58,10 +61,10 @@ impl BlockData { Self { sdb, code_db, + chain_id: geth_data.chain_id, + history_hashes: geth_data.history_hashes, eth_block: geth_data.eth_block, eth_tx: geth_data.eth_tx, - c_constant: geth_data.c_constant, - b_constant: geth_data.b_constant, geth_trace: geth_data.geth_trace, } } diff --git a/bus-mapping/src/operation.rs b/bus-mapping/src/operation.rs index f07a2413c4..be5f0f54ef 100644 --- a/bus-mapping/src/operation.rs +++ b/bus-mapping/src/operation.rs @@ -13,6 +13,7 @@ use core::cmp::Ordering; use core::fmt; use core::fmt::Debug; use eth_types::{Address, Word}; +use std::mem::swap; /// Marker that defines whether an Operation performs a `READ` or a `WRITE`. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -26,15 +27,12 @@ pub enum RW { impl RW { /// Returns true if the RW corresponds internally to a [`READ`](RW::READ). pub const fn is_read(&self) -> bool { - match self { - RW::READ => true, - RW::WRITE => false, - } + matches!(self, RW::READ) } /// Returns true if the RW corresponds internally to a [`WRITE`](RW::WRITE). pub const fn is_write(&self) -> bool { - !self.is_read() + matches!(self, RW::WRITE) } } @@ -106,13 +104,18 @@ pub enum Target { Account, /// Means the target of the operation is the AccountDestructed. AccountDestructed, + /// Means the target of the operation is the CallContext. + CallContext, } /// Trait used for Operation Kinds. -pub trait Op: Eq + Ord { + +pub trait Op: Clone + Eq + Ord { /// Turn the Generic Op into an OpEnum so that we can match it into a /// particular Operation Kind. fn into_enum(self) -> OpEnum; + /// Return a copy of the operation reversed. + fn reverse(&self) -> Self; } /// Represents a [`READ`](RW::READ)/[`WRITE`](RW::WRITE) into the memory implied @@ -120,8 +123,6 @@ pub trait Op: Eq + Ord { /// the [`ExecStep`](crate::circuit_input_builder::ExecStep). #[derive(Clone, PartialEq, Eq)] pub struct MemoryOp { - /// RW - pub rw: RW, /// Call ID pub call_id: usize, /// Memory Address @@ -134,8 +135,8 @@ impl fmt::Debug for MemoryOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("MemoryOp { ")?; f.write_fmt(format_args!( - "{:?}, addr: {:?}, value: 0x{:02x}", - self.rw, self.address, self.value + "call_id: {:?}, addr: {:?}, value: 0x{:02x}", + self.call_id, self.address, self.value ))?; f.write_str(" }") } @@ -143,26 +144,24 @@ impl fmt::Debug for MemoryOp { impl MemoryOp { /// Create a new instance of a `MemoryOp` from it's components. - pub fn new(rw: RW, call_id: usize, address: MemoryAddress, value: u8) -> MemoryOp { + pub fn new(call_id: usize, address: MemoryAddress, value: u8) -> MemoryOp { MemoryOp { - rw, call_id, address, value, } } - /// Returns the internal [`RW`] which says whether the operation corresponds - /// to a Read or a Write into memory. - pub const fn rw(&self) -> RW { - self.rw - } - /// Returns the [`Target`] (operation type) of this operation. pub const fn target(&self) -> Target { Target::Memory } + /// Returns the call id associated to this Operation. + pub const fn call_id(&self) -> usize { + self.call_id + } + /// Returns the [`MemoryAddress`] associated to this Operation. pub const fn address(&self) -> &MemoryAddress { &self.address @@ -178,6 +177,10 @@ impl Op for MemoryOp { fn into_enum(self) -> OpEnum { OpEnum::Memory(self) } + + fn reverse(&self) -> Self { + unreachable!() + } } impl PartialOrd for MemoryOp { @@ -197,8 +200,6 @@ impl Ord for MemoryOp { /// the [`ExecStep`](crate::circuit_input_builder::ExecStep). #[derive(Clone, PartialEq, Eq)] pub struct StackOp { - /// RW - pub rw: RW, /// Call ID pub call_id: usize, /// Stack Address @@ -211,8 +212,8 @@ impl fmt::Debug for StackOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("StackOp { ")?; f.write_fmt(format_args!( - "{:?}, addr: {:?}, val: 0x{:x}", - self.rw, self.address, self.value + "call_id: {:?}, addr: {:?}, val: 0x{:x}", + self.call_id, self.address, self.value ))?; f.write_str(" }") } @@ -220,26 +221,24 @@ impl fmt::Debug for StackOp { impl StackOp { /// Create a new instance of a `MemoryOp` from it's components. - pub const fn new(rw: RW, call_id: usize, address: StackAddress, value: Word) -> StackOp { + pub const fn new(call_id: usize, address: StackAddress, value: Word) -> StackOp { StackOp { - rw, call_id, address, value, } } - /// Returns the internal [`RW`] which says whether the operation corresponds - /// to a Read or a Write into memory. - pub const fn rw(&self) -> RW { - self.rw - } - /// Returns the [`Target`] (operation type) of this operation. pub const fn target(&self) -> Target { Target::Stack } + /// Returns the call id associated to this Operation. + pub const fn call_id(&self) -> usize { + self.call_id + } + /// Returns the [`StackAddress`] associated to this Operation. pub const fn address(&self) -> &StackAddress { &self.address @@ -255,6 +254,10 @@ impl Op for StackOp { fn into_enum(self) -> OpEnum { OpEnum::Stack(self) } + + fn reverse(&self) -> Self { + unreachable!() + } } impl PartialOrd for StackOp { @@ -275,8 +278,6 @@ impl Ord for StackOp { /// the [`ExecStep`](crate::circuit_input_builder::ExecStep). #[derive(Clone, PartialEq, Eq)] pub struct StorageOp { - /// RW - pub rw: RW, /// Account Address pub address: Address, /// Storage Key @@ -291,8 +292,8 @@ impl fmt::Debug for StorageOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("StorageOp { ")?; f.write_fmt(format_args!( - "{:?}, addr: {:?}, key: {:?}, val_prev: 0x{:x}, val: 0x{:x}", - self.rw, self.address, self.key, self.value_prev, self.value, + "addr: {:?}, key: {:?}, val_prev: 0x{:x}, val: 0x{:x}", + self.address, self.key, self.value_prev, self.value, ))?; f.write_str(" }") } @@ -300,15 +301,8 @@ impl fmt::Debug for StorageOp { impl StorageOp { /// Create a new instance of a `StorageOp` from it's components. - pub const fn new( - rw: RW, - address: Address, - key: Word, - value: Word, - value_prev: Word, - ) -> StorageOp { + pub const fn new(address: Address, key: Word, value: Word, value_prev: Word) -> StorageOp { StorageOp { - rw, address, key, value, @@ -316,12 +310,6 @@ impl StorageOp { } } - /// Returns the internal [`RW`] which says whether the operation corresponds - /// to a Read or a Write into storage. - pub const fn rw(&self) -> RW { - self.rw - } - /// Returns the [`Target`] (operation type) of this operation. pub const fn target(&self) -> Target { Target::Storage @@ -352,6 +340,12 @@ impl Op for StorageOp { fn into_enum(self) -> OpEnum { OpEnum::Storage(self) } + + fn reverse(&self) -> Self { + let mut rev = self.clone(); + swap(&mut rev.value, &mut rev.value_prev); + rev + } } impl PartialOrd for StorageOp { @@ -408,6 +402,12 @@ impl Op for TxAccessListAccountOp { fn into_enum(self) -> OpEnum { OpEnum::TxAccessListAccount(self) } + + fn reverse(&self) -> Self { + let mut rev = self.clone(); + swap(&mut rev.value, &mut rev.value_prev); + rev + } } /// Represents a change in the Storage AccessList implied by an `SSTORE` or @@ -453,6 +453,12 @@ impl Op for TxAccessListAccountStorageOp { fn into_enum(self) -> OpEnum { OpEnum::TxAccessListAccountStorage(self) } + + fn reverse(&self) -> Self { + let mut rev = self.clone(); + swap(&mut rev.value, &mut rev.value_prev); + rev + } } /// Represents a change in the Transaction Refund AccessList implied by an @@ -460,8 +466,6 @@ impl Op for TxAccessListAccountStorageOp { /// [`ExecStep`](crate::circuit_input_builder::ExecStep). #[derive(Clone, PartialEq, Eq)] pub struct TxRefundOp { - /// RW - pub rw: RW, /// Transaction ID: Transaction index in the block starting at 1. pub tx_id: usize, /// Refund Value after the operation @@ -474,8 +478,8 @@ impl fmt::Debug for TxRefundOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("TxRefundOp { ")?; f.write_fmt(format_args!( - "{:?} tx_id: {:?}, val_prev: 0x{:x}, val: 0x{:x}", - self.rw, self.tx_id, self.value_prev, self.value + "tx_id: {:?}, val_prev: 0x{:x}, val: 0x{:x}", + self.tx_id, self.value_prev, self.value ))?; f.write_str(" }") } @@ -497,6 +501,12 @@ impl Op for TxRefundOp { fn into_enum(self) -> OpEnum { OpEnum::TxRefund(self) } + + fn reverse(&self) -> Self { + let mut rev = self.clone(); + swap(&mut rev.value, &mut rev.value_prev); + rev + } } /// Represents a field parameter of the Account that can be accessed via EVM @@ -516,8 +526,6 @@ pub enum AccountField { /// `CREATE*`, `STOP`, `RETURN` or `REVERT` step. #[derive(Clone, PartialEq, Eq)] pub struct AccountOp { - /// RW - pub rw: RW, /// Account Address pub address: Address, /// Field @@ -532,8 +540,8 @@ impl fmt::Debug for AccountOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("AccountOp { ")?; f.write_fmt(format_args!( - "{:?}, addr: {:?}, field: {:?}, val_prev: 0x{:x}, val: 0x{:x}", - self.rw, self.address, self.field, self.value_prev, self.value + "addr: {:?}, field: {:?}, val_prev: 0x{:x}, val: 0x{:x}", + self.address, self.field, self.value_prev, self.value ))?; f.write_str(" }") } @@ -555,6 +563,12 @@ impl Op for AccountOp { fn into_enum(self) -> OpEnum { OpEnum::Account(self) } + + fn reverse(&self) -> Self { + let mut rev = self.clone(); + swap(&mut rev.value, &mut rev.value_prev); + rev + } } /// Represents an Account destruction implied by a `SELFDESTRUCT` step of the @@ -598,10 +612,107 @@ impl Op for AccountDestructedOp { fn into_enum(self) -> OpEnum { OpEnum::AccountDestructed(self) } + + fn reverse(&self) -> Self { + let mut rev = self.clone(); + swap(&mut rev.value, &mut rev.value_prev); + rev + } +} + +/// Represents a field parameter of the CallContext that can be accessed via EVM +/// execution. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum CallContextField { + /// RwCounterEndOfReversion + RwCounterEndOfReversion, + /// CallerId + CallerId, + /// TxId + TxId, + /// Depth + Depth, + /// CallerAddress + CallerAddress, + /// CalleeAddress + CalleeAddress, + /// CallDataOffset + CallDataOffset, + /// CallDataLength + CallDataLength, + /// ReturnDataOffset + ReturnDataOffset, + /// ReturnDataLength + ReturnDataLength, + /// Value + Value, + /// IsSuccess + IsSuccess, + /// IsPersistent + IsPersistent, + /// IsStatic + IsStatic, + /// IsRoot + IsRoot, + /// IsCreate + IsCreate, + /// CodeSource + CodeSource, + /// ProgramCounter + ProgramCounter, + /// StackPointer + StackPointer, + /// GasLeft + GasLeft, + /// MemorySize + MemorySize, + /// StateWriteCounter + StateWriteCounter, +} + +/// Represents an CallContext read/write operation. +#[derive(Clone, PartialEq, Eq)] +pub struct CallContextOp { + /// call_id of CallContext + pub call_id: usize, + /// field of CallContext + pub field: CallContextField, + /// value of CallContext + pub value: Word, +} + +impl fmt::Debug for CallContextOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("CallContextOp { ")?; + f.write_fmt(format_args!( + "call_id: {:?}, field: {:?}, value: {:?}", + self.call_id, self.field, self.value, + ))?; + f.write_str(" }") + } } -// TODO: https://github.com/appliedzkp/zkevm-circuits/issues/225 -// pub struct CallContextOp{} +impl PartialOrd for CallContextOp { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for CallContextOp { + fn cmp(&self, other: &Self) -> Ordering { + (&self.call_id, &self.field).cmp(&(&other.call_id, &other.field)) + } +} + +impl Op for CallContextOp { + fn into_enum(self) -> OpEnum { + OpEnum::CallContext(self) + } + + fn reverse(&self) -> Self { + unreachable!() + } +} /// Generic enum that wraps over all the operation types possible. /// In particular [`StackOp`], [`MemoryOp`] and [`StorageOp`]. @@ -623,16 +734,18 @@ pub enum OpEnum { Account(AccountOp), /// AccountDestructed AccountDestructed(AccountDestructedOp), - /* TODO: https://github.com/appliedzkp/zkevm-circuits/issues/225 - * CallContext(CallContextOp), */ + /// CallContext + CallContext(CallContextOp), } /// Operation is a Wrapper over a type that implements Op with a RWCounter. #[derive(Debug, Clone)] pub struct Operation { rwc: RWCounter, - /// True when this Operation is a revert of its mirror - revert: bool, + rw: RW, + /// True when this Operation should be reverted or not when + /// handle_reversion. + reversible: bool, op: T, } @@ -661,10 +774,21 @@ impl Ord for Operation { impl Operation { /// Create a new Operation from an `op` with a `rwc` - pub fn new(rwc: RWCounter, op: T) -> Self { + pub fn new(rwc: RWCounter, rw: RW, op: T) -> Self { Self { rwc, - revert: false, + rw, + reversible: false, + op, + } + } + + /// Create a new reversible Operation from an `op` with a `rwc` + pub fn new_reversible(rwc: RWCounter, rw: RW, op: T) -> Self { + Self { + rwc, + rw, + reversible: true, op, } } @@ -674,11 +798,26 @@ impl Operation { self.rwc } + /// Return this `Operation` `rw` + pub fn rw(&self) -> RW { + self.rw + } + + /// Return this `Operation` `reversible` + pub fn reversible(&self) -> bool { + self.reversible + } + /// Return this `Operation` `op` pub fn op(&self) -> &T { &self.op } + /// Return this `Operation` `op` as mutable reference + pub fn op_mut(&mut self) -> &mut T { + &mut self.op + } + // /// Matches over an `Operation` returning the [`Target`] of the iternal // op /// it stores inside. // pub const fn target(&self) -> Target { @@ -740,13 +879,13 @@ mod operation_tests { #[test] fn unchecked_op_transmutations_are_safe() { - let stack_op = StackOp::new(RW::WRITE, 1, StackAddress::from(1024), Word::from(0x40)); + let stack_op = StackOp::new(1, StackAddress::from(1024), Word::from(0x40)); - let stack_op_as_operation = Operation::new(RWCounter(1), stack_op.clone()); + let stack_op_as_operation = Operation::new(RWCounter(1), RW::WRITE, stack_op.clone()); - let memory_op = MemoryOp::new(RW::WRITE, 1, MemoryAddress(0x40), 0x40); + let memory_op = MemoryOp::new(1, MemoryAddress(0x40), 0x40); - let memory_op_as_operation = Operation::new(RWCounter(1), memory_op.clone()); + let memory_op_as_operation = Operation::new(RWCounter(1), RW::WRITE, memory_op.clone()); assert_eq!(stack_op, stack_op_as_operation.op); assert_eq!(memory_op, memory_op_as_operation.op) diff --git a/bus-mapping/src/operation/container.rs b/bus-mapping/src/operation/container.rs index 4ad1863a70..e1d673faad 100644 --- a/bus-mapping/src/operation/container.rs +++ b/bus-mapping/src/operation/container.rs @@ -1,6 +1,6 @@ use super::{ - AccountDestructedOp, AccountOp, MemoryOp, Op, OpEnum, Operation, StackOp, StorageOp, Target, - TxAccessListAccountOp, TxAccessListAccountStorageOp, TxRefundOp, + AccountDestructedOp, AccountOp, CallContextOp, MemoryOp, Op, OpEnum, Operation, StackOp, + StorageOp, Target, TxAccessListAccountOp, TxAccessListAccountStorageOp, TxRefundOp, }; use crate::exec_trace::OperationRef; use itertools::Itertools; @@ -21,16 +21,24 @@ use itertools::Itertools; /// order to construct the State proof. #[derive(Debug, Clone, PartialEq, Eq)] pub struct OperationContainer { - pub(crate) memory: Vec>, - pub(crate) stack: Vec>, - pub(crate) storage: Vec>, - pub(crate) tx_access_list_account: Vec>, - pub(crate) tx_access_list_account_storage: Vec>, - pub(crate) tx_refund: Vec>, - pub(crate) account: Vec>, - pub(crate) account_destructed: Vec>, - /* TODO - * pub(crate) call_context: Vec>, */ + /// Operations of MemoryOp + pub memory: Vec>, + /// Operations of StackOp + pub stack: Vec>, + /// Operations of StorageOp + pub storage: Vec>, + /// Operations of TxAccessListAccountOp + pub tx_access_list_account: Vec>, + /// Operations of TxAccessListAccountStorageOp + pub tx_access_list_account_storage: Vec>, + /// Operations of TxRefundOp + pub tx_refund: Vec>, + /// Operations of AccountOp + pub account: Vec>, + /// Operations of AccountDestructedOp + pub account_destructed: Vec>, + /// Operations of CallContextOp + pub call_context: Vec>, } impl Default for OperationContainer { @@ -52,6 +60,7 @@ impl OperationContainer { tx_refund: Vec::new(), account: Vec::new(), account_destructed: Vec::new(), + call_context: Vec::new(), } } @@ -61,45 +70,74 @@ impl OperationContainer { /// vector. pub fn insert(&mut self, op: Operation) -> OperationRef { let rwc = op.rwc(); + let rw = op.rw(); + let reversible = op.reversible(); match op.op.into_enum() { OpEnum::Memory(op) => { - self.memory.push(Operation::new(rwc, op)); - OperationRef::from((Target::Memory, self.memory.len())) + self.memory.push(Operation::new(rwc, rw, op)); + OperationRef::from((Target::Memory, self.memory.len() - 1)) } OpEnum::Stack(op) => { - self.stack.push(Operation::new(rwc, op)); - OperationRef::from((Target::Stack, self.stack.len())) + self.stack.push(Operation::new(rwc, rw, op)); + OperationRef::from((Target::Stack, self.stack.len() - 1)) } OpEnum::Storage(op) => { - self.storage.push(Operation::new(rwc, op)); - OperationRef::from((Target::Storage, self.storage.len())) + self.storage.push(if reversible { + Operation::new_reversible(rwc, rw, op) + } else { + Operation::new(rwc, rw, op) + }); + OperationRef::from((Target::Storage, self.storage.len() - 1)) } OpEnum::TxAccessListAccount(op) => { - self.tx_access_list_account.push(Operation::new(rwc, op)); + self.tx_access_list_account.push(if reversible { + Operation::new_reversible(rwc, rw, op) + } else { + Operation::new(rwc, rw, op) + }); OperationRef::from(( Target::TxAccessListAccount, - self.tx_access_list_account.len(), + self.tx_access_list_account.len() - 1, )) } OpEnum::TxAccessListAccountStorage(op) => { - self.tx_access_list_account_storage - .push(Operation::new(rwc, op)); + self.tx_access_list_account_storage.push(if reversible { + Operation::new_reversible(rwc, rw, op) + } else { + Operation::new(rwc, rw, op) + }); OperationRef::from(( Target::TxAccessListAccountStorage, - self.tx_access_list_account_storage.len(), + self.tx_access_list_account_storage.len() - 1, )) } OpEnum::TxRefund(op) => { - self.tx_refund.push(Operation::new(rwc, op)); - OperationRef::from((Target::TxRefund, self.tx_refund.len())) + self.tx_refund.push(if reversible { + Operation::new_reversible(rwc, rw, op) + } else { + Operation::new(rwc, rw, op) + }); + OperationRef::from((Target::TxRefund, self.tx_refund.len() - 1)) } OpEnum::Account(op) => { - self.account.push(Operation::new(rwc, op)); - OperationRef::from((Target::Account, self.account.len())) + self.account.push(if reversible { + Operation::new_reversible(rwc, rw, op) + } else { + Operation::new(rwc, rw, op) + }); + OperationRef::from((Target::Account, self.account.len() - 1)) } OpEnum::AccountDestructed(op) => { - self.account_destructed.push(Operation::new(rwc, op)); - OperationRef::from((Target::AccountDestructed, self.account_destructed.len())) + self.account_destructed.push(if reversible { + Operation::new_reversible(rwc, rw, op) + } else { + Operation::new(rwc, rw, op) + }); + OperationRef::from((Target::AccountDestructed, self.account_destructed.len() - 1)) + } + OpEnum::CallContext(op) => { + self.call_context.push(Operation::new(rwc, rw, op)); + OperationRef::from((Target::CallContext, self.call_context.len() - 1)) } } } @@ -137,16 +175,18 @@ mod container_test { let mut operation_container = OperationContainer::default(); let stack_operation = Operation::new( global_counter.inc_pre(), - StackOp::new(RW::WRITE, 1, StackAddress(1023), Word::from(0x100)), + RW::WRITE, + StackOp::new(1, StackAddress(1023), Word::from(0x100)), ); let memory_operation = Operation::new( global_counter.inc_pre(), - MemoryOp::new(RW::WRITE, 1, MemoryAddress::from(1), 1), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(1), 1), ); let storage_operation = Operation::new( global_counter.inc_pre(), + RW::WRITE, StorageOp::new( - RW::WRITE, Address::zero(), Word::default(), Word::from(0x1), @@ -160,8 +200,8 @@ mod container_test { assert_eq!(operation_container.sorted_stack()[0], stack_operation); assert_eq!(operation_container.sorted_memory()[0], memory_operation); assert_eq!(operation_container.sorted_storage()[0], storage_operation); - assert_eq!(stack_ref, OperationRef::from((Target::Stack, 1))); - assert_eq!(memory_ref, OperationRef::from((Target::Memory, 1))); - assert_eq!(storage_ref, OperationRef::from((Target::Storage, 1))); + assert_eq!(stack_ref, OperationRef::from((Target::Stack, 0))); + assert_eq!(memory_ref, OperationRef::from((Target::Memory, 0))); + assert_eq!(storage_ref, OperationRef::from((Target::Storage, 0))); } } diff --git a/bus-mapping/src/state_db.rs b/bus-mapping/src/state_db.rs index 2bbcb7971c..bf7ebc26bf 100644 --- a/bus-mapping/src/state_db.rs +++ b/bus-mapping/src/state_db.rs @@ -1,10 +1,10 @@ //! Implementation of an in-memory key-value database to represent the //! Ethereum State Trie. -use eth_types::{Address, Hash, Word, H256}; +use eth_types::{Address, Hash, Word, H256, U256}; use ethers_core::utils::keccak256; use lazy_static::lazy_static; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; lazy_static! { static ref ACCOUNT_ZERO: Account = Account::zero(); @@ -59,12 +59,22 @@ impl Account { code_hash: *CODE_HASH_ZERO, } } + + /// Return if account is empty or not. + pub fn is_empty(&self) -> bool { + self.nonce.is_zero() + && self.balance.is_zero() + && self.storage.is_empty() + && self.code_hash.eq(&CODE_HASH_ZERO) + } } /// In-memory key-value database that represents the Ethereum State Trie. #[derive(Debug, Clone)] pub struct StateDB { state: HashMap, + access_list_account: HashSet

, + access_list_account_storage: HashSet<(Address, U256)>, } impl Default for StateDB { @@ -78,6 +88,8 @@ impl StateDB { pub fn new() -> Self { Self { state: HashMap::new(), + access_list_account: HashSet::new(), + access_list_account_storage: HashSet::new(), } } @@ -135,6 +147,28 @@ impl StateDB { }; (found, acc.storage.get_mut(key).expect("key not inserted")) } + + /// Add `addr` into account access list. Returns `true` if it's not in the + /// access list before. + pub fn add_account_to_access_list(&mut self, addr: Address) -> bool { + self.access_list_account.insert(addr) + } + + /// Remove `addr` from account access list. + pub fn remove_account_from_access_list(&mut self, addr: &Address) { + assert!(self.access_list_account.remove(addr)); + } + + /// Add `(addr, key)` into account storage access list. Returns `true` if + /// it's not in the access list before. + pub fn add_account_storage_to_access_list(&mut self, (addr, key): (Address, Word)) -> bool { + self.access_list_account_storage.insert((addr, key)) + } + + /// Remove `(addr, key)` from account storage access list. + pub fn remove_account_storage_from_access_list(&mut self, pair: &(Address, Word)) { + assert!(self.access_list_account_storage.remove(pair)); + } } #[cfg(test)] diff --git a/eth-types/src/error.rs b/eth-types/src/error.rs index f17b048ffe..f4a1642b72 100644 --- a/eth-types/src/error.rs +++ b/eth-types/src/error.rs @@ -9,7 +9,9 @@ pub enum Error { /// Serde de/serialization error. SerdeError(serde_json::error::Error), /// Error while generating a trace. - TracingError, + TracingError(String), + /// Block is missing information about number or base_fee + IncompleteBlock, /// Error while parsing an `Instruction/Opcode`. OpcodeParsing, /// Error while parsing a `MemoryAddress`. diff --git a/eth-types/src/evm_types/stack.rs b/eth-types/src/evm_types/stack.rs index 9f308fd960..68990c50b2 100644 --- a/eth-types/src/evm_types/stack.rs +++ b/eth-types/src/evm_types/stack.rs @@ -90,7 +90,7 @@ impl Stack { pub fn stack_pointer(&self) -> StackAddress { // Stack has 1024 slots. // First allocation slot for us in the stack is 1023. - StackAddress::from(1023 - self.0.len()) + StackAddress::from(1024 - self.0.len()) } /// Returns the last filled `StackAddress`. @@ -145,7 +145,7 @@ mod stack_tests { fn stack_pointer() -> Result<(), Error> { let stack = setup_stack(["0x15", "0x16", "0x17"]); - assert_eq!(stack.stack_pointer(), StackAddress(1020)); + assert_eq!(stack.stack_pointer(), StackAddress(1021)); assert_eq!(stack.last_filled(), StackAddress(1021)); assert_eq!(stack.nth_last_filled(1), StackAddress(1022)); Ok(()) diff --git a/eth-types/src/geth_types.rs b/eth-types/src/geth_types.rs index 5a69fdf887..81ee241f23 100644 --- a/eth-types/src/geth_types.rs +++ b/eth-types/src/geth_types.rs @@ -1,25 +1,28 @@ //! Types needed for generating Ethereum traces -use crate::{Address, Block, Bytes, ChainConstants, GethExecTrace, Hash, Word, U64}; +use crate::{AccessList, Address, Block, Bytes, Error, GethExecTrace, Word, U64}; use serde::Serialize; +use std::collections::HashMap; /// Definition of all of the data related to an account. -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Default, Clone, Serialize)] pub struct Account { /// Address pub address: Address, + /// nonce + pub nonce: Word, /// Balance pub balance: Word, /// EVM Code pub code: Bytes, + /// Storage + pub storage: HashMap, } /// Definition of all of the constants related to an Ethereum block and /// chain to be used as setup for the external tracer. -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)] pub struct BlockConstants { - /// hash - pub hash: Hash, /// coinbase pub coinbase: Address, /// time @@ -30,85 +33,101 @@ pub struct BlockConstants { pub difficulty: Word, /// gas limit pub gas_limit: Word, - /// chain id - pub chain_id: Word, /// base fee pub base_fee: Word, } +impl TryFrom<&Block> for BlockConstants { + type Error = Error; + + fn try_from(block: &Block) -> Result { + Ok(Self { + coinbase: block.author, + timestamp: block.timestamp, + number: block.number.ok_or(Error::IncompleteBlock)?, + difficulty: block.difficulty, + gas_limit: block.gas_limit, + base_fee: block.base_fee_per_gas.ok_or(Error::IncompleteBlock)?, + }) + } +} + impl BlockConstants { - #[allow(clippy::too_many_arguments)] /// Generates a new `BlockConstants` instance from it's fields. pub fn new( - hash: Hash, coinbase: Address, timestamp: Word, number: U64, difficulty: Word, gas_limit: Word, - chain_id: Word, base_fee: Word, ) -> BlockConstants { BlockConstants { - hash, coinbase, timestamp, number, difficulty, gas_limit, - chain_id, base_fee, } } - - /// Generate a BlockConstants from an ethereum block, useful for testing. - pub fn from_eth_block(block: &Block, chain_id: &Word) -> Self { - Self { - hash: block.hash.unwrap(), - coinbase: block.author, - timestamp: block.timestamp, - number: block.number.unwrap(), - difficulty: block.difficulty, - gas_limit: block.gas_limit, - chain_id: *chain_id, - base_fee: block.base_fee_per_gas.unwrap(), - } - } } /// Definition of all of the constants related to an Ethereum transaction. -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Default, Clone, Serialize)] pub struct Transaction { - /// Origin Address - pub origin: Address, + /// From Address + pub from: Address, + /// To Address + pub to: Option
, + /// Nonce + pub nonce: Word, /// Gas Limit pub gas_limit: Word, - /// Target Address - pub target: Address, + /// Value + pub value: Word, + /// Gas Price + pub gas_price: Word, + /// Gas fee cap + pub gas_fee_cap: Word, + /// Gas tip cap + pub gas_tip_cap: Word, + /// Call data + pub call_data: Bytes, + /// Access list + pub access_list: Option, } impl Transaction { /// Create Self from a web3 transaction pub fn from_eth_tx(tx: &crate::Transaction) -> Self { Self { - origin: tx.from, + from: tx.from, + to: tx.to, + nonce: tx.nonce, gas_limit: tx.gas, - target: tx.to.unwrap(), + value: tx.value, + gas_price: tx.gas_price.unwrap_or_default(), + gas_fee_cap: tx.max_priority_fee_per_gas.unwrap_or_default(), + gas_tip_cap: tx.max_fee_per_gas.unwrap_or_default(), + call_data: tx.input.clone(), + access_list: tx.access_list.clone(), } } } /// GethData is a type that contains all the information of a Ethereum block -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct GethData { + /// chain id + pub chain_id: Word, + /// history hashes contains most recent 256 block hashes in history, where + /// the lastest one is at history_hashes[history_hashes.len() - 1]. + pub history_hashes: Vec, /// Block from geth pub eth_block: Block<()>, /// Transaction from geth pub eth_tx: crate::Transaction, - /// Constants - pub c_constant: ChainConstants, - /// Constants - pub b_constant: BlockConstants, /// Execution Trace from geth pub geth_trace: GethExecTrace, /// Accounts diff --git a/eth-types/src/lib.rs b/eth-types/src/lib.rs index e446046048..3bdad48766 100644 --- a/eth-types/src/lib.rs +++ b/eth-types/src/lib.rs @@ -28,7 +28,8 @@ use crate::evm_types::{memory::Memory, stack::Stack, storage::Storage}; use crate::evm_types::{Gas, GasCost, OpcodeId, ProgramCounter}; use ethers_core::types; pub use ethers_core::types::{ - transaction::response::Transaction, Address, Block, Bytes, H160, H256, U256, U64, + transaction::{eip2930::AccessList, response::Transaction}, + Address, Block, Bytes, H160, H256, U256, U64, }; use pairing::arithmetic::FieldExt; use serde::{de, Deserialize}; @@ -149,6 +150,12 @@ impl ToAddress for U256 { /// Ethereum Hash (256 bits). pub type Hash = types::H256; +impl ToWord for Hash { + fn to_word(&self) -> Word { + Word::from(self.as_bytes()) + } +} + impl ToWord for Address { fn to_word(&self) -> Word { let mut bytes = [0u8; 32]; @@ -166,13 +173,6 @@ impl ToScalar for Address { } } -/// Chain specific constants -#[derive(Debug, Clone)] -pub struct ChainConstants { - /// Chain ID - pub chain_id: u64, -} - /// Struct used to define the storage proof #[derive(Debug, Default, Clone, PartialEq, Deserialize)] pub struct StorageProof { diff --git a/external-tracer/src/lib.rs b/external-tracer/src/lib.rs index 231f76ce6f..8c7618cf8c 100644 --- a/external-tracer/src/lib.rs +++ b/external-tracer/src/lib.rs @@ -1,34 +1,37 @@ //! This module generates traces by connecting to an external tracer -use eth_types::geth_types::{Account, BlockConstants, Transaction}; -use eth_types::{fix_geth_trace_memory_size, Error, GethExecStep}; +use eth_types::{ + geth_types::{Account, BlockConstants, Transaction}, + Address, Error, GethExecTrace, Word, +}; use serde::Serialize; +use std::collections::HashMap; -#[derive(Debug, Clone, Serialize)] -struct GethConfig { - block_constants: BlockConstants, - transaction: Transaction, - accounts: Vec, +/// Configuration structure for `geth_utlis::trace` +#[derive(Debug, Default, Clone, Serialize)] +pub struct TraceConfig { + /// chain id + pub chain_id: Word, + /// history hashes contains most recent 256 block hashes in history, where + /// the lastest one is at history_hashes[history_hashes.len() - 1]. + pub history_hashes: Vec, + /// block constants + pub block_constants: BlockConstants, + /// accounts + pub accounts: HashMap, + /// transaction + pub transaction: Transaction, } /// Creates a trace for the specified config -pub fn trace( - block_constants: &BlockConstants, - tx: &Transaction, - accounts: &[Account], -) -> Result, Error> { - let geth_config = GethConfig { - block_constants: block_constants.clone(), - transaction: tx.clone(), - accounts: accounts.to_vec(), - }; - +pub fn trace(config: &TraceConfig) -> Result { // Get the trace - let trace_string = geth_utils::trace(&serde_json::to_string(&geth_config).unwrap()) - .map_err(|_| Error::TracingError)?; + let trace_string = geth_utils::trace(&serde_json::to_string(config).unwrap()).map_err( + |error| match error { + geth_utils::Error::TracingError(error) => Error::TracingError(error), + }, + )?; - let mut trace: Vec = - serde_json::from_str(&trace_string).map_err(Error::SerdeError)?; - fix_geth_trace_memory_size(&mut trace); + let trace = serde_json::from_str(&trace_string).map_err(Error::SerdeError)?; Ok(trace) } diff --git a/geth-utils/example/add_sub.go b/geth-utils/example/add_sub.go index 301eb2444b..bb01649bf7 100644 --- a/geth-utils/example/add_sub.go +++ b/geth-utils/example/add_sub.go @@ -6,22 +6,23 @@ import ( "os" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm/runtime" "main/gethutil" ) func main() { address := common.BytesToAddress([]byte{0xff}) - asm := gethutil.NewAssembly().Add(0xdeadbeef, 0xcafeb0ba).Sub(0xfaceb00c, 0xb0bacafe) - contracts := []gethutil.Account{{Address: address, Bytecode: asm.Bytecode}} + assembly := gethutil.NewAssembly().Add(0xdeadbeef, 0xcafeb0ba).Sub(0xfaceb00c, 0xb0bacafe) - logs, err := gethutil.TraceTx(&address, nil, &runtime.Config{GasLimit: 100}, contracts) + accounts := map[common.Address]gethutil.Account{address: {Code: assembly.Bytecode()}} + tx := gethutil.Transaction{To: &address, GasLimit: 21100} + + result, err := gethutil.TraceTx(gethutil.TraceConfig{Accounts: accounts, Transaction: tx}) if err != nil { fmt.Fprintf(os.Stderr, "failed to trace tx, err: %v\n", err) } - bytes, err := json.MarshalIndent(logs, "", " ") + bytes, err := json.MarshalIndent(result.StructLogs, "", " ") if err != nil { fmt.Fprintf(os.Stderr, "failed to marshal logs, err: %v\n", err) } diff --git a/geth-utils/example/msize.go b/geth-utils/example/msize.go index 331729c0fc..f220550265 100644 --- a/geth-utils/example/msize.go +++ b/geth-utils/example/msize.go @@ -1,29 +1,28 @@ package main import ( - "encoding/hex" "encoding/json" "fmt" "os" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm/runtime" "main/gethutil" ) func main() { address := common.BytesToAddress([]byte{0xff}) - asm := gethutil.NewAssembly().MStore(0x40, 0x80).MSize().Stop() - contracts := []gethutil.Account{{Address: address, Bytecode: asm.Bytecode}} - fmt.Fprintln(os.Stdout, hex.EncodeToString(asm.Bytecode)) + assembly := gethutil.NewAssembly().MStore(0x40, 0x80).MSize().Stop() - logs, err := gethutil.TraceTx(&address, nil, &runtime.Config{GasLimit: 100}, contracts) + accounts := map[common.Address]gethutil.Account{address: {Code: assembly.Bytecode()}} + tx := gethutil.Transaction{To: &address, GasLimit: 21100} + + result, err := gethutil.TraceTx(gethutil.TraceConfig{Accounts: accounts, Transaction: tx}) if err != nil { fmt.Fprintf(os.Stderr, "failed to trace tx, err: %v\n", err) } - bytes, err := json.MarshalIndent(logs, "", " ") + bytes, err := json.MarshalIndent(result.StructLogs, "", " ") if err != nil { fmt.Fprintf(os.Stderr, "failed to marshal logs, err: %v\n", err) } diff --git a/geth-utils/example/mstore_mload.go b/geth-utils/example/mstore_mload.go index edfeb6d181..786c859107 100644 --- a/geth-utils/example/mstore_mload.go +++ b/geth-utils/example/mstore_mload.go @@ -6,22 +6,23 @@ import ( "os" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm/runtime" "main/gethutil" ) func main() { address := common.BytesToAddress([]byte{0xff}) - asm := gethutil.NewAssembly().MStore(0x40, 0x80).MLoad(0x40) - contracts := []gethutil.Account{{Address: address, Bytecode: asm.Bytecode}} + assembly := gethutil.NewAssembly().MStore(0x40, 0x80).MLoad(0x40) - logs, err := gethutil.TraceTx(&address, nil, &runtime.Config{GasLimit: 100}, contracts) + accounts := map[common.Address]gethutil.Account{address: {Code: assembly.Bytecode()}} + tx := gethutil.Transaction{To: &address, GasLimit: 21100} + + result, err := gethutil.TraceTx(gethutil.TraceConfig{Accounts: accounts, Transaction: tx}) if err != nil { fmt.Fprintf(os.Stderr, "failed to trace tx, err: %v\n", err) } - bytes, err := json.MarshalIndent(logs, "", " ") + bytes, err := json.MarshalIndent(result.StructLogs, "", " ") if err != nil { fmt.Fprintf(os.Stderr, "failed to marshal logs, err: %v\n", err) } diff --git a/geth-utils/example/sstore_sload.go b/geth-utils/example/sstore_sload.go index b6ce04271b..1546abfb22 100644 --- a/geth-utils/example/sstore_sload.go +++ b/geth-utils/example/sstore_sload.go @@ -6,22 +6,23 @@ import ( "os" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm/runtime" "main/gethutil" ) func main() { address := common.BytesToAddress([]byte{0xff}) - asm := gethutil.NewAssembly().SStore(0, 0xcafeb0ba).SLoad(0).SStore(0, 0xdeabbeef).SLoad(0) - contracts := []gethutil.Account{{Address: address, Bytecode: asm.Bytecode}} + assembly := gethutil.NewAssembly().SStore(0, 0xcafeb0ba).SLoad(0).SStore(0, 0xdeabbeef).SLoad(0) - logs, err := gethutil.TraceTx(&address, nil, &runtime.Config{GasLimit: 25000}, contracts) + accounts := map[common.Address]gethutil.Account{address: {Code: assembly.Bytecode()}} + tx := gethutil.Transaction{To: &address, GasLimit: 46000} + + result, err := gethutil.TraceTx(gethutil.TraceConfig{Accounts: accounts, Transaction: tx}) if err != nil { fmt.Fprintf(os.Stderr, "failed to trace tx, err: %v\n", err) } - bytes, err := json.MarshalIndent(logs, "", " ") + bytes, err := json.MarshalIndent(result.StructLogs, "", " ") if err != nil { fmt.Fprintf(os.Stderr, "failed to marshal logs, err: %v\n", err) } diff --git a/geth-utils/gethutil/asm.go b/geth-utils/gethutil/asm.go index e3495cb7cc..b5546f876a 100644 --- a/geth-utils/gethutil/asm.go +++ b/geth-utils/gethutil/asm.go @@ -4,29 +4,41 @@ import ( "fmt" "io" "math/big" + "strings" "github.com/ethereum/go-ethereum/core/vm" ) -type asm struct { - Bytecode []byte +type Asm struct { + bytecode []byte labelMap map[string]int pendingLabelsMap map[string][]int } -func NewAssembly() *asm { - return &asm{ +func NewAssembly() *Asm { + return &Asm{ labelMap: make(map[string]int), pendingLabelsMap: make(map[string][]int), } } -func (a *asm) PrintMnemonics(out io.Writer) { - for idx := 0; idx < len(a.Bytecode); { - code := vm.OpCode(a.Bytecode[idx]) +func (a *Asm) Bytecode() []byte { + if len(a.pendingLabelsMap) > 0 { + pendingLabels := make([]string, 0, len(a.pendingLabelsMap)) + for pendingLabel := range a.pendingLabelsMap { + pendingLabels = append(pendingLabels, pendingLabel) + } + panic(fmt.Sprintf("pending labels are not defined yet: %v", strings.Join(pendingLabels, ", "))) + } + return a.bytecode +} + +func (a *Asm) PrintMnemonics(out io.Writer) { + for idx := 0; idx < len(a.bytecode); { + code := vm.OpCode(a.bytecode[idx]) if code.IsPush() { n := int(code) - int(vm.PUSH1) + 1 - fmt.Fprintf(out, "%02d\t%s\t0x%x\n", idx, code.String(), a.Bytecode[idx+1:idx+1+n]) + fmt.Fprintf(out, "%02d\t%s\t0x%x\n", idx, code.String(), a.bytecode[idx+1:idx+1+n]) idx += n + 1 } else { fmt.Fprintf(out, "%02d\t%s\n", idx, code.String()) @@ -36,123 +48,123 @@ func (a *asm) PrintMnemonics(out io.Writer) { } // 0x0 range -func (a *asm) Stop() *asm { return a.appendByte(vm.STOP) } -func (a *asm) Add(v ...interface{}) *asm { return a.opWithPush(vm.ADD, v...) } -func (a *asm) Mul(v ...interface{}) *asm { return a.opWithPush(vm.MUL, v...) } -func (a *asm) Sub(v ...interface{}) *asm { return a.opWithPush(vm.SUB, v...) } -func (a *asm) Div(v ...interface{}) *asm { return a.opWithPush(vm.DIV, v...) } -func (a *asm) SDiv(v ...interface{}) *asm { return a.opWithPush(vm.SDIV, v...) } -func (a *asm) Mod(v ...interface{}) *asm { return a.opWithPush(vm.MOD, v...) } -func (a *asm) SMod(v ...interface{}) *asm { return a.opWithPush(vm.SMOD, v...) } -func (a *asm) AddMod(v ...interface{}) *asm { return a.opWithPush(vm.ADDMOD, v...) } -func (a *asm) MulMod(v ...interface{}) *asm { return a.opWithPush(vm.MULMOD, v...) } -func (a *asm) Exp(v ...interface{}) *asm { return a.opWithPush(vm.EXP, v...) } -func (a *asm) SignExtend(v ...interface{}) *asm { return a.opWithPush(vm.SIGNEXTEND, v...) } +func (a *Asm) Stop() *Asm { return a.appendByte(vm.STOP) } +func (a *Asm) Add(v ...interface{}) *Asm { return a.opWithPush(vm.ADD, v...) } +func (a *Asm) Mul(v ...interface{}) *Asm { return a.opWithPush(vm.MUL, v...) } +func (a *Asm) Sub(v ...interface{}) *Asm { return a.opWithPush(vm.SUB, v...) } +func (a *Asm) Div(v ...interface{}) *Asm { return a.opWithPush(vm.DIV, v...) } +func (a *Asm) SDiv(v ...interface{}) *Asm { return a.opWithPush(vm.SDIV, v...) } +func (a *Asm) Mod(v ...interface{}) *Asm { return a.opWithPush(vm.MOD, v...) } +func (a *Asm) SMod(v ...interface{}) *Asm { return a.opWithPush(vm.SMOD, v...) } +func (a *Asm) AddMod(v ...interface{}) *Asm { return a.opWithPush(vm.ADDMOD, v...) } +func (a *Asm) MulMod(v ...interface{}) *Asm { return a.opWithPush(vm.MULMOD, v...) } +func (a *Asm) Exp(v ...interface{}) *Asm { return a.opWithPush(vm.EXP, v...) } +func (a *Asm) SignExtend(v ...interface{}) *Asm { return a.opWithPush(vm.SIGNEXTEND, v...) } // 0x10 range -func (a *asm) Lt(v ...interface{}) *asm { return a.opWithPush(vm.LT, v...) } -func (a *asm) Gt(v ...interface{}) *asm { return a.opWithPush(vm.GT, v...) } -func (a *asm) SLt(v ...interface{}) *asm { return a.opWithPush(vm.SLT, v...) } -func (a *asm) SGt(v ...interface{}) *asm { return a.opWithPush(vm.SGT, v...) } -func (a *asm) Eq(v ...interface{}) *asm { return a.opWithPush(vm.EQ, v...) } -func (a *asm) IsZero(v ...interface{}) *asm { return a.opWithPush(vm.ISZERO, v...) } -func (a *asm) And(v ...interface{}) *asm { return a.opWithPush(vm.AND, v...) } -func (a *asm) Or(v ...interface{}) *asm { return a.opWithPush(vm.OR, v...) } -func (a *asm) Xor(v ...interface{}) *asm { return a.opWithPush(vm.XOR, v...) } -func (a *asm) Not(v ...interface{}) *asm { return a.opWithPush(vm.NOT, v...) } -func (a *asm) Byte(v ...interface{}) *asm { return a.opWithPush(vm.BYTE, v...) } -func (a *asm) Shl(v ...interface{}) *asm { return a.opWithPush(vm.SHL, v...) } -func (a *asm) Shr(v ...interface{}) *asm { return a.opWithPush(vm.SHR, v...) } -func (a *asm) Sar(v ...interface{}) *asm { return a.opWithPush(vm.SAR, v...) } -func (a *asm) Sha3(v ...interface{}) *asm { return a.opWithPush(vm.SHA3, v...) } +func (a *Asm) Lt(v ...interface{}) *Asm { return a.opWithPush(vm.LT, v...) } +func (a *Asm) Gt(v ...interface{}) *Asm { return a.opWithPush(vm.GT, v...) } +func (a *Asm) SLt(v ...interface{}) *Asm { return a.opWithPush(vm.SLT, v...) } +func (a *Asm) SGt(v ...interface{}) *Asm { return a.opWithPush(vm.SGT, v...) } +func (a *Asm) Eq(v ...interface{}) *Asm { return a.opWithPush(vm.EQ, v...) } +func (a *Asm) IsZero(v ...interface{}) *Asm { return a.opWithPush(vm.ISZERO, v...) } +func (a *Asm) And(v ...interface{}) *Asm { return a.opWithPush(vm.AND, v...) } +func (a *Asm) Or(v ...interface{}) *Asm { return a.opWithPush(vm.OR, v...) } +func (a *Asm) Xor(v ...interface{}) *Asm { return a.opWithPush(vm.XOR, v...) } +func (a *Asm) Not(v ...interface{}) *Asm { return a.opWithPush(vm.NOT, v...) } +func (a *Asm) Byte(v ...interface{}) *Asm { return a.opWithPush(vm.BYTE, v...) } +func (a *Asm) Shl(v ...interface{}) *Asm { return a.opWithPush(vm.SHL, v...) } +func (a *Asm) Shr(v ...interface{}) *Asm { return a.opWithPush(vm.SHR, v...) } +func (a *Asm) Sar(v ...interface{}) *Asm { return a.opWithPush(vm.SAR, v...) } +func (a *Asm) Sha3(v ...interface{}) *Asm { return a.opWithPush(vm.KECCAK256, v...) } // 0x30 range -func (a *asm) Address() *asm { return a.appendByte(vm.ADDRESS) } -func (a *asm) Balance(v ...interface{}) *asm { return a.opWithPush(vm.BALANCE, v...) } -func (a *asm) Origin() *asm { return a.appendByte(vm.ORIGIN) } -func (a *asm) Caller() *asm { return a.appendByte(vm.CALLER) } -func (a *asm) CallValue() *asm { return a.appendByte(vm.CALLVALUE) } -func (a *asm) CallDataLoad(v ...interface{}) *asm { return a.opWithPush(vm.CALLDATALOAD, v...) } -func (a *asm) CallDataSize() *asm { return a.appendByte(vm.CALLDATASIZE) } -func (a *asm) CallDataCopy(v ...interface{}) *asm { return a.opWithPush(vm.CALLDATACOPY, v...) } -func (a *asm) CodeSize() *asm { return a.appendByte(vm.CODESIZE) } -func (a *asm) CodeCopy(v ...interface{}) *asm { return a.opWithPush(vm.CODECOPY, v...) } -func (a *asm) GasPrice() *asm { return a.appendByte(vm.GASPRICE) } -func (a *asm) ExtCodeSize(v ...interface{}) *asm { return a.opWithPush(vm.EXTCODESIZE, v...) } -func (a *asm) ExtCodeCopy(v ...interface{}) *asm { return a.opWithPush(vm.EXTCODECOPY, v...) } -func (a *asm) ReturnDataSize() *asm { return a.appendByte(vm.RETURNDATASIZE) } -func (a *asm) ReturnDataCopy(v ...interface{}) *asm { return a.opWithPush(vm.RETURNDATACOPY, v...) } -func (a *asm) ExtCodeHash(v ...interface{}) *asm { return a.opWithPush(vm.EXTCODEHASH, v...) } +func (a *Asm) Address() *Asm { return a.appendByte(vm.ADDRESS) } +func (a *Asm) Balance(v ...interface{}) *Asm { return a.opWithPush(vm.BALANCE, v...) } +func (a *Asm) Origin() *Asm { return a.appendByte(vm.ORIGIN) } +func (a *Asm) Caller() *Asm { return a.appendByte(vm.CALLER) } +func (a *Asm) CallValue() *Asm { return a.appendByte(vm.CALLVALUE) } +func (a *Asm) CallDataLoad(v ...interface{}) *Asm { return a.opWithPush(vm.CALLDATALOAD, v...) } +func (a *Asm) CallDataSize() *Asm { return a.appendByte(vm.CALLDATASIZE) } +func (a *Asm) CallDataCopy(v ...interface{}) *Asm { return a.opWithPush(vm.CALLDATACOPY, v...) } +func (a *Asm) CodeSize() *Asm { return a.appendByte(vm.CODESIZE) } +func (a *Asm) CodeCopy(v ...interface{}) *Asm { return a.opWithPush(vm.CODECOPY, v...) } +func (a *Asm) GasPrice() *Asm { return a.appendByte(vm.GASPRICE) } +func (a *Asm) ExtCodeSize(v ...interface{}) *Asm { return a.opWithPush(vm.EXTCODESIZE, v...) } +func (a *Asm) ExtCodeCopy(v ...interface{}) *Asm { return a.opWithPush(vm.EXTCODECOPY, v...) } +func (a *Asm) ReturnDataSize() *Asm { return a.appendByte(vm.RETURNDATASIZE) } +func (a *Asm) ReturnDataCopy(v ...interface{}) *Asm { return a.opWithPush(vm.RETURNDATACOPY, v...) } +func (a *Asm) ExtCodeHash(v ...interface{}) *Asm { return a.opWithPush(vm.EXTCODEHASH, v...) } // 0x40 range -func (a *asm) BlockHash(v ...interface{}) *asm { return a.opWithPush(vm.BLOCKHASH, v...) } -func (a *asm) Coinbase() *asm { return a.appendByte(vm.COINBASE) } -func (a *asm) Timestamp() *asm { return a.appendByte(vm.TIMESTAMP) } -func (a *asm) Number() *asm { return a.appendByte(vm.NUMBER) } -func (a *asm) Difficulty() *asm { return a.appendByte(vm.DIFFICULTY) } -func (a *asm) GasLimit() *asm { return a.appendByte(vm.GASLIMIT) } -func (a *asm) ChainID() *asm { return a.appendByte(vm.CHAINID) } -func (a *asm) SelfBalance() *asm { return a.appendByte(vm.SELFBALANCE) } -func (a *asm) BaseFee() *asm { return a.appendByte(vm.BASEFEE) } +func (a *Asm) BlockHash(v ...interface{}) *Asm { return a.opWithPush(vm.BLOCKHASH, v...) } +func (a *Asm) Coinbase() *Asm { return a.appendByte(vm.COINBASE) } +func (a *Asm) Timestamp() *Asm { return a.appendByte(vm.TIMESTAMP) } +func (a *Asm) Number() *Asm { return a.appendByte(vm.NUMBER) } +func (a *Asm) Difficulty() *Asm { return a.appendByte(vm.DIFFICULTY) } +func (a *Asm) GasLimit() *Asm { return a.appendByte(vm.GASLIMIT) } +func (a *Asm) ChainID() *Asm { return a.appendByte(vm.CHAINID) } +func (a *Asm) SelfBalance() *Asm { return a.appendByte(vm.SELFBALANCE) } +func (a *Asm) BaseFee() *Asm { return a.appendByte(vm.BASEFEE) } // 0x50 range -func (a *asm) Pop() *asm { return a.appendByte(vm.POP) } -func (a *asm) MLoad(v ...interface{}) *asm { return a.opWithPush(vm.MLOAD, v...) } -func (a *asm) MStore(v ...interface{}) *asm { return a.opWithPush(vm.MSTORE, v...) } -func (a *asm) MStore8(v ...interface{}) *asm { return a.opWithPush(vm.MSTORE8, v...) } -func (a *asm) SLoad(v ...interface{}) *asm { return a.opWithPush(vm.SLOAD, v...) } -func (a *asm) SStore(v ...interface{}) *asm { return a.opWithPush(vm.SSTORE, v...) } -func (a *asm) Jump(label ...string) *asm { return a.jump(vm.JUMP, label...) } -func (a *asm) JumpI(label ...string) *asm { return a.jump(vm.JUMPI, label...) } -func (a *asm) PC() *asm { return a.appendByte(vm.PC) } -func (a *asm) MSize() *asm { return a.appendByte(vm.MSIZE) } -func (a *asm) Gas() *asm { return a.appendByte(vm.GAS) } -func (a *asm) JumpDest(label ...string) *asm { return a.jumpDest(label...) } +func (a *Asm) Pop() *Asm { return a.appendByte(vm.POP) } +func (a *Asm) MLoad(v ...interface{}) *Asm { return a.opWithPush(vm.MLOAD, v...) } +func (a *Asm) MStore(v ...interface{}) *Asm { return a.opWithPush(vm.MSTORE, v...) } +func (a *Asm) MStore8(v ...interface{}) *Asm { return a.opWithPush(vm.MSTORE8, v...) } +func (a *Asm) SLoad(v ...interface{}) *Asm { return a.opWithPush(vm.SLOAD, v...) } +func (a *Asm) SStore(v ...interface{}) *Asm { return a.opWithPush(vm.SSTORE, v...) } +func (a *Asm) Jump(label ...string) *Asm { return a.jump(vm.JUMP, label...) } +func (a *Asm) JumpI(label ...string) *Asm { return a.jump(vm.JUMPI, label...) } +func (a *Asm) PC() *Asm { return a.appendByte(vm.PC) } +func (a *Asm) MSize() *Asm { return a.appendByte(vm.MSIZE) } +func (a *Asm) Gas() *Asm { return a.appendByte(vm.GAS) } +func (a *Asm) JumpDest(label ...string) *Asm { return a.jumpDest(label...) } // 0x60 range -func (a *asm) PushX(val interface{}) *asm { return a.push(val) } -func (a *asm) DupX(x int) *asm { +func (a *Asm) PushX(val interface{}) *Asm { return a.push(val) } +func (a *Asm) DupX(x int) *Asm { rangeCheck(x, 1, 16, "X") return a.appendByte(int(vm.DUP1) + x - 1) } -func (a *asm) SwapX(x int) *asm { +func (a *Asm) SwapX(x int) *Asm { rangeCheck(x, 1, 16, "X") return a.appendByte(int(vm.SWAP1) + x - 1) } // 0xa0 range -func (a *asm) LogX(x int) *asm { +func (a *Asm) LogX(x int) *Asm { rangeCheck(x, 0, 5, "X") return a.appendByte(int(vm.LOG0) + x) } // 0xf0 range -func (a *asm) Create(v ...interface{}) *asm { return a.opWithPush(vm.CREATE, v...) } -func (a *asm) Call(v ...interface{}) *asm { return a.opWithPush(vm.CALL, v...) } -func (a *asm) CallCode(v ...interface{}) *asm { return a.opWithPush(vm.CALLCODE, v...) } -func (a *asm) Return(v ...interface{}) *asm { return a.opWithPush(vm.RETURN, v...) } -func (a *asm) DelegateCall(v ...interface{}) *asm { return a.opWithPush(vm.DELEGATECALL, v...) } -func (a *asm) Create2(v ...interface{}) *asm { return a.opWithPush(vm.CREATE2, v...) } -func (a *asm) StaticCall(v ...interface{}) *asm { return a.opWithPush(vm.STATICCALL, v...) } -func (a *asm) Revert(v ...interface{}) *asm { return a.opWithPush(vm.REVERT, v...) } -func (a *asm) SelfDestruct() *asm { return a.appendByte(vm.SELFDESTRUCT) } - -func (a *asm) jump(op vm.OpCode, label ...string) *asm { +func (a *Asm) Create(v ...interface{}) *Asm { return a.opWithPush(vm.CREATE, v...) } +func (a *Asm) Call(v ...interface{}) *Asm { return a.opWithPush(vm.CALL, v...) } +func (a *Asm) CallCode(v ...interface{}) *Asm { return a.opWithPush(vm.CALLCODE, v...) } +func (a *Asm) Return(v ...interface{}) *Asm { return a.opWithPush(vm.RETURN, v...) } +func (a *Asm) DelegateCall(v ...interface{}) *Asm { return a.opWithPush(vm.DELEGATECALL, v...) } +func (a *Asm) Create2(v ...interface{}) *Asm { return a.opWithPush(vm.CREATE2, v...) } +func (a *Asm) StaticCall(v ...interface{}) *Asm { return a.opWithPush(vm.STATICCALL, v...) } +func (a *Asm) Revert(v ...interface{}) *Asm { return a.opWithPush(vm.REVERT, v...) } +func (a *Asm) SelfDestruct() *Asm { return a.appendByte(vm.SELFDESTRUCT) } + +func (a *Asm) jump(op vm.OpCode, label ...string) *Asm { if len(label) > 0 { rangeCheck(len(label), 1, 1, "len(label)") if pos, ok := a.labelMap[label[0]]; ok { a.PushX(pos) } else { - a.pendingLabelsMap[label[0]] = append(a.pendingLabelsMap[label[0]], len(a.Bytecode)) - a.PushX([]byte{0, 0}) + a.pendingLabelsMap[label[0]] = append(a.pendingLabelsMap[label[0]], len(a.bytecode)) + a.PushX([]byte{0, 0, 0}) } } return a.appendByte(op) } -func (a *asm) jumpDest(label ...string) *asm { +func (a *Asm) jumpDest(label ...string) *Asm { a.appendByte(vm.JUMPDEST) if len(label) > 0 { @@ -162,15 +174,14 @@ func (a *asm) jumpDest(label ...string) *asm { panic("label already defined") } - a.labelMap[label[0]] = len(a.Bytecode) + a.labelMap[label[0]] = len(a.bytecode) - pos := big.NewInt(int64(len(a.Bytecode) - 1)).Bytes() - if len(pos) == 1 { - pos = []byte{0, pos[0]} + pos := big.NewInt(int64(len(a.bytecode) - 1)).Bytes() + if len(pos) < 3 { + pos = append(make([]byte, 3-len(pos)), pos...) } for _, pendingLabel := range a.pendingLabelsMap[label[0]] { - a.Bytecode[pendingLabel+1] = pos[0] - a.Bytecode[pendingLabel+2] = pos[1] + copy(a.bytecode[pendingLabel+1:pendingLabel+4], pos) } delete(a.pendingLabelsMap, label[0]) @@ -179,12 +190,12 @@ func (a *asm) jumpDest(label ...string) *asm { return a } -func (a *asm) opWithPush(op vm.OpCode, v ...interface{}) *asm { +func (a *Asm) opWithPush(op vm.OpCode, v ...interface{}) *Asm { opPushRangeCheck(op, len(v)) return a.pushRev(v...).appendByte(op) } -func (a *asm) push(v ...interface{}) *asm { +func (a *Asm) push(v ...interface{}) *Asm { for _, v := range v { bytes := toBytes(v) @@ -199,19 +210,19 @@ func (a *asm) push(v ...interface{}) *asm { return a } -func (a *asm) pushRev(v ...interface{}) *asm { +func (a *Asm) pushRev(v ...interface{}) *Asm { reverse(v) return a.push(v...) } -func (a *asm) appendByte(v interface{}) *asm { +func (a *Asm) appendByte(v interface{}) *Asm { switch v := v.(type) { case vm.OpCode: - a.Bytecode = append(a.Bytecode, byte(v)) + a.bytecode = append(a.bytecode, byte(v)) case byte: - a.Bytecode = append(a.Bytecode, v) + a.bytecode = append(a.bytecode, v) case int: - a.Bytecode = append(a.Bytecode, byte(v)) + a.bytecode = append(a.bytecode, byte(v)) default: panic(fmt.Sprintf("unexpected appendByte type %T", v)) } diff --git a/geth-utils/gethutil/trace.go b/geth-utils/gethutil/trace.go index 6972f96d0b..3acce4d66a 100644 --- a/geth-utils/gethutil/trace.go +++ b/geth-utils/gethutil/trace.go @@ -5,12 +5,29 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/core/vm/runtime" + "github.com/ethereum/go-ethereum/eth/tracers/logger" + "github.com/ethereum/go-ethereum/params" ) +// Copied from github.com/ethereum/go-ethereum/internal/ethapi.ExecutionResult +// ExecutionResult groups all structured logs emitted by the EVM +// while replaying a transaction in debug mode as well as transaction +// execution status, the amount of gas used and the return value +type ExecutionResult struct { + Gas uint64 `json:"gas"` + Failed bool `json:"failed"` + ReturnValue string `json:"returnValue"` + StructLogs []StructLogRes `json:"structLogs"` +} + +// StructLogRes stores a structured log emitted by the EVM while replaying a +// transaction in debug mode // Copied from github.com/ethereum/go-ethereum/internal/ethapi.StructLogRes type StructLogRes struct { Pc uint64 `json:"pc"` @@ -24,9 +41,9 @@ type StructLogRes struct { Storage *map[string]string `json:"storage,omitempty"` } -// FormatLogs formats EVM returned structured logs for json output. // Copied from github.com/ethereum/go-ethereum/internal/ethapi.FormatLogs -func FormatLogs(logs []vm.StructLog) []StructLogRes { +// FormatLogs formats EVM returned structured logs for json output +func FormatLogs(logs []logger.StructLog) []StructLogRes { formatted := make([]StructLogRes, len(logs)) for index, trace := range logs { formatted[index] = StructLogRes{ @@ -62,42 +79,139 @@ func FormatLogs(logs []vm.StructLog) []StructLogRes { return formatted } +type Block struct { + Coinbase common.Address `json:"coinbase"` + Timestamp *hexutil.Big `json:"timestamp"` + Number *hexutil.Big `json:"number"` + Difficulty *hexutil.Big `json:"difficulty"` + GasLimit *hexutil.Big `json:"gas_limit"` + BaseFee *hexutil.Big `json:"base_fee"` +} + type Account struct { - Address common.Address - Balance *big.Int - Bytecode []byte + Nonce hexutil.Uint64 `json:"nonce"` + Balance *hexutil.Big `json:"balance"` + Code hexutil.Bytes `json:"code"` + Storage map[common.Hash]common.Hash `json:"storage"` } -func TraceTx(toAddress *common.Address, calldata []byte, config *runtime.Config, accounts []Account) ([]StructLogRes, error) { - // Overwrite state - newState, err := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - if err != nil { - return nil, fmt.Errorf("failed to initialize new state: %w", err) +type Transaction struct { + From common.Address `json:"from"` + To *common.Address `json:"to"` + Nonce hexutil.Uint64 `json:"nonce"` + Value *hexutil.Big `json:"value"` + GasLimit hexutil.Uint64 `json:"gas_limit"` + GasPrice *hexutil.Big `json:"gas_price"` + GasFeeCap *hexutil.Big `json:"gas_fee_cap"` + GasTipCap *hexutil.Big `json:"gas_tip_cap"` + CallData hexutil.Bytes `json:"call_data"` + AccessList []struct { + Address common.Address `json:"address"` + StorageKeys []common.Hash `json:"storage_keys"` + } `json:"access_list"` +} + +type TraceConfig struct { + ChainID *hexutil.Big `json:"chain_id"` + // HistoryHashes contains most recent 256 block hashes in history, + // where the lastest one is at HistoryHashes[len(HistoryHashes)-1]. + HistoryHashes []*hexutil.Big `json:"history_hashes"` + Block Block `json:"block_constants"` + Accounts map[common.Address]Account `json:"accounts"` + Transaction Transaction `json:"transaction"` +} + +func TraceTx(config TraceConfig) (*ExecutionResult, error) { + chainConfig := params.ChainConfig{ + ChainID: toBigInt(config.ChainID), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: big.NewInt(0), + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP150Hash: common.Hash{}, + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + } + + // If gas price is specified directly, the tx is treated as legacy one + if config.Transaction.GasPrice != nil { + config.Block.BaseFee = new(hexutil.Big) + config.Transaction.GasFeeCap = config.Transaction.GasPrice + config.Transaction.GasTipCap = config.Transaction.GasPrice } - for _, account := range accounts { + + blockCtx := vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + GetHash: func(n uint64) common.Hash { + number := config.Block.Number.ToInt().Uint64() + if number > n && number-n <= 256 { + index := uint64(len(config.HistoryHashes)) - number + n + return common.BigToHash(toBigInt(config.HistoryHashes[index])) + } + return common.Hash{} + }, + Coinbase: config.Block.Coinbase, + BlockNumber: toBigInt(config.Block.Number), + Time: toBigInt(config.Block.Timestamp), + Difficulty: toBigInt(config.Block.Difficulty), + BaseFee: toBigInt(config.Block.BaseFee), + GasLimit: toBigInt(config.Block.GasLimit).Uint64(), + } + + txAccessList := make(types.AccessList, len(config.Transaction.AccessList)) + for i, accessList := range config.Transaction.AccessList { + txAccessList[i].Address = accessList.Address + txAccessList[i].StorageKeys = accessList.StorageKeys + } + message := types.NewMessage( + config.Transaction.From, + config.Transaction.To, + uint64(config.Transaction.Nonce), + toBigInt(config.Transaction.Value), + uint64(config.Transaction.GasLimit), + toBigInt(config.Transaction.GasPrice), + toBigInt(config.Transaction.GasFeeCap), + toBigInt(config.Transaction.GasTipCap), + config.Transaction.CallData, + txAccessList, + false, + ) + txContext := core.NewEVMTxContext(message) + + // Setup state db with accounts from argument + stateDB, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + for address, account := range config.Accounts { + stateDB.SetNonce(address, uint64(account.Nonce)) + stateDB.SetCode(address, account.Code) if account.Balance != nil { - newState.SetBalance(account.Address, account.Balance) + stateDB.SetBalance(address, toBigInt(account.Balance)) + } + for key, value := range account.Storage { + stateDB.SetState(address, key, value) } - newState.SetCode(account.Address, account.Bytecode) } - newState.Finalise(true) - config.State = newState - - // Overwrite config with tracer - tracer := vm.NewStructLogger(&vm.LogConfig{ - EnableMemory: true, - EnableReturnData: true, - }) - config.EVMConfig.Debug = true - config.EVMConfig.Tracer = tracer - - if toAddress == nil { - _, _, _, err = runtime.Create(calldata, config) - } else { - _, _, err = runtime.Call(*toAddress, nil, config) + stateDB.Finalise(chainConfig.IsByzantium(blockCtx.BlockNumber)) + + // Run the transaction with tracing enabled. + tracer := logger.NewStructLogger(&logger.Config{EnableMemory: true}) + evm := vm.NewEVM(blockCtx, txContext, stateDB, &chainConfig, vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true}) + result, err := core.ApplyMessage(evm, message, new(core.GasPool).AddGas(message.Gas())) + if err != nil { + return nil, err } - return FormatLogs(tracer.StructLogs()), err + return &ExecutionResult{ + Gas: result.UsedGas, + Failed: result.Failed(), + ReturnValue: fmt.Sprintf("%x", result.ReturnData), + StructLogs: FormatLogs(tracer.StructLogs()), + }, nil } - -// TODO: TraceBlock diff --git a/geth-utils/gethutil/util.go b/geth-utils/gethutil/util.go index 8c0de34241..374c9fa529 100644 --- a/geth-utils/gethutil/util.go +++ b/geth-utils/gethutil/util.go @@ -2,9 +2,11 @@ package gethutil import ( "fmt" + "math/big" "unsafe" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/vm" "github.com/holiman/uint256" ) @@ -62,6 +64,10 @@ func toBytes(value interface{}) []byte { bytes = value.Bytes() case *uint256.Int: bytes = value.Bytes() + case *big.Int: + bytes = value.Bytes() + default: + panic(fmt.Errorf("Unsupported type %T", value)) } if len(bytes) == 0 { @@ -70,3 +76,10 @@ func toBytes(value interface{}) []byte { return bytes } + +func toBigInt(value *hexutil.Big) *big.Int { + if value != nil { + return value.ToInt() + } + return big.NewInt(0) +} diff --git a/geth-utils/go.mod b/geth-utils/go.mod index 0c9d73de9a..d12e17c68f 100644 --- a/geth-utils/go.mod +++ b/geth-utils/go.mod @@ -3,7 +3,7 @@ module main go 1.16 require ( - github.com/ethereum/go-ethereum v1.10.12 + github.com/ethereum/go-ethereum v1.10.15 github.com/holiman/uint256 v1.2.0 ) diff --git a/geth-utils/go.sum b/geth-utils/go.sum index 1f2a441f4c..8f13c30f0a 100644 --- a/geth-utils/go.sum +++ b/geth-utils/go.sum @@ -107,8 +107,8 @@ github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/go-ethereum v1.10.12 h1:el/KddB3gLEsnNgGQ3SQuZuiZjwnFTYHe5TwUet5Om4= -github.com/ethereum/go-ethereum v1.10.12/go.mod h1:W3yfrFyL9C1pHcwY5hmRHVDaorTiQxhYBkKyu5mEDHw= +github.com/ethereum/go-ethereum v1.10.15 h1:E9o0kMbD8HXhp7g6UwIwntY05WTDheCGziMhegcBsQw= +github.com/ethereum/go-ethereum v1.10.15/go.mod h1:W3yfrFyL9C1pHcwY5hmRHVDaorTiQxhYBkKyu5mEDHw= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= diff --git a/geth-utils/lib/lib.go b/geth-utils/lib/lib.go index 848b93abf8..634d4f7fc6 100644 --- a/geth-utils/lib/lib.go +++ b/geth-utils/lib/lib.go @@ -8,35 +8,27 @@ import ( "encoding/json" "fmt" "main/gethutil" - "math/big" - "os" "unsafe" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/core/vm/runtime" - "github.com/ethereum/go-ethereum/params" ) // TODO: Add proper error handling. For example, return an int, where 0 means // ok, and !=0 means error. //export CreateTrace -func CreateTrace(config *C.char) *C.char { - var gethConfig GethConfig - err := json.Unmarshal([]byte(C.GoString(config)), &gethConfig) +func CreateTrace(configStr *C.char) *C.char { + var config gethutil.TraceConfig + err := json.Unmarshal([]byte(C.GoString(configStr)), &config) if err != nil { - fmt.Fprintf(os.Stderr, "failed to load trace config, err: %v\n", err) + return C.CString(fmt.Sprintf("Failed to unmarshal config, err: %v", err)) } - logs, err := gethutil.TraceTx(&gethConfig.target, nil, &gethConfig.config, gethConfig.contracts) + executionResult, err := gethutil.TraceTx(config) if err != nil { - fmt.Fprintf(os.Stderr, "trace stopped unexpectedly, err: %v\n", err) + return C.CString(fmt.Sprintf("Failed to trace tx, err: %v", err)) } - bytes, err := json.MarshalIndent(logs, "", " ") + bytes, err := json.MarshalIndent(executionResult, "", " ") if err != nil { - fmt.Fprintf(os.Stderr, "failed to marshal logs, err: %v\n", err) + return C.CString(fmt.Sprintf("Failed to marshal ExecutionResult, err: %v", err)) } return C.CString(string(bytes)) @@ -47,87 +39,4 @@ func FreeString(str *C.char) { C.free(unsafe.Pointer(str)) } -// FIXME: GethConfig unmarshals from JsonConfig, which is extremely confusing. -// https://github.com/appliedzkp/zkevm-circuits/issues/188 -type GethConfig struct { - config runtime.Config - contracts []gethutil.Account - target common.Address -} - -type BlockConstants struct { - Hash common.Hash `json:"hash"` - Coinbase common.Address `json:"coinbase"` - Timestamp *hexutil.Big `json:"timestamp"` - BlockNumber *hexutil.Big `json:"number"` - Difficulty *hexutil.Big `json:"difficulty"` - GasLimit *hexutil.Big `json:"gas_limit"` - ChainID *hexutil.Big `json:"chain_id"` - BaseFee *hexutil.Big `json:"base_fee"` -} - -type Transaction struct { - Origin common.Address `json:"origin"` - GasLimit *hexutil.Big `json:"gas_limit"` - Target common.Address `json:"target"` -} - -type AccountData struct { - Address common.Address `json:"address"` - Balance *hexutil.Big `json:"balance"` - Code hexutil.Bytes `json:"code"` -} - -type JsonConfig struct { - Block BlockConstants `json:"block_constants"` - Transaction Transaction `json:"transaction"` - Accounts []AccountData `json:"accounts"` -} - -func (this *GethConfig) UnmarshalJSON(b []byte) error { - var jConfig JsonConfig - err := json.Unmarshal(b, &jConfig) - if err != nil { - return err - } - - this.config = runtime.Config{ - Origin: jConfig.Transaction.Origin, - GasLimit: jConfig.Transaction.GasLimit.ToInt().Uint64(), - Difficulty: jConfig.Block.Difficulty.ToInt(), - Time: jConfig.Block.Timestamp.ToInt(), - Coinbase: jConfig.Block.Coinbase, - BlockNumber: jConfig.Block.BlockNumber.ToInt(), - ChainConfig: ¶ms.ChainConfig{ - ChainID: jConfig.Block.ChainID.ToInt(), - HomesteadBlock: big.NewInt(0), - DAOForkBlock: big.NewInt(0), - DAOForkSupport: true, - EIP150Block: big.NewInt(0), - EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - BerlinBlock: big.NewInt(0), - LondonBlock: big.NewInt(0), - }, - EVMConfig: vm.Config{}, - } - - for _, contract := range jConfig.Accounts { - address := contract.Address - balance := contract.Balance.ToInt() - code := contract.Code - this.contracts = append(this.contracts, gethutil.Account{Address: address, Balance: balance, Bytecode: code}) - } - - this.target = jConfig.Transaction.Target - - return nil -} - func main() {} diff --git a/geth-utils/src/lib.rs b/geth-utils/src/lib.rs index 0633c1156f..30268ed162 100644 --- a/geth-utils/src/lib.rs +++ b/geth-utils/src/lib.rs @@ -1,7 +1,6 @@ //! Connection to external EVM tracer. use core::fmt::{Display, Formatter, Result as FmtResult}; -use std::error::Error as StdError; use std::ffi::{CStr, CString}; use std::os::raw::c_char; @@ -30,18 +29,17 @@ pub fn trace(config: &str) -> Result { unsafe { FreeString(c_result.as_ptr()) }; // Return the trace - match result.is_empty() || result.starts_with("Error") { - // TODO: Embed error from result into TracingError if possible - true => Err(Error::TracingError), + match result.is_empty() || result.starts_with("Failed") { + true => Err(Error::TracingError(result)), false => Ok(result), } } /// Error type for any geth-utils related failure. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone)] pub enum Error { /// Error while tracing. - TracingError, + TracingError(String), } impl Display for Error { @@ -50,4 +48,77 @@ impl Display for Error { } } -impl StdError for Error {} +#[cfg(test)] +mod test { + use crate::trace; + + #[test] + fn valid_tx() { + for config in [ + // Minimal call tx with gas_limit = 21000 + r#"{ + "transaction": { + "from": "0x00000000000000000000000000000000000000fe", + "to": "0x00000000000000000000000000000000000000ff", + "gas_limit": "0x5208" + } + }"#, + // Minimal creation tx with gas_limit = 53000 + r#"{ + "transaction": { + "from": "0x00000000000000000000000000000000000000fe", + "gas_limit": "0xcf08" + } + }"#, + // Normal call tx with gas_limit = 21000 and gas_price = 2 Gwei + r#"{ + "accounts": { + "0x00000000000000000000000000000000000000fe": { + "balance": "0x2632e314a000" + } + }, + "transaction": { + "from": "0x00000000000000000000000000000000000000fe", + "to": "0x00000000000000000000000000000000000000ff", + "gas_limit": "0x5208", + "gas_price": "0x77359400" + } + }"#, + ] { + assert!(trace(config).is_ok()); + } + } + + #[test] + fn invalid_tx() { + for config in [ + // Insufficient gas for intrinsic usage + r#"{ + "transaction": { + "from": "0x00000000000000000000000000000000000000fe", + "to": "0x00000000000000000000000000000000000000ff" + } + }"#, + // Insufficient balance to buy gas + r#"{ + "transaction": { + "from": "0x00000000000000000000000000000000000000fe", + "to": "0x00000000000000000000000000000000000000ff", + "gas_limit": "0x5208", + "gas_price": "0x1111" + } + }"#, + // Insufficient balance to do the first transfer + r#"{ + "transaction": { + "from": "0x00000000000000000000000000000000000000fe", + "to": "0x00000000000000000000000000000000000000ff", + "value": "0x100", + "gas_limit": "0x5208" + } + }"#, + ] { + assert!(trace(config).is_err()) + } + } +} diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index a73d954ddf..167e14f814 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -5,7 +5,7 @@ use bus_mapping::rpc::GethClient; use env_logger::Env; -use eth_types::{Address, ChainConstants}; +use eth_types::Address; use ethers::{ abi, core::k256::ecdsa::SigningKey, @@ -64,12 +64,10 @@ pub fn get_provider() -> Provider { Provider::new(transport).interval(Duration::from_millis(100)) } -/// Get the chain constants by querying the geth client. -pub async fn get_chain_constants() -> ChainConstants { +/// Get the chain id by querying the geth client. +pub async fn get_chain_id() -> u64 { let client = get_client(); - ChainConstants { - chain_id: client.get_chain_id().await.unwrap(), - } + client.get_chain_id().await.unwrap() } const PHRASE: &str = diff --git a/integration-tests/tests/circuits.rs b/integration-tests/tests/circuits.rs index 2dd1e64db4..d9ad6d81e1 100644 --- a/integration-tests/tests/circuits.rs +++ b/integration-tests/tests/circuits.rs @@ -27,7 +27,7 @@ mod test_evm_circuit { use zkevm_circuits::evm_circuit::{ param::STEP_HEIGHT, table::FixedTableTag, - witness::{Block, Bytecode, Rw, Transaction}, + witness::{Block, Bytecode, RwMap, Transaction}, EvmCircuit, }; @@ -43,7 +43,7 @@ mod test_evm_circuit { fn load_txs( &self, layouter: &mut impl Layouter, - txs: &[Transaction], + txs: &[Transaction], randomness: F, ) -> Result<(), Error> { layouter.assign_region( @@ -81,7 +81,7 @@ mod test_evm_circuit { fn load_rws( &self, layouter: &mut impl Layouter, - rws: &[Rw], + rws: &RwMap, randomness: F, ) -> Result<(), Error> { layouter.assign_region( @@ -98,7 +98,7 @@ mod test_evm_circuit { } offset += 1; - for rw in rws.iter() { + for rw in rws.0.values().flat_map(|rws| rws.iter()) { for (column, value) in self.rw_table.iter().zip(rw.table_assignment(randomness)) { @@ -224,7 +224,9 @@ mod test_evm_circuit { config.load_txs(&mut layouter, &self.block.txs, self.block.randomness)?; config.load_rws(&mut layouter, &self.block.rws, self.block.randomness)?; config.load_bytecodes(&mut layouter, &self.block.bytecodes, self.block.randomness)?; - config.evm_circuit.assign_block(&mut layouter, &self.block) + config + .evm_circuit + .assign_block_exact(&mut layouter, &self.block) } } @@ -266,8 +268,6 @@ mod test_evm_circuit { } async fn test_evm_circuit_block(block_num: u64) { - use halo2::arithmetic::BaseExt; - use pairing::bn256::Fr; use test_evm_circuit::*; let cli = get_client(); @@ -275,13 +275,7 @@ async fn test_evm_circuit_block(block_num: u64) { let builder = cli.gen_inputs(block_num).await.unwrap(); // Generate evm_circuit proof - let code_hash = builder.block.txs()[0].calls()[0].code_hash; - let bytecode = builder - .code_db - .0 - .get(&code_hash) - .expect("code_hash not found"); - let block = block_convert(Fr::rand(), bytecode, &builder.block); + let block = block_convert(&builder.block, &builder.code_db); run_test_circuit_complete_fixed_table(block).expect("evm_circuit verification failed"); } diff --git a/mock/src/lib.rs b/mock/src/lib.rs index 2b3bfb629d..8d4242595f 100644 --- a/mock/src/lib.rs +++ b/mock/src/lib.rs @@ -1,12 +1,13 @@ //! Mock types and functions to generate GethData used for tests -use eth_types::bytecode::Bytecode; -use eth_types::evm_types::Gas; -use eth_types::geth_types::{Account, BlockConstants, GethData, Transaction}; use eth_types::{ - address, Address, Block, Bytes, ChainConstants, Error, GethExecStep, GethExecTrace, Hash, Word, - U64, + address, + bytecode::Bytecode, + evm_types::Gas, + geth_types::{Account, BlockConstants, GethData, Transaction}, + Address, Block, Bytes, Error, Hash, Word, U64, }; +use external_tracer::{trace, TraceConfig}; use lazy_static::lazy_static; /// Mock chain ID @@ -30,20 +31,25 @@ pub fn new_single_tx_trace_accounts_gas( let eth_block = new_block(); let mut eth_tx = new_tx(ð_block); eth_tx.gas = Word::from(gas.0); - let c_constant = new_chain_constants(); - let b_constant = BlockConstants::from_eth_block(ð_block, &Word::from(c_constant.chain_id)); - let tracer_tx = Transaction::from_eth_tx(ð_tx); - let geth_trace = GethExecTrace { - gas: Gas(eth_tx.gas.as_u64()), - failed: false, - struct_logs: external_tracer::trace(&b_constant, &tracer_tx, &accounts)?.to_vec(), + + let trace_config = TraceConfig { + chain_id: MOCK_CHAIN_ID.into(), + // TODO: Add mocking history_hashes when nedded. + history_hashes: Vec::new(), + block_constants: BlockConstants::try_from(ð_block)?, + accounts: accounts + .iter() + .map(|account| (account.address, account.clone())) + .collect(), + transaction: Transaction::from_eth_tx(ð_tx), }; + let geth_trace = trace(&trace_config)?; Ok(GethData { + chain_id: trace_config.chain_id, + history_hashes: trace_config.history_hashes, eth_block, eth_tx, - c_constant, - b_constant, geth_trace, accounts, }) @@ -94,29 +100,6 @@ pub fn new_single_tx_trace_code_at_start(code: &Bytecode) -> Result) -> GethData { - let eth_block = new_block(); - let eth_tx = new_tx(ð_block); - let c_constant = new_chain_constants(); - let b_constant = BlockConstants::from_eth_block(ð_block, &Word::from(c_constant.chain_id)); - let geth_trace = eth_types::GethExecTrace { - gas: Gas(eth_tx.gas.as_u64()), - failed: false, - struct_logs: geth_steps, - }; - - GethData { - eth_block, - eth_tx, - c_constant, - b_constant, - geth_trace, - accounts: vec![], - } -} - /// Generate a new mock block with preloaded data, useful for tests. pub fn new_block() -> Block<()> { eth_types::Block { @@ -170,19 +153,13 @@ pub fn new_tx(block: &Block) -> eth_types::Transaction { } } -/// Generate a new mock chain constants, useful for tests. -fn new_chain_constants() -> eth_types::ChainConstants { - ChainConstants { - chain_id: MOCK_CHAIN_ID, - } -} - /// Generate a new mock tracer Account with preloaded data, useful for tests. fn new_tracer_account(code: &Bytecode) -> Account { Account { - address: new_tracer_tx().target, + address: new_tracer_tx().to.unwrap(), balance: Word::from(555u64), code: Bytes::from(code.to_vec()), + ..Default::default() } } @@ -190,8 +167,9 @@ fn new_tracer_account(code: &Bytecode) -> Account { /// tests. pub fn new_tracer_tx() -> Transaction { Transaction { - origin: *MOCK_COINBASE, + from: *MOCK_COINBASE, + to: Some(Address::zero()), gas_limit: Word::from(1_000_000u64), - target: Address::zero(), + ..Default::default() } } diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index c88a1d4ca4..3ed5e0ef62 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -104,7 +104,7 @@ pub(crate) mod test { evm_circuit::{ param::STEP_HEIGHT, table::FixedTableTag, - witness::{Block, BlockContext, Bytecode, Rw, Transaction}, + witness::{Block, BlockContext, Bytecode, RwMap, Transaction}, EvmCircuit, }, util::Expr, @@ -160,7 +160,7 @@ pub(crate) mod test { fn load_txs( &self, layouter: &mut impl Layouter, - txs: &[Transaction], + txs: &[Transaction], randomness: F, ) -> Result<(), Error> { layouter.assign_region( @@ -198,7 +198,7 @@ pub(crate) mod test { fn load_rws( &self, layouter: &mut impl Layouter, - rws: &[Rw], + rws: &RwMap, randomness: F, ) -> Result<(), Error> { layouter.assign_region( @@ -215,7 +215,7 @@ pub(crate) mod test { } offset += 1; - for rw in rws.iter() { + for rw in rws.0.values().flat_map(|rws| rws.iter()) { for (column, value) in self.rw_table.iter().zip(rw.table_assignment(randomness)) { @@ -271,10 +271,10 @@ pub(crate) mod test { ) } - fn load_blocks( + fn load_block( &self, layouter: &mut impl Layouter, - block: &BlockContext, + block: &BlockContext, randomness: F, ) -> Result<(), Error> { layouter.assign_region( @@ -379,7 +379,7 @@ pub(crate) mod test { config.load_txs(&mut layouter, &self.block.txs, self.block.randomness)?; config.load_rws(&mut layouter, &self.block.rws, self.block.randomness)?; config.load_bytecodes(&mut layouter, &self.block.bytecodes, self.block.randomness)?; - config.load_blocks(&mut layouter, &self.block.context, self.block.randomness)?; + config.load_block(&mut layouter, &self.block.context, self.block.randomness)?; config .evm_circuit .assign_block_exact(&mut layouter, &self.block) diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 4db5031865..b4a9e6a93b 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -76,8 +76,8 @@ pub(crate) trait ExecutionGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - transaction: &Transaction, - call: &Call, + transaction: &Transaction, + call: &Call, step: &ExecStep, ) -> Result<(), Error>; } @@ -278,9 +278,8 @@ impl ExecutionConfig { let gadget = G::configure(&mut cb); let (constraints, constraints_first_step, lookups, presets) = cb.build(); - let insert_result = presets_map.insert(G::EXECUTION_STATE, presets); debug_assert!( - insert_result.is_none(), + presets_map.insert(G::EXECUTION_STATE, presets).is_none(), "execution state already configured" ); @@ -438,11 +437,12 @@ impl ExecutionConfig { region: &mut Region<'_, F>, offset: usize, block: &Block, - transaction: &Transaction, - call: &Call, + transaction: &Transaction, + call: &Call, step: &ExecStep, ) -> Result<(), Error> { - self.step.assign_exec_step(region, offset, call, step)?; + self.step + .assign_exec_step(region, offset, block, transaction, call, step)?; for (cell, value) in self .presets_map diff --git a/zkevm-circuits/src/evm_circuit/execution/add.rs b/zkevm-circuits/src/evm_circuit/execution/add.rs index bb15a70e8d..4bb2f64dfe 100644 --- a/zkevm-circuits/src/evm_circuit/execution/add.rs +++ b/zkevm-circuits/src/evm_circuit/execution/add.rs @@ -74,8 +74,8 @@ impl ExecutionGadget for AddGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; diff --git a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs index 9cb9b35690..f1efa8899d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs @@ -120,11 +120,8 @@ impl ExecutionGadget for BeginTxGadget { rw_counter_end_of_reversion.expr(), ); - // Assume it's not a creation transaction nor calling a precompiled - cb.add_constraint( - "Handling of creation transaction has yet to be determined", - tx_is_create.expr(), - ); + // TODO: Handle creation transaction + // TODO: Handle precompiled // Read code_hash of callee let code_hash = cb.query_cell(); @@ -147,7 +144,7 @@ impl ExecutionGadget for BeginTxGadget { (CallContextFieldTag::Value, tx_value.expr()), (CallContextFieldTag::IsStatic, 0.expr()), ] { - cb.call_context_lookup(Some(call_id.expr()), field_tag, value); + cb.call_context_lookup(false.expr(), Some(call_id.expr()), field_tag, value); } cb.require_step_state_transition(StepStateTransition { @@ -170,14 +167,14 @@ impl ExecutionGadget for BeginTxGadget { // - Read CallContext IsStatic rw_counter: Delta(16.expr()), call_id: To(call_id.expr()), - is_root: To(1.expr()), - is_create: To(0.expr()), - opcode_source: To(code_hash.expr()), + is_root: To(true.expr()), + is_create: To(false.expr()), + code_source: To(code_hash.expr()), program_counter: To(0.expr()), stack_pointer: To(STACK_CAPACITY.expr()), gas_left: To(gas_left), - memory_word_size: To(0.expr()), state_write_counter: To(2.expr()), + ..StepStateTransition::new_context() }); Self { @@ -205,8 +202,8 @@ impl ExecutionGadget for BeginTxGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - tx: &Transaction, - call: &Call, + tx: &Transaction, + call: &Call, step: &ExecStep, ) -> Result<(), Error> { let gas_fee = tx.gas_price * tx.gas; @@ -270,17 +267,19 @@ mod test { use crate::evm_circuit::{ param::STACK_CAPACITY, step::ExecutionState, - table::{AccountFieldTag, CallContextFieldTag}, - test::{rand_fp, rand_range, run_test_circuit_incomplete_fixed_table}, - util::RandomLinearCombination, - witness::{Block, Bytecode, Call, ExecStep, Rw, Transaction}, + table::{AccountFieldTag, CallContextFieldTag, RwTableTag}, + test::{rand_bytes, rand_fp, rand_range, run_test_circuit_incomplete_fixed_table}, + witness::{Block, Bytecode, Call, CodeSource, ExecStep, Rw, RwMap, Transaction}, + }; + use eth_types::{ + self, address, + evm_types::{GasCost, OpcodeId}, + Address, ToWord, Word, }; - use eth_types::evm_types::{GasCost, OpcodeId}; - use eth_types::{self, address, Address, ToLittleEndian, ToWord, Word}; use std::convert::TryInto; - fn test_ok(tx: eth_types::Transaction, result: bool) { - let rw_counter_end_of_reversion = if result { 0 } else { 20 }; + fn test_ok(tx: eth_types::Transaction, is_success: bool) { + let rw_counter_end_of_reversion = if is_success { 0 } else { 23 }; let gas_fee = tx.gas * tx.gas_price.unwrap_or_else(Word::zero); let call_data_gas_cost = tx @@ -319,18 +318,40 @@ mod test { id: 1, is_root: true, is_create: false, - opcode_source: RandomLinearCombination::random_linear_combine( - bytecode.hash.to_le_bytes(), - randomness, - ), - result: Word::from(result as usize), + code_source: CodeSource::Account(bytecode.hash), rw_counter_end_of_reversion, - is_persistent: result, + is_persistent: is_success, + is_success, ..Default::default() }], steps: vec![ ExecStep { - rw_indices: (0..16 + if result { 0 } else { 2 }).collect(), + rw_indices: [ + vec![ + (RwTableTag::CallContext, 0), + (RwTableTag::CallContext, 1), + (RwTableTag::CallContext, 2), + (RwTableTag::Account, 0), + (RwTableTag::TxAccessListAccount, 0), + (RwTableTag::TxAccessListAccount, 1), + (RwTableTag::Account, 1), + (RwTableTag::Account, 2), + (RwTableTag::Account, 3), + (RwTableTag::CallContext, 3), + (RwTableTag::CallContext, 4), + (RwTableTag::CallContext, 5), + (RwTableTag::CallContext, 6), + (RwTableTag::CallContext, 7), + (RwTableTag::CallContext, 8), + (RwTableTag::CallContext, 9), + ], + if is_success { + vec![] + } else { + vec![(RwTableTag::Account, 4), (RwTableTag::Account, 5)] + }, + ] + .concat(), execution_state: ExecutionState::BeginTx, rw_counter: 1, gas_cost: intrinsic_gas_cost, @@ -348,151 +369,169 @@ mod test { }, ], }], - rws: [ - vec![ - Rw::CallContext { - rw_counter: 1, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::TxId, - value: Word::one(), - }, - Rw::CallContext { - rw_counter: 2, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::RwCounterEndOfReversion, - value: Word::from(rw_counter_end_of_reversion), - }, - Rw::CallContext { - rw_counter: 3, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::IsPersistent, - value: Word::from(result as u64), - }, - Rw::Account { - rw_counter: 4, - is_write: true, - account_address: tx.from, - field_tag: AccountFieldTag::Nonce, - value: tx.nonce + Word::one(), - value_prev: tx.nonce, - }, - Rw::TxAccessListAccount { - rw_counter: 5, - is_write: true, - tx_id: 1, - account_address: tx.from, - value: true, - value_prev: false, - }, - Rw::TxAccessListAccount { - rw_counter: 6, - is_write: true, - tx_id: 1, - account_address: tx.to.unwrap(), - value: true, - value_prev: false, - }, - Rw::Account { - rw_counter: 7, - is_write: true, - account_address: tx.from, - field_tag: AccountFieldTag::Balance, - value: from_balance, - value_prev: from_balance_prev, - }, - Rw::Account { - rw_counter: 8, - is_write: true, - account_address: tx.to.unwrap_or_else(Address::zero), - field_tag: AccountFieldTag::Balance, - value: to_balance, - value_prev: to_balance_prev, - }, - Rw::Account { - rw_counter: 9, - is_write: false, - account_address: tx.to.unwrap_or_else(Address::zero), - field_tag: AccountFieldTag::CodeHash, - value: bytecode.hash, - value_prev: bytecode.hash, - }, - Rw::CallContext { - rw_counter: 10, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::Depth, - value: Word::one(), - }, - Rw::CallContext { - rw_counter: 11, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::CallerAddress, - value: tx.from.to_word(), - }, - Rw::CallContext { - rw_counter: 12, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::CalleeAddress, - value: tx.to.unwrap_or_else(Address::zero).to_word(), - }, - Rw::CallContext { - rw_counter: 13, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::CallDataOffset, - value: Word::zero(), - }, - Rw::CallContext { - rw_counter: 14, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::CallDataLength, - value: tx.input.0.len().into(), - }, - Rw::CallContext { - rw_counter: 15, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::Value, - value: tx.value, - }, - Rw::CallContext { - rw_counter: 16, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::IsStatic, - value: Word::zero(), - }, - ], - if result { - vec![] - } else { - vec![ - Rw::Account { - rw_counter: 19, - is_write: true, - account_address: tx.to.unwrap_or_else(Address::zero), - field_tag: AccountFieldTag::Balance, - value: to_balance_prev, - value_prev: to_balance, - }, - Rw::Account { - rw_counter: 20, - is_write: true, - account_address: tx.from, - field_tag: AccountFieldTag::Balance, - value: from_balance_prev, - value_prev: from_balance, - }, - ] - }, - ] - .concat(), + rws: RwMap( + [ + ( + RwTableTag::TxAccessListAccount, + vec![ + Rw::TxAccessListAccount { + rw_counter: 5, + is_write: true, + tx_id: 1, + account_address: tx.from, + value: true, + value_prev: false, + }, + Rw::TxAccessListAccount { + rw_counter: 6, + is_write: true, + tx_id: 1, + account_address: tx.to.unwrap(), + value: true, + value_prev: false, + }, + ], + ), + ( + RwTableTag::Account, + [ + vec![ + Rw::Account { + rw_counter: 4, + is_write: true, + account_address: tx.from, + field_tag: AccountFieldTag::Nonce, + value: tx.nonce + Word::one(), + value_prev: tx.nonce, + }, + Rw::Account { + rw_counter: 7, + is_write: true, + account_address: tx.from, + field_tag: AccountFieldTag::Balance, + value: from_balance, + value_prev: from_balance_prev, + }, + Rw::Account { + rw_counter: 8, + is_write: true, + account_address: tx.to.unwrap_or_else(Address::zero), + field_tag: AccountFieldTag::Balance, + value: to_balance, + value_prev: to_balance_prev, + }, + Rw::Account { + rw_counter: 9, + is_write: false, + account_address: tx.to.unwrap_or_else(Address::zero), + field_tag: AccountFieldTag::CodeHash, + value: bytecode.hash, + value_prev: bytecode.hash, + }, + ], + if is_success { + vec![] + } else { + vec![ + Rw::Account { + rw_counter: rw_counter_end_of_reversion - 1, + is_write: true, + account_address: tx.to.unwrap_or_else(Address::zero), + field_tag: AccountFieldTag::Balance, + value: to_balance_prev, + value_prev: to_balance, + }, + Rw::Account { + rw_counter: rw_counter_end_of_reversion, + is_write: true, + account_address: tx.from, + field_tag: AccountFieldTag::Balance, + value: from_balance_prev, + value_prev: from_balance, + }, + ] + }, + ] + .concat(), + ), + ( + RwTableTag::CallContext, + vec![ + Rw::CallContext { + rw_counter: 1, + is_write: false, + call_id: 1, + field_tag: CallContextFieldTag::TxId, + value: Word::one(), + }, + Rw::CallContext { + rw_counter: 2, + is_write: false, + call_id: 1, + field_tag: CallContextFieldTag::RwCounterEndOfReversion, + value: Word::from(rw_counter_end_of_reversion), + }, + Rw::CallContext { + rw_counter: 3, + is_write: false, + call_id: 1, + field_tag: CallContextFieldTag::IsPersistent, + value: Word::from(is_success as u64), + }, + Rw::CallContext { + rw_counter: 10, + is_write: false, + call_id: 1, + field_tag: CallContextFieldTag::Depth, + value: Word::one(), + }, + Rw::CallContext { + rw_counter: 11, + is_write: false, + call_id: 1, + field_tag: CallContextFieldTag::CallerAddress, + value: tx.from.to_word(), + }, + Rw::CallContext { + rw_counter: 12, + is_write: false, + call_id: 1, + field_tag: CallContextFieldTag::CalleeAddress, + value: tx.to.unwrap_or_else(Address::zero).to_word(), + }, + Rw::CallContext { + rw_counter: 13, + is_write: false, + call_id: 1, + field_tag: CallContextFieldTag::CallDataOffset, + value: Word::zero(), + }, + Rw::CallContext { + rw_counter: 14, + is_write: false, + call_id: 1, + field_tag: CallContextFieldTag::CallDataLength, + value: tx.input.0.len().into(), + }, + Rw::CallContext { + rw_counter: 15, + is_write: false, + call_id: 1, + field_tag: CallContextFieldTag::Value, + value: tx.value, + }, + Rw::CallContext { + rw_counter: 16, + is_write: false, + call_id: 1, + field_tag: CallContextFieldTag::IsStatic, + value: Word::zero(), + }, + ], + ), + ] + .into(), + ), bytecodes: vec![bytecode], ..Default::default() }; @@ -507,7 +546,7 @@ mod test { ) -> eth_types::Transaction { let from = address!("0x00000000000000000000000000000000000000fe"); let to = address!("0x00000000000000000000000000000000000000ff"); - let minimal_gas = Word::from(21000); + let minimal_gas = Word::from(GasCost::TX.as_u64()); let one_ether = Word::from(10).pow(18.into()); let two_gwei = Word::from(2_000_000_000); eth_types::Transaction { @@ -538,10 +577,12 @@ mod test { #[test] fn begin_tx_gadget_rand() { + let one_hundred_ether = Word::from(10).pow(20.into()); + // Transfer random ether, successfully test_ok( mock_tx( - Some(Word::from(rand_range(0..=u64::MAX))), + Some(Word::from_little_endian(&rand_bytes(32)) % one_hundred_ether), None, None, vec![], @@ -563,7 +604,7 @@ mod test { // Transfer random ether, tx reverts test_ok( mock_tx( - Some(Word::from(rand_range(0..=u64::MAX))), + Some(Word::from_little_endian(&rand_bytes(32)) % one_hundred_ether), None, None, vec![], diff --git a/zkevm-circuits/src/evm_circuit/execution/bitwise.rs b/zkevm-circuits/src/evm_circuit/execution/bitwise.rs index a8c9831f8b..1e3264fd64 100644 --- a/zkevm-circuits/src/evm_circuit/execution/bitwise.rs +++ b/zkevm-circuits/src/evm_circuit/execution/bitwise.rs @@ -81,8 +81,8 @@ impl ExecutionGadget for BitwiseGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; diff --git a/zkevm-circuits/src/evm_circuit/execution/byte.rs b/zkevm-circuits/src/evm_circuit/execution/byte.rs index 2a41afb56e..fb503ea632 100644 --- a/zkevm-circuits/src/evm_circuit/execution/byte.rs +++ b/zkevm-circuits/src/evm_circuit/execution/byte.rs @@ -92,8 +92,8 @@ impl ExecutionGadget for ByteGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; diff --git a/zkevm-circuits/src/evm_circuit/execution/coinbase.rs b/zkevm-circuits/src/evm_circuit/execution/coinbase.rs index 68eef861b2..81ff08b718 100644 --- a/zkevm-circuits/src/evm_circuit/execution/coinbase.rs +++ b/zkevm-circuits/src/evm_circuit/execution/coinbase.rs @@ -1,12 +1,13 @@ use crate::{ evm_circuit::{ execution::ExecutionGadget, + param::N_BYTES_ACCOUNT_ADDRESS, step::ExecutionState, table::BlockContextFieldTag, util::{ common_gadget::SameContextGadget, constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, - Cell, Word, + from_bytes, RandomLinearCombination, }, witness::{Block, Call, ExecStep, Transaction}, }, @@ -14,11 +15,12 @@ use crate::{ }; use eth_types::ToLittleEndian; use halo2::{arithmetic::FieldExt, circuit::Region, plonk::Error}; +use std::convert::TryInto; #[derive(Clone, Debug)] pub(crate) struct CoinbaseGadget { same_context: SameContextGadget, - coinbase_address: Cell, + coinbase_address: RandomLinearCombination, } impl ExecutionGadget for CoinbaseGadget { @@ -27,7 +29,7 @@ impl ExecutionGadget for CoinbaseGadget { const EXECUTION_STATE: ExecutionState = ExecutionState::COINBASE; fn configure(cb: &mut ConstraintBuilder) -> Self { - let coinbase_address = cb.query_cell(); + let coinbase_address = cb.query_rlc(); // Push the value to the stack cb.stack_push(coinbase_address.expr()); @@ -36,7 +38,7 @@ impl ExecutionGadget for CoinbaseGadget { cb.block_lookup( BlockContextFieldTag::Coinbase.expr(), None, - coinbase_address.expr(), + from_bytes::expr(&coinbase_address.cells), ); // State transition @@ -60,8 +62,8 @@ impl ExecutionGadget for CoinbaseGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; @@ -71,10 +73,11 @@ impl ExecutionGadget for CoinbaseGadget { self.coinbase_address.assign( region, offset, - Some(Word::random_linear_combine( - coinbase.to_le_bytes(), - block.randomness, - )), + Some( + coinbase.to_le_bytes()[..N_BYTES_ACCOUNT_ADDRESS] + .try_into() + .unwrap(), + ), )?; Ok(()) diff --git a/zkevm-circuits/src/evm_circuit/execution/comparator.rs b/zkevm-circuits/src/evm_circuit/execution/comparator.rs index 11434a3582..615d3c48c2 100644 --- a/zkevm-circuits/src/evm_circuit/execution/comparator.rs +++ b/zkevm-circuits/src/evm_circuit/execution/comparator.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, from_bytes, math_gadget::{ComparisonGadget, IsEqualGadget}, - select, Word, + select, Cell, Word, }, witness::{Block, Call, ExecStep, Transaction}, }, @@ -22,6 +22,7 @@ pub(crate) struct ComparatorGadget { same_context: SameContextGadget, a: Word, b: Word, + result: Cell, comparison_lo: ComparisonGadget, comparison_hi: ComparisonGadget, is_eq: IsEqualGadget, @@ -71,7 +72,8 @@ impl ExecutionGadget for ComparatorGadget { // The result is: // - `lt` when LT or GT // - `eq` when EQ - let result = select::expr(is_eq.expr(), eq, lt); + // Use copy to avoid degree too high for stack_push below. + let result = cb.copy(select::expr(is_eq.expr(), eq, lt)); // Pop a and b from the stack, push the result on the stack. // When swap is enabled we swap stack places between a and b. @@ -79,7 +81,7 @@ impl ExecutionGadget for ComparatorGadget { // it only uses the LSB of a word. cb.stack_pop(select::expr(is_gt.expr(), b.expr(), a.expr())); cb.stack_pop(select::expr(is_gt.expr(), a.expr(), b.expr())); - cb.stack_push(result); + cb.stack_push(result.expr()); // State transition let step_state_transition = StepStateTransition { @@ -94,6 +96,7 @@ impl ExecutionGadget for ComparatorGadget { same_context, a, b, + result, comparison_lo, comparison_hi, is_eq, @@ -106,8 +109,8 @@ impl ExecutionGadget for ComparatorGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; @@ -136,6 +139,7 @@ impl ExecutionGadget for ComparatorGadget { [step.rw_indices[0], step.rw_indices[1]] }; let [a, b] = indices.map(|idx| block.rws[idx].stack_value().to_le_bytes()); + let result = block.rws[step.rw_indices[2]].stack_value(); // `a[0..16] <= b[0..16]` self.comparison_lo.assign( @@ -155,6 +159,8 @@ impl ExecutionGadget for ComparatorGadget { self.a.assign(region, offset, Some(a))?; self.b.assign(region, offset, Some(b))?; + self.result + .assign(region, offset, Some(F::from(result.low_u64())))?; Ok(()) } diff --git a/zkevm-circuits/src/evm_circuit/execution/dup.rs b/zkevm-circuits/src/evm_circuit/execution/dup.rs index c5103985b9..147a3dab41 100644 --- a/zkevm-circuits/src/evm_circuit/execution/dup.rs +++ b/zkevm-circuits/src/evm_circuit/execution/dup.rs @@ -59,8 +59,8 @@ impl ExecutionGadget for DupGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_pure_memory.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_pure_memory.rs index 7166c70d01..0ec9a984c5 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_pure_memory.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_pure_memory.rs @@ -93,8 +93,8 @@ impl ExecutionGadget for ErrorOOGPureMemoryGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { let opcode = step.opcode.unwrap(); diff --git a/zkevm-circuits/src/evm_circuit/execution/gas.rs b/zkevm-circuits/src/evm_circuit/execution/gas.rs index 35f16bda21..dafbe9e03b 100644 --- a/zkevm-circuits/src/evm_circuit/execution/gas.rs +++ b/zkevm-circuits/src/evm_circuit/execution/gas.rs @@ -62,8 +62,8 @@ impl ExecutionGadget for GasGadget { region: &mut Region<'_, F>, offset: usize, _block: &Block, - _transaction: &Transaction, - _call: &Call, + _transaction: &Transaction, + _call: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; @@ -124,7 +124,7 @@ mod test { builder .handle_tx(&block_trace.eth_tx, &block_trace.geth_trace) .expect("could not handle block tx"); - let mut block = block_convert(config.randomness, bytecode.code(), &builder.block); + let mut block = block_convert(&builder.block, &builder.code_db); // The above block has 2 steps (GAS and STOP). We forcefully assign a // wrong `gas_left` value for the second step, to assert that diff --git a/zkevm-circuits/src/evm_circuit/execution/jump.rs b/zkevm-circuits/src/evm_circuit/execution/jump.rs index 602e0bfcf5..c55b69286f 100644 --- a/zkevm-circuits/src/evm_circuit/execution/jump.rs +++ b/zkevm-circuits/src/evm_circuit/execution/jump.rs @@ -65,8 +65,8 @@ impl ExecutionGadget for JumpGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; diff --git a/zkevm-circuits/src/evm_circuit/execution/jumpdest.rs b/zkevm-circuits/src/evm_circuit/execution/jumpdest.rs index f4b35e7d7c..5225030230 100644 --- a/zkevm-circuits/src/evm_circuit/execution/jumpdest.rs +++ b/zkevm-circuits/src/evm_circuit/execution/jumpdest.rs @@ -39,8 +39,8 @@ impl ExecutionGadget for JumpdestGadget { region: &mut Region<'_, F>, offset: usize, _: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step) diff --git a/zkevm-circuits/src/evm_circuit/execution/jumpi.rs b/zkevm-circuits/src/evm_circuit/execution/jumpi.rs index 940731c259..61afb16823 100644 --- a/zkevm-circuits/src/evm_circuit/execution/jumpi.rs +++ b/zkevm-circuits/src/evm_circuit/execution/jumpi.rs @@ -87,8 +87,8 @@ impl ExecutionGadget for JumpiGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; diff --git a/zkevm-circuits/src/evm_circuit/execution/memory.rs b/zkevm-circuits/src/evm_circuit/execution/memory.rs index 0a1e971b5c..eb6a3dc74c 100644 --- a/zkevm-circuits/src/evm_circuit/execution/memory.rs +++ b/zkevm-circuits/src/evm_circuit/execution/memory.rs @@ -142,8 +142,8 @@ impl ExecutionGadget for MemoryGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; @@ -194,16 +194,16 @@ impl ExecutionGadget for MemoryGadget { #[cfg(test)] mod test { - use crate::{ evm_circuit::test::rand_word, test_util::{run_test_circuits_with_config, BytecodeTestConfig}, }; + use eth_types::bytecode; use eth_types::evm_types::{GasCost, OpcodeId}; - use eth_types::{bytecode, Word}; + use eth_types::Word; use std::iter; - fn test_ok(opcode: OpcodeId, address: Word, value: Word, _memory_size: u64, gas_cost: u64) { + fn test_ok(opcode: OpcodeId, address: Word, value: Word, gas_cost: u64) { let bytecode = bytecode! { PUSH32(value) PUSH32(address) @@ -213,7 +213,10 @@ mod test { }; let test_config = BytecodeTestConfig { - gas_limit: gas_cost + 100_000, + gas_limit: GasCost::TX.as_u64() + + OpcodeId::PUSH32.as_u64() + + OpcodeId::PUSH32.as_u64() + + gas_cost, // we have to disable state circit now, since the memory size used // here is too large enable_state_circuit_test: false, @@ -228,56 +231,55 @@ mod test { OpcodeId::MSTORE, Word::from(0x12FFFF), Word::from_big_endian(&(1..33).collect::>()), - 38913, 3074206, ); test_ok( OpcodeId::MLOAD, Word::from(0x12FFFF), Word::from_big_endian(&(1..33).collect::>()), - 38913, 3074206, ); test_ok( OpcodeId::MLOAD, Word::from(0x12FFFF) + 16, Word::from_big_endian(&(17..33).chain(iter::repeat(0).take(16)).collect::>()), - 38914, 3074361, ); test_ok( OpcodeId::MSTORE8, Word::from(0x12FFFF), Word::from_big_endian(&(1..33).collect::>()), - 38912, 3074051, ); } #[test] fn memory_gadget_rand() { - let calc_memory_size_and_gas_cost = |opcode, address: Word| { - let memory_size = (address.as_u64() + let calc_gas_cost = |opcode, memory_address: Word| { + let memory_address = memory_address.as_u64() + match opcode { OpcodeId::MSTORE | OpcodeId::MLOAD => 32, OpcodeId::MSTORE8 => 1, _ => 0, } - + 31) - / 32; - let gas_cost = - memory_size * memory_size / 512 + 3 * memory_size + GasCost::FASTEST.as_u64(); - (memory_size, gas_cost) + + 31; + let memory_size = memory_address / 32; + + GasCost::FASTEST.as_u64() + 3 * memory_size + memory_size * memory_size / 512 }; for opcode in [OpcodeId::MSTORE, OpcodeId::MLOAD, OpcodeId::MSTORE8] { // we use 15-bit here to reduce testing resource consumption. - // In real cases the address is 5 bytes (40 bits) - let max_memory_size_pow_of_two = 15; - let address = rand_word() % (1u64 << max_memory_size_pow_of_two); + // In real cases the memory_address is 5 bytes (40 bits) + let max_memory_address_pow_of_two = 15; + let memory_address = rand_word() % (1u64 << max_memory_address_pow_of_two); let value = rand_word(); - let (memory_size, gas_cost) = calc_memory_size_and_gas_cost(opcode, address); - test_ok(opcode, address, value, memory_size, gas_cost); + test_ok( + opcode, + memory_address, + value, + calc_gas_cost(opcode, memory_address), + ); } } } diff --git a/zkevm-circuits/src/evm_circuit/execution/msize.rs b/zkevm-circuits/src/evm_circuit/execution/msize.rs index b524b4089e..a601200d55 100644 --- a/zkevm-circuits/src/evm_circuit/execution/msize.rs +++ b/zkevm-circuits/src/evm_circuit/execution/msize.rs @@ -59,8 +59,8 @@ impl ExecutionGadget for MsizeGadget { region: &mut Region<'_, F>, offset: usize, _: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; diff --git a/zkevm-circuits/src/evm_circuit/execution/mul.rs b/zkevm-circuits/src/evm_circuit/execution/mul.rs index 1ab07e64ba..206ffa86aa 100644 --- a/zkevm-circuits/src/evm_circuit/execution/mul.rs +++ b/zkevm-circuits/src/evm_circuit/execution/mul.rs @@ -60,8 +60,8 @@ impl ExecutionGadget for MulGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; diff --git a/zkevm-circuits/src/evm_circuit/execution/pc.rs b/zkevm-circuits/src/evm_circuit/execution/pc.rs index cc17381a07..56bac21937 100644 --- a/zkevm-circuits/src/evm_circuit/execution/pc.rs +++ b/zkevm-circuits/src/evm_circuit/execution/pc.rs @@ -59,8 +59,8 @@ impl ExecutionGadget for PcGadget { region: &mut Region<'_, F>, offset: usize, _: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; diff --git a/zkevm-circuits/src/evm_circuit/execution/pop.rs b/zkevm-circuits/src/evm_circuit/execution/pop.rs index 7f6648deca..faffdc5d8c 100644 --- a/zkevm-circuits/src/evm_circuit/execution/pop.rs +++ b/zkevm-circuits/src/evm_circuit/execution/pop.rs @@ -52,8 +52,8 @@ impl ExecutionGadget for PopGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; diff --git a/zkevm-circuits/src/evm_circuit/execution/push.rs b/zkevm-circuits/src/evm_circuit/execution/push.rs index f2bd5ecd30..1038e1d076 100644 --- a/zkevm-circuits/src/evm_circuit/execution/push.rs +++ b/zkevm-circuits/src/evm_circuit/execution/push.rs @@ -31,6 +31,7 @@ impl ExecutionGadget for PushGadget { fn configure(cb: &mut ConstraintBuilder) -> Self { let opcode = cb.query_cell(); + let value = cb.query_rlc(); // Query selectors for each opcode_lookup let selectors = array_init(|_| cb.query_bool()); @@ -47,10 +48,10 @@ impl ExecutionGadget for PushGadget { // â–¼ â–¼ // [byte31, ..., byte2, byte1, byte0] // - let bytes = array_init(|idx| { + for idx in 0..32 { + let byte = &value.cells[idx]; let index = cb.curr.state.program_counter.expr() + opcode.expr() - (OpcodeId::PUSH1.as_u8() - 1 + idx as u8).expr(); - let byte = cb.query_cell(); if idx == 0 { cb.opcode_lookup_at(index, byte.expr(), 0.expr()) } else { @@ -58,8 +59,7 @@ impl ExecutionGadget for PushGadget { cb.opcode_lookup_at(index, byte.expr(), 0.expr()) }); } - byte - }); + } for idx in 0..31 { let selector_prev = if idx == 0 { @@ -77,7 +77,7 @@ impl ExecutionGadget for PushGadget { // byte should be 0 when selector is 0 cb.require_zero( "Constrain byte == 0 when selector == 0", - bytes[idx + 1].expr() * (1.expr() - selectors[idx].expr()), + value.cells[idx + 1].expr() * (1.expr() - selectors[idx].expr()), ); } @@ -93,7 +93,6 @@ impl ExecutionGadget for PushGadget { ); // Push the value on the stack - let value = Word::new(bytes, cb.power_of_randomness()); cb.stack_push(value.expr()); // State transition @@ -118,8 +117,8 @@ impl ExecutionGadget for PushGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; diff --git a/zkevm-circuits/src/evm_circuit/execution/signed_comparator.rs b/zkevm-circuits/src/evm_circuit/execution/signed_comparator.rs index da3cbeaa0d..8fd1e3bf53 100644 --- a/zkevm-circuits/src/evm_circuit/execution/signed_comparator.rs +++ b/zkevm-circuits/src/evm_circuit/execution/signed_comparator.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{ConstraintBuilder, StepStateTransition, Transition}, from_bytes, math_gadget::{ComparisonGadget, IsEqualGadget, LtGadget}, - select, Word, + select, Cell, Word, }, witness::{Block, Call, ExecStep, Transaction}, }, @@ -30,6 +30,7 @@ pub(crate) struct SignedComparatorGadget { sign_check_b: LtGadget, lt_lo: LtGadget, comparison_hi: ComparisonGadget, + a_lt_b: Cell, is_sgt: IsEqualGadget, } @@ -94,7 +95,9 @@ impl ExecutionGadget for SignedComparatorGadget { // // for e.g.: consider 8-bit signed integers -1 (0xff) and -2 (0xfe): // -2 < -1 and 0xfe < 0xff - let a_lt_b = select::expr(a_lt_b_hi, 1.expr(), a_eq_b_hi * a_lt_b_lo); + // + // Use copy to avoid degree too high for stack_push below. + let a_lt_b = cb.copy(select::expr(a_lt_b_hi, 1.expr(), a_eq_b_hi * a_lt_b_lo)); // Add a trivial selector: if only a or only b is negative we have the // result. @@ -107,7 +110,7 @@ impl ExecutionGadget for SignedComparatorGadget { // a_neg_b_pos => result = 1 // b_neg_a_pos => result = 0 // 1 - a_neg_b_pos - b_neg_a_pos => result = a_lt_b - let result = a_neg_b_pos.clone() + (1.expr() - a_neg_b_pos - b_neg_a_pos) * a_lt_b; + let result = a_neg_b_pos.clone() + (1.expr() - a_neg_b_pos - b_neg_a_pos) * a_lt_b.expr(); // Pop a and b from the stack, push the result on the stack. cb.stack_pop(select::expr(is_sgt.expr(), b.expr(), a.expr())); @@ -134,6 +137,7 @@ impl ExecutionGadget for SignedComparatorGadget { sign_check_b, lt_lo, comparison_hi, + a_lt_b, is_sgt, } } @@ -143,8 +147,8 @@ impl ExecutionGadget for SignedComparatorGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _transaction: &Transaction, - _call: &Call, + _transaction: &Transaction, + _call: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; @@ -163,14 +167,24 @@ impl ExecutionGadget for SignedComparatorGadget { } else { [step.rw_indices[0], step.rw_indices[1]] }; - let [a, b] = indices.map(|idx| block.rws[idx].stack_value().to_le_bytes()); + let [a, b] = indices.map(|idx| block.rws[idx].stack_value()); + let a_le_bytes = a.to_le_bytes(); + let b_le_bytes = b.to_le_bytes(); // Assign to the sign check gadgets. Since both a and b are in the // little-endian form, the most significant byte is the last byte. - self.sign_check_a - .assign(region, offset, F::from(a[31] as u64), F::from(128u64))?; - self.sign_check_b - .assign(region, offset, F::from(b[31] as u64), F::from(128u64))?; + self.sign_check_a.assign( + region, + offset, + F::from(a_le_bytes[31] as u64), + F::from(128u64), + )?; + self.sign_check_b.assign( + region, + offset, + F::from(b_le_bytes[31] as u64), + F::from(128u64), + )?; // Assign to the comparison gadgets. The first 16 bytes are assigned to // the `lo` less-than gadget while the last 16 bytes are assigned to @@ -178,18 +192,25 @@ impl ExecutionGadget for SignedComparatorGadget { self.lt_lo.assign( region, offset, - from_bytes::value(&a[0..16]), - from_bytes::value(&b[0..16]), + from_bytes::value(&a_le_bytes[0..16]), + from_bytes::value(&b_le_bytes[0..16]), )?; self.comparison_hi.assign( region, offset, - from_bytes::value(&a[16..32]), - from_bytes::value(&b[16..32]), + from_bytes::value(&a_le_bytes[16..32]), + from_bytes::value(&b_le_bytes[16..32]), )?; - self.a.assign(region, offset, Some(a))?; - self.b.assign(region, offset, Some(b))?; + // Assign to intermediate witness a_lt_b. + self.a_lt_b.assign( + region, + offset, + Some(if a < b { F::one() } else { F::zero() }), + )?; + + self.a.assign(region, offset, Some(a_le_bytes))?; + self.b.assign(region, offset, Some(b_le_bytes))?; Ok(()) } @@ -197,19 +218,20 @@ impl ExecutionGadget for SignedComparatorGadget { #[cfg(test)] mod test { + use eth_types::bytecode; use eth_types::evm_types::OpcodeId; - use eth_types::{bytecode, Word}; + use eth_types::Word; use crate::{evm_circuit::test::rand_word, test_util::run_test_circuits}; - fn test_ok(opcode: OpcodeId, a: Word, b: Word) { - let bytecode = bytecode! { - PUSH32(b) - PUSH32(a) - #[start] - .write_op(opcode) - STOP - }; + fn test_ok(pairs: Vec<(OpcodeId, Word, Word)>) { + let mut bytecode = bytecode! { #[start] }; + for (opcode, a, b) in pairs { + bytecode.push(32, b); + bytecode.push(32, a); + bytecode.write_op(opcode); + } + bytecode.write_op(OpcodeId::STOP); assert_eq!(run_test_circuits(bytecode), Ok(())); } @@ -221,10 +243,12 @@ mod test { bytes[31] = 254u8; Word::from_big_endian(&bytes) }; - test_ok(OpcodeId::SLT, minus_2, minus_1); - test_ok(OpcodeId::SGT, minus_2, minus_1); - test_ok(OpcodeId::SLT, minus_1, minus_2); - test_ok(OpcodeId::SGT, minus_1, minus_2); + test_ok(vec![ + (OpcodeId::SLT, minus_2, minus_1), + (OpcodeId::SGT, minus_2, minus_1), + (OpcodeId::SLT, minus_1, minus_2), + (OpcodeId::SGT, minus_1, minus_2), + ]); } #[test] @@ -235,46 +259,53 @@ mod test { Word::from_big_endian(&bytes) }; let plus_2 = plus_1 + 1; - test_ok(OpcodeId::SLT, plus_1, plus_2); - test_ok(OpcodeId::SGT, plus_1, plus_2); - test_ok(OpcodeId::SLT, plus_2, plus_1); - test_ok(OpcodeId::SGT, plus_2, plus_1); + test_ok(vec![ + (OpcodeId::SLT, plus_1, plus_2), + (OpcodeId::SGT, plus_1, plus_2), + (OpcodeId::SLT, plus_2, plus_1), + (OpcodeId::SGT, plus_2, plus_1), + ]); } #[test] fn signed_comparator_gadget_a_b_eq_hi_pos() { let a = Word::from_big_endian(&[[1u8; 16], [2u8; 16]].concat()); let b = Word::from_big_endian(&[[1u8; 16], [3u8; 16]].concat()); - test_ok(OpcodeId::SLT, a, b); - test_ok(OpcodeId::SGT, a, b); - test_ok(OpcodeId::SLT, b, a); - test_ok(OpcodeId::SGT, b, a); + test_ok(vec![ + (OpcodeId::SLT, a, b), + (OpcodeId::SGT, a, b), + (OpcodeId::SLT, b, a), + (OpcodeId::SGT, b, a), + ]); } #[test] fn signed_comparator_gadget_a_b_eq_hi_neg() { let a = Word::from_big_endian(&[[129u8; 16], [2u8; 16]].concat()); let b = Word::from_big_endian(&[[129u8; 16], [3u8; 16]].concat()); - test_ok(OpcodeId::SLT, a, b); - test_ok(OpcodeId::SGT, a, b); - test_ok(OpcodeId::SLT, b, a); - test_ok(OpcodeId::SGT, b, a); + test_ok(vec![ + (OpcodeId::SLT, a, b), + (OpcodeId::SGT, a, b), + (OpcodeId::SLT, b, a), + (OpcodeId::SGT, b, a), + ]); } #[test] fn signed_comparator_gadget_a_eq_b() { let a = rand_word(); - test_ok(OpcodeId::SLT, a, a); - test_ok(OpcodeId::SGT, a, a); + test_ok(vec![(OpcodeId::SLT, a, a), (OpcodeId::SGT, a, a)]); } #[test] fn signed_comparator_gadget_rand() { let a = rand_word(); let b = rand_word(); - test_ok(OpcodeId::SLT, a, b); - test_ok(OpcodeId::SGT, a, b); - test_ok(OpcodeId::SLT, b, a); - test_ok(OpcodeId::SGT, b, a); + test_ok(vec![ + (OpcodeId::SLT, a, b), + (OpcodeId::SGT, a, b), + (OpcodeId::SLT, b, a), + (OpcodeId::SGT, b, a), + ]); } } diff --git a/zkevm-circuits/src/evm_circuit/execution/signextend.rs b/zkevm-circuits/src/evm_circuit/execution/signextend.rs index a06cb6df6d..a9f18dac12 100644 --- a/zkevm-circuits/src/evm_circuit/execution/signextend.rs +++ b/zkevm-circuits/src/evm_circuit/execution/signextend.rs @@ -154,8 +154,8 @@ impl ExecutionGadget for SignextendGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; diff --git a/zkevm-circuits/src/evm_circuit/execution/stop.rs b/zkevm-circuits/src/evm_circuit/execution/stop.rs index a26fd38be7..9d4349c6ae 100644 --- a/zkevm-circuits/src/evm_circuit/execution/stop.rs +++ b/zkevm-circuits/src/evm_circuit/execution/stop.rs @@ -34,8 +34,8 @@ impl ExecutionGadget for StopGadget { region: &mut Region<'_, F>, offset: usize, _: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { let opcode = step.opcode.unwrap(); diff --git a/zkevm-circuits/src/evm_circuit/execution/swap.rs b/zkevm-circuits/src/evm_circuit/execution/swap.rs index af850ef48a..620c74d4ef 100644 --- a/zkevm-circuits/src/evm_circuit/execution/swap.rs +++ b/zkevm-circuits/src/evm_circuit/execution/swap.rs @@ -63,8 +63,8 @@ impl ExecutionGadget for SwapGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; diff --git a/zkevm-circuits/src/evm_circuit/execution/timestamp.rs b/zkevm-circuits/src/evm_circuit/execution/timestamp.rs index 792226ad6d..8d91b37f0a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/timestamp.rs +++ b/zkevm-circuits/src/evm_circuit/execution/timestamp.rs @@ -1,7 +1,7 @@ -use crate::evm_circuit::param::N_BYTES_U64; use crate::{ evm_circuit::{ execution::ExecutionGadget, + param::N_BYTES_U64, step::ExecutionState, table::BlockContextFieldTag, util::{ @@ -33,7 +33,7 @@ impl ExecutionGadget for TimestampGadget { // Lookup block table with timestamp cb.block_lookup( - BlockContextFieldTag::Time.expr(), + BlockContextFieldTag::Timestamp.expr(), None, from_bytes::expr(×tamp.cells), ); @@ -59,8 +59,8 @@ impl ExecutionGadget for TimestampGadget { region: &mut Region<'_, F>, offset: usize, block: &Block, - _: &Transaction, - _: &Call, + _: &Transaction, + _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.same_context.assign_exec_step(region, offset, step)?; diff --git a/zkevm-circuits/src/evm_circuit/step.rs b/zkevm-circuits/src/evm_circuit/step.rs index 92ed93d5c7..b2da7f9810 100644 --- a/zkevm-circuits/src/evm_circuit/step.rs +++ b/zkevm-circuits/src/evm_circuit/step.rs @@ -1,12 +1,13 @@ use crate::{ evm_circuit::{ param::{N_CELLS_STEP_STATE, STEP_HEIGHT, STEP_WIDTH}, - util::Cell, - witness::{Call, ExecStep}, + util::{Cell, RandomLinearCombination}, + witness::{Block, Call, CodeSource, ExecStep, Transaction}, }, util::Expr, }; use bus_mapping::evm::OpcodeId; +use eth_types::ToLittleEndian; use halo2::{ arithmetic::FieldExt, circuit::Region, @@ -399,14 +400,14 @@ pub(crate) struct StepState { /// Whether the call is a create call pub(crate) is_create: Cell, // This is the identifier of current executed bytecode, which is used to - // lookup current executed opcode and used to do code copy. In most time, + // lookup current executed code and used to do code copy. In most time, // it would be bytecode_hash, but when it comes to root creation call, the // executed bytecode is actually from transaction calldata, so it might be // tx_id if we decide to lookup different table. // However, how to handle root creation call is yet to be determined, see // issue https://github.com/appliedzkp/zkevm-specs/issues/73 for more // discussion. - pub(crate) opcode_source: Cell, + pub(crate) code_source: Cell, /// The program counter pub(crate) program_counter: Cell, /// The stack pointer @@ -458,7 +459,7 @@ impl Step { call_id: cells.pop_front().unwrap(), is_root: cells.pop_front().unwrap(), is_create: cells.pop_front().unwrap(), - opcode_source: cells.pop_front().unwrap(), + code_source: cells.pop_front().unwrap(), program_counter: cells.pop_front().unwrap(), stack_pointer: cells.pop_front().unwrap(), gas_left: cells.pop_front().unwrap(), @@ -496,7 +497,9 @@ impl Step { &self, region: &mut Region<'_, F>, offset: usize, - call: &Call, + block: &Block, + _: &Transaction, + call: &Call, step: &ExecStep, ) -> Result<(), Error> { for (idx, cell) in self.state.execution_state.iter().enumerate() { @@ -522,9 +525,18 @@ impl Step { self.state .is_create .assign(region, offset, Some(F::from(call.is_create as u64)))?; - self.state - .opcode_source - .assign(region, offset, Some(call.opcode_source))?; + match call.code_source { + CodeSource::Account(code_hash) => { + self.state.code_source.assign( + region, + offset, + Some(RandomLinearCombination::random_linear_combine( + code_hash.to_le_bytes(), + block.randomness, + )), + )?; + } + } self.state.program_counter.assign( region, offset, diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index 3489939b87..704289981c 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -120,14 +120,14 @@ pub enum TxContextFieldTag { pub enum BlockContextFieldTag { Coinbase = 1, GasLimit, - BlockNumber, - Time, + Number, + Timestamp, Difficulty, BaseFee, BlockHash, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum RwTableTag { TxAccessListAccount = 1, TxAccessListAccountStorage, @@ -161,10 +161,10 @@ pub enum AccountFieldTag { CodeHash, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum CallContextFieldTag { RwCounterEndOfReversion = 1, - CallerCallId, + CallerId, TxId, Depth, CallerAddress, @@ -174,13 +174,13 @@ pub enum CallContextFieldTag { ReturnDataOffset, ReturnDataLength, Value, - Result, + IsSuccess, IsPersistent, IsStatic, IsRoot, IsCreate, - OpcodeSource, + CodeSource, ProgramCounter, StackPointer, GasLeft, diff --git a/zkevm-circuits/src/evm_circuit/util.rs b/zkevm-circuits/src/evm_circuit/util.rs index 00e5255519..a3f197bf27 100644 --- a/zkevm-circuits/src/evm_circuit/util.rs +++ b/zkevm-circuits/src/evm_circuit/util.rs @@ -12,9 +12,6 @@ pub(crate) mod constraint_builder; pub(crate) mod math_gadget; pub(crate) mod memory_gadget; -type Address = u64; -type MemorySize = u64; - #[derive(Clone, Debug)] pub(crate) struct Cell { // expression for constraint @@ -74,7 +71,7 @@ pub(crate) struct RandomLinearCombination { } impl RandomLinearCombination { - const NUM_BYTES: usize = N; + const N_BYTES: usize = N; pub(crate) fn random_linear_combine(bytes: [u8; N], randomness: F) -> F { bytes.iter().rev().fold(F::zero(), |acc, byte| { diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index 3ccacb18fa..1433cf50e9 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -13,9 +13,13 @@ use halo2::{arithmetic::FieldExt, plonk::Expression}; use std::convert::TryInto; // Max degree allowed in all expressions passing through the ConstraintBuilder. -const MAX_DEGREE: usize = 2usize.pow(3) + 1; -// Degree added for expressions used in lookups. -const LOOKUP_DEGREE: usize = 3; +// It aims to cap `extended_k` to 3, which allows constraint degree to 2^3+1, +// but each ExecutionGadget has implicit selector degree 2, so here it only +// allows 2^3+1-2 = 7. +const MAX_DEGREE: usize = 7; +// Implicit degree added to input expressions of lookups. It assumes blind +// factors have been disabled, and table expressions with degree 1. +const LOOKUP_DEGREE: usize = 2; #[derive(Clone, Debug, Default)] struct StepRowUsage { @@ -41,7 +45,7 @@ pub(crate) struct StepStateTransition { pub(crate) call_id: Transition>, pub(crate) is_root: Transition>, pub(crate) is_create: Transition>, - pub(crate) opcode_source: Transition>, + pub(crate) code_source: Transition>, pub(crate) program_counter: Transition>, pub(crate) stack_pointer: Transition>, pub(crate) gas_left: Transition>, @@ -49,6 +53,17 @@ pub(crate) struct StepStateTransition { pub(crate) state_write_counter: Transition>, } +impl StepStateTransition { + pub(crate) fn new_context() -> Self { + Self { + program_counter: Transition::To(0.expr()), + stack_pointer: Transition::To(1024.expr()), + memory_word_size: Transition::To(0.expr()), + ..Self::default() + } + } +} + #[derive(Default)] pub struct BaseConstraintBuilder { pub constraints: Vec<(&'static str, Expression)>, @@ -257,6 +272,12 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { // Query + pub(crate) fn copy>(&mut self, value: E) -> Cell { + let cell = self.query_cell(); + self.require_equal("Copy value to new cell", cell.expr(), value.expr()); + cell + } + pub(crate) fn query_bool(&mut self) -> Cell { let [cell] = self.query_cells::<1>(false); self.require_boolean("Constrain cell to be a bool", cell.expr()); @@ -372,10 +393,10 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { step_state_transition.is_create, ), ( - "State transition constrain of opcode_source", - &self.curr.state.opcode_source, - &self.next.state.opcode_source, - step_state_transition.opcode_source, + "State transition constrain of code_source", + &self.curr.state.code_source, + &self.next.state.code_source, + step_state_transition.code_source, ), ( "State transition constrain of program_counter", @@ -462,7 +483,7 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { self.add_lookup( "Opcode lookup", Lookup::Bytecode { - hash: self.curr.state.opcode_source.expr(), + hash: self.curr.state.code_source.expr(), index, value: opcode, is_code, @@ -610,21 +631,23 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { account_address: Expression, value: Expression, value_prev: Expression, - ) { + ) -> Expression { self.rw_lookup( - "AccountAccessList write", + "TxAccessListAccount write", true.expr(), RwTableTag::TxAccessListAccount, [ tx_id, account_address, 0.expr(), - value, - value_prev, + value.clone(), + value_prev.clone(), 0.expr(), 0.expr(), ], ); + + value - value_prev } // Account @@ -708,19 +731,20 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { field_tag: CallContextFieldTag, ) -> Cell { let cell = self.query_cell(); - self.call_context_lookup(call_id, field_tag, cell.expr()); + self.call_context_lookup(false.expr(), call_id, field_tag, cell.expr()); cell } pub(crate) fn call_context_lookup( &mut self, + is_write: Expression, call_id: Option>, field_tag: CallContextFieldTag, value: Expression, ) { self.rw_lookup( "CallContext lookup", - false.expr(), + is_write, RwTableTag::CallContext, [ call_id.unwrap_or_else(|| self.curr.state.call_id.expr()), @@ -856,7 +880,8 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { Some(condition) => condition.clone() * constraint, None => constraint, }; - self.validate_degree(constraint.degree(), name); + // Add 1 more degree due to the selector + self.validate_degree(constraint.degree() + 1, name); self.constraints_first_step.push((name, constraint)); } diff --git a/zkevm-circuits/src/evm_circuit/util/memory_gadget.rs b/zkevm-circuits/src/evm_circuit/util/memory_gadget.rs index abc1fd9bda..55db2de2f5 100644 --- a/zkevm-circuits/src/evm_circuit/util/memory_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/memory_gadget.rs @@ -32,7 +32,7 @@ pub(crate) mod address_low { pub(crate) fn value(address: [u8; 32]) -> u64 { let mut bytes = [0; 8]; - bytes.copy_from_slice(&address[..N_BYTES_MEMORY_ADDRESS]); + bytes[..N_BYTES_MEMORY_ADDRESS].copy_from_slice(&address[..N_BYTES_MEMORY_ADDRESS]); u64::from_le_bytes(bytes) } } diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index 7c5bdb69ab..9640511acf 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -7,56 +7,56 @@ use crate::evm_circuit::{ }, util::RandomLinearCombination, }; -use eth_types::evm_types::OpcodeId; -use eth_types::{Address, ToLittleEndian, ToScalar, ToWord, Word}; -use halo2::arithmetic::FieldExt; +use bus_mapping::{ + circuit_input_builder, + operation::{self, AccountField, CallContextField}, +}; +use eth_types::{evm_types::OpcodeId, Address, ToLittleEndian, ToScalar, ToWord, Word}; +use halo2::arithmetic::{BaseExt, FieldExt}; use pairing::bn256::Fr as Fp; use sha3::{Digest, Keccak256}; -use std::convert::TryInto; +use std::{collections::HashMap, convert::TryInto}; #[derive(Debug, Default)] pub struct Block { /// The randomness for random linear combination pub randomness: F, /// Transactions in the block - pub txs: Vec>, + pub txs: Vec, /// Read write events in the RwTable - pub rws: Vec, + pub rws: RwMap, /// Bytecode used in the block pub bytecodes: Vec, /// The block context - pub context: BlockContext, + pub context: BlockContext, } #[derive(Debug, Default)] -pub struct BlockContext { +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 block number - pub block_number: F, + /// The number of the block + pub number: Word, /// The timestamp of the block - pub time: u64, + 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 previous_block_hashes: Vec, + pub history_hashes: Vec, } -impl BlockContext { - pub fn table_assignments(&self, randomness: F) -> Vec<[F; 3]> { +impl BlockContext { + pub fn table_assignments(&self, randomness: F) -> Vec<[F; 3]> { [ vec![ [ F::from(BlockContextFieldTag::Coinbase as u64), F::zero(), - RandomLinearCombination::random_linear_combine( - self.coinbase.to_word().to_le_bytes(), - randomness, - ), + self.coinbase.to_scalar().unwrap(), ], [ F::from(BlockContextFieldTag::GasLimit as u64), @@ -64,14 +64,17 @@ impl BlockContext { F::from(self.gas_limit), ], [ - F::from(BlockContextFieldTag::BlockNumber as u64), + F::from(BlockContextFieldTag::Number as u64), F::zero(), - self.block_number, + RandomLinearCombination::random_linear_combine( + self.number.to_le_bytes(), + randomness, + ), ], [ - F::from(BlockContextFieldTag::Time as u64), + F::from(BlockContextFieldTag::Timestamp as u64), F::zero(), - F::from(self.time), + self.timestamp.to_scalar().unwrap(), ], [ F::from(BlockContextFieldTag::Difficulty as u64), @@ -90,13 +93,13 @@ impl BlockContext { ), ], ], - self.previous_block_hashes + self.history_hashes .iter() .enumerate() .map(|(idx, hash)| { [ F::from(BlockContextFieldTag::BlockHash as u64), - self.block_number - F::from((idx + 1) as u64), + (self.number - idx - 1).to_scalar().unwrap(), RandomLinearCombination::random_linear_combine( hash.to_le_bytes(), randomness, @@ -110,8 +113,8 @@ impl BlockContext { } #[derive(Debug, Default)] -pub struct Transaction { - /// The transaction index in the block +pub struct Transaction { + /// The transaction identifier in the block pub id: usize, /// The sender account nonce of the transaction pub nonce: u64, @@ -134,13 +137,13 @@ pub struct Transaction { /// The gas cost for transaction call data pub call_data_gas_cost: u64, /// The calls made in the transaction - pub calls: Vec>, + pub calls: Vec, /// The steps executioned in the transaction pub steps: Vec, } -impl Transaction { - pub fn table_assignments(&self, randomness: F) -> Vec<[F; 4]> { +impl Transaction { + pub fn table_assignments(&self, randomness: F) -> Vec<[F; 4]> { [ vec![ [ @@ -221,8 +224,19 @@ impl Transaction { } } +#[derive(Debug)] +pub enum CodeSource { + Account(Word), +} + +impl Default for CodeSource { + fn default() -> Self { + Self::Account(0.into()) + } +} + #[derive(Debug, Default)] -pub struct Call { +pub struct Call { /// The unique identifier of call in the whole proof, using the /// `rw_counter` at the call step. pub id: usize, @@ -231,12 +245,12 @@ pub struct Call { /// Indicate if the call is a create call pub is_create: bool, /// The identifier of current executed bytecode - pub opcode_source: F, + pub code_source: CodeSource, /// 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_call_id: usize, + pub caller_id: usize, /// The depth in the call stack pub depth: usize, /// The caller address @@ -244,17 +258,17 @@ pub struct Call { /// The callee address pub callee_address: Address, /// The call data offset in the memory - pub call_data_offset: usize, + pub call_data_offset: u64, /// The length of call data - pub call_data_length: usize, + pub call_data_length: u64, /// The return data offset in the memory - pub return_data_offset: usize, + pub return_data_offset: u64, /// The length of return data - pub return_data_length: usize, + pub return_data_length: u64, /// The ether amount of the transaction pub value: Word, - /// TBD, Han will update this field - pub result: 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 @@ -266,7 +280,7 @@ 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, + pub rw_indices: Vec<(RwTableTag, usize)>, /// The execution state for the step pub execution_state: ExecutionState, /// The Read/Write counter before the step @@ -305,10 +319,8 @@ pub struct Bytecode { impl Bytecode { pub fn new(bytes: Vec) -> Self { - Self { - hash: Word::from_big_endian(Keccak256::digest(&bytes).as_slice()), - bytes, - } + let hash = Word::from_big_endian(Keccak256::digest(&bytes).as_slice()); + Self { hash, bytes } } pub fn table_assignments<'a, F: FieldExt>( @@ -364,6 +376,17 @@ impl Bytecode { } } +#[derive(Debug, Default)] +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, Debug)] pub enum Rw { TxAccessListAccount { @@ -377,10 +400,18 @@ pub enum Rw { TxAccessListAccountStorage { rw_counter: usize, is_write: bool, + tx_id: usize, + account_address: Address, + storage_key: Word, + value: bool, + value_prev: bool, }, TxRefund { rw_counter: usize, is_write: bool, + tx_id: usize, + value: Word, + value_prev: Word, }, Account { rw_counter: usize, @@ -393,10 +424,18 @@ pub enum Rw { AccountStorage { rw_counter: usize, is_write: bool, + account_address: Address, + storage_key: Word, + value: Word, + value_prev: Word, }, AccountDestructed { rw_counter: usize, is_write: bool, + tx_id: usize, + account_address: Address, + value: bool, + value_prev: bool, }, CallContext { rw_counter: usize, @@ -422,6 +461,18 @@ pub enum Rw { } impl Rw { + pub fn tx_access_list_value_pair(&self) -> (bool, bool) { + match self { + Self::TxAccessListAccount { + value, value_prev, .. + } => (*value, *value_prev), + Self::TxAccessListAccountStorage { + value, value_prev, .. + } => (*value, *value_prev), + _ => unreachable!(), + } + } + pub fn account_value_pair(&self) -> (Word, Word) { match self { Self::Account { @@ -431,6 +482,13 @@ impl Rw { } } + 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, @@ -459,6 +517,29 @@ impl Rw { F::zero(), F::zero(), ], + Self::TxAccessListAccountStorage { + rw_counter, + is_write, + tx_id, + account_address, + storage_key, + value, + value_prev, + } => [ + F::from(*rw_counter as u64), + F::from(*is_write as u64), + F::from(RwTableTag::TxAccessListAccount as u64), + F::from(*tx_id as u64), + account_address.to_scalar().unwrap(), + RandomLinearCombination::random_linear_combine( + storage_key.to_le_bytes(), + randomness, + ), + F::from(*value as u64), + F::from(*value_prev as u64), + F::zero(), + F::zero(), + ], Self::Account { rw_counter, is_write, @@ -501,7 +582,7 @@ impl Rw { F::from(*field_tag as u64), F::zero(), match field_tag { - CallContextFieldTag::OpcodeSource | CallContextFieldTag::Value => { + CallContextFieldTag::CodeSource | CallContextFieldTag::Value => { RandomLinearCombination::random_linear_combine( value.to_le_bytes(), randomness, @@ -509,8 +590,8 @@ impl Rw { } CallContextFieldTag::CallerAddress | CallContextFieldTag::CalleeAddress - | CallContextFieldTag::Result => value.to_scalar().unwrap(), - _ => value.to_scalar().unwrap(), + | CallContextFieldTag::IsSuccess => value.to_scalar().unwrap(), + _ => F::from(value.low_u64()), }, F::zero(), F::zero(), @@ -557,8 +638,196 @@ impl Rw { } } -impl From<&bus_mapping::circuit_input_builder::ExecStep> for ExecutionState { - fn from(step: &bus_mapping::circuit_input_builder::ExecStep) -> Self { +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(), + } + } +} + +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, + value: op.op().value, + value_prev: op.op().value_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, + value: op.op().value, + value_prev: op.op().value_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, + }) + .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, + value: op.op().value, + value_prev: op.op().value_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::IsRoot => CallContextFieldTag::IsRoot, + CallContextField::IsCreate => CallContextFieldTag::IsCreate, + CallContextField::CodeSource => CallContextFieldTag::CodeSource, + CallContextField::ProgramCounter => CallContextFieldTag::ProgramCounter, + CallContextField::StackPointer => CallContextFieldTag::StackPointer, + CallContextField::GasLeft => CallContextFieldTag::GasLeft, + CallContextField::MemorySize => CallContextFieldTag::MemorySize, + CallContextField::StateWriteCounter => { + CallContextFieldTag::StateWriteCounter + } + }, + 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(), + ); + + Self(rws) + } +} + +impl From<&circuit_input_builder::ExecStep> for ExecutionState { + fn from(step: &circuit_input_builder::ExecStep) -> Self { // TODO: error reporting. (errors are defined in // circuit_input_builder.rs) debug_assert!(step.error.is_none()); @@ -601,32 +870,33 @@ impl From<&bus_mapping::circuit_input_builder::ExecStep> for ExecutionState { } } -impl From<ð_types::Bytecode> for Bytecode { - fn from(b: ð_types::Bytecode) -> Self { +impl From<ð_types::bytecode::Bytecode> for Bytecode { + fn from(b: ð_types::bytecode::Bytecode) -> Self { Bytecode::new(b.to_vec()) } } -fn step_convert( - step: &bus_mapping::circuit_input_builder::ExecStep, - ops_len: (usize, usize, usize), -) -> ExecStep { - let (stack_ops_len, memory_ops_len, _storage_ops_len) = ops_len; - // TODO: call_index is not set in the ExecStep - let result = ExecStep { +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 index = x.as_usize() - 1; - match x.target() { - bus_mapping::operation::Target::Stack => index, - bus_mapping::operation::Target::Memory => index + stack_ops_len, - bus_mapping::operation::Target::Storage => { - index + stack_ops_len + memory_ops_len + 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 } - _ => unimplemented!(), - } + operation::Target::TxRefund => RwTableTag::TxRefund, + operation::Target::Account => RwTableTag::Account, + operation::Target::AccountDestructed => RwTableTag::AccountDestructed, + operation::Target::CallContext => RwTableTag::CallContext, + }; + (tag, x.as_usize()) }) .collect(), execution_state: ExecutionState::from(step), @@ -637,94 +907,75 @@ fn step_convert( gas_cost: step.gas_cost.as_u64(), opcode: Some(step.op), memory_size: step.memory_size as u64, - ..Default::default() - }; - result + state_write_counter: step.swc, + } } -fn tx_convert( - randomness: Fp, - bytecode: &Bytecode, - tx: &bus_mapping::circuit_input_builder::Transaction, - ops_len: (usize, usize, usize), -) -> Transaction { - Transaction:: { - calls: vec![Call { - id: 1, - is_root: true, - is_create: tx.is_create(), - opcode_source: RandomLinearCombination::random_linear_combine( - bytecode.hash.to_le_bytes(), - randomness, - ), - ..Default::default() - }], - steps: tx - .steps() +fn tx_convert(tx: &circuit_input_builder::Transaction) -> Transaction { + Transaction { + id: 1, + 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() - .map(|step| step_convert(step, ops_len)) + .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_source: match call.code_source { + circuit_input_builder::CodeSource::Address(_) => { + CodeSource::Account(call.code_hash.to_word()) + } + _ => unimplemented!(), + }, + 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(), - ..Default::default() + steps: tx.steps().iter().map(step_convert).collect(), } } pub fn block_convert( - randomness: Fp, - bytecode: &[u8], - b: &bus_mapping::circuit_input_builder::Block, + block: &circuit_input_builder::Block, + code_db: &bus_mapping::state_db::CodeDB, ) -> Block { - let bytecode = Bytecode::new(bytecode.to_vec()); - - // here stack_ops/memory_ops/etc are merged into a single array - // in EVM circuit, we need rwc-sorted ops - let mut stack_ops = b.container.sorted_stack(); - stack_ops.sort_by_key(|s| usize::from(s.rwc())); - let mut memory_ops = b.container.sorted_memory(); - memory_ops.sort_by_key(|s| usize::from(s.rwc())); - let mut storage_ops = b.container.sorted_storage(); - storage_ops.sort_by_key(|s| usize::from(s.rwc())); - - // converting to block context - let context = BlockContext { - coinbase: b.block_const.coinbase, - time: b.block_const.timestamp.try_into().unwrap(), - ..Default::default() - }; - - let mut block = Block { - randomness, - context, - txs: b + Block { + randomness: Fp::rand(), + context: block.into(), + rws: RwMap::from(&block.container), + txs: block.txs().iter().map(tx_convert).collect(), + bytecodes: block .txs() .iter() - .map(|tx| { - tx_convert( - randomness, - &bytecode, - tx, - (stack_ops.len(), memory_ops.len(), storage_ops.len()), - ) + .flat_map(|tx| { + tx.calls() + .iter() + .map(|call| Bytecode::new(code_db.0.get(&call.code_hash).unwrap().to_vec())) }) .collect(), - bytecodes: vec![bytecode], - ..Default::default() - }; - - block.rws.extend(stack_ops.iter().map(|s| Rw::Stack { - rw_counter: s.rwc().into(), - is_write: s.op().rw().is_write(), - call_id: 1, - stack_pointer: usize::from(*s.op().address()), - value: *s.op().value(), - })); - block.rws.extend(memory_ops.iter().map(|s| Rw::Memory { - rw_counter: s.rwc().into(), - is_write: s.op().rw().is_write(), - call_id: 1, - memory_address: u64::from_le_bytes(s.op().address().to_le_bytes()[..8].try_into().unwrap()), - byte: s.op().value(), - })); - // TODO add storage ops - - block + } } diff --git a/zkevm-circuits/src/state_circuit/state.rs b/zkevm-circuits/src/state_circuit/state.rs index d879ff90db..7963311c88 100644 --- a/zkevm-circuits/src/state_circuit/state.rs +++ b/zkevm-circuits/src/state_circuit/state.rs @@ -741,7 +741,7 @@ impl< address, rwc, val, - op.rw().is_write(), + oper.rw().is_write(), target, F::zero(), F::zero(), @@ -793,7 +793,7 @@ impl< address, rwc, val, - op.rw().is_write(), + oper.rw().is_write(), target, F::zero(), F::zero(), @@ -857,7 +857,7 @@ impl< address, rwc, val, - op.rw().is_write(), + oper.rw().is_write(), target, storage_key, val_prev, @@ -1323,35 +1323,41 @@ mod tests { fn state_circuit() { let memory_op_0 = Operation::new( RWCounter::from(12), - MemoryOp::new(RW::WRITE, 1, MemoryAddress::from(0), 32), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), ); let memory_op_1 = Operation::new( RWCounter::from(24), - MemoryOp::new(RW::READ, 1, MemoryAddress::from(0), 32), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(0), 32), ); let memory_op_2 = Operation::new( RWCounter::from(17), - MemoryOp::new(RW::WRITE, 1, MemoryAddress::from(1), 32), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(1), 32), ); let memory_op_3 = Operation::new( RWCounter::from(87), - MemoryOp::new(RW::READ, 1, MemoryAddress::from(1), 32), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(1), 32), ); let stack_op_0 = Operation::new( RWCounter::from(17), - StackOp::new(RW::WRITE, 1, StackAddress::from(1), Word::from(32)), + RW::WRITE, + StackOp::new(1, StackAddress::from(1), Word::from(32)), ); let stack_op_1 = Operation::new( RWCounter::from(87), - StackOp::new(RW::READ, 1, StackAddress::from(1), Word::from(32)), + RW::READ, + StackOp::new(1, StackAddress::from(1), Word::from(32)), ); let storage_op_0 = Operation::new( RWCounter::from(17), + RW::WRITE, StorageOp::new( - RW::WRITE, address!("0x0000000000000000000000000000000000000001"), Word::from(0x40), Word::from(32), @@ -1360,8 +1366,8 @@ mod tests { ); let storage_op_1 = Operation::new( RWCounter::from(18), + RW::WRITE, StorageOp::new( - RW::WRITE, address!("0x0000000000000000000000000000000000000001"), Word::from(0x40), Word::from(32), @@ -1370,8 +1376,8 @@ mod tests { ); let storage_op_2 = Operation::new( RWCounter::from(19), + RW::WRITE, StorageOp::new( - RW::WRITE, address!("0x0000000000000000000000000000000000000001"), Word::from(0x40), Word::from(32), @@ -1398,29 +1404,35 @@ mod tests { fn no_stack_padding() { let memory_op_0 = Operation::new( RWCounter::from(12), - MemoryOp::new(RW::WRITE, 1, MemoryAddress::from(0), 32), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), ); let memory_op_1 = Operation::new( RWCounter::from(24), - MemoryOp::new(RW::READ, 1, MemoryAddress::from(0), 32), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(0), 32), ); let memory_op_2 = Operation::new( RWCounter::from(17), - MemoryOp::new(RW::WRITE, 1, MemoryAddress::from(1), 32), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(1), 32), ); let memory_op_3 = Operation::new( RWCounter::from(87), - MemoryOp::new(RW::READ, 1, MemoryAddress::from(1), 32), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(1), 32), ); let stack_op_0 = Operation::new( RWCounter::from(17), - StackOp::new(RW::WRITE, 1, StackAddress::from(1), Word::from(32)), + RW::WRITE, + StackOp::new(1, StackAddress::from(1), Word::from(32)), ); let stack_op_1 = Operation::new( RWCounter::from(87), - StackOp::new(RW::READ, 1, StackAddress::from(1), Word::from(32)), + RW::READ, + StackOp::new(1, StackAddress::from(1), Word::from(32)), ); const STACK_ROWS_MAX: usize = 2; @@ -1443,12 +1455,13 @@ mod tests { fn same_address_read() { let memory_op_0 = Operation::new( RWCounter::from(12), - MemoryOp::new(RW::WRITE, 1, MemoryAddress::from(0), 31), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 31), ); let memory_op_1 = Operation::new( RWCounter::from(24), + RW::READ, MemoryOp::new( - RW::READ, 1, MemoryAddress::from(0), 32, @@ -1459,12 +1472,13 @@ mod tests { let stack_op_0 = Operation::new( RWCounter::from(19), - StackOp::new(RW::WRITE, 1, StackAddress::from(0), Word::from(12)), + RW::WRITE, + StackOp::new(1, StackAddress::from(0), Word::from(12)), ); let stack_op_1 = Operation::new( RWCounter::from(28), + RW::READ, StackOp::new( - RW::READ, 1, StackAddress::from(0), Word::from(13), @@ -1492,14 +1506,16 @@ mod tests { fn first_write() { let stack_op_0 = Operation::new( RWCounter::from(28), - StackOp::new(RW::READ, 1, StackAddress::from(0), Word::from(13)), + RW::READ, + StackOp::new(1, StackAddress::from(0), Word::from(13)), ); let storage_op_0 = Operation::new( RWCounter::from(17), + RW::READ, StorageOp::new( - RW::READ, /* Fails because the first storage op needs to be - * write. */ + /* Fails because the first storage op needs to be + * write. */ address!("0x0000000000000000000000000000000000000002"), Word::from(0x40), Word::from(32), @@ -1508,9 +1524,10 @@ mod tests { ); let storage_op_1 = Operation::new( RWCounter::from(18), + RW::READ, StorageOp::new( - RW::READ, /* Fails because when storage key changes, the op - * needs to be write. */ + /* Fails because when storage key changes, the op + * needs to be write. */ address!("0x0000000000000000000000000000000000000002"), Word::from(0x41), Word::from(32), @@ -1520,9 +1537,10 @@ mod tests { let storage_op_2 = Operation::new( RWCounter::from(19), + RW::READ, StorageOp::new( - RW::READ, /* Fails because when address changes, the op - * needs to be write. */ + /* Fails because when address changes, the op + * needs to be write. */ address!("0x0000000000000000000000000000000000000003"), Word::from(0x40), /* Intentionally different storage key as the last one in the previous ops to @@ -1552,67 +1570,51 @@ mod tests { fn max_values() { let memory_op_0 = Operation::new( RWCounter::from(12), - MemoryOp::new(RW::WRITE, 1, MemoryAddress::from(MEMORY_ADDRESS_MAX), 32), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX), 32), ); let memory_op_1 = Operation::new( RWCounter::from(GLOBAL_COUNTER_MAX), - MemoryOp::new(RW::READ, 1, MemoryAddress::from(MEMORY_ADDRESS_MAX), 32), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX), 32), ); let memory_op_2 = Operation::new( RWCounter::from(GLOBAL_COUNTER_MAX + 1), - MemoryOp::new(RW::WRITE, 1, MemoryAddress::from(MEMORY_ADDRESS_MAX), 32), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX), 32), ); let memory_op_3 = Operation::new( RWCounter::from(12), - MemoryOp::new( - RW::WRITE, - 1, - MemoryAddress::from(MEMORY_ADDRESS_MAX + 1), - 32, - ), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX + 1), 32), ); let memory_op_4 = Operation::new( RWCounter::from(24), - MemoryOp::new(RW::READ, 1, MemoryAddress::from(MEMORY_ADDRESS_MAX + 1), 32), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX + 1), 32), ); let stack_op_0 = Operation::new( RWCounter::from(12), - StackOp::new( - RW::WRITE, - 1, - StackAddress::from(STACK_ADDRESS_MAX), - Word::from(12), - ), + RW::WRITE, + StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX), Word::from(12)), ); let stack_op_1 = Operation::new( RWCounter::from(24), - StackOp::new( - RW::READ, - 1, - StackAddress::from(STACK_ADDRESS_MAX), - Word::from(12), - ), + RW::READ, + StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX), Word::from(12)), ); let stack_op_2 = Operation::new( RWCounter::from(17), - StackOp::new( - RW::WRITE, - 1, - StackAddress::from(STACK_ADDRESS_MAX + 1), - Word::from(12), - ), + RW::WRITE, + StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX + 1), Word::from(12)), ); let stack_op_3 = Operation::new( RWCounter::from(GLOBAL_COUNTER_MAX + 1), - StackOp::new( - RW::WRITE, - 1, - StackAddress::from(STACK_ADDRESS_MAX + 1), - Word::from(12), - ), + RW::WRITE, + StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX + 1), Word::from(12)), ); // Small MEMORY_MAX_ROWS is set to avoid having padded rows (all padded @@ -1651,8 +1653,8 @@ mod tests { // too let memory_op_0 = Operation::new( RWCounter::from(12), + RW::WRITE, MemoryOp::new( - RW::WRITE, 1, MemoryAddress::from(MEMORY_ADDRESS_MAX + 1), // This address is not in the allowed range @@ -1662,21 +1664,13 @@ mod tests { let stack_op_0 = Operation::new( RWCounter::from(12), - StackOp::new( - RW::WRITE, - 1, - StackAddress::from(STACK_ADDRESS_MAX + 1), - Word::from(12), - ), + RW::WRITE, + StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX + 1), Word::from(12)), ); let stack_op_1 = Operation::new( RWCounter::from(24), - StackOp::new( - RW::READ, - 1, - StackAddress::from(STACK_ADDRESS_MAX + 1), - Word::from(12), - ), + RW::READ, + StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX + 1), Word::from(12)), ); // Small MEMORY_MAX_ROWS is set to avoid having padded rows (all padded @@ -1707,36 +1701,42 @@ mod tests { fn non_monotone_global_counter() { let memory_op_0 = Operation::new( RWCounter::from(1352), - MemoryOp::new(RW::WRITE, 1, MemoryAddress::from(0), 32), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), ); let memory_op_1 = Operation::new( RWCounter::from(1255), - MemoryOp::new(RW::READ, 1, MemoryAddress::from(0), 32), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(0), 32), ); // fails because it needs to be strictly monotone let memory_op_2 = Operation::new( RWCounter::from(1255), - MemoryOp::new(RW::WRITE, 1, MemoryAddress::from(0), 32), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), ); let stack_op_0 = Operation::new( RWCounter::from(228), - StackOp::new(RW::WRITE, 1, StackAddress::from(1), Word::from(12)), + RW::WRITE, + StackOp::new(1, StackAddress::from(1), Word::from(12)), ); let stack_op_1 = Operation::new( RWCounter::from(217), - StackOp::new(RW::READ, 1, StackAddress::from(1), Word::from(12)), + RW::READ, + StackOp::new(1, StackAddress::from(1), Word::from(12)), ); let stack_op_2 = Operation::new( RWCounter::from(217), - StackOp::new(RW::READ, 1, StackAddress::from(1), Word::from(12)), + RW::READ, + StackOp::new(1, StackAddress::from(1), Word::from(12)), ); let storage_op_0 = Operation::new( RWCounter::from(301), + RW::WRITE, StorageOp::new( - RW::WRITE, address!("0x0000000000000000000000000000000000000001"), Word::from(0x40), Word::from(32), @@ -1745,8 +1745,8 @@ mod tests { ); let storage_op_1 = Operation::new( RWCounter::from(302), + RW::READ, StorageOp::new( - RW::READ, address!("0x0000000000000000000000000000000000000001"), Word::from(0x40), Word::from(32), @@ -1755,8 +1755,8 @@ mod tests { ); let storage_op_2 = Operation::new( RWCounter::from(302), + RW::READ, StorageOp::new( - RW::READ, /*fails because the address and * storage key are the same as in * the previous row */ @@ -1768,8 +1768,8 @@ mod tests { ); let storage_op_3 = Operation::new( RWCounter::from(297), + RW::WRITE, StorageOp::new( - RW::WRITE, // Global counter goes down, but it doesn't fail because // the storage key is not the same as in the previous row. address!("0x0000000000000000000000000000000000000001"), @@ -1781,8 +1781,8 @@ mod tests { let storage_op_4 = Operation::new( RWCounter::from(296), + RW::WRITE, StorageOp::new( - RW::WRITE, // Global counter goes down, but it doesn't fail because the // address is not the same as in the previous row (while the // storage key is). @@ -1819,31 +1819,36 @@ mod tests { fn non_monotone_address() { let memory_op_0 = Operation::new( RWCounter::from(1352), - MemoryOp::new(RW::WRITE, 1, MemoryAddress::from(0), 32), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), ); let memory_op_1 = Operation::new( RWCounter::from(1255), - MemoryOp::new(RW::WRITE, 1, MemoryAddress::from(1), 32), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(1), 32), ); // fails because it's not monotone let memory_op_2 = Operation::new( RWCounter::from(1255), - MemoryOp::new(RW::WRITE, 1, MemoryAddress::from(0), 32), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), ); let stack_op_0 = Operation::new( RWCounter::from(228), - StackOp::new(RW::WRITE, 1, StackAddress::from(0), Word::from(12)), + RW::WRITE, + StackOp::new(1, StackAddress::from(0), Word::from(12)), ); let stack_op_1 = Operation::new( RWCounter::from(229), - StackOp::new(RW::WRITE, 1, StackAddress::from(1), Word::from(12)), + RW::WRITE, + StackOp::new(1, StackAddress::from(1), Word::from(12)), ); let stack_op_2 = Operation::new( RWCounter::from(230), + RW::WRITE, StackOp::new( - RW::WRITE, 1, StackAddress::from(0), /* this fails because the * address is not @@ -1871,8 +1876,8 @@ mod tests { fn storage() { let storage_op_0 = Operation::new( RWCounter::from(18), + RW::WRITE, StorageOp::new( - RW::WRITE, address!("0x0000000000000000000000000000000000000001"), Word::from(0x40), Word::from(32), @@ -1881,8 +1886,8 @@ mod tests { ); let storage_op_1 = Operation::new( RWCounter::from(19), + RW::READ, StorageOp::new( - RW::READ, address!("0x0000000000000000000000000000000000000001"), Word::from(0x40), Word::from(33), /* Fails because it is READ op @@ -1894,8 +1899,8 @@ mod tests { ); let storage_op_2 = Operation::new( RWCounter::from(20), + RW::WRITE, StorageOp::new( - RW::WRITE, address!("0x0000000000000000000000000000000000000001"), Word::from(0x40), Word::from(32), @@ -1906,8 +1911,8 @@ mod tests { ); let storage_op_3 = Operation::new( RWCounter::from(21), + RW::READ, StorageOp::new( - RW::READ, address!("0x0000000000000000000000000000000000000001"), Word::from(0x40), Word::from(32), diff --git a/zkevm-circuits/src/test_util.rs b/zkevm-circuits/src/test_util.rs index c1b9b8332e..702b3d1cf7 100644 --- a/zkevm-circuits/src/test_util.rs +++ b/zkevm-circuits/src/test_util.rs @@ -1,12 +1,8 @@ +use crate::{evm_circuit::table::FixedTableTag, state_circuit::StateCircuit}; use eth_types::evm_types::Gas; -use halo2::{ - arithmetic::BaseExt, - dev::{MockProver, VerifyFailure}, -}; +use halo2::dev::{MockProver, VerifyFailure}; use pairing::bn256::Fr; -use crate::{evm_circuit::table::FixedTableTag, state_circuit::StateCircuit}; - pub enum FixedTableConfig { Incomplete, Complete, @@ -29,7 +25,6 @@ pub fn get_fixed_table(conf: FixedTableConfig) -> Vec { } pub struct BytecodeTestConfig { - pub randomness: Fr, pub enable_evm_circuit_test: bool, pub evm_circuit_lookup_tags: Vec, pub enable_state_circuit_test: bool, @@ -42,7 +37,6 @@ impl Default for BytecodeTestConfig { gas_limit: 1_000_000u64, enable_evm_circuit_test: true, enable_state_circuit_test: true, - randomness: Fr::rand(), evm_circuit_lookup_tags: get_fixed_table(FixedTableConfig::Incomplete), } } @@ -65,17 +59,12 @@ pub fn run_test_circuits_with_config( .handle_tx(&block_trace.eth_tx, &block_trace.geth_trace) .unwrap(); + let block = crate::evm_circuit::witness::block_convert(&builder.block, &builder.code_db); + let randomness = block.randomness; + // Step 2: run evm circuit test if config.enable_evm_circuit_test { - let block_for_evm_circuit = crate::evm_circuit::witness::block_convert( - config.randomness, - bytecode.code(), - &builder.block, - ); - crate::evm_circuit::test::run_test_circuit( - block_for_evm_circuit, - config.evm_circuit_lookup_tags, - )?; + crate::evm_circuit::test::run_test_circuit(block, config.evm_circuit_lookup_tags)?; } // Step 3: run state circuit test @@ -87,7 +76,7 @@ pub fn run_test_circuits_with_config( if config.enable_state_circuit_test { let block_for_state_circuit = builder.block; let state_circuit = StateCircuit:: { - randomness: config.randomness, + randomness, memory_ops: block_for_state_circuit.container.sorted_memory(), stack_ops: block_for_state_circuit.container.sorted_stack(), storage_ops: block_for_state_circuit.container.sorted_storage(),