From 6b5222b26fe0867324b5ecbbf882c10b6308fc1f Mon Sep 17 00:00:00 2001 From: Eduard S Date: Tue, 23 Nov 2021 17:10:53 +0100 Subject: [PATCH] bus-mapping: Extend circuit_input_builder Extend the types returned by the CircuitInputBuilder to match the requirements from the circuits. Rename CallContext to Call because the struct will be required for the circuits. Instead of keeping call metadata in a call stack, keep all call metadata in a vector, and use a call stack with indices to this vector. Extend Call, Transaction, ExecStep and Block with fields required by the circuits. Calculate addresses in bus-mapping. Integrate StateDB into CircuitInputBuilder and detect InsufficientBalance and ContractAddressCollision execution errors. Remove web3 dependency in favour of ethers-core for eth_types. Resolve https://github.com/appliedzkp/zkevm-circuits/issues/190 Resolve https://github.com/appliedzkp/zkevm-circuits/issues/187 Resolve https://github.com/appliedzkp/zkevm-circuits/issues/180 Resolve https://github.com/appliedzkp/zkevm-circuits/issues/172 --- bus-mapping/Cargo.toml | 4 +- bus-mapping/src/circuit_input_builder.rs | 892 ++++++++++++++++---- bus-mapping/src/error.rs | 9 + bus-mapping/src/eth_types.rs | 10 +- bus-mapping/src/evm/opcodes/dup.rs | 2 + bus-mapping/src/evm/opcodes/mload.rs | 2 + bus-mapping/src/evm/opcodes/mstore.rs | 2 + bus-mapping/src/evm/opcodes/pc.rs | 2 + bus-mapping/src/evm/opcodes/push.rs | 2 + bus-mapping/src/evm/opcodes/sload.rs | 4 +- bus-mapping/src/evm/opcodes/stackonlyop.rs | 6 + bus-mapping/src/evm/opcodes/swap.rs | 2 + bus-mapping/src/external_tracer.rs | 2 +- bus-mapping/src/lib.rs | 2 +- bus-mapping/src/mock.rs | 46 +- bus-mapping/src/operation.rs | 8 +- bus-mapping/src/{statedb.rs => state_db.rs} | 10 +- geth-utils/README.md | 13 + geth-utils/go.mod | 3 + 19 files changed, 818 insertions(+), 203 deletions(-) rename bus-mapping/src/{statedb.rs => state_db.rs} (97%) diff --git a/bus-mapping/Cargo.toml b/bus-mapping/Cargo.toml index 004f872cff2..b08591add18 100644 --- a/bus-mapping/Cargo.toml +++ b/bus-mapping/Cargo.toml @@ -13,9 +13,9 @@ lazy_static = "1.4" serde_json = "1.0.66" hex = "0.4" geth-utils = { path = "../geth-utils" } -web3 = {version = "0.17", default-features = false} uint = "0.9.1" -ethers-providers = "0.5.5" +ethers-providers = "0.6.0" +ethers-core = "0.6.0" regex = "1.5.4" [dev-dependencies] diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index 6788fc01de4..4f06026a362 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -1,14 +1,18 @@ //! This module contains the CircuitInputBuilder, which is an object that takes //! types from geth / web3 and outputs the circuit inputs. -use crate::eth_types::{self, Address, GethExecStep, GethExecTrace, Word}; -use crate::evm::GlobalCounter; -use crate::evm::OpcodeId; +use crate::eth_types::{ + self, Address, GethExecStep, GethExecTrace, ToBigEndian, Word, H256, +}; +use crate::evm::{Gas, GasCost, GlobalCounter, OpcodeId, ProgramCounter}; use crate::exec_trace::OperationRef; use crate::geth_errors::*; use crate::operation::container::OperationContainer; use crate::operation::{Op, Operation}; +use crate::state_db::StateDB; use crate::{BlockConstants, Error}; use core::fmt::Debug; +use ethers_core::utils::{get_contract_address, get_create2_address}; +use std::collections::HashMap; /// Out of Gas errors by opcode #[derive(Debug, PartialEq)] @@ -86,22 +90,54 @@ pub enum ExecError { pub struct ExecStep { /// The opcode ID pub op: OpcodeId, - /// The global counter when this step was executed + /// Program Counter + pub pc: ProgramCounter, + /// Stack size + pub stack_size: usize, + /// Memory size + pub memory_size: usize, + /// Gas left + pub gas_left: Gas, + /// Gas cost of the step. If the error is OutOfGas caused by a "gas uint64 + /// overflow", this value will **not** be the actual Gas cost of the + /// step. + pub gas_cost: GasCost, + /// Call index within the [`Transaction`] + pub call_index: usize, + /// The global counter when this step was executed. pub gc: GlobalCounter, + /// State Write Counter. Counter of state write operations in the call + /// that haven't been reverted yet up to this step. + pub swc: usize, /// The list of references to Operations in the container pub bus_mapping_instance: Vec, /// Error generated by this step pub error: Option, + /// The step has been reverted + pub reverted: bool, } impl ExecStep { - /// Create a new Self from a `geth_step`. - pub fn new(geth_step: &GethExecStep, gc: GlobalCounter) -> Self { + /// Create a new Self from a [`GethExecStep`]. + pub fn new( + step: &GethExecStep, + call_index: usize, + gc: GlobalCounter, + swc: usize, // State Write Counter + ) -> Self { ExecStep { - op: geth_step.op, + op: step.op, + pc: step.pc, + stack_size: step.stack.0.len(), + memory_size: step.memory.0.len(), + gas_left: step.gas, + gas_cost: step.gas_cost, + call_index, gc, + swc, bus_mapping_instance: Vec::new(), error: None, + reverted: false, } } } @@ -136,6 +172,7 @@ pub struct Block { /// Container of operations done in this block. pub container: OperationContainer, txs: Vec, + code: HashMap>, } impl Block { @@ -148,6 +185,7 @@ impl Block { constants, container: OperationContainer::new(), txs: Vec::new(), + code: HashMap::new(), } } @@ -164,9 +202,7 @@ impl Block { /// Type of a *CALL* Function. #[derive(Debug, PartialEq)] -pub enum CallType { - /// Implicit call of a transaction - Root, +pub enum CallKind { /// CALL Call, /// CALLCODE @@ -181,101 +217,164 @@ pub enum CallType { Create2, } -impl CallType { +impl CallKind { fn is_create(&self) -> bool { matches!(self, Self::Create | Self::Create2) } } -impl TryFrom for CallType { +impl TryFrom for CallKind { type Error = Error; fn try_from(op: OpcodeId) -> Result { Ok(match op { - OpcodeId::CALL => CallType::Call, - OpcodeId::CALLCODE => CallType::CallCode, - OpcodeId::DELEGATECALL => CallType::DelegateCall, - OpcodeId::STATICCALL => CallType::StaticCall, - OpcodeId::CREATE => CallType::Create, - OpcodeId::CREATE2 => CallType::Create2, + OpcodeId::CALL => CallKind::Call, + OpcodeId::CALLCODE => CallKind::CallCode, + OpcodeId::DELEGATECALL => CallKind::DelegateCall, + OpcodeId::STATICCALL => CallKind::StaticCall, + OpcodeId::CREATE => CallKind::Create, + OpcodeId::CREATE2 => CallKind::Create2, _ => return Err(Error::OpcodeIdNotCallType), }) } } -/// Context of a Call during a [`Transaction`] which can mutate in an -/// [`ExecStep`]. +/// Circuit Input related to an Ethereum Call #[derive(Debug)] -pub struct CallContext { +pub struct Call { /// Type of call - call: CallType, + kind: CallKind, /// This call is being executed without write access (STATIC) is_static: bool, - /// This call is root call with tx.to == null, or op == CREATE or op == - /// CREATE2 - is_create: bool, + /// This call generated implicity by a Transaction. + is_root: bool, /// Address where this call is being executed pub address: Address, + /// Code Hash + code_hash: H256, +} + +impl Call { + /// This call is root call with tx.to == null, or op == CREATE or op == + /// CREATE2 + pub fn is_create(&self) -> bool { + self.kind.is_create() + } +} + +/// Context of a [`Call`]. +#[derive(Debug)] +pub struct CallContext { + /// 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 + /// number of successful state writes in the subcall. + pub swc: usize, } #[derive(Debug)] /// Context of a [`Transaction`] which can mutate in an [`ExecStep`]. pub struct TransactionContext { - call_stack: Vec, + /// 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)>, } impl TransactionContext { /// Create a new Self. - pub fn new(eth_tx: ð_types::Transaction) -> Self { - let mut call_stack = Vec::new(); - if let Some(address) = eth_tx.to { - call_stack.push(CallContext { - call: CallType::Root, - is_static: false, - is_create: false, - address, - }); - } else { - call_stack.push(CallContext { - call: CallType::Root, - is_static: false, - is_create: true, - address: Address::zero(), - }); + pub fn new(_eth_tx: ð_types::Transaction) -> Self { + Self { + call_stack: vec![(0, CallContext { swc: 0 })], } - Self { call_stack } } - /// Return a reference to the current call context (the last call context in - /// the call stack). - pub fn call_ctx(&self) -> &CallContext { - self.call_stack.last().expect("call_stack is empty") + /// Return the index and context 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 } - /// Push a new call context into the call stack. - pub fn push_call_ctx(&mut self, call: CallType, address: Address) { - let is_static = - call == CallType::StaticCall || self.call_ctx().is_static; - let is_create = call.is_create(); - self.call_stack.push(CallContext { - call, - is_static, - is_create, - address, - }); + fn call_ctx(&self) -> &CallContext { + let (_, call_ctx) = + self.call_stack.last().expect("call_stack is empty"); + call_ctx + } + + 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 + } + + /// 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)); + } + + /// Pop the last entry in the call stack. + fn pop_call_index_ctx(&mut self) -> Option<(usize, CallContext)> { + self.call_stack.pop() } } #[derive(Debug)] /// Result of the parsing of an Ethereum Transaction. pub struct Transaction { + /// Nonce + pub nonce: u64, + /// Gas + pub gas: u64, + /// From / Caller Address + pub from: Address, // caller_address + /// To / Callee Address + pub to: Address, // callee_address + /// Value + pub value: Word, + /// Input / Call Data + pub input: Vec, // call_data + calls: Vec, steps: Vec, } impl Transaction { /// Create a new Self. - pub fn new(_eth_tx: ð_types::Transaction) -> Self { - Self { steps: Vec::new() } + pub fn new(eth_tx: ð_types::Transaction) -> Self { + let mut calls = Vec::new(); + let code_hash = H256::zero(); + if let Some(address) = eth_tx.to { + calls.push(Call { + kind: CallKind::Call, + is_static: false, + is_root: true, + address, + code_hash, + }); + } else { + calls.push(Call { + kind: CallKind::Create, + is_static: false, + is_root: true, + address: Address::zero(), + code_hash, + }); + } + Self { + nonce: eth_tx.nonce.as_u64(), + gas: eth_tx.gas.as_u64(), + from: eth_tx.from, + to: eth_tx.to.unwrap_or_default(), + value: eth_tx.value, + input: eth_tx.input.to_vec(), + + calls, + steps: Vec::new(), + } + } + + /// Wether this [`Transaction`] is a create one + pub fn is_create(&self) -> bool { + self.calls[0].is_create() } /// Return the list of execution steps of this transaction. @@ -287,11 +386,32 @@ impl Transaction { pub fn steps_mut(&mut self) -> &mut Vec { &mut self.steps } + + fn push_call( + &mut self, + parent_index: usize, + kind: CallKind, + address: Address, + ) -> usize { + let is_static = + kind == CallKind::StaticCall || self.calls[parent_index].is_static; + let code_hash = H256::zero(); + self.calls.push(Call { + kind, + is_static, + is_root: false, + address, + code_hash, + }); + self.calls.len() - 1 + } } /// Reference to the internal state of the CircuitInputBuilder in a particular /// [`ExecStep`]. pub struct CircuitInputStateRef<'a> { + /// StateDB + pub sdb: &'a mut StateDB, /// Block pub block: &'a mut Block, /// Block Context @@ -304,6 +424,18 @@ pub struct CircuitInputStateRef<'a> { pub step: &'a mut ExecStep, } +/// Helper function to push a call into a Transaction and TransactionContext +fn push_call( + tx: &mut Transaction, + tx_ctx: &mut TransactionContext, + kind: CallKind, + address: Address, +) { + let parent_index = tx_ctx.call_index(); + let index = tx.push_call(parent_index, kind, address); + tx_ctx.push_call_index_ctx(index, CallContext { swc: 0 }); +} + impl<'a> CircuitInputStateRef<'a> { /// Push an [`Operation`] into the [`OperationContainer`] with the next /// [`GlobalCounter`] and then adds a reference to the stored operation @@ -316,6 +448,27 @@ impl<'a> CircuitInputStateRef<'a> { .insert(Operation::new(self.block_ctx.gc.inc_pre(), op)); self.step.bus_mapping_instance.push(op_ref); } + + /// Reference to the current Call + pub fn call(&self) -> &Call { + &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()] + } + + /// 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) { + push_call(self.tx, self.tx_ctx, kind, address) + } } #[derive(Debug)] @@ -338,6 +491,8 @@ impl<'a> CircuitInputStateRef<'a> { /// the State Proof witnesses are already generated on a structured manner and /// ready to be added into the State circuit. pub struct CircuitInputBuilder { + /// StateDB key-value DB + pub sdb: StateDB, /// Block pub block: Block, /// Block Context @@ -352,6 +507,7 @@ impl<'a> CircuitInputBuilder { constants: BlockConstants, ) -> Self { Self { + sdb: StateDB::new(), block: Block::new(ð_block, constants), block_ctx: BlockContext::new(), } @@ -367,6 +523,7 @@ impl<'a> CircuitInputBuilder { step: &'a mut ExecStep, ) -> CircuitInputStateRef { CircuitInputStateRef { + sdb: &mut self.sdb, block: &mut self.block, block_ctx: &mut self.block_ctx, tx, @@ -387,13 +544,44 @@ impl<'a> CircuitInputBuilder { let mut tx = Transaction::new(eth_tx); let mut tx_ctx = TransactionContext::new(eth_tx); for (index, geth_step) in geth_trace.struct_logs.iter().enumerate() { - let mut step = ExecStep::new(geth_step, self.block_ctx.gc); + if index != 0 { + let geth_prev_step = &geth_trace.struct_logs[index - 1]; + // Handle *CALL* + if geth_step.depth == geth_prev_step.depth + 1 { + // TODO: Set the proper address according to the call kind. + let address = Address::zero(); + let kind = CallKind::try_from(geth_step.op)?; + push_call(&mut tx, &mut tx_ctx, kind, address); + } + } + + let mut step = ExecStep::new( + geth_step, + tx_ctx.call_index(), + self.block_ctx.gc, + tx_ctx.call_ctx().swc, + ); let mut state_ref = self.state_ref(&mut tx, &mut tx_ctx, &mut step); geth_step.op.gen_associated_ops( &mut state_ref, &geth_trace.struct_logs[index..], )?; tx.steps.push(step); + + if let Some(geth_next_step) = geth_trace.struct_logs.get(index + 1) + { + // Handle *CALL* return + if geth_step.depth == geth_next_step.depth - 1 { + let (_, call_ctx) = + tx_ctx.pop_call_index_ctx().ok_or_else(|| { + Error::InvalidGethExecStep( + "*CALL* return with empty call stack", + Box::new(geth_step.clone()), + ) + })?; + tx_ctx.call_ctx_mut().swc += call_ctx.swc; + } + } } self.block.txs.push(tx); Ok(()) @@ -442,14 +630,48 @@ fn get_step_reported_error(op: &OpcodeId, error: &str) -> ExecError { } } +/// Retreive the init_code from memory for {CREATE, CREATE2} +pub fn get_create_init_code(step: &GethExecStep) -> Result<&[u8], Error> { + let offset = step.stack.nth_last(1)?; + let length = step.stack.nth_last(2)?; + Ok(&step.memory.0[offset.low_u64() as usize + ..(offset.low_u64() + length.low_u64()) as usize]) +} + impl<'a> CircuitInputStateRef<'a> { + fn create_address(&self) -> Result { + let sender = self.call().address; + let (found, account) = self.sdb.get_account(&sender); + if !found { + return Err(Error::AccountNotFound(sender)); + } + Ok(get_contract_address(sender, account.nonce)) + } + + fn create2_address(&self, step: &GethExecStep) -> Result { + let salt = step.stack.nth_last(3)?; + let init_code = get_create_init_code(step)?; + Ok(get_create2_address( + self.call().address, + salt.to_be_bytes().to_vec(), + init_code.to_vec(), + )) + } + fn get_step_err( &self, step: &GethExecStep, next_step: Option<&GethExecStep>, - ) -> Option { + ) -> Result, Error> { if let Some(error) = &step.error { - return Some(get_step_reported_error(&step.op, error)); + return Ok(Some(get_step_reported_error(&step.op, error))); + } + + // 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); } let next_depth = next_step.map(|s| s.depth).unwrap_or(0); @@ -459,58 +681,69 @@ impl<'a> CircuitInputStateRef<'a> { // Return from a call with a failure if step.depth != next_depth && next_result == Word::zero() { - if step.op != OpcodeId::RETURN { + if !matches!(step.op, OpcodeId::RETURN) { // Without calling RETURN - return Some(match step.op { + return Ok(Some(match step.op { OpcodeId::REVERT => ExecError::ExecutionReverted, OpcodeId::JUMP | OpcodeId::JUMPI => ExecError::InvalidJump, OpcodeId::RETURNDATACOPY => { ExecError::ReturnDataOutOfBounds } _ => { - panic!( - "Cannot figure out call failure error in {:?}", - step - ) + return Err(Error::UnexpectedExecStepError( + "call failure without return", + Box::new(step.clone()), + )); } - }); + })); } else { // Calling RETURN - let call_ctx = self.tx_ctx.call_ctx(); + let call = self.call(); // Return from a {CREATE, CREATE2} with a failure, via RETURN - if call_ctx.call != CallType::Root && call_ctx.is_create { - let offset = step.stack.nth_last(0).unwrap(); - let length = step.stack.nth_last(1).unwrap(); - if length > Word::from(0x6000) { - return Some(ExecError::MaxCodeSizeExceeded); + if !call.is_root && call.is_create() { + let offset = step.stack.nth_last(0)?; + let length = step.stack.nth_last(1)?; + if length > Word::from(0x6000u64) { + return Ok(Some(ExecError::MaxCodeSizeExceeded)); } else if length > Word::zero() && !step.memory.0.is_empty() && step.memory.0.get(offset.low_u64() as usize) == Some(&0xef) { - return Some(ExecError::InvalidCode); - } else if Word::from(200) * length > Word::from(step.gas.0) + return Ok(Some(ExecError::InvalidCode)); + } else if Word::from(200u64) * length + > Word::from(step.gas.0) { - return Some(ExecError::CodeStoreOutOfGas); + return Ok(Some(ExecError::CodeStoreOutOfGas)); } else { - panic!("Cannot figure out RETURN error in {:?}", step); + return Err(Error::UnexpectedExecStepError( + "failure in RETURN from {CREATE, CREATE2}", + Box::new(step.clone()), + )); } } else { - panic!("Cannot figure out RETURN error in {:?}", step); + return Err(Error::UnexpectedExecStepError( + "failure in RETURN", + Box::new(step.clone()), + )); } } } - // Return from a call via RETURN and having a success result is OK. + // Return from a call via RETURN or STOP and having a success result is + // OK. - // Return from a call without calling RETURN and having success is - // unexpected. + // Return from a call without calling RETURN or STOP and having success + // is unexpected. if step.depth != next_depth && next_result != Word::zero() - && step.op != OpcodeId::RETURN + && !matches!(step.op, OpcodeId::RETURN | OpcodeId::STOP) { - panic!("Cannot figure out call failure error in {:?}", step); + return Err(Error::UnexpectedExecStepError( + "success result without {RETURN, STOP}", + Box::new(step.clone()), + )); } // The *CALL* code was not executed @@ -527,65 +760,48 @@ impl<'a> CircuitInputStateRef<'a> { && next_pc != 0 { if step.depth == 1025 { - return Some(ExecError::Depth); - } - // TODO: address_collision - // https://github.com/appliedzkp/zkevm-circuits/issues/180 - if matches!(step.op, OpcodeId::CREATE | OpcodeId::CREATE2) { - // TODO: Calculate address - // https://github.com/appliedzkp/zkevm-circuits/issues/187 - let _address = if matches!(step.op, OpcodeId::CREATE) { - // CREATE - let _value = step.stack.nth_last(0).unwrap(); - let _offset = step.stack.nth_last(1).unwrap(); - let _length = step.stack.nth_last(2).unwrap(); - // TODO: - // let caller_address = self.tx_ctx.caller_addr(); - // let caller = self.get_account(caller_address) - // let nonce = caller.nonce; - // https://github.com/appliedzkp/zkevm-circuits/issues/186 - Address::zero() - } else { - // CREATE2 - let _value = step.stack.nth_last(0).unwrap(); - let _offset = step.stack.nth_last(1).unwrap(); - let _length = step.stack.nth_last(2).unwrap(); - let _salt = step.stack.nth_last(3).unwrap(); - Address::zero() - }; - // TODO: - // if Some(_) = self.get_account(_address) { - // return Some(ExecError::ContractAddressCollision); - // } - // https://github.com/appliedzkp/zkevm-circuits/issues/186 + return Ok(Some(ExecError::Depth)); } - // TODO: insufficient_balance - // https://github.com/appliedzkp/zkevm-circuits/issues/180 - let _value = match step.op { + // Insufficient_balance + let value = match step.op { OpcodeId::CALL | OpcodeId::CALLCODE => { - step.stack.nth_last(2).unwrap() + step.stack.nth_last(2)? } OpcodeId::CREATE | OpcodeId::CREATE2 => { - step.stack.nth_last(0).unwrap() + step.stack.nth_last(0)? } _ => Word::zero(), }; - // TODO - // let caller_address = self.tx_ctx.caller_addr(); - // let caller = self.get_account(caller_address) - // if caller.balance < value { - // return Some(ExecError::InsufficientBalance); - // } - // https://github.com/appliedzkp/zkevm-circuits/issues/186 - - panic!( - "Cannot figure out *CALL* code not executed error in {:?}", - step - ); + let sender = self.call().address; + let (found, account) = self.sdb.get_account(&sender); + if !found { + return Err(Error::AccountNotFound(sender)); + } + if account.balance < value { + return Ok(Some(ExecError::InsufficientBalance)); + } + + // Address collision + if matches!(step.op, OpcodeId::CREATE | OpcodeId::CREATE2) { + let address = match step.op { + OpcodeId::CREATE => self.create_address()?, + OpcodeId::CREATE2 => self.create2_address(step)?, + _ => unreachable!(), + }; + let (found, _) = self.sdb.get_account(&address); + if found { + return Ok(Some(ExecError::ContractAddressCollision)); + } + } + + return Err(Error::UnexpectedExecStepError( + "*CALL* code not executed", + Box::new(step.clone()), + )); } - None + Ok(None) } } @@ -597,7 +813,9 @@ mod tracer_tests { bytecode::Bytecode, eth_types::{ToWord, Word}, evm::{stack::Stack, Gas, OpcodeId}, - mock, word, + mock, + state_db::Account, + word, }; use lazy_static::lazy_static; @@ -621,7 +839,7 @@ mod tracer_tests { ), tx: Transaction::new(&block.eth_tx), tx_ctx: TransactionContext::new(&block.eth_tx), - step: ExecStep::new(geth_step, GlobalCounter(0)), + step: ExecStep::new(geth_step, 0, GlobalCounter(0), 0), } } @@ -712,7 +930,7 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( - builder.state_ref().get_step_err(step, next_step), + builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::Depth) ); } @@ -764,12 +982,21 @@ mod tracer_tests { assert_eq!(next_step.unwrap().op, OpcodeId::PUSH2); assert_eq!(next_step.unwrap().stack, Stack(vec![Word::from(0)])); // success = 0 - // TODO: Uncomment once get_step_err correctly detects - // InsufficientBalance let mut builder = - // CircuitInputBuilderTx::new(&block, step); assert_eq!( - // builder.state_ref().get_step_err(step, next_step), - // Some(ExecError::InsufficientBalance) - // ); + let mut builder = CircuitInputBuilderTx::new(&block, step); + builder.builder.sdb.set_account( + &ADDR_A, + Account { + nonce: Word::zero(), + balance: Word::from(555u64), /* same value as in + * `mock::new_tracer_account` */ + storage: HashMap::new(), + codeHash: H256::zero(), + }, + ); + assert_eq!( + builder.state_ref().get_step_err(step, next_step).unwrap(), + Some(ExecError::InsufficientBalance) + ); } fn check_err_address_collision( @@ -830,13 +1057,13 @@ mod tracer_tests { code_b.write_op(OpcodeId::MSTORE); } let code_b_end = bytecode! { - PUSH1(0x00) // salt + PUSH3(0x123456) // salt PUSH1(len) // length PUSH1(0x00) // offset PUSH1(0x00) // value CREATE2 - PUSH1(0x00) // salt + PUSH3(0x123456) // salt PUSH1(len) // length PUSH1(0x00) // offset PUSH1(0x00) // value @@ -861,12 +1088,47 @@ mod tracer_tests { let next_step = block.geth_trace.struct_logs.get(index + 1); assert!(check_err_address_collision(step, next_step)); - // TODO: Uncomment once get_step_err correctly detects - // ContractAddressCollision let mut builder = - // CircuitInputBuilderTx::new(&block, step); assert_eq!( - // builder.state_ref().get_step_err(step, next_step), - // Some(ExecError::ContractAddressCollision) - // ); + let create2_address: Address = { + // get first RETURN + let (index, _) = block + .geth_trace + .struct_logs + .iter() + .enumerate() + .find(|(_, s)| s.op == OpcodeId::RETURN) + .unwrap(); + let next_step = block.geth_trace.struct_logs.get(index + 1); + let addr_word = next_step.unwrap().stack.last().unwrap(); + Address::from_slice(&addr_word.to_be_bytes()[12..]) + }; + + let mut builder = CircuitInputBuilderTx::new(&block, step); + // Set up call context at CREATE2 + builder.state_ref().push_call(CallKind::Create, *ADDR_B); + // Set up account and contract that exist during the second CREATE2 + builder.builder.sdb.set_account( + &ADDR_B, + Account { + nonce: Word::zero(), + balance: Word::from(555u64), /* same value as in + * `mock::new_tracer_account` */ + storage: HashMap::new(), + codeHash: H256::zero(), + }, + ); + builder.builder.sdb.set_account( + &create2_address, + Account { + nonce: Word::zero(), + balance: Word::zero(), + storage: HashMap::new(), + codeHash: H256::zero(), + }, + ); + assert_eq!( + builder.state_ref().get_step_err(step, next_step).unwrap(), + Some(ExecError::ContractAddressCollision) + ); } fn check_err_code_store_out_of_gas( @@ -948,9 +1210,10 @@ mod tracer_tests { assert!(check_err_code_store_out_of_gas(step, next_step)); let mut builder = CircuitInputBuilderTx::new(&block, step); - builder.tx_ctx.push_call_ctx(CallType::Create, *ADDR_B); + // Set up call context at CREATE + builder.state_ref().push_call(CallKind::Create, *ADDR_B); assert_eq!( - builder.state_ref().get_step_err(step, next_step), + builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::CodeStoreOutOfGas) ); } @@ -1036,9 +1299,10 @@ mod tracer_tests { assert!(check_err_invalid_code(step, next_step)); let mut builder = CircuitInputBuilderTx::new(&block, step); - builder.tx_ctx.push_call_ctx(CallType::Create, *ADDR_B); + // Set up call context at RETURN + builder.state_ref().push_call(CallKind::Create, *ADDR_B); assert_eq!( - builder.state_ref().get_step_err(step, next_step), + builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::InvalidCode) ); } @@ -1122,13 +1386,86 @@ mod tracer_tests { assert!(check_err_max_code_size_exceeded(step, next_step)); let mut builder = CircuitInputBuilderTx::new(&block, step); - builder.tx_ctx.push_call_ctx(CallType::Create, *ADDR_B); + // Set up call context at RETURN + builder.state_ref().push_call(CallKind::Create, *ADDR_B); assert_eq!( - builder.state_ref().get_step_err(step, next_step), + builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::MaxCodeSizeExceeded) ); } + #[test] + fn tracer_create_stop() { + // code_creator doesn't output anything because it stops. + let code_creator = bytecode! { + PUSH32(word!("0xef00000000000000000000000000000000000000000000000000000000000000")) // value + PUSH1(0x00) // offset + MSTORE + PUSH1(0x01) // length + PUSH1(0x00) // offset + STOP + }; + + // code_a calls code_b which executes code_creator in CREATE + let code_a = bytecode! { + PUSH1(0x0) // retLength + PUSH1(0x0) // retOffset + PUSH1(0x0) // argsLength + PUSH1(0x0) // argsOffset + PUSH1(0x0) // value + PUSH32(*WORD_ADDR_B) // addr + PUSH32(0x1_0000) // gas + CALL + + PUSH2(0xaa) + }; + + let mut code_b = Bytecode::default(); + // pad code_creator to multiple of 32 bytes + let len = code_creator.code().len(); + let code_creator: Vec = code_creator + .code() + .iter() + .cloned() + .chain(0u8..((32 - len % 32) as u8)) + .collect(); + for (index, word) in code_creator.chunks(32).enumerate() { + code_b.push(32, Word::from_big_endian(word)); + code_b.push(32, Word::from(index * 32)); + code_b.write_op(OpcodeId::MSTORE); + } + let code_b_end = bytecode! { + PUSH1(len) // length + PUSH1(0x00) // offset + PUSH1(0x00) // value + CREATE + + PUSH3(0xbb) + }; + code_b.append(&code_b_end); + let block = + mock::BlockData::new_single_tx_trace_code_2(&code_a, &code_b) + .unwrap(); + + // get first STOP + let (index, step) = block + .geth_trace + .struct_logs + .iter() + .enumerate() + .find(|(_, s)| s.op == OpcodeId::STOP) + .unwrap(); + let next_step = block.geth_trace.struct_logs.get(index + 1); + + let mut builder = CircuitInputBuilderTx::new(&block, step); + // Set up call context at STOP + builder.state_ref().push_call(CallKind::Create, *ADDR_B); + assert_eq!( + builder.state_ref().get_step_err(step, next_step).unwrap(), + None + ); + } + // // Geth Errors not reported // @@ -1171,7 +1508,7 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( - builder.state_ref().get_step_err(step, next_step), + builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::InvalidJump) ); @@ -1198,7 +1535,7 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( - builder.state_ref().get_step_err(step, next_step), + builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::InvalidJump) ); } @@ -1233,7 +1570,7 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( - builder.state_ref().get_step_err(step, next_step), + builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::ExecutionReverted) ); @@ -1261,11 +1598,48 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( - builder.state_ref().get_step_err(step, next_step), + builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::ExecutionReverted) ); } + #[test] + fn tracer_stop() { + // Do a STOP + let code = bytecode! { + PUSH1(0x0) + PUSH2(0x0) + STOP + PUSH3(0x12) + STOP + }; + + // code_a calls code + let code_a = bytecode! { + PUSH1(0x0) // retLength + PUSH1(0x0) // retOffset + PUSH1(0x0) // argsLength + PUSH1(0x0) // argsOffset + PUSH1(0x0) // value + PUSH32(*WORD_ADDR_B) // addr + PUSH32(0x1_0000) // gas + CALL + + PUSH2(0xaa) + }; + let index = 10; // STOP + let block = mock::BlockData::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); + + let mut builder = CircuitInputBuilderTx::new(&block, step); + assert_eq!( + builder.state_ref().get_step_err(step, next_step).unwrap(), + None + ); + } + fn check_err_return_data_out_of_bounds( step: &GethExecStep, next_step: Option<&GethExecStep>, @@ -1324,7 +1698,7 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( - builder.state_ref().get_step_err(step, next_step), + builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::ReturnDataOutOfBounds) ); } @@ -1353,7 +1727,7 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( - builder.state_ref().get_step_err(step, next_step), + builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::OutOfGas(OogError::PureMemory)) ); } @@ -1380,7 +1754,7 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( - builder.state_ref().get_step_err(step, next_step), + builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::InvalidOpcode) ); } @@ -1418,7 +1792,7 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( - builder.state_ref().get_step_err(step, next_step), + builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::WriteProtection) ); } @@ -1458,7 +1832,7 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( - builder.state_ref().get_step_err(step, next_step), + builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::StackOverflow) ); } @@ -1481,8 +1855,190 @@ mod tracer_tests { let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( - builder.state_ref().get_step_err(step, next_step), + builder.state_ref().get_step_err(step, next_step).unwrap(), Some(ExecError::StackUnderflow) ); } + + // + // Circuit Input Builder tests + // + + #[test] + fn create2_address() { + // code_creator outputs 0x6050. + let code_creator = bytecode! { + PUSH32(word!("0x6050000000000000000000000000000000000000000000000000000000000000")) // value + PUSH1(0x00) // offset + MSTORE + PUSH1(0x02) // length + PUSH1(0x00) // offset + RETURN + }; + + // code_a calls code_b which executes code_creator in CREATE + let code_a = bytecode! { + PUSH1(0x0) // retLength + PUSH1(0x0) // retOffset + PUSH1(0x0) // argsLength + PUSH1(0x0) // argsOffset + PUSH1(0x0) // value + PUSH32(*WORD_ADDR_B) // addr + PUSH32(0x1_0000) // gas + CALL + + PUSH2(0xaa) + }; + + let mut code_b = Bytecode::default(); + // pad code_creator to multiple of 32 bytes + let len = code_creator.code().len(); + let code_creator: Vec = code_creator + .code() + .iter() + .cloned() + .chain(0u8..((32 - len % 32) as u8)) + .collect(); + for (index, word) in code_creator.chunks(32).enumerate() { + code_b.push(32, Word::from_big_endian(word)); + code_b.push(32, Word::from(index * 32)); + code_b.write_op(OpcodeId::MSTORE); + } + let code_b_end = bytecode! { + PUSH3(0x123456) // salt + PUSH1(len) // length + PUSH1(0x00) // offset + PUSH1(0x00) // value + CREATE2 + + PUSH3(0xbb) + }; + code_b.append(&code_b_end); + let block = + mock::BlockData::new_single_tx_trace_code_2(&code_a, &code_b) + .unwrap(); + + // get RETURN + let (index_return, _) = block + .geth_trace + .struct_logs + .iter() + .enumerate() + .find(|(_, s)| s.op == OpcodeId::RETURN) + .unwrap(); + let next_step_return = + block.geth_trace.struct_logs.get(index_return + 1); + let addr_expect = next_step_return.unwrap().stack.last().unwrap(); + + // get CREATE2 + let step_create2 = block + .geth_trace + .struct_logs + .iter() + .find(|s| s.op == OpcodeId::CREATE2) + .unwrap(); + let mut builder = CircuitInputBuilderTx::new(&block, step_create2); + // Set up call context at CREATE2 + builder.state_ref().push_call(CallKind::Create, *ADDR_B); + let addr = builder.state_ref().create2_address(step_create2).unwrap(); + + assert_eq!(addr.to_word(), addr_expect); + } + + #[test] + fn create_address() { + // code_creator outputs 0x6050. + let code_creator = bytecode! { + PUSH32(word!("0x6050000000000000000000000000000000000000000000000000000000000000")) // value + PUSH1(0x00) // offset + MSTORE + PUSH1(0x02) // length + PUSH1(0x00) // offset + RETURN + }; + + // code_a calls code_b which executes code_creator in CREATE + let code_a = bytecode! { + PUSH1(0x0) // retLength + PUSH1(0x0) // retOffset + PUSH1(0x0) // argsLength + PUSH1(0x0) // argsOffset + PUSH1(0x0) // value + PUSH32(*WORD_ADDR_B) // addr + PUSH32(0x1_0000) // gas + CALL + + PUSH2(0xaa) + }; + + let mut code_b = Bytecode::default(); + // pad code_creator to multiple of 32 bytes + let len = code_creator.code().len(); + let code_creator: Vec = code_creator + .code() + .iter() + .cloned() + .chain(0u8..((32 - len % 32) as u8)) + .collect(); + for (index, word) in code_creator.chunks(32).enumerate() { + code_b.push(32, Word::from_big_endian(word)); + code_b.push(32, Word::from(index * 32)); + code_b.write_op(OpcodeId::MSTORE); + } + // We do CREATE 2 times to use a nonce != 0 in the second one. + let code_b_end = bytecode! { + PUSH1(len) // length + PUSH1(0x00) // offset + PUSH1(0x00) // value + CREATE + + PUSH1(len) // length + PUSH1(0x00) // offset + PUSH1(0x00) // value + CREATE + + PUSH3(0xbb) + }; + code_b.append(&code_b_end); + let block = + mock::BlockData::new_single_tx_trace_code_2(&code_a, &code_b) + .unwrap(); + + // get last RETURN + let (index_return, _) = block + .geth_trace + .struct_logs + .iter() + .enumerate() + .rev() + .find(|(_, s)| s.op == OpcodeId::RETURN) + .unwrap(); + let next_step_return = + block.geth_trace.struct_logs.get(index_return + 1); + let addr_expect = next_step_return.unwrap().stack.last().unwrap(); + + // get last CREATE + let step_create = block + .geth_trace + .struct_logs + .iter() + .rev() + .find(|s| s.op == OpcodeId::CREATE) + .unwrap(); + let mut builder = CircuitInputBuilderTx::new(&block, step_create); + // Set up call context at CREATE + builder.state_ref().push_call(CallKind::Create, *ADDR_B); + builder.builder.sdb.set_account( + &ADDR_B, + Account { + nonce: Word::from(1), + balance: Word::zero(), + storage: HashMap::new(), + codeHash: H256::zero(), + }, + ); + let addr = builder.state_ref().create_address().unwrap(); + + assert_eq!(addr.to_word(), addr_expect); + } } diff --git a/bus-mapping/src/error.rs b/bus-mapping/src/error.rs index 4dd3778f1f7..a01117cba09 100644 --- a/bus-mapping/src/error.rs +++ b/bus-mapping/src/error.rs @@ -1,5 +1,6 @@ //! Error module for the bus-mapping crate +use crate::eth_types::{Address, GethExecStep, Word}; use core::fmt::{Display, Formatter, Result as FmtResult}; use ethers_providers::ProviderError; use std::error::Error as StdError; @@ -32,6 +33,14 @@ pub enum Error { JSONRpcError(ProviderError), /// OpcodeId is not a call type. OpcodeIdNotCallType, + /// Account not found in the StateDB + AccountNotFound(Address), + /// Storage key not found in the StateDB + StorageKeyNotFound(Address, Word), + /// Unable to figure out error at a [`GethExecStep`] + UnexpectedExecStepError(&'static str, Box), + /// Invalid [`GethExecStep`] due to an invalid/unexpected value in it. + InvalidGethExecStep(&'static str, Box), } impl From for Error { diff --git a/bus-mapping/src/eth_types.rs b/bus-mapping/src/eth_types.rs index e46035e13cd..1a2ada7725b 100644 --- a/bus-mapping/src/eth_types.rs +++ b/bus-mapping/src/eth_types.rs @@ -2,15 +2,15 @@ use crate::evm::{memory::Memory, stack::Stack, storage::Storage}; use crate::evm::{Gas, GasCost, OpcodeId, ProgramCounter}; +use ethers_core::types; +pub use ethers_core::types::{ + transaction::response::Transaction, Address, Block, Bytes, H160, H256, + U256, U64, +}; use pasta_curves::arithmetic::FieldExt; use serde::{de, Deserialize, Serialize}; use std::collections::HashMap; use std::str::FromStr; -use web3::types; -pub use web3::types::{ - AccessList, Address, Block, Bytes, Index, Transaction, H2048, H256, H64, - U256, U64, -}; /// Trait used to define types that can be converted to a 256 bit scalar value. pub trait ToScalar { diff --git a/bus-mapping/src/evm/opcodes/dup.rs b/bus-mapping/src/evm/opcodes/dup.rs index 49ba9163532..a4a23f116df 100644 --- a/bus-mapping/src/evm/opcodes/dup.rs +++ b/bus-mapping/src/evm/opcodes/dup.rs @@ -82,7 +82,9 @@ mod dup_tests { { let mut step = ExecStep::new( &block.geth_trace.struct_logs[i], + 0, test_builder.block_ctx.gc, + 0, ); let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); diff --git a/bus-mapping/src/evm/opcodes/mload.rs b/bus-mapping/src/evm/opcodes/mload.rs index 48d4b90f79b..17e6c42dae4 100644 --- a/bus-mapping/src/evm/opcodes/mload.rs +++ b/bus-mapping/src/evm/opcodes/mload.rs @@ -100,7 +100,9 @@ mod mload_tests { // Generate step corresponding to MLOAD let mut step = ExecStep::new( &block.geth_trace.struct_logs[0], + 0, test_builder.block_ctx.gc, + 0, ); let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); diff --git a/bus-mapping/src/evm/opcodes/mstore.rs b/bus-mapping/src/evm/opcodes/mstore.rs index 12c4159d221..5ac6010a650 100644 --- a/bus-mapping/src/evm/opcodes/mstore.rs +++ b/bus-mapping/src/evm/opcodes/mstore.rs @@ -90,7 +90,9 @@ mod mstore_tests { // Generate step corresponding to MSTORE let mut step = ExecStep::new( &block.geth_trace.struct_logs[0], + 0, test_builder.block_ctx.gc, + 0, ); let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); diff --git a/bus-mapping/src/evm/opcodes/pc.rs b/bus-mapping/src/evm/opcodes/pc.rs index af88e6c108d..021cf758b2b 100644 --- a/bus-mapping/src/evm/opcodes/pc.rs +++ b/bus-mapping/src/evm/opcodes/pc.rs @@ -73,7 +73,9 @@ mod pc_tests { // Generate step corresponding to MLOAD let mut step = ExecStep::new( &block.geth_trace.struct_logs[0], + 0, test_builder.block_ctx.gc, + 0, ); let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); diff --git a/bus-mapping/src/evm/opcodes/push.rs b/bus-mapping/src/evm/opcodes/push.rs index dd69d594f5c..780d9105b06 100644 --- a/bus-mapping/src/evm/opcodes/push.rs +++ b/bus-mapping/src/evm/opcodes/push.rs @@ -84,7 +84,9 @@ mod push_tests { { let mut step = ExecStep::new( &block.geth_trace.struct_logs[i], + 0, test_builder.block_ctx.gc, + 0, ); let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); diff --git a/bus-mapping/src/evm/opcodes/sload.rs b/bus-mapping/src/evm/opcodes/sload.rs index d24047568a4..f591aed736f 100644 --- a/bus-mapping/src/evm/opcodes/sload.rs +++ b/bus-mapping/src/evm/opcodes/sload.rs @@ -30,7 +30,7 @@ impl Opcode for Sload { let storage_value_read = step.storage.get_or_err(&stack_value_read)?; state.push_op(StorageOp::new( RW::READ, - state.tx_ctx.call_ctx().address, + state.call().address, stack_value_read, storage_value_read, storage_value_read, @@ -96,7 +96,9 @@ mod sload_tests { // Generate step corresponding to SLOAD let mut step = ExecStep::new( &block.geth_trace.struct_logs[0], + 0, test_builder.block_ctx.gc, + 0, ); let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); diff --git a/bus-mapping/src/evm/opcodes/stackonlyop.rs b/bus-mapping/src/evm/opcodes/stackonlyop.rs index e6a8040af33..fd1784a7b9c 100644 --- a/bus-mapping/src/evm/opcodes/stackonlyop.rs +++ b/bus-mapping/src/evm/opcodes/stackonlyop.rs @@ -85,7 +85,9 @@ mod stackonlyop_tests { // Generate step corresponding to NOT let mut step = ExecStep::new( &block.geth_trace.struct_logs[0], + 0, test_builder.block_ctx.gc, + 0, ); let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); @@ -149,7 +151,9 @@ mod stackonlyop_tests { // Generate step corresponding to ADD let mut step = ExecStep::new( &block.geth_trace.struct_logs[0], + 0, test_builder.block_ctx.gc, + 0, ); let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); @@ -227,7 +231,9 @@ mod stackonlyop_tests { // Generate step corresponding to ADDMOD let mut step = ExecStep::new( &block.geth_trace.struct_logs[0], + 0, test_builder.block_ctx.gc, + 0, ); let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); diff --git a/bus-mapping/src/evm/opcodes/swap.rs b/bus-mapping/src/evm/opcodes/swap.rs index e2212faed59..31578534d00 100644 --- a/bus-mapping/src/evm/opcodes/swap.rs +++ b/bus-mapping/src/evm/opcodes/swap.rs @@ -101,7 +101,9 @@ mod swap_tests { for (i, (a, b)) in [(6, 5), (5, 3), (3, 1)].iter().enumerate() { let mut step = ExecStep::new( &block.geth_trace.struct_logs[i], + 0, test_builder.block_ctx.gc, + 0, ); let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); diff --git a/bus-mapping/src/external_tracer.rs b/bus-mapping/src/external_tracer.rs index 17521d054e0..6060fbc0c9e 100644 --- a/bus-mapping/src/external_tracer.rs +++ b/bus-mapping/src/external_tracer.rs @@ -20,7 +20,7 @@ impl Transaction { /// Create Self from a web3 transaction pub fn from_eth_tx(tx: ð_types::Transaction) -> Self { Self { - origin: tx.from.unwrap(), + origin: tx.from, gas_limit: tx.gas, target: tx.to.unwrap(), } diff --git a/bus-mapping/src/lib.rs b/bus-mapping/src/lib.rs index fafd0a5e397..afdefa8d75a 100644 --- a/bus-mapping/src/lib.rs +++ b/bus-mapping/src/lib.rs @@ -226,6 +226,6 @@ pub mod eth_types; pub(crate) mod geth_errors; pub mod mock; pub mod rpc; -pub(crate) mod statedb; +pub(crate) mod state_db; pub use error::Error; pub use exec_trace::BlockConstants; diff --git a/bus-mapping/src/mock.rs b/bus-mapping/src/mock.rs index c753de0e050..8389a377d7e 100644 --- a/bus-mapping/src/mock.rs +++ b/bus-mapping/src/mock.rs @@ -1,7 +1,7 @@ //! Mock types and functions to generate mock data useful for tests use crate::address; use crate::bytecode::Bytecode; -use crate::eth_types::{self, Address, Bytes, Hash, Index, Word, H64, U64}; +use crate::eth_types::{self, Address, Bytes, Hash, Word, U64}; use crate::evm::Gas; use crate::external_tracer; use crate::BlockConstants; @@ -10,18 +10,18 @@ use crate::Error; /// Generate a new mock block with preloaded data, useful for tests. pub fn new_block() -> eth_types::Block<()> { eth_types::Block { - hash: Some(Hash::from([0u8; 32])), - parent_hash: Hash::from([0u8; 32]), - uncles_hash: Hash::from([0u8; 32]), - author: Address::from([0u8; 20]), - state_root: Hash::from([0u8; 32]), - transactions_root: Hash::from([0u8; 32]), - receipts_root: Hash::from([0u8; 32]), + hash: Some(Hash::zero()), + parent_hash: Hash::zero(), + uncles_hash: Hash::zero(), + author: Address::zero(), + state_root: Hash::zero(), + transactions_root: Hash::zero(), + receipts_root: Hash::zero(), number: Some(U64([123456u64])), gas_used: Word::from(15_000_000u64), gas_limit: Word::from(15_000_000u64), base_fee_per_gas: Some(Word::from(97u64)), - extra_data: Bytes(Vec::new()), + extra_data: Bytes::default(), logs_bloom: None, timestamp: Word::from(1633398551u64), difficulty: Word::from(0x200000u64), @@ -31,30 +31,32 @@ pub fn new_block() -> eth_types::Block<()> { transactions: Vec::new(), size: None, mix_hash: None, - nonce: Some(H64([0u8; 8])), + nonce: Some(U64::zero()), } } /// Generate a new mock transaction with preloaded data, useful for tests. pub fn new_tx(block: ð_types::Block) -> eth_types::Transaction { eth_types::Transaction { - hash: Hash::from([0u8; 32]), - nonce: Word::from([0u8; 32]), + hash: Hash::zero(), + nonce: Word::zero(), block_hash: block.hash, block_number: block.number, - transaction_index: Some(Index::from(0u64)), - from: Some(address!("0x00000000000000000000000000000000c014ba5e")), + transaction_index: Some(U64::zero()), + from: address!("0x00000000000000000000000000000000c014ba5e"), to: Some(Address::zero()), - value: Word::from([0u8; 32]), - gas_price: Word::from([0u8; 32]), + value: Word::zero(), + gas_price: Some(Word::zero()), gas: Word::from(1_000_000u64), - input: Bytes(Vec::new()), - v: Some(U64([0u64])), - r: Some(Word::from([0u8; 32])), - s: Some(Word::from([0u8; 32])), - raw: Some(Bytes(Vec::new())), - transaction_type: Some(U64([0u64])), + input: Bytes::default(), + v: U64::zero(), + r: Word::zero(), + s: Word::zero(), + transaction_type: Some(U64::zero()), access_list: None, + max_priority_fee_per_gas: Some(Word::zero()), + max_fee_per_gas: Some(Word::zero()), + chain_id: Some(Word::zero()), } } diff --git a/bus-mapping/src/operation.rs b/bus-mapping/src/operation.rs index cc6214b7eec..492991b148f 100644 --- a/bus-mapping/src/operation.rs +++ b/bus-mapping/src/operation.rs @@ -265,6 +265,8 @@ pub enum OpEnum { #[derive(Debug, Clone)] pub struct Operation { gc: GlobalCounter, + /// True when this Operation is a revert of its mirror + revert: bool, op: T, } @@ -294,7 +296,11 @@ impl Ord for Operation { impl Operation { /// Create a new Operation from an `op` with a `gc` pub fn new(gc: GlobalCounter, op: T) -> Self { - Self { gc, op } + Self { + gc, + revert: false, + op, + } } /// Return this `Operation` `gc` diff --git a/bus-mapping/src/statedb.rs b/bus-mapping/src/state_db.rs similarity index 97% rename from bus-mapping/src/statedb.rs rename to bus-mapping/src/state_db.rs index 270212f48e5..96143f7750d 100644 --- a/bus-mapping/src/statedb.rs +++ b/bus-mapping/src/state_db.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; /// Account of the Ethereum State Trie, which contains an in-memory key-value /// database that represents the Account Storage Trie. #[derive(Debug, PartialEq)] -pub(crate) struct Account { +pub struct Account { pub nonce: Word, pub balance: Word, pub storage: HashMap, @@ -24,12 +24,18 @@ impl Account { /// In-memory key-value database that represents the Ethereum State Trie. #[derive(Debug)] -pub(crate) struct StateDB { +pub struct StateDB { state: HashMap, acc_zero: Account, value_zero: Word, } +impl Default for StateDB { + fn default() -> Self { + Self::new() + } +} + impl StateDB { /// Create an empty Self pub fn new() -> Self { diff --git a/geth-utils/README.md b/geth-utils/README.md index 82deb759f2f..71b754408be 100644 --- a/geth-utils/README.md +++ b/geth-utils/README.md @@ -14,3 +14,16 @@ For [`./example/mstore_mload.go`](./example/mstore_mload.go) as an example, it d ```bash go run ./example/mstore_mload.go > ./mstore_mload.json ``` + +### Debuging + +The execution traces returned by geth omit some information like execution +errors in some situations. Moreover you may want to inspect some intermediate +values of the EVM execution for debugging purposes. + +Print debugging can be easily achieved by replacing the dependency of `go-ethereum` by a local copy of the repository. Just clone `go-ethereum` into a folder next to the `zkevm-circuits` repository, and uncomment the following line in `go.mod`: +``` +replace github.com/ethereum/go-ethereum => ../../go-ethereum +``` + +Now you can add print logs in your `go-ethereum` copy as necessary. diff --git a/geth-utils/go.mod b/geth-utils/go.mod index a522054ab66..0c9d73de9ab 100644 --- a/geth-utils/go.mod +++ b/geth-utils/go.mod @@ -6,3 +6,6 @@ require ( github.com/ethereum/go-ethereum v1.10.12 github.com/holiman/uint256 v1.2.0 ) + +// Uncomment for debugging +// replace github.com/ethereum/go-ethereum => ../../go-ethereum