diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index adc38e572b..75bdf703ab 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -33,4 +33,5 @@ jobs: - run: ./run.sh --steps "setup" - run: ./run.sh --steps "gendata" - run: ./run.sh --steps "tests" --tests "rpc" + - run: ./run.sh --steps "tests" --tests "circuit_input_builder" - run: ./run.sh --steps "cleanup" diff --git a/Cargo.lock b/Cargo.lock index 50d0b53f4a..7dc440cd37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -351,6 +351,7 @@ dependencies = [ "hex", "itertools", "lazy_static", + "log", "pairing_bn256", "pretty_assertions", "regex", @@ -1084,6 +1085,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "eth-keystore" version = "0.3.0" @@ -1806,6 +1820,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.16" @@ -1954,8 +1974,10 @@ name = "integration-tests" version = "0.1.0" dependencies = [ "bus-mapping", + "env_logger", "ethers", "lazy_static", + "log", "pretty_assertions", "serde", "serde_json", @@ -3350,6 +3372,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + [[package]] name = "textwrap" version = "0.11.0" diff --git a/bus-mapping/Cargo.toml b/bus-mapping/Cargo.toml index c2ce4070c0..bc71ee5e46 100644 --- a/bus-mapping/Cargo.toml +++ b/bus-mapping/Cargo.toml @@ -17,6 +17,7 @@ uint = "0.9.1" ethers-providers = "0.6" ethers-core = "0.6" regex = "1.5.4" +log = "0.4.14" [dev-dependencies] url = "2.2.2" diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index 1c46823028..eb5c837f8d 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -1,8 +1,8 @@ //! 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, ToAddress, ToBigEndian, Word, - H256, + self, Address, ChainConstants, GethExecStep, GethExecTrace, Hash, + ToAddress, ToBigEndian, Word, }; use crate::evm::{Gas, GasCost, GlobalCounter, OpcodeId, ProgramCounter}; use crate::exec_trace::OperationRef; @@ -11,7 +11,7 @@ use crate::operation::container::OperationContainer; use crate::operation::RW; use crate::operation::{Op, Operation}; use crate::state_db::StateDB; -use crate::{BlockConstants, Error}; +use crate::Error; use core::fmt::Debug; use ethers_core::utils::{get_contract_address, get_create2_address}; use std::collections::{hash_map::Entry, HashMap, HashSet}; @@ -170,18 +170,18 @@ impl BlockContext { #[derive(Debug)] pub struct Block { /// Constants associated to this block and the chain. - pub constants: BlockConstants, + pub constants: ChainConstants, /// Container of operations done in this block. pub container: OperationContainer, txs: Vec, - code: HashMap>, + code: HashMap>, } impl Block { /// Create a new block. pub fn new( _eth_block: ð_types::Block, - constants: BlockConstants, + constants: ChainConstants, ) -> Self { Self { constants, @@ -202,7 +202,7 @@ impl Block { } } -/// Type of a *CALL* Function. +/// Type of a *CALL*/CREATE* Function. #[derive(Debug, PartialEq)] pub enum CallKind { /// CALL @@ -253,7 +253,7 @@ pub struct Call { /// Address where this call is being executed pub address: Address, /// Code Hash - code_hash: H256, + code_hash: Hash, } impl Call { @@ -343,7 +343,7 @@ impl Transaction { /// Create a new Self. pub fn new(eth_tx: ð_types::Transaction) -> Self { let mut calls = Vec::new(); - let code_hash = H256::zero(); + let code_hash = Hash::zero(); if let Some(address) = eth_tx.to { calls.push(Call { kind: CallKind::Call, @@ -397,7 +397,7 @@ impl Transaction { ) -> usize { let is_static = kind == CallKind::StaticCall || self.calls[parent_index].is_static; - let code_hash = H256::zero(); + let code_hash = Hash::zero(); self.calls.push(Call { kind, is_static, @@ -495,6 +495,8 @@ impl<'a> CircuitInputStateRef<'a> { pub struct CircuitInputBuilder { /// StateDB key-value DB pub sdb: StateDB, + /// Map of account codes by code hash + pub codes: HashMap>, /// Block pub block: Block, /// Block Context @@ -505,12 +507,13 @@ impl<'a> CircuitInputBuilder { /// Create a new CircuitInputBuilder from the given `eth_block` and /// `constants`. pub fn new( - eth_block: eth_types::Block, - constants: BlockConstants, + eth_block: ð_types::Block, + constants: ChainConstants, ) -> Self { Self { sdb: StateDB::new(), - block: Block::new(ð_block, constants), + codes: HashMap::new(), + block: Block::new(eth_block, constants), block_ctx: BlockContext::new(), } } @@ -557,18 +560,16 @@ impl<'a> CircuitInputBuilder { &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) { if geth_step.depth + 1 == geth_next_step.depth { - // Handle *CALL* - // TODO: Set the proper address according to the call kind. - let address = Address::zero(); + // Handle *CALL*/CREATE* + let address = state_ref.call_address(geth_step)?; let kind = CallKind::try_from(geth_step.op)?; push_call(&mut tx, &mut tx_ctx, kind, address); } else if geth_step.depth - 1 == geth_next_step.depth { - // Handle *CALL* return + // Handle *CALL*/CREATE* return if tx_ctx.call_stack.len() == 1 { return Err(Error::InvalidGethExecStep( "handle_tx: call stack will be empty", @@ -585,6 +586,7 @@ impl<'a> CircuitInputBuilder { } } } + tx.steps.push(step); } self.block.txs.push(tx); Ok(()) @@ -642,6 +644,8 @@ pub fn get_create_init_code(step: &GethExecStep) -> Result<&[u8], Error> { } impl<'a> CircuitInputStateRef<'a> { + /// Return the contract address of a CREATE step. This is calculated by + /// inspecting the current address and its nonce from the StateDB. fn create_address(&self) -> Result { let sender = self.call().address; let (found, account) = self.sdb.get_account(&sender); @@ -651,6 +655,8 @@ impl<'a> CircuitInputStateRef<'a> { Ok(get_contract_address(sender, account.nonce)) } + /// Return the contract address of a CREATE2 step. This is calculated + /// deterministically from the arguments in the stack. fn create2_address(&self, step: &GethExecStep) -> Result { let salt = step.stack.nth_last(3)?; let init_code = get_create_init_code(step)?; @@ -661,6 +667,19 @@ 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), + }) + } + fn get_step_err( &self, step: &GethExecStep, @@ -749,7 +768,7 @@ impl<'a> CircuitInputStateRef<'a> { )); } - // The *CALL* code was not executed + // The *CALL*/CREATE* code was not executed let next_pc = next_step.map(|s| s.pc.0).unwrap_or(1); if matches!( step.op, @@ -799,7 +818,7 @@ impl<'a> CircuitInputStateRef<'a> { } return Err(Error::UnexpectedExecStepError( - "*CALL* code not executed", + "*CALL*/CREATE* code not executed", Box::new(step.clone()), )); } @@ -810,15 +829,29 @@ impl<'a> CircuitInputStateRef<'a> { /// State and Code Access with "keys/index" used in the access operation. #[derive(Debug, PartialEq)] -enum AccessValue { - Account { address: Address }, - Storage { address: Address, key: Word }, - Code { address: Address }, +pub enum AccessValue { + /// Account access + Account { + /// Account address + address: Address, + }, + /// Storage access + Storage { + /// Storage account address + address: Address, + /// Storage key + key: Word, + }, + /// Code access + Code { + /// Code address + address: Address, + }, } /// State Access caused by a transaction or an execution step #[derive(Debug, PartialEq)] -struct Access { +pub struct Access { step_index: Option, rw: RW, value: AccessValue, @@ -834,8 +867,8 @@ impl Access { } } -/// Given a trace and assuming that the first step is a *CALL* kind opcode, -/// return the result if found. +/// Given a trace and assuming that the first step is a *CALL*/CREATE* kind +/// opcode, return the result if found. fn get_call_result(trace: &[GethExecStep]) -> Option { let depth = trace[0].depth; trace[1..] @@ -847,9 +880,11 @@ fn get_call_result(trace: &[GethExecStep]) -> Option { /// State and Code Access set. #[derive(Debug, PartialEq)] -struct AccessSet { - state: HashMap>, - code: HashSet
, +pub struct AccessSet { + /// Set of accounts + pub state: HashMap>, + /// Set of accounts code + pub code: HashSet
, } impl From> for AccessSet { @@ -882,10 +917,21 @@ impl From> for AccessSet { } } +/// Source of the code in the EVM execution. +#[derive(Clone, Copy)] +pub enum CodeSource { + /// Code comes from a deployed contract at `Address`. + Address(Address), + /// Code comes from tx.data when tx.to == null. + Tx, + /// Code comes from Memory by a CREATE* opcode. + Memory, +} + /// Generate the State Access trace from the given trace. All state read/write /// accesses are reported, without distinguishing those that happen in revert /// sections. -fn gen_state_access_trace( +pub fn gen_state_access_trace( _block: ð_types::Block, tx: ð_types::Transaction, geth_trace: &GethExecTrace, @@ -893,14 +939,16 @@ fn gen_state_access_trace( use AccessValue::{Account, Code, Storage}; use RW::{READ, WRITE}; - let mut call_address_stack = vec![tx.from]; + let mut call_stack: Vec<(Address, CodeSource)> = Vec::new(); let mut accs = vec![Access::new(None, WRITE, Account { address: tx.from })]; if let Some(to) = tx.to { + call_stack.push((to, CodeSource::Address(to))); accs.push(Access::new(None, WRITE, Account { address: to })); // Code may be null if the account is not a contract accs.push(Access::new(None, READ, Code { address: to })); } else { let address = get_contract_address(tx.from, tx.nonce); + call_stack.push((address, CodeSource::Tx)); accs.push(Access::new(None, WRITE, Account { address })); accs.push(Access::new(None, WRITE, Code { address })); } @@ -908,26 +956,32 @@ fn gen_state_access_trace( for (index, step) in geth_trace.struct_logs.iter().enumerate() { let next_step = geth_trace.struct_logs.get(index + 1); let i = Some(index); - let sender = call_address_stack[call_address_stack.len() - 1]; + let (contract_address, code_source) = &call_stack[call_stack.len() - 1]; + let (contract_address, code_source) = (*contract_address, *code_source); match step.op { OpcodeId::SSTORE => { - let address = sender; + let address = contract_address; let key = step.stack.nth_last(0)?; accs.push(Access::new(i, WRITE, Storage { address, key })); } OpcodeId::SLOAD => { - let address = sender; + let address = contract_address; let key = step.stack.nth_last(0)?; accs.push(Access::new(i, READ, Storage { address, key })); } OpcodeId::SELFBALANCE => { - accs.push(Access::new(i, READ, Account { address: sender })); + let address = contract_address; + accs.push(Access::new(i, READ, Account { address })); } OpcodeId::CODESIZE => { - accs.push(Access::new(i, READ, Code { address: sender })); + if let CodeSource::Address(address) = code_source { + accs.push(Access::new(i, READ, Code { address })); + } } OpcodeId::CODECOPY => { - accs.push(Access::new(i, READ, Code { address: sender })); + if let CodeSource::Address(address) = code_source { + accs.push(Access::new(i, READ, Code { address })); + } } OpcodeId::BALANCE => { let address = step.stack.nth_last(0)?.to_address(); @@ -946,7 +1000,8 @@ fn gen_state_access_trace( accs.push(Access::new(i, READ, Code { address })); } OpcodeId::SELFDESTRUCT => { - accs.push(Access::new(i, WRITE, Account { address: sender })); + let address = contract_address; + accs.push(Access::new(i, WRITE, Account { address })); let address = step.stack.nth_last(0)?.to_address(); accs.push(Access::new(i, WRITE, Account { address })); } @@ -971,41 +1026,46 @@ fn gen_state_access_trace( } } OpcodeId::CALL => { - accs.push(Access::new(i, WRITE, Account { address: sender })); + let address = contract_address; + accs.push(Access::new(i, WRITE, Account { address })); + let address = step.stack.nth_last(1)?.to_address(); accs.push(Access::new(i, WRITE, Account { address })); accs.push(Access::new(i, READ, Code { address })); - call_address_stack.push(address); + call_stack.push((address, CodeSource::Address(address))); } OpcodeId::CALLCODE => { - accs.push(Access::new(i, WRITE, Account { address: sender })); + let address = contract_address; + accs.push(Access::new(i, WRITE, Account { address })); + let address = step.stack.nth_last(1)?.to_address(); accs.push(Access::new(i, WRITE, Account { address })); accs.push(Access::new(i, READ, Code { address })); - call_address_stack.push(address); + call_stack.push((address, CodeSource::Address(address))); } OpcodeId::DELEGATECALL => { let address = step.stack.nth_last(1)?.to_address(); accs.push(Access::new(i, READ, Code { address })); - call_address_stack.push(sender); + call_stack + .push((contract_address, CodeSource::Address(address))); } OpcodeId::STATICCALL => { let address = step.stack.nth_last(1)?.to_address(); accs.push(Access::new(i, READ, Code { address })); - call_address_stack.push(address); + call_stack.push((address, CodeSource::Address(address))); } _ => {} } if let Some(next_step) = next_step { - // return from a *CALL* + // return from a *CALL*/CREATE* if step.depth - 1 == next_step.depth { - if call_address_stack.len() == 1 { + if call_stack.len() == 1 { return Err(Error::InvalidGethExecStep( "gen_state_access_trace: call stack will be empty", Box::new(step.clone()), )); } - call_address_stack.pop().expect("call stack is empty"); + call_stack.pop().expect("call stack is empty"); } } } @@ -1043,8 +1103,8 @@ mod tracer_tests { fn new(block: &mock::BlockData, geth_step: &GethExecStep) -> Self { Self { builder: CircuitInputBuilder::new( - block.eth_block.clone(), - block.block_ctants.clone(), + &block.eth_block, + block.ctants.clone(), ), tx: Transaction::new(&block.eth_tx), tx_ctx: TransactionContext::new(&block.eth_tx), @@ -1191,7 +1251,7 @@ mod tracer_tests { balance: Word::from(555u64), /* same value as in * `mock::new_tracer_account` */ storage: HashMap::new(), - codeHash: H256::zero(), + code_hash: Hash::zero(), }, ); assert_eq!( @@ -1298,7 +1358,7 @@ mod tracer_tests { balance: Word::from(555u64), /* same value as in * `mock::new_tracer_account` */ storage: HashMap::new(), - codeHash: H256::zero(), + code_hash: Hash::zero(), }, ); builder.builder.sdb.set_account( @@ -1307,7 +1367,7 @@ mod tracer_tests { nonce: Word::zero(), balance: Word::zero(), storage: HashMap::new(), - codeHash: H256::zero(), + code_hash: Hash::zero(), }, ); assert_eq!( @@ -2219,7 +2279,7 @@ mod tracer_tests { nonce: Word::from(1), balance: Word::zero(), storage: HashMap::new(), - codeHash: H256::zero(), + code_hash: Hash::zero(), }, ); let addr = builder.state_ref().create_address().unwrap(); @@ -2289,7 +2349,7 @@ mod tracer_tests { Access { step_index: Some(7), rw: WRITE, - value: Account { address: ADDR_0 } + value: Account { address: *ADDR_A } }, Access { step_index: Some(7), diff --git a/bus-mapping/src/eth_types.rs b/bus-mapping/src/eth_types.rs index b8b1e44935..d618432001 100644 --- a/bus-mapping/src/eth_types.rs +++ b/bus-mapping/src/eth_types.rs @@ -123,7 +123,7 @@ impl ToAddress for U256 { } } -/// Ethereum Hash (160 bits). +/// Ethereum Hash (256 bits). pub type Hash = types::H256; impl ToWord for Address { @@ -142,6 +142,15 @@ impl ToScalar for Address { } } +/// Chain specific constants +#[derive(Debug, Clone)] +pub struct ChainConstants { + /// Coinbase + pub coinbase: Address, + /// 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/bus-mapping/src/evm.rs b/bus-mapping/src/evm.rs index e63fdace98..6025aa56ff 100644 --- a/bus-mapping/src/evm.rs +++ b/bus-mapping/src/evm.rs @@ -6,6 +6,7 @@ pub mod stack; pub mod storage; use serde::{Deserialize, Serialize}; +use std::fmt; pub use { memory::{Memory, MemoryAddress}, opcodes::{ids::OpcodeId, Opcode}, @@ -15,10 +16,16 @@ pub use { /// Wrapper type over `usize` which represents the program counter of the Evm. #[derive( - Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, + Clone, Copy, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, )] pub struct ProgramCounter(pub(crate) usize); +impl fmt::Debug for ProgramCounter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("0x{:06x}", self.0)) + } +} + impl From for usize { fn from(addr: ProgramCounter) -> usize { addr.0 @@ -50,9 +57,15 @@ impl ProgramCounter { /// [`Operation`](crate::operation::Operation). The purpose of the /// `GlobalCounter` is to enforce that each Opcode/Instruction and Operation is /// unique and just executed once. -#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] pub struct GlobalCounter(pub(crate) usize); +impl fmt::Debug for GlobalCounter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("{}", self.0)) + } +} + impl From for usize { fn from(addr: GlobalCounter) -> usize { addr.0 @@ -93,17 +106,29 @@ impl GlobalCounter { /// Defines the gas left to perate in a /// [`ExecStep`](crate::circuit_input_builder::ExecStep). #[derive( - Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize, + Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize, )] pub struct Gas(pub u64); +impl fmt::Debug for Gas { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("{}", self.0)) + } +} + /// Defines the gas consumed by an /// [`ExecStep`](crate::circuit_input_builder::ExecStep). #[derive( - Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize, + Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize, )] pub struct GasCost(pub(crate) u64); +impl fmt::Debug for GasCost { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("{}", self.0)) + } +} + impl GasCost { /// Constant cost for free step pub const ZERO: Self = Self(0); diff --git a/bus-mapping/src/evm/memory.rs b/bus-mapping/src/evm/memory.rs index 7da2ffed01..c4a7eb177e 100644 --- a/bus-mapping/src/evm/memory.rs +++ b/bus-mapping/src/evm/memory.rs @@ -9,9 +9,15 @@ use core::str::FromStr; use std::fmt; /// Represents a `MemoryAddress` of the EVM. -#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] pub struct MemoryAddress(pub(crate) usize); +impl fmt::Debug for MemoryAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("0x{:06x}", self.0)) + } +} + impl MemoryAddress { /// Returns the zero address for Memory targets. pub const fn zero() -> MemoryAddress { diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index 44a7f49e5e..8d6ef5391f 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -15,6 +15,7 @@ use crate::eth_types::GethExecStep; use crate::Error; use core::fmt::Debug; use ids::OpcodeId; +use log::warn; use self::push::Push; use dup::Dup; @@ -42,6 +43,13 @@ pub trait Opcode: Debug { ) -> Result<(), Error>; } +fn dummy_gen_associated_ops( + _state: &mut CircuitInputStateRef, + _next_steps: &[GethExecStep], +) -> Result<(), Error> { + Ok(()) +} + type FnGenAssociatedOps = fn( state: &mut CircuitInputStateRef, next_steps: &[GethExecStep], @@ -192,7 +200,12 @@ impl OpcodeId { // OpcodeId::STATICCALL => {}, // OpcodeId::REVERT => {}, // OpcodeId::SELFDESTRUCT => {}, - _ => unimplemented!(), + // _ => panic!("Opcode {:?} gen_associated_ops not implemented", + // self), + _ => { + warn!("Using dummy gen_associated_ops for opcode {:?}", self); + dummy_gen_associated_ops + } } } diff --git a/bus-mapping/src/evm/opcodes/dup.rs b/bus-mapping/src/evm/opcodes/dup.rs index a4a23f116d..0ad835af11 100644 --- a/bus-mapping/src/evm/opcodes/dup.rs +++ b/bus-mapping/src/evm/opcodes/dup.rs @@ -62,16 +62,12 @@ mod dup_tests { let block = mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); - let mut builder = CircuitInputBuilder::new( - block.eth_block.clone(), - block.block_ctants.clone(), - ); + let mut builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - let mut test_builder = CircuitInputBuilder::new( - block.eth_block, - block.block_ctants.clone(), - ); + let mut test_builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); let mut tx = Transaction::new(&block.eth_tx); let mut tx_ctx = TransactionContext::new(&block.eth_tx); diff --git a/bus-mapping/src/evm/opcodes/mload.rs b/bus-mapping/src/evm/opcodes/mload.rs index 17e6c42dae..31bcf3506a 100644 --- a/bus-mapping/src/evm/opcodes/mload.rs +++ b/bus-mapping/src/evm/opcodes/mload.rs @@ -1,6 +1,6 @@ use super::Opcode; use crate::circuit_input_builder::CircuitInputStateRef; -use crate::eth_types::{GethExecStep, ToBigEndian}; +use crate::eth_types::{GethExecStep, ToBigEndian, Word}; use crate::{ evm::MemoryAddress, operation::{MemoryOp, StackOp, RW}, @@ -33,7 +33,12 @@ impl Opcode for Mload { // Read the memory let mut mem_read_addr: MemoryAddress = stack_value_read.try_into()?; - let mem_read_value = steps[1].memory.read_word(mem_read_addr)?; + // Accesses to memory that hasn't been initialized are valid, and return + // 0. + let mem_read_value = steps[1] + .memory + .read_word(mem_read_addr) + .unwrap_or_else(|_| Word::zero()); // // First stack write @@ -84,16 +89,12 @@ mod mload_tests { let block = mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); - let mut builder = CircuitInputBuilder::new( - block.eth_block.clone(), - block.block_ctants.clone(), - ); + let mut builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - let mut test_builder = CircuitInputBuilder::new( - block.eth_block, - block.block_ctants.clone(), - ); + let mut test_builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); let mut tx = Transaction::new(&block.eth_tx); let mut tx_ctx = TransactionContext::new(&block.eth_tx); diff --git a/bus-mapping/src/evm/opcodes/mstore.rs b/bus-mapping/src/evm/opcodes/mstore.rs index 5ac6010a65..9d5a35bfd6 100644 --- a/bus-mapping/src/evm/opcodes/mstore.rs +++ b/bus-mapping/src/evm/opcodes/mstore.rs @@ -74,16 +74,12 @@ mod mstore_tests { let block = mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); - let mut builder = CircuitInputBuilder::new( - block.eth_block.clone(), - block.block_ctants.clone(), - ); + let mut builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - let mut test_builder = CircuitInputBuilder::new( - block.eth_block, - block.block_ctants.clone(), - ); + let mut test_builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); let mut tx = Transaction::new(&block.eth_tx); let mut tx_ctx = TransactionContext::new(&block.eth_tx); diff --git a/bus-mapping/src/evm/opcodes/pc.rs b/bus-mapping/src/evm/opcodes/pc.rs index 021cf758b2..96d3ef9025 100644 --- a/bus-mapping/src/evm/opcodes/pc.rs +++ b/bus-mapping/src/evm/opcodes/pc.rs @@ -57,16 +57,12 @@ mod pc_tests { let block = mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); - let mut builder = CircuitInputBuilder::new( - block.eth_block.clone(), - block.block_ctants.clone(), - ); + let mut builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - let mut test_builder = CircuitInputBuilder::new( - block.eth_block, - block.block_ctants.clone(), - ); + let mut test_builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); let mut tx = Transaction::new(&block.eth_tx); let mut tx_ctx = TransactionContext::new(&block.eth_tx); diff --git a/bus-mapping/src/evm/opcodes/push.rs b/bus-mapping/src/evm/opcodes/push.rs index 780d9105b0..f1502ae3da 100644 --- a/bus-mapping/src/evm/opcodes/push.rs +++ b/bus-mapping/src/evm/opcodes/push.rs @@ -59,16 +59,12 @@ mod push_tests { let block = mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); - let mut builder = CircuitInputBuilder::new( - block.eth_block.clone(), - block.block_ctants.clone(), - ); + let mut builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - let mut test_builder = CircuitInputBuilder::new( - block.eth_block, - block.block_ctants.clone(), - ); + let mut test_builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); let mut tx = Transaction::new(&block.eth_tx); let mut tx_ctx = TransactionContext::new(&block.eth_tx); diff --git a/bus-mapping/src/evm/opcodes/sload.rs b/bus-mapping/src/evm/opcodes/sload.rs index f591aed736..0ee05c6e09 100644 --- a/bus-mapping/src/evm/opcodes/sload.rs +++ b/bus-mapping/src/evm/opcodes/sload.rs @@ -80,16 +80,12 @@ mod sload_tests { let block = mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); - let mut builder = CircuitInputBuilder::new( - block.eth_block.clone(), - block.block_ctants.clone(), - ); + let mut builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - let mut test_builder = CircuitInputBuilder::new( - block.eth_block, - block.block_ctants.clone(), - ); + let mut test_builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); let mut tx = Transaction::new(&block.eth_tx); let mut tx_ctx = TransactionContext::new(&block.eth_tx); diff --git a/bus-mapping/src/evm/opcodes/stackonlyop.rs b/bus-mapping/src/evm/opcodes/stackonlyop.rs index fd1784a7b9..69c0706d81 100644 --- a/bus-mapping/src/evm/opcodes/stackonlyop.rs +++ b/bus-mapping/src/evm/opcodes/stackonlyop.rs @@ -69,16 +69,12 @@ mod stackonlyop_tests { let block = mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); - let mut builder = CircuitInputBuilder::new( - block.eth_block.clone(), - block.block_ctants.clone(), - ); + let mut builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - let mut test_builder = CircuitInputBuilder::new( - block.eth_block, - block.block_ctants.clone(), - ); + let mut test_builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); let mut tx = Transaction::new(&block.eth_tx); let mut tx_ctx = TransactionContext::new(&block.eth_tx); @@ -135,16 +131,12 @@ mod stackonlyop_tests { let block = mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); - let mut builder = CircuitInputBuilder::new( - block.eth_block.clone(), - block.block_ctants.clone(), - ); + let mut builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - let mut test_builder = CircuitInputBuilder::new( - block.eth_block, - block.block_ctants.clone(), - ); + let mut test_builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); let mut tx = Transaction::new(&block.eth_tx); let mut tx_ctx = TransactionContext::new(&block.eth_tx); @@ -215,16 +207,12 @@ mod stackonlyop_tests { let block = mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); - let mut builder = CircuitInputBuilder::new( - block.eth_block.clone(), - block.block_ctants.clone(), - ); + let mut builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - let mut test_builder = CircuitInputBuilder::new( - block.eth_block, - block.block_ctants.clone(), - ); + let mut test_builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); let mut tx = Transaction::new(&block.eth_tx); let mut tx_ctx = TransactionContext::new(&block.eth_tx); diff --git a/bus-mapping/src/evm/opcodes/swap.rs b/bus-mapping/src/evm/opcodes/swap.rs index 31578534d0..cc336eea5f 100644 --- a/bus-mapping/src/evm/opcodes/swap.rs +++ b/bus-mapping/src/evm/opcodes/swap.rs @@ -84,16 +84,12 @@ mod swap_tests { let block = mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); - let mut builder = CircuitInputBuilder::new( - block.eth_block.clone(), - block.block_ctants.clone(), - ); + let mut builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - let mut test_builder = CircuitInputBuilder::new( - block.eth_block, - block.block_ctants.clone(), - ); + let mut test_builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); let mut tx = Transaction::new(&block.eth_tx); let mut tx_ctx = TransactionContext::new(&block.eth_tx); diff --git a/bus-mapping/src/evm/stack.rs b/bus-mapping/src/evm/stack.rs index 1e28d35d43..15cdeeb28e 100644 --- a/bus-mapping/src/evm/stack.rs +++ b/bus-mapping/src/evm/stack.rs @@ -7,9 +7,15 @@ use std::fmt; /// Represents a `StackAddress` of the EVM. /// The address range goes `TOP -> DOWN (1024, 0]`. -#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] pub struct StackAddress(pub(crate) usize); +impl fmt::Debug for StackAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("{}", self.0)) + } +} + impl StackAddress { /// Generates a new StackAddress given a `usize`. pub const fn new(addr: usize) -> Self { diff --git a/bus-mapping/src/exec_trace.rs b/bus-mapping/src/exec_trace.rs index cd66be2c3b..22a5a1ac34 100644 --- a/bus-mapping/src/exec_trace.rs +++ b/bus-mapping/src/exec_trace.rs @@ -1,141 +1,27 @@ //! This module contains the logic for parsing and interacting with EVM //! execution traces. -use crate::eth_types::Address; -use crate::eth_types::U64; -use crate::eth_types::{Block, Hash, Word}; use crate::operation::Target; -use serde::Serialize; -use std::str::FromStr; +use std::fmt; -/// Definition of all of the constants related to an Ethereum block and -/// chain. -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] -pub struct BlockConstants { - hash: Hash, // Until we know how to deal with it - coinbase: Address, - timestamp: Word, - number: U64, // u64 - difficulty: Word, - gas_limit: Word, - chain_id: Word, - base_fee: Word, -} - -impl BlockConstants { - /// Generate a BlockConstants from an ethereum block, useful for testing. - pub fn from_eth_block( - block: &Block, - chain_id: &Word, - &coinbase: &Address, - ) -> Self { - Self { - hash: block.hash.unwrap(), - coinbase, - 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(), - } - } - - /// Generate a new mock BlockConstants, useful for testing. - pub fn mock() -> Self { - BlockConstants { - hash: Hash::from([0u8; 32]), - coinbase: Address::from_str( - "0x00000000000000000000000000000000c014ba5e", - ) - .unwrap(), - timestamp: Word::from(1633398551u64), - number: U64([123456u64]), - difficulty: Word::from(0x200000u64), - gas_limit: Word::from(15_000_000u64), - chain_id: Word::one(), - base_fee: Word::from(97u64), - } - } -} - -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, - } - } - #[inline] - /// Return the hash of a block. - pub fn hash(&self) -> &Hash { - &self.hash - } - - #[inline] - /// Return the coinbase of a block. - pub fn coinbase(&self) -> &Address { - &self.coinbase - } - - #[inline] - /// Return the timestamp of a block. - pub fn timestamp(&self) -> &Word { - &self.timestamp - } - - #[inline] - /// Return the block number. - pub fn number(&self) -> &U64 { - &self.number - } - - #[inline] - /// Return the difficulty of a block. - pub fn difficulty(&self) -> &Word { - &self.difficulty - } - - #[inline] - /// Return the gas_limit of a block. - pub fn gas_limit(&self) -> &Word { - &self.gas_limit - } - - #[inline] - /// Return the chain ID associated to a block. - pub fn chain_id(&self) -> &Word { - &self.chain_id - } - - #[inline] - /// Return the base fee of a block. - pub fn base_fee(&self) -> &Word { - &self.base_fee - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] /// The target and index of an `Operation` in the context of an /// `ExecutionTrace`. pub struct OperationRef(Target, usize); +impl fmt::Debug for OperationRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!( + "OperationRef{{ {}, {} }}", + match self.0 { + Target::Memory => "Memory", + Target::Stack => "Stack", + Target::Storage => "Storage", + }, + self.1 + )) + } +} + impl From<(Target, usize)> for OperationRef { fn from(op_ref_data: (Target, usize)) -> Self { match op_ref_data.0 { diff --git a/bus-mapping/src/external_tracer.rs b/bus-mapping/src/external_tracer.rs index 6060fbc0c9..b913e819e0 100644 --- a/bus-mapping/src/external_tracer.rs +++ b/bus-mapping/src/external_tracer.rs @@ -1,10 +1,69 @@ //! This module generates traces by connecting to an external tracer -use crate::eth_types::{self, Address, GethExecStep, Word}; -use crate::BlockConstants; +use crate::eth_types::{self, Address, Block, GethExecStep, Hash, Word, U64}; use crate::Error; use geth_utils; use serde::Serialize; +/// 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)] +pub struct BlockConstants { + hash: Hash, + coinbase: Address, + timestamp: Word, + number: U64, + difficulty: Word, + gas_limit: Word, + chain_id: Word, + base_fee: Word, +} + +impl BlockConstants { + /// Generate a BlockConstants from an ethereum block, useful for testing. + pub fn from_eth_block( + block: &Block, + chain_id: &Word, + &coinbase: &Address, + ) -> Self { + Self { + hash: block.hash.unwrap(), + coinbase, + 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(), + } + } +} + +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, + } + } +} + /// Definition of all of the constants related to an Ethereum transaction. #[derive(Debug, Clone, Serialize)] pub struct Transaction { diff --git a/bus-mapping/src/lib.rs b/bus-mapping/src/lib.rs index 80d3d1329a..66b98dfe4a 100644 --- a/bus-mapping/src/lib.rs +++ b/bus-mapping/src/lib.rs @@ -48,10 +48,12 @@ //! by the provided trace. //! //! ```rust -//! use bus_mapping::{BlockConstants, Error}; +//! use bus_mapping::Error; //! use bus_mapping::evm::Gas; //! use bus_mapping::mock; -//! use bus_mapping::eth_types::{self, Address, Word, Hash, U64, GethExecTrace, GethExecStep}; +//! use bus_mapping::eth_types::{ +//! self, Address, Word, Hash, U64, GethExecTrace, GethExecStep, ChainConstants +//! }; //! use bus_mapping::circuit_input_builder::CircuitInputBuilder; //! use pairing::arithmetic::FieldExt; //! @@ -103,23 +105,17 @@ //! ] //! "#; //! -//! let block_ctants = BlockConstants::new( -//! Hash::zero(), -//! Address::zero(), -//! Word::zero(), -//! U64::zero(), -//! Word::zero(), -//! Word::zero(), -//! Word::zero(), -//! Word::zero(), -//! ); +//! let ctants = ChainConstants{ +//! coinbase: Address::zero(), +//! 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 builder = -//! CircuitInputBuilder::new(eth_block, block_ctants); +//! CircuitInputBuilder::new(ð_block, ctants); //! //! let geth_steps: Vec = serde_json::from_str(input_trace).unwrap(); //! let geth_trace = GethExecTrace { @@ -226,6 +222,5 @@ pub mod eth_types; pub(crate) mod geth_errors; pub mod mock; pub mod rpc; -pub(crate) mod state_db; +pub 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 8389a377d7..008e821bba 100644 --- a/bus-mapping/src/mock.rs +++ b/bus-mapping/src/mock.rs @@ -1,11 +1,29 @@ //! 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, Word, U64}; +use crate::eth_types::{self, Address, Bytes, ChainConstants, Hash, Word, U64}; use crate::evm::Gas; use crate::external_tracer; -use crate::BlockConstants; +use crate::external_tracer::BlockConstants; use crate::Error; +use lazy_static::lazy_static; + +/// Mock chain ID +pub const CHAIN_ID: u64 = 1338; + +lazy_static! { + /// Mock coinbase value + pub static ref COINBASE: Address = + address!("0x00000000000000000000000000000000c014ba5e"); +} + +/// Generate a new mock chain constants, useful for tests. +pub fn new_chain_constants() -> eth_types::ChainConstants { + ChainConstants { + chain_id: CHAIN_ID, + coinbase: *COINBASE, + } +} /// Generate a new mock block with preloaded data, useful for tests. pub fn new_block() -> eth_types::Block<()> { @@ -43,7 +61,7 @@ pub fn new_tx(block: ð_types::Block) -> eth_types::Transaction { block_hash: block.hash, block_number: block.number, transaction_index: Some(U64::zero()), - from: address!("0x00000000000000000000000000000000c014ba5e"), + from: *COINBASE, to: Some(Address::zero()), value: Word::zero(), gas_price: Some(Word::zero()), @@ -56,7 +74,7 @@ pub fn new_tx(block: ð_types::Block) -> eth_types::Transaction { access_list: None, max_priority_fee_per_gas: Some(Word::zero()), max_fee_per_gas: Some(Word::zero()), - chain_id: Some(Word::zero()), + chain_id: Some(Word::from(CHAIN_ID)), } } @@ -68,8 +86,8 @@ pub struct BlockData { pub eth_block: eth_types::Block<()>, /// Transaction from geth pub eth_tx: eth_types::Transaction, - /// Block Constants - pub block_ctants: BlockConstants, + /// Constants + pub ctants: ChainConstants, /// Execution Trace from geth pub geth_trace: eth_types::GethExecTrace, } @@ -87,10 +105,11 @@ impl BlockData { let eth_block = new_block(); let mut eth_tx = new_tx(ð_block); eth_tx.gas = Word::from(gas.0); + let ctants = new_chain_constants(); let block_ctants = BlockConstants::from_eth_block( ð_block, - ð_types::Word::one(), - &address!("0x00000000000000000000000000000000c014ba5e"), + &Word::from(ctants.chain_id), + &ctants.coinbase, ); let tracer_tx = external_tracer::Transaction::from_eth_tx(ð_tx); let geth_trace = eth_types::GethExecTrace { @@ -106,7 +125,7 @@ impl BlockData { Ok(Self { eth_block, eth_tx, - block_ctants, + ctants, geth_trace, }) } @@ -177,11 +196,7 @@ impl BlockData { ) -> Self { let eth_block = new_block(); let eth_tx = new_tx(ð_block); - let block_ctants = BlockConstants::from_eth_block( - ð_block, - ð_types::Word::one(), - &crate::address!("0x00000000000000000000000000000000c014ba5e"), - ); + let ctants = new_chain_constants(); let geth_trace = eth_types::GethExecTrace { gas: Gas(eth_tx.gas.as_u64()), failed: false, @@ -190,7 +205,7 @@ impl BlockData { Self { eth_block, eth_tx, - block_ctants, + ctants, geth_trace, } } @@ -200,7 +215,7 @@ impl BlockData { /// tests. pub fn new_tracer_tx() -> external_tracer::Transaction { external_tracer::Transaction { - origin: address!("0x00000000000000000000000000000000c014ba5e"), + origin: *COINBASE, gas_limit: Word::from(1_000_000u64), target: Address::zero(), } diff --git a/bus-mapping/src/operation.rs b/bus-mapping/src/operation.rs index 492991b148..b4327590d3 100644 --- a/bus-mapping/src/operation.rs +++ b/bus-mapping/src/operation.rs @@ -10,6 +10,7 @@ pub use super::evm::{GlobalCounter, MemoryAddress, StackAddress}; use crate::eth_types::{Address, Word}; pub use container::OperationContainer; use core::cmp::Ordering; +use core::fmt; use core::fmt::Debug; /// Marker that defines whether an Operation performs a `READ` or a `WRITE`. @@ -57,13 +58,24 @@ pub trait Op: Eq + Ord { /// Represents a [`READ`](RW::READ)/[`WRITE`](RW::WRITE) into the memory implied /// by an specific [`OpcodeId`](crate::evm::opcodes::ids::OpcodeId) of the /// [`ExecStep`](crate::circuit_input_builder::ExecStep). -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] pub struct MemoryOp { rw: RW, addr: MemoryAddress, value: u8, } +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.addr, self.value + ))?; + f.write_str(" }") + } +} + impl MemoryOp { /// Create a new instance of a `MemoryOp` from it's components. pub fn new(rw: RW, addr: MemoryAddress, value: u8) -> MemoryOp { @@ -113,13 +125,24 @@ impl Ord for MemoryOp { /// Represents a [`READ`](RW::READ)/[`WRITE`](RW::WRITE) into the stack implied /// by an specific [`OpcodeId`](crate::evm::opcodes::ids::OpcodeId) of the /// [`ExecStep`](crate::circuit_input_builder::ExecStep). -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] pub struct StackOp { rw: RW, addr: StackAddress, value: Word, } +impl fmt::Debug for StackOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("StackOp { ")?; + f.write_fmt(format_args!( + "{:?}, addr: {:?}, value: {:?}", + self.rw, self.addr, self.value + ))?; + f.write_str(" }") + } +} + impl StackOp { /// Create a new instance of a `MemoryOp` from it's components. pub const fn new(rw: RW, addr: StackAddress, value: Word) -> StackOp { @@ -169,7 +192,7 @@ impl Ord for StackOp { /// Represents a [`READ`](RW::READ)/[`WRITE`](RW::WRITE) into the storage /// implied by an specific [`OpcodeId`](crate::evm::opcodes::ids::OpcodeId) of /// the [`ExecStep`](crate::circuit_input_builder::ExecStep). -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] pub struct StorageOp { rw: RW, address: Address, @@ -178,6 +201,17 @@ pub struct StorageOp { value_prev: Word, } +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: {:?}, val: {:?}", + self.rw, self.address, self.key, self.value_prev, self.value, + ))?; + f.write_str(" }") + } +} + impl StorageOp { /// Create a new instance of a `StorageOp` from it's components. pub const fn new( diff --git a/bus-mapping/src/rpc.rs b/bus-mapping/src/rpc.rs index 6fe87c773d..53645e7aed 100644 --- a/bus-mapping/src/rpc.rs +++ b/bus-mapping/src/rpc.rs @@ -6,8 +6,8 @@ use crate::eth_types::{ ResultGethExecTraces, Transaction, Word, U64, }; use crate::Error; +pub use ethers_core::types::BlockNumber; use ethers_providers::JsonRpcClient; -use serde::{Serialize, Serializer}; /// Serialize a type. /// @@ -18,40 +18,6 @@ pub fn serialize(t: &T) -> serde_json::Value { serde_json::to_value(t).expect("Types never fail to serialize.") } -/// Struct used to define the input that you want to provide to the -/// `eth_getBlockByNumber` call as it mixes numbers with string literals. -#[derive(Debug)] -pub enum BlockNumber { - /// Specific block number - Num(u64), - /// Earliest block - Earliest, - /// Latest block - Latest, - /// Pending block - Pending, -} - -impl From for BlockNumber { - fn from(num: u64) -> Self { - BlockNumber::Num(num) - } -} - -impl Serialize for BlockNumber { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - BlockNumber::Num(num) => U64::from(*num).serialize(serializer), - BlockNumber::Earliest => "earliest".serialize(serializer), - BlockNumber::Latest => "latest".serialize(serializer), - BlockNumber::Pending => "pending".serialize(serializer), - } - } -} - /// Placeholder structure designed to contain the methods that the BusMapping /// needs in order to enable Geth queries. pub struct GethClient(pub P); @@ -62,6 +28,24 @@ impl GethClient

{ Self(provider) } + /// Calls `eth_coinbase` via JSON-RPC returning the coinbase of the network. + pub async fn get_coinbase(&self) -> Result { + self.0 + .request("eth_coinbase", ()) + .await + .map_err(|e| Error::JSONRpcError(e.into())) + } + + /// Calls `eth_chainId` via JSON-RPC returning the chain id of the network. + pub async fn get_chain_id(&self) -> Result { + let net_id: U64 = self + .0 + .request("eth_chainId", ()) + .await + .map_err(|e| Error::JSONRpcError(e.into()))?; + Ok(net_id.as_u64()) + } + /// Calls `eth_getBlockByHash` via JSON-RPC returning a [`Block`] returning /// all the block information including it's transaction's details. pub async fn get_block_by_hash( @@ -124,7 +108,7 @@ impl GethClient

{ } /// Calls `eth_getCode` via JSON-RPC returning a contract code - pub async fn get_code_by_address( + pub async fn get_code( &self, contract_address: Address, block_num: BlockNumber, diff --git a/bus-mapping/src/state_db.rs b/bus-mapping/src/state_db.rs index 96143f7750..060be84300 100644 --- a/bus-mapping/src/state_db.rs +++ b/bus-mapping/src/state_db.rs @@ -1,14 +1,21 @@ -use crate::eth_types::{Address, Word, H256}; +//! Implementation of an in-memory key-value database to represent the +//! Ethereum State Trie. + +use crate::eth_types::{Address, Hash, Word}; 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 struct Account { + /// Nonce pub nonce: Word, + /// Balance pub balance: Word, + /// Storage key-value map pub storage: HashMap, - pub codeHash: H256, + /// Code hash + pub code_hash: Hash, } impl Account { @@ -17,7 +24,7 @@ impl Account { nonce: Word::zero(), balance: Word::zero(), storage: HashMap::new(), - codeHash: H256::zero(), + code_hash: Hash::zero(), } } } diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 06e6def79d..0ed4dfa505 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -14,6 +14,8 @@ bus-mapping = { path = "../bus-mapping"} tokio = { version = "1.13", features = ["macros", "rt-multi-thread"] } url = "2.2.2" pretty_assertions = "1.0.0" +log = "0.4.14" +env_logger = "0.9" [dev-dependencies] pretty_assertions = "1.0.0" @@ -21,3 +23,4 @@ pretty_assertions = "1.0.0" [features] default = [] rpc = [] +circuit_input_builder = [] diff --git a/integration-tests/run.sh b/integration-tests/run.sh index d68bb28a7b..996379dc92 100755 --- a/integration-tests/run.sh +++ b/integration-tests/run.sh @@ -3,7 +3,7 @@ set -e ARG_DEFAULT_SUDO= ARG_DEFAULT_STEPS="setup gendata tests cleanup" -ARG_DEFAULT_TESTS="rpc" +ARG_DEFAULT_TESTS="rpc circuit_input_builder" usage() { cat >&2 << EOF @@ -97,7 +97,7 @@ fi if [ -n "$STEP_TESTS" ]; then for testname in $ARG_TESTS; do echo "+ Running test group $testname" - cargo test --test $testname --features $testname + cargo test --test $testname --features $testname -- --nocapture done fi diff --git a/integration-tests/src/bin/gen_blockchain_data.rs b/integration-tests/src/bin/gen_blockchain_data.rs index ee59057edd..82f1368377 100644 --- a/integration-tests/src/bin/gen_blockchain_data.rs +++ b/integration-tests/src/bin/gen_blockchain_data.rs @@ -9,9 +9,10 @@ use ethers::{ solc::Solc, }; use integration_tests::{ - get_provider, get_wallet, CompiledContract, GenDataOutput, CONTRACTS, - CONTRACTS_PATH, + get_provider, get_wallet, log_init, CompiledContract, GenDataOutput, + CONTRACTS, CONTRACTS_PATH, }; +use log::{debug, info}; use std::collections::HashMap; use std::fs::File; use std::path::Path; @@ -28,7 +29,7 @@ where T: Tokenize, M: Middleware, { - println!("Deploying {}...", compiled.name); + info!("Deploying {}...", compiled.name); let factory = ContractFactory::new(compiled.abi.clone(), compiled.bin.clone(), prov); factory @@ -42,7 +43,9 @@ where #[tokio::main] async fn main() { + log_init(); // Compile contracts + info!("Compiling contracts..."); let mut contracts = HashMap::new(); for (name, contract_path) in CONTRACTS { let path_sol = Path::new(CONTRACTS_PATH).join(contract_path); @@ -90,11 +93,11 @@ async fn main() { loop { match prov.client_version().await { Ok(version) => { - println!("Geth online: {}", version); + info!("Geth online: {}", version); break; } Err(err) => { - println!("Geth not available: {:?}", err); + debug!("Geth not available: {:?}", err); sleep(Duration::from_millis(500)); } } @@ -114,9 +117,10 @@ async fn main() { let accounts = prov.get_accounts().await.expect("cannot get accounts"); let wallet0 = get_wallet(0); - println!("wallet0: {:x}", wallet0.address()); + info!("wallet0: {:x}", wallet0.address()); // Transfer funds to our account. + info!("Transferring funds from coinbase..."); let tx = TransactionRequest::new() .to(wallet0.address()) .value(WEI_IN_ETHER) // send 1 ETH diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index 0cc20a0cf6..6fae4635e1 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -3,8 +3,9 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(missing_docs)] -use bus_mapping::eth_types::Address; +use bus_mapping::eth_types::{Address, ChainConstants}; use bus_mapping::rpc::GethClient; +use env_logger::Env; use ethers::{ abi, core::k256::ecdsa::SigningKey, @@ -41,6 +42,12 @@ lazy_static! { }; } +/// Initialize log +pub fn log_init() { + env_logger::Builder::from_env(Env::default().default_filter_or("info")) + .init(); +} + /// Get the integration test [`GethClient`] pub fn get_client() -> GethClient { let transport = Http::new(Url::parse(&GETH0_URL).expect("invalid url")); @@ -53,6 +60,15 @@ 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 { + let client = get_client(); + ChainConstants { + coinbase: client.get_coinbase().await.unwrap(), + chain_id: client.get_chain_id().await.unwrap(), + } +} + const PHRASE: &str = "work man father plunge mystery proud hollow address reunion sauce theory bonus"; /// Get a wallet by index diff --git a/integration-tests/tests/circuit_input_builder.rs b/integration-tests/tests/circuit_input_builder.rs new file mode 100644 index 0000000000..caea5dc81d --- /dev/null +++ b/integration-tests/tests/circuit_input_builder.rs @@ -0,0 +1,109 @@ +#![cfg(feature = "circuit_input_builder")] + +use bus_mapping::circuit_input_builder::{ + gen_state_access_trace, AccessSet, CircuitInputBuilder, +}; +use bus_mapping::eth_types::{Word, H256}; +use bus_mapping::state_db; +use ethers::core::utils::keccak256; +use integration_tests::{ + get_chain_constants, get_client, log_init, GenDataOutput, +}; +use lazy_static::lazy_static; +use log::trace; +use std::collections::HashMap; + +lazy_static! { + pub static ref GEN_DATA: GenDataOutput = GenDataOutput::load(); +} + +/// This test builds the complete circuit inputs for the block where the Greeter +/// contract is deployed. +#[tokio::test] +async fn test_circuit_input_builder_block_a() { + log_init(); + let (block_num, _address) = GEN_DATA.deployments.get("Greeter").unwrap(); + let cli = get_client(); + + // 1. Query geth for Block, Txs and TxExecTraces + let eth_block = cli.get_block_by_number((*block_num).into()).await.unwrap(); + let geth_trace = cli + .trace_block_by_number((*block_num).into()) + .await + .unwrap(); + let tx_index = 0; + + // 2. Get State Accesses from TxExecTraces + let access_trace = gen_state_access_trace( + ð_block, + ð_block.transactions[tx_index], + &geth_trace[tx_index], + ) + .unwrap(); + trace!("AccessTrace:"); + for access in &access_trace { + trace!("{:#?}", access); + } + + let access_set = AccessSet::from(access_trace); + trace!("AccessSet: {:#?}", access_set); + + // 3. Query geth for all accounts, storage keys, and codes from Accesses + let mut proofs = Vec::new(); + for (address, key_set) in access_set.state { + let mut keys: Vec = key_set.iter().cloned().collect(); + keys.sort(); + let proof = cli + .get_proof(address, keys, (*block_num - 1).into()) + .await + .unwrap(); + proofs.push(proof); + } + let mut codes = HashMap::new(); + for address in access_set.code { + let code = cli + .get_code(address, (*block_num - 1).into()) + .await + .unwrap(); + codes.insert(address, code); + } + + let constants = get_chain_constants().await; + let mut builder = CircuitInputBuilder::new(ð_block, constants); + + // 4. Build a partial StateDB from step 3 + for proof in proofs { + let mut storage = HashMap::new(); + for storage_proof in proof.storage_proof { + storage.insert(storage_proof.key, storage_proof.value); + } + builder.sdb.set_account( + &proof.address, + state_db::Account { + nonce: proof.nonce, + balance: proof.balance, + storage, + code_hash: proof.code_hash, + }, + ) + } + trace!("StateDB: {:#?}", builder.sdb); + + for (_address, code) in codes { + let hash = H256(keccak256(&code)); + builder.codes.insert(hash, code.clone()); + } + + // 5. For each step in TxExecTraces, gen the associated ops and state + // circuit inputs + let block_geth_trace = cli + .trace_block_by_number((*block_num).into()) + .await + .unwrap(); + for (tx_index, tx) in eth_block.transactions.iter().enumerate() { + let geth_trace = &block_geth_trace[tx_index]; + builder.handle_tx(tx, geth_trace).unwrap(); + } + + trace!("CircuitInputBuilder: {:#?}", builder); +} diff --git a/integration-tests/tests/rpc.rs b/integration-tests/tests/rpc.rs index b2b76859a5..dc60aaa32e 100644 --- a/integration-tests/tests/rpc.rs +++ b/integration-tests/tests/rpc.rs @@ -2,7 +2,7 @@ use bus_mapping::eth_types::{StorageProof, Word}; use integration_tests::{ - get_client, CompiledContract, GenDataOutput, CONTRACTS_PATH, + get_client, CompiledContract, GenDataOutput, CHAIN_ID, CONTRACTS_PATH, }; use lazy_static::lazy_static; use pretty_assertions::assert_eq; @@ -13,6 +13,20 @@ lazy_static! { pub static ref GEN_DATA: GenDataOutput = GenDataOutput::load(); } +#[tokio::test] +async fn test_get_chain_id() { + let cli = get_client(); + let chain_id = cli.get_chain_id().await.unwrap(); + assert_eq!(CHAIN_ID, chain_id); +} + +#[tokio::test] +async fn test_get_coinbase() { + let cli = get_client(); + let coinbase = cli.get_coinbase().await.unwrap(); + assert_eq!(GEN_DATA.coinbase, coinbase); +} + #[tokio::test] async fn test_get_block_by_number_by_hash() { let cli = get_client(); @@ -55,10 +69,7 @@ async fn test_get_contract_code() { .expect("cannot deserialize json from file"); let cli = get_client(); - let code = cli - .get_code_by_address(*address, (*block_num).into()) - .await - .unwrap(); + let code = cli.get_code(*address, (*block_num).into()).await.unwrap(); assert_eq!(compiled.bin_runtime.to_vec(), code); } diff --git a/zkevm-circuits/src/state_circuit/state.rs b/zkevm-circuits/src/state_circuit/state.rs index 782a252092..fde1afb159 100644 --- a/zkevm-circuits/src/state_circuit/state.rs +++ b/zkevm-circuits/src/state_circuit/state.rs @@ -1971,10 +1971,8 @@ mod tests { let geth_steps: Vec = serde_json::from_str(input_trace).expect("Error on trace parsing"); let block = mock::BlockData::new_single_tx_geth_steps(geth_steps); - let mut builder = CircuitInputBuilder::new( - block.eth_block.clone(), - block.block_ctants.clone(), - ); + let mut builder = + CircuitInputBuilder::new(&block.eth_block, block.ctants.clone()); builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); let stack_ops = builder.block.container.sorted_stack();