diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index 30d1e9dbc9..5bfc7cf0d3 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -1,6 +1,6 @@ //! This module contains the CircuitInputBuilder, which is an object that takes //! types from geth / web3 and outputs the circuit inputs. -use crate::evm::opcodes::gen_associated_ops; +use crate::evm::opcodes::{gen_associated_ops, gen_begin_tx_ops, gen_end_tx_ops}; use crate::exec_trace::OperationRef; use crate::geth_errors::*; use crate::operation::container::OperationContainer; @@ -23,21 +23,33 @@ use ethers_providers::JsonRpcClient; pub enum OogError { /// Out of Gas for opcodes which have non-zero constant gas cost Constant, - /// Out of Gas for opcodes MLOAD, MSTORE, MSTORE8, CREATE, RETURN, REVERT, - /// which have pure memory expansion gas cost - PureMemory, + /// Out of Gas for MLOAD, MSTORE, MSTORE8, which have static memory + /// expansion gas cost + StaticMemoryExpansion, + /// Out of Gas for CREATE, RETURN, REVERT, which have dynamic memory + /// expansion gas cost + DynamicMemoryExpansion, + /// Out of Gas for CALLDATACOPY, CODECOPY, RETURNDATACOPY, which copy a + /// specified chunk of memory + MemoryCopy, + /// Out of Gas for BALANCE, EXTCODESIZE, EXTCODEHASH, which possibly touch + /// an extra account + AccountAccess, + /// Out of Gas for RETURN which has code storing gas cost when it's is + /// creation + CodeStore, + /// Out of Gas for LOG0, LOG1, LOG2, LOG3, LOG4 + Log, + /// Out of Gas for EXP + Exp, /// Out of Gas for SHA3 Sha3, - /// Out of Gas for CALLDATACOPY - CallDataCopy, - /// Out of Gas for CODECOPY - CodeCopy, /// Out of Gas for EXTCODECOPY ExtCodeCopy, - /// Out of Gas for RETURNDATACOPY - ReturnDataCopy, - /// Out of Gas for LOG - Log, + /// Out of Gas for SLOAD + Sload, + /// Out of Gas for SSTORE + Sstore, /// Out of Gas for CALL Call, /// Out of Gas for CALLCODE @@ -48,13 +60,13 @@ pub enum OogError { Create2, /// Out of Gas for STATICCALL StaticCall, + /// Out of Gas for SELFDESTRUCT + SelfDestruct, } /// EVM Execution Error #[derive(Debug, PartialEq)] pub enum ExecError { - /// Always returned for REVERT - Reverted, /// Invalid Opcode InvalidOpcode, /// For opcodes who push more than pop @@ -143,6 +155,24 @@ impl ExecStep { } } +impl Default for ExecStep { + fn default() -> Self { + Self { + op: OpcodeId::INVALID(0), + pc: ProgramCounter(0), + stack_size: 0, + memory_size: 0, + gas_left: Gas(0), + gas_cost: GasCost(0), + call_index: 0, + rwc: RWCounter(0), + swc: 0, + bus_mapping_instance: Vec::new(), + error: None, + } + } +} + /// Context of a [`Block`] which can mutate in a [`Transaction`]. #[derive(Debug)] pub struct BlockContext { @@ -264,6 +294,12 @@ impl CallKind { } } +impl Default for CallKind { + fn default() -> Self { + Self::Call + } +} + impl TryFrom for CallKind { type Error = Error; @@ -281,7 +317,7 @@ impl TryFrom for CallKind { } /// Circuit Input related to an Ethereum Call -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Call { /// Unique call identifier within the Block. pub call_id: usize, @@ -360,6 +396,8 @@ pub struct ReversionGroup { pub struct TransactionContext { /// Unique identifier of transaction of the block. The value is `index + 1`. id: usize, + /// Identifier if this transaction is last one of the block or not. + is_last_tx: bool, /// Call stack. calls: Vec, /// Call `is_success` indexed by `call_index`. @@ -374,7 +412,11 @@ pub struct TransactionContext { impl TransactionContext { /// Create a new Self. - pub fn new(eth_tx: ð_types::Transaction, geth_trace: &GethExecTrace) -> Result { + pub fn new( + eth_tx: ð_types::Transaction, + geth_trace: &GethExecTrace, + is_last_tx: bool, + ) -> Result { // Iterate over geth_trace to inspect and collect each call's is_success, which // is at the top of stack at the step after a call. let call_is_success = { @@ -404,6 +446,7 @@ impl TransactionContext { .ok_or(Error::EthTypeError(eth_types::Error::IncompleteBlock))? .as_u64() as usize + 1, + is_last_tx, call_is_success, calls: Vec::new(), reversion_groups: Vec::new(), @@ -418,17 +461,31 @@ impl TransactionContext { self.id } - /// Return the index of the current call (the last call in the call stack). - fn call_index(&self) -> usize { - self.calls.last().expect("calls should not be empty").index + /// Return is_last_tx of the this transaction. + pub fn is_last_tx(&self) -> bool { + self.is_last_tx } - fn call_ctx(&self) -> &CallContext { - self.calls.last().expect("calls should not be empty") + /// Return the index of the current call (the last call in the call stack). + fn call_index(&self) -> Result { + self.calls + .last() + .ok_or(Error::InvalidGethExecTrace( + "Call stack is empty but call is used", + )) + .map(|call| call.index) + } + + fn call_ctx(&self) -> Result<&CallContext, Error> { + self.calls.last().ok_or(Error::InvalidGethExecTrace( + "Call stack is empty but call is used", + )) } - fn call_ctx_mut(&mut self) -> &mut CallContext { - self.calls.last_mut().expect("calls should not be empty") + fn call_ctx_mut(&mut self) -> Result<&mut CallContext, Error> { + self.calls.last_mut().ok_or(Error::InvalidGethExecTrace( + "Call stack is empty but call is used", + )) } /// Push a new call context and its index into the call stack. @@ -512,46 +569,35 @@ impl Transaction { let code_hash = account.code_hash; Call { call_id, - caller_id: 0, kind: CallKind::Call, - is_static: false, is_root: true, is_persistent: is_success, is_success, - rw_counter_end_of_reversion: 0, caller_address: eth_tx.from, address, code_source: CodeSource::Address(address), code_hash, depth: 1, value: eth_tx.value, - call_data_offset: 0, call_data_length: eth_tx.input.as_ref().len() as u64, - return_data_offset: 0, - return_data_length: 0, + ..Default::default() } } else { // Contract creation let code_hash = code_db.insert(eth_tx.input.to_vec()); Call { call_id, - caller_id: 0, kind: CallKind::Create, - is_static: false, is_root: true, is_persistent: is_success, is_success, - rw_counter_end_of_reversion: 0, caller_address: eth_tx.from, address: get_contract_address(eth_tx.from, eth_tx.nonce), code_source: CodeSource::Tx, code_hash, depth: 1, value: eth_tx.value, - call_data_offset: 0, - call_data_length: 0, - return_data_offset: 0, - return_data_length: 0, + ..Default::default() } }; @@ -633,7 +679,7 @@ impl<'a> CircuitInputStateRef<'a> { /// This method should be used in `Opcode::gen_associated_ops` instead of /// `push_op` when the operation is `RW::WRITE` and it can be reverted (for /// example, a write `StorageOp`). - pub fn push_op_reversible(&mut self, rw: RW, op: T) { + pub fn push_op_reversible(&mut self, rw: RW, op: T) -> Result<(), Error> { let op_ref = self.block.container.insert(Operation::new_reversible( self.block_ctx.rwc.inc_pre(), rw, @@ -642,10 +688,10 @@ impl<'a> CircuitInputStateRef<'a> { self.step.bus_mapping_instance.push(op_ref); // Increase state_write_counter - self.call_ctx_mut().swc += 1; + self.call_ctx_mut()?.swc += 1; // Add the operation into reversible_ops if this call is not persistent - if !self.call().is_persistent { + if !self.call()?.is_persistent { self.tx_ctx .reversion_groups .last_mut() @@ -653,6 +699,8 @@ impl<'a> CircuitInputStateRef<'a> { .op_refs .push((self.tx.steps.len(), op_ref)); } + + Ok(()) } /// Push a [`MemoryOp`] into the [`OperationContainer`] with the next @@ -660,16 +708,15 @@ impl<'a> CircuitInputStateRef<'a> { /// the stored operation ([`OperationRef`]) inside the bus-mapping /// instance of the current [`ExecStep`]. Then increase the `block_ctx` /// [`RWCounter`] by one. - pub fn push_memory_op(&mut self, rw: RW, address: MemoryAddress, value: u8) { - let call_id = self.call().call_id; - self.push_op( - rw, - MemoryOp { - call_id, - address, - value, - }, - ); + pub fn push_memory_op( + &mut self, + rw: RW, + address: MemoryAddress, + value: u8, + ) -> Result<(), Error> { + let call_id = self.call()?.call_id; + self.push_op(rw, MemoryOp::new(call_id, address, value)); + Ok(()) } /// Push a [`StackOp`] into the [`OperationContainer`] with the next @@ -677,35 +724,38 @@ impl<'a> CircuitInputStateRef<'a> { /// the stored operation ([`OperationRef`]) inside the bus-mapping /// instance of the current [`ExecStep`]. Then increase the `block_ctx` /// [`RWCounter`] by one. - pub fn push_stack_op(&mut self, rw: RW, address: StackAddress, value: Word) { - let call_id = self.call().call_id; - self.push_op( - rw, - StackOp { - call_id, - address, - value, - }, - ); + pub fn push_stack_op( + &mut self, + rw: RW, + address: StackAddress, + value: Word, + ) -> Result<(), Error> { + let call_id = self.call()?.call_id; + self.push_op(rw, StackOp::new(call_id, address, value)); + Ok(()) } /// Reference to the current Call - pub fn call(&self) -> &Call { - &self.tx.calls[self.tx_ctx.call_index()] + pub fn call(&self) -> Result<&Call, Error> { + self.tx_ctx + .call_index() + .map(|call_idx| &self.tx.calls[call_idx]) } /// Mutable reference to the current Call - pub fn call_mut(&mut self) -> &mut Call { - &mut self.tx.calls[self.tx_ctx.call_index()] + pub fn call_mut(&mut self) -> Result<&mut Call, Error> { + self.tx_ctx + .call_index() + .map(|call_idx| &mut self.tx.calls[call_idx]) } /// Reference to the current CallContext - pub fn call_ctx(&self) -> &CallContext { + pub fn call_ctx(&self) -> Result<&CallContext, Error> { self.tx_ctx.call_ctx() } /// Mutable reference to the call CallContext - pub fn call_ctx_mut(&mut self) -> &mut CallContext { + pub fn call_ctx_mut(&mut self) -> Result<&mut CallContext, Error> { self.tx_ctx.call_ctx_mut() } @@ -714,18 +764,19 @@ impl<'a> CircuitInputStateRef<'a> { pub fn push_call(&mut self, call: Call) { let call_id = call.call_id; - self.tx_ctx.push_call_ctx(self.tx.calls.len()); + let call_idx = self.tx.calls.len(); + self.tx_ctx.push_call_ctx(call_idx); self.tx.push_call(call); self.block_ctx .call_map - .insert(call_id, (self.block.txs.len(), self.tx_ctx.call_index())); + .insert(call_id, (self.block.txs.len(), call_idx)); } /// 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 sender = self.call()?.address; let (found, account) = self.sdb.get_account(&sender); if !found { return Err(Error::AccountNotFound(sender)); @@ -739,12 +790,17 @@ impl<'a> CircuitInputStateRef<'a> { let salt = step.stack.nth_last(3)?; let init_code = get_create_init_code(step)?; Ok(get_create2_address( - self.call().address, + self.call()?.address, salt.to_be_bytes().to_vec(), init_code.to_vec(), )) } + /// Check if address is a precompiled or not. + pub fn is_precompiled(&self, address: &Address) -> bool { + address.0[0..19] == [0u8; 19] && (1..=9).contains(&address.0[19]) + } + /// Parse [`Call`] from a *CALL*/CREATE* step. pub fn parse_call(&mut self, step: &GethExecStep) -> Result { let is_success = *self @@ -753,31 +809,24 @@ impl<'a> CircuitInputStateRef<'a> { .get(self.tx.calls().len()) .unwrap(); let kind = CallKind::try_from(step.op)?; + let caller = self.call()?; let (caller_address, address, value) = match kind { CallKind::Call => ( - self.call().address, + caller.address, step.stack.nth_last(1)?.to_address(), step.stack.nth_last(2)?, ), - CallKind::CallCode => ( - self.call().address, - self.call().address, - step.stack.nth_last(2)?, - ), - CallKind::DelegateCall => (self.call().caller_address, self.call().address, 0.into()), + CallKind::CallCode => (caller.address, caller.address, step.stack.nth_last(2)?), + CallKind::DelegateCall => (caller.caller_address, caller.address, 0.into()), CallKind::StaticCall => ( - self.call().address, + caller.address, step.stack.nth_last(1)?.to_address(), 0.into(), ), - CallKind::Create => ( - self.call().address, - self.create_address()?, - step.stack.last()?, - ), + CallKind::Create => (caller.address, self.create_address()?, step.stack.last()?), CallKind::Create2 => ( - self.call().address, + caller.address, self.create2_address(step)?, step.stack.last()?, ), @@ -804,30 +853,22 @@ impl<'a> CircuitInputStateRef<'a> { } }; - let get_memory_offset_length = |nth: usize| -> Result<_, Error> { - let offset = step.stack.nth_last(nth)?; - let length = step.stack.nth_last(nth + 1)?; - if length.is_zero() { - return Ok((0, 0)); - } - Ok((offset.low_u64(), length.low_u64())) - }; let (call_data_offset, call_data_length, return_data_offset, return_data_length) = match kind { CallKind::Call | CallKind::CallCode => { - let call_data = get_memory_offset_length(3)?; - let return_data = get_memory_offset_length(5)?; + let call_data = get_call_memory_offset_length(step, 3)?; + let return_data = get_call_memory_offset_length(step, 5)?; (call_data.0, call_data.1, return_data.0, return_data.1) } CallKind::DelegateCall | CallKind::StaticCall => { - let call_data = get_memory_offset_length(2)?; - let return_data = get_memory_offset_length(4)?; + let call_data = get_call_memory_offset_length(step, 2)?; + let return_data = get_call_memory_offset_length(step, 4)?; (call_data.0, call_data.1, return_data.0, return_data.1) } CallKind::Create | CallKind::Create2 => (0, 0, 0, 0), }; - let caller = self.call(); + let caller = self.call()?; let call = Call { call_id: self.block_ctx.rwc.0, caller_id: caller.call_id, @@ -991,7 +1032,7 @@ impl<'a> CircuitInputStateRef<'a> { /// previous call context. pub fn handle_return(&mut self) -> Result<(), Error> { // Handle reversion if this call doens't end successfully - if !self.call().is_success { + if !self.call()?.is_success { self.handle_reversion(); } @@ -1013,8 +1054,13 @@ impl<'a> CircuitInputStateRef<'a> { return Ok(Some(ExecError::InvalidOpcode)); } - // When last step is RETURN or STOP there's no error. - if matches!(next_step, None) && matches!(step.op, OpcodeId::RETURN | OpcodeId::STOP) { + // When last step has opcodes that halt, there's no error. + if matches!(next_step, None) + && matches!( + step.op, + OpcodeId::STOP | OpcodeId::RETURN | OpcodeId::REVERT | OpcodeId::SELFDESTRUCT + ) + { return Ok(None); } @@ -1023,16 +1069,16 @@ impl<'a> CircuitInputStateRef<'a> { .map(|s| s.stack.last().unwrap_or_else(|_| Word::zero())) .unwrap_or_else(Word::zero); + let call = self.call()?; + // Return from a call with a failure if step.depth != next_depth && next_result.is_zero() { if !matches!(step.op, OpcodeId::RETURN) { // Without calling RETURN - return Ok(Some(match step.op { - OpcodeId::REVERT => ExecError::Reverted, - OpcodeId::JUMP | OpcodeId::JUMPI => ExecError::InvalidJump, - OpcodeId::RETURNDATACOPY => ExecError::ReturnDataOutOfBounds, - // Break write protection (CALL with value will be handled - // below) + return Ok(match step.op { + OpcodeId::JUMP | OpcodeId::JUMPI => Some(ExecError::InvalidJump), + OpcodeId::RETURNDATACOPY => Some(ExecError::ReturnDataOutOfBounds), + // Break write protection (CALL with value will be handled below) OpcodeId::SSTORE | OpcodeId::CREATE | OpcodeId::CREATE2 @@ -1042,21 +1088,19 @@ impl<'a> CircuitInputStateRef<'a> { | OpcodeId::LOG2 | OpcodeId::LOG3 | OpcodeId::LOG4 - if self.call().is_static => + if call.is_static => { - ExecError::WriteProtection + Some(ExecError::WriteProtection) } + OpcodeId::REVERT => None, _ => { return Err(Error::UnexpectedExecStepError( "call failure without return", step.clone(), )); } - })); + }); } else { - // Calling RETURN - let call = self.call(); - // Return from a {CREATE, CREATE2} with a failure, via RETURN if !call.is_root && call.is_create() { let offset = step.stack.nth_last(0)?; @@ -1125,11 +1169,11 @@ impl<'a> CircuitInputStateRef<'a> { }; // CALL with value - if matches!(step.op, OpcodeId::CALL) && !value.is_zero() && self.call().is_static { + if matches!(step.op, OpcodeId::CALL) && !value.is_zero() && self.call()?.is_static { return Ok(Some(ExecError::WriteProtection)); } - let sender = self.call().address; + let sender = self.call()?.address; let (found, account) = self.sdb.get_account(&sender); if !found { return Err(Error::AccountNotFound(sender)); @@ -1246,7 +1290,7 @@ impl<'a> CircuitInputBuilder { } /// Iterate over all generated CallContext RwCounterEndOfReversion - /// operations and set the correct value. This is required because when we + /// operations and set the correct value. This is required because when we /// generate the RwCounterEndOfReversion operation in /// `gen_associated_ops` we don't know yet which value it will take, /// so we put a placeholder; so we do it here after the values are known. @@ -1275,7 +1319,7 @@ impl<'a> CircuitInputBuilder { ) -> Result<(), Error> { for (tx_index, tx) in eth_block.transactions.iter().enumerate() { let geth_trace = &geth_traces[tx_index]; - self.handle_tx(tx, geth_trace)?; + self.handle_tx(tx, geth_trace, tx_index + 1 == eth_block.transactions.len())?; } self.set_value_ops_call_context_rwc_eor(); Ok(()) @@ -1285,21 +1329,31 @@ impl<'a> CircuitInputBuilder { /// all the associated operations. Each operation is registered in /// `self.block.container`, and each step stores the [`OperationRef`] to /// each of the generated operations. - pub fn handle_tx( + fn handle_tx( &mut self, eth_tx: ð_types::Transaction, geth_trace: &GethExecTrace, + is_last_tx: bool, ) -> Result<(), Error> { let mut tx = self.new_tx(eth_tx, !geth_trace.failed)?; - let mut tx_ctx = TransactionContext::new(eth_tx, geth_trace)?; + let mut tx_ctx = TransactionContext::new(eth_tx, geth_trace, is_last_tx)?; + + // TODO: Move into gen_associated_steps with + // - execution_state: BeginTx + // - op: None + // Generate BeginTx step + let mut step = ExecStep { + gas_left: Gas(tx.gas), + rwc: self.block_ctx.rwc, + ..Default::default() + }; + gen_begin_tx_ops(&mut self.state_ref(&mut tx, &mut tx_ctx, &mut step))?; + tx.steps.push(step); for (index, geth_step) in geth_trace.struct_logs.iter().enumerate() { - let mut step = ExecStep::new( - geth_step, - tx_ctx.call_index(), - self.block_ctx.rwc, - tx_ctx.call_ctx().swc, - ); + let call_ctx = tx_ctx.call_ctx()?; + let mut step = + ExecStep::new(geth_step, call_ctx.index, self.block_ctx.rwc, call_ctx.swc); let mut state_ref = self.state_ref(&mut tx, &mut tx_ctx, &mut step); gen_associated_ops( @@ -1311,7 +1365,30 @@ impl<'a> CircuitInputBuilder { tx.steps.push(step); } + // TODO: Move into gen_associated_steps with + // - execution_state: EndTx + // - op: None + // Generate EndTx step + let step_prev = tx + .steps + .last() + .expect("steps should have at least one BeginTx step"); + let mut step = ExecStep { + gas_left: Gas(step_prev.gas_left.0 - step_prev.gas_cost.0), + rwc: self.block_ctx.rwc, + // For tx without code execution + swc: if let Some(call_ctx) = tx_ctx.calls.last() { + call_ctx.swc + } else { + 0 + }, + ..Default::default() + }; + gen_end_tx_ops(&mut self.state_ref(&mut tx, &mut tx_ctx, &mut step))?; + tx.steps.push(step); + self.block.txs.push(tx); + self.sdb.clear_access_list_and_refund(); Ok(()) } @@ -1321,23 +1398,32 @@ fn get_step_reported_error(op: &OpcodeId, error: &str) -> ExecError { if error == GETH_ERR_OUT_OF_GAS || error == GETH_ERR_GAS_UINT_OVERFLOW { // NOTE: We report a GasUintOverflow error as an OutOfGas error let oog_err = match op { + OpcodeId::MLOAD | OpcodeId::MSTORE | OpcodeId::MSTORE8 => { + OogError::StaticMemoryExpansion + } + OpcodeId::CREATE | OpcodeId::RETURN | OpcodeId::REVERT => { + OogError::DynamicMemoryExpansion + } + OpcodeId::CALLDATACOPY | OpcodeId::CODECOPY | OpcodeId::RETURNDATACOPY => { + OogError::MemoryCopy + } + OpcodeId::BALANCE | OpcodeId::EXTCODESIZE | OpcodeId::EXTCODEHASH => { + OogError::AccountAccess + } + OpcodeId::LOG0 | OpcodeId::LOG1 | OpcodeId::LOG2 | OpcodeId::LOG3 | OpcodeId::LOG4 => { + OogError::Log + } + OpcodeId::EXP => OogError::Exp, OpcodeId::SHA3 => OogError::Sha3, - OpcodeId::CALLDATACOPY => OogError::CallDataCopy, - OpcodeId::CODECOPY => OogError::CodeCopy, OpcodeId::EXTCODECOPY => OogError::ExtCodeCopy, - OpcodeId::RETURNDATACOPY => OogError::ReturnDataCopy, - OpcodeId::LOG0 | OpcodeId::LOG2 | OpcodeId::LOG3 | OpcodeId::LOG4 => OogError::Log, + OpcodeId::SLOAD => OogError::Sload, + OpcodeId::SSTORE => OogError::Sstore, OpcodeId::CALL => OogError::Call, OpcodeId::CALLCODE => OogError::CallCode, OpcodeId::DELEGATECALL => OogError::DelegateCall, OpcodeId::CREATE2 => OogError::Create2, OpcodeId::STATICCALL => OogError::StaticCall, - OpcodeId::MLOAD - | OpcodeId::MSTORE - | OpcodeId::MSTORE8 - | OpcodeId::CREATE - | OpcodeId::RETURN - | OpcodeId::REVERT => OogError::PureMemory, + OpcodeId::SELFDESTRUCT => OogError::SelfDestruct, _ => OogError::Constant, }; ExecError::OutOfGas(oog_err) @@ -1349,14 +1435,24 @@ fn get_step_reported_error(op: &OpcodeId, error: &str) -> ExecError { panic!("Unknown GethExecStep.error: {}", error); } } - -/// Retreive the init_code from memory for {CREATE, CREATE2} +/// Retrieve 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]) } +/// Retrieve the memory offset and length of call. +pub fn get_call_memory_offset_length(step: &GethExecStep, nth: usize) -> Result<(u64, u64), Error> { + let offset = step.stack.nth_last(nth)?; + let length = step.stack.nth_last(nth + 1)?; + if length.is_zero() { + Ok((0, 0)) + } else { + Ok((offset.low_u64(), length.low_u64())) + } +} + /// State and Code Access with "keys/index" used in the access operation. #[derive(Debug, PartialEq)] pub enum AccessValue { @@ -1456,6 +1552,12 @@ pub enum CodeSource { Memory, } +impl Default for CodeSource { + fn default() -> Self { + Self::Tx + } +} + /// Generate the State Access trace from the given trace. All state read/write /// accesses are reported, without distinguishing those that happen in revert /// sections. @@ -1768,14 +1870,17 @@ mod tracer_tests { fn new(geth_data: &GethData, geth_step: &GethExecStep) -> Self { let block = crate::mock::BlockData::new_from_geth_data(geth_data.clone()); let mut builder = block.new_circuit_input_builder(); - let tx = builder.new_tx(&block.eth_tx, true).unwrap(); + let tx = builder + .new_tx(&block.eth_block.transactions[0], true) + .unwrap(); let tx_ctx = TransactionContext::new( - &block.eth_tx, + &block.eth_block.transactions[0], &GethExecTrace { gas: Gas(0), failed: false, struct_logs: vec![geth_step.clone()], }, + false, ) .unwrap(); Self { @@ -1860,18 +1965,17 @@ mod tracer_tests { }; let block = mock::new_single_tx_trace_code_gas(&code, Gas(1_000_000_000_000_000u64)).unwrap(); - let struct_logs = &block.geth_trace.struct_logs; + let struct_logs = &block.geth_traces[0].struct_logs; // get last CALL - let (index, step) = block - .geth_trace + let (index, step) = block.geth_traces[0] .struct_logs .iter() .enumerate() .rev() .find(|(_, s)| s.op == OpcodeId::CALL) .unwrap(); - let next_step = block.geth_trace.struct_logs.get(index + 1); + let next_step = block.geth_traces[0].struct_logs.get(index + 1); assert_eq!(step.op, OpcodeId::CALL); assert_eq!(step.depth, 1025u16); assert_eq!(step.error, None); @@ -1915,15 +2019,14 @@ mod tracer_tests { let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get last CALL - let (index, step) = block - .geth_trace + let (index, step) = block.geth_traces[0] .struct_logs .iter() .enumerate() .rev() .find(|(_, s)| s.op == OpcodeId::CALL) .unwrap(); - let next_step = block.geth_trace.struct_logs.get(index + 1); + let next_step = block.geth_traces[0].struct_logs.get(index + 1); assert_eq!(step.error, None); assert_eq!(next_step.unwrap().op, OpcodeId::PUSH2); assert_eq!(next_step.unwrap().stack, Stack(vec![Word::from(0)])); // success = 0 @@ -2006,26 +2109,24 @@ mod tracer_tests { let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get last CREATE2 - let (index, step) = block - .geth_trace + let (index, step) = block.geth_traces[0] .struct_logs .iter() .enumerate() .rev() .find(|(_, s)| s.op == OpcodeId::CREATE2) .unwrap(); - let next_step = block.geth_trace.struct_logs.get(index + 1); + let next_step = block.geth_traces[0].struct_logs.get(index + 1); let create2_address: Address = { // get first RETURN - let (index, _) = block - .geth_trace + let (index, _) = block.geth_traces[0] .struct_logs .iter() .enumerate() .find(|(_, s)| s.op == OpcodeId::RETURN) .unwrap(); - let next_step = block.geth_trace.struct_logs.get(index + 1); + let next_step = block.geth_traces[0].struct_logs.get(index + 1); let addr_word = next_step.unwrap().stack.last().unwrap(); addr_word.to_address() }; @@ -2125,15 +2226,14 @@ mod tracer_tests { let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get last RETURN - let (index, step) = block - .geth_trace + let (index, step) = block.geth_traces[0] .struct_logs .iter() .enumerate() .rev() .find(|(_, s)| s.op == OpcodeId::RETURN) .unwrap(); - let next_step = block.geth_trace.struct_logs.get(index + 1); + let next_step = block.geth_traces[0].struct_logs.get(index + 1); assert!(check_err_code_store_out_of_gas(step, next_step)); let mut builder = CircuitInputBuilderTx::new(&block, step); @@ -2210,15 +2310,14 @@ mod tracer_tests { let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get last RETURN - let (index, step) = block - .geth_trace + let (index, step) = block.geth_traces[0] .struct_logs .iter() .enumerate() .rev() .find(|(_, s)| s.op == OpcodeId::RETURN) .unwrap(); - let next_step = block.geth_trace.struct_logs.get(index + 1); + let next_step = block.geth_traces[0].struct_logs.get(index + 1); assert!(check_err_invalid_code(step, next_step)); let mut builder = CircuitInputBuilderTx::new(&block, step); @@ -2296,15 +2395,14 @@ mod tracer_tests { let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get last RETURN - let (index, step) = block - .geth_trace + let (index, step) = block.geth_traces[0] .struct_logs .iter() .enumerate() .rev() .find(|(_, s)| s.op == OpcodeId::RETURN) .unwrap(); - let next_step = block.geth_trace.struct_logs.get(index + 1); + let next_step = block.geth_traces[0].struct_logs.get(index + 1); assert!(check_err_max_code_size_exceeded(step, next_step)); let mut builder = CircuitInputBuilderTx::new(&block, step); @@ -2369,14 +2467,13 @@ mod tracer_tests { let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get first STOP - let (index, step) = block - .geth_trace + let (index, step) = block.geth_traces[0] .struct_logs .iter() .enumerate() .find(|(_, s)| s.op == OpcodeId::STOP) .unwrap(); - let next_step = block.geth_trace.struct_logs.get(index + 1); + let next_step = block.geth_traces[0].struct_logs.get(index + 1); let mut builder = CircuitInputBuilderTx::new(&block, step); // Set up call context at STOP @@ -2420,9 +2517,9 @@ mod tracer_tests { }; let index = 1; // JUMP let block = mock::new_single_tx_trace_code(&code).unwrap(); - assert_eq!(block.geth_trace.struct_logs.len(), 2); - let step = &block.geth_trace.struct_logs[index]; - let next_step = block.geth_trace.struct_logs.get(index + 1); + assert_eq!(block.geth_traces[0].struct_logs.len(), 2); + let step = &block.geth_traces[0].struct_logs[index]; + let next_step = block.geth_traces[0].struct_logs.get(index + 1); assert!(check_err_invalid_jump(step, next_step)); let mut builder = CircuitInputBuilderTx::new(&block, step); @@ -2447,8 +2544,8 @@ mod tracer_tests { }; let index = 8; // JUMP let block = mock::new_single_tx_trace_code_2(&code_a, &code).unwrap(); - let step = &block.geth_trace.struct_logs[index]; - let next_step = block.geth_trace.struct_logs.get(index + 1); + let step = &block.geth_traces[0].struct_logs[index]; + let next_step = block.geth_traces[0].struct_logs.get(index + 1); assert!(check_err_invalid_jump(step, next_step)); let mut builder = CircuitInputBuilderTx::new(&block, step); @@ -2478,15 +2575,15 @@ mod tracer_tests { }; let index = 2; // REVERT let block = mock::new_single_tx_trace_code(&code).unwrap(); - assert_eq!(block.geth_trace.struct_logs.len(), 3); - let step = &block.geth_trace.struct_logs[index]; - let next_step = block.geth_trace.struct_logs.get(index + 1); + assert_eq!(block.geth_traces[0].struct_logs.len(), 3); + let step = &block.geth_traces[0].struct_logs[index]; + let next_step = block.geth_traces[0].struct_logs.get(index + 1); assert!(check_err_execution_reverted(step, next_step)); let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( builder.state_ref().get_step_err(step, next_step).unwrap(), - Some(ExecError::Reverted) + None ); // With CALL @@ -2506,14 +2603,14 @@ mod tracer_tests { }; let index = 10; // REVERT let block = mock::new_single_tx_trace_code_2(&code_a, &code).unwrap(); - let step = &block.geth_trace.struct_logs[index]; - let next_step = block.geth_trace.struct_logs.get(index + 1); + let step = &block.geth_traces[0].struct_logs[index]; + let next_step = block.geth_traces[0].struct_logs.get(index + 1); assert!(check_err_execution_reverted(step, next_step)); let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( builder.state_ref().get_step_err(step, next_step).unwrap(), - Some(ExecError::Reverted) + None ); } @@ -2543,8 +2640,8 @@ mod tracer_tests { }; let index = 10; // STOP let block = mock::new_single_tx_trace_code_2(&code_a, &code).unwrap(); - let step = &block.geth_trace.struct_logs[index]; - let next_step = block.geth_trace.struct_logs.get(index + 1); + let step = &block.geth_traces[0].struct_logs[index]; + let next_step = block.geth_traces[0].struct_logs.get(index + 1); let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( @@ -2596,15 +2693,14 @@ mod tracer_tests { let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get last RETURNDATACOPY - let (index, step) = block - .geth_trace + let (index, step) = block.geth_traces[0] .struct_logs .iter() .enumerate() .rev() .find(|(_, s)| s.op == OpcodeId::RETURNDATACOPY) .unwrap(); - let next_step = block.geth_trace.struct_logs.get(index + 1); + let next_step = block.geth_traces[0].struct_logs.get(index + 1); assert!(check_err_return_data_out_of_bounds(step, next_step)); let mut builder = CircuitInputBuilderTx::new(&block, step); @@ -2631,15 +2727,15 @@ mod tracer_tests { let block = mock::new_single_tx_trace_code(&code).unwrap(); let index = 2; // MSTORE - let step = &block.geth_trace.struct_logs[index]; - let next_step = block.geth_trace.struct_logs.get(index + 1); + let step = &block.geth_traces[0].struct_logs[index]; + let next_step = block.geth_traces[0].struct_logs.get(index + 1); assert_eq!(step.op, OpcodeId::MSTORE); assert_eq!(step.error, Some(GETH_ERR_GAS_UINT_OVERFLOW.to_string())); let mut builder = CircuitInputBuilderTx::new(&block, step); assert_eq!( builder.state_ref().get_step_err(step, next_step).unwrap(), - Some(ExecError::OutOfGas(OogError::PureMemory)) + Some(ExecError::OutOfGas(OogError::StaticMemoryExpansion)) ); } @@ -2651,9 +2747,9 @@ mod tracer_tests { code.write(0x0f); let block = mock::new_single_tx_trace_code(&code).unwrap(); - let index = block.geth_trace.struct_logs.len() - 1; // 0x0f - let step = &block.geth_trace.struct_logs[index]; - let next_step = block.geth_trace.struct_logs.get(index + 1); + let index = block.geth_traces[0].struct_logs.len() - 1; // 0x0f + let step = &block.geth_traces[0].struct_logs[index]; + let next_step = block.geth_traces[0].struct_logs.get(index + 1); assert_eq!(step.op, OpcodeId::INVALID(0x0f)); let mut builder = CircuitInputBuilderTx::new(&block, step); @@ -2687,8 +2783,8 @@ mod tracer_tests { let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); let index = 9; // SSTORE - let step = &block.geth_trace.struct_logs[index]; - let next_step = block.geth_trace.struct_logs.get(index + 1); + let step = &block.geth_traces[0].struct_logs[index]; + let next_step = block.geth_traces[0].struct_logs.get(index + 1); assert_eq!(step.op, OpcodeId::SSTORE); let mut builder = CircuitInputBuilderTx::new(&block, step); @@ -2729,7 +2825,7 @@ mod tracer_tests { PUSH1(0x2) }; let block = mock::new_single_tx_trace_code_gas(&code, Gas(21004)).unwrap(); - let struct_logs = block.geth_trace.struct_logs; + let struct_logs = &block.geth_traces[0].struct_logs; assert_eq!(struct_logs[1].error, Some(GETH_ERR_OUT_OF_GAS.to_string())); } @@ -2743,9 +2839,9 @@ mod tracer_tests { } let block = mock::new_single_tx_trace_code(&code).unwrap(); - let index = block.geth_trace.struct_logs.len() - 1; // PUSH2 - let step = &block.geth_trace.struct_logs[index]; - let next_step = block.geth_trace.struct_logs.get(index + 1); + let index = block.geth_traces[0].struct_logs.len() - 1; // PUSH2 + let step = &block.geth_traces[0].struct_logs[index]; + let next_step = block.geth_traces[0].struct_logs.get(index + 1); assert_eq!( step.error, Some(format!("{} 1024 (1023)", GETH_ERR_STACK_OVERFLOW)) @@ -2767,8 +2863,8 @@ mod tracer_tests { let block = mock::new_single_tx_trace_code(&code).unwrap(); let index = 0; // SWAP5 - let step = &block.geth_trace.struct_logs[index]; - let next_step = block.geth_trace.struct_logs.get(index + 1); + let step = &block.geth_traces[0].struct_logs[index]; + let next_step = block.geth_traces[0].struct_logs.get(index + 1); assert_eq!( step.error, Some(format!("{} (0 <=> 6)", GETH_ERR_STACK_UNDERFLOW)) @@ -2838,19 +2934,17 @@ mod tracer_tests { let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get RETURN - let (index_return, _) = block - .geth_trace + let (index_return, _) = block.geth_traces[0] .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 next_step_return = block.geth_traces[0].struct_logs.get(index_return + 1); let addr_expect = next_step_return.unwrap().stack.last().unwrap(); // get CREATE2 - let step_create2 = block - .geth_trace + let step_create2 = block.geth_traces[0] .struct_logs .iter() .find(|s| s.op == OpcodeId::CREATE2) @@ -2922,20 +3016,18 @@ mod tracer_tests { let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); // get last RETURN - let (index_return, _) = block - .geth_trace + let (index_return, _) = block.geth_traces[0] .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 next_step_return = block.geth_traces[0].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 + let step_create = block.geth_traces[0] .struct_logs .iter() .rev() @@ -2991,8 +3083,12 @@ mod tracer_tests { PUSH3(0xbb) }; let block = mock::new_single_tx_trace_code_2(&code_a, &code_b).unwrap(); - let access_trace = - gen_state_access_trace(&block.eth_block, &block.eth_tx, &block.geth_trace).unwrap(); + let access_trace = gen_state_access_trace( + &block.eth_block, + &block.eth_block.transactions[0], + &block.geth_traces[0], + ) + .unwrap(); assert_eq!( access_trace, diff --git a/bus-mapping/src/error.rs b/bus-mapping/src/error.rs index 7386f1e02d..2e10604536 100644 --- a/bus-mapping/src/error.rs +++ b/bus-mapping/src/error.rs @@ -20,6 +20,9 @@ pub enum Error { StorageKeyNotFound(Address, Word), /// Unable to figure out error at a [`GethExecStep`] UnexpectedExecStepError(&'static str, GethExecStep), + /// Invalid [`eth_types::GethExecTrace`] due to an invalid/unexpected value + /// in it. + InvalidGethExecTrace(&'static str), /// Invalid [`GethExecStep`] due to an invalid/unexpected value in it. InvalidGethExecStep(&'static str, GethExecStep), /// Eth type related error. diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index 3c4bb4fb6d..d68743c213 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -1,48 +1,38 @@ //! Definition of each opcode of the EVM. -use crate::circuit_input_builder::CircuitInputStateRef; -use crate::Error; +use crate::{ + circuit_input_builder::CircuitInputStateRef, + evm::OpcodeId, + operation::{ + AccountField, AccountOp, CallContextField, CallContextOp, TxAccessListAccountOp, + TxRefundOp, RW, + }, + Error, +}; use core::fmt::Debug; -use eth_types::GethExecStep; +use eth_types::{ + evm_types::{GasCost, MAX_REFUND_QUOTIENT_OF_GAS_USED}, + GethExecStep, ToWord, +}; +use log::warn; mod calldatasize; mod caller; mod callvalue; -mod coinbase; mod dup; -mod gas; -mod jump; -mod jumpdest; -mod jumpi; mod mload; -mod msize; mod mstore; -mod number; -mod pc; -mod pop; -mod push; mod selfbalance; mod sload; mod stackonlyop; mod stop; mod swap; -mod timestamp; -use crate::evm::OpcodeId; -use log::warn; -use self::push::Push; use calldatasize::Calldatasize; use caller::Caller; use callvalue::Callvalue; use dup::Dup; -use gas::Gas; -use jump::Jump; -use jumpdest::Jumpdest; -use jumpi::Jumpi; use mload::Mload; -use msize::Msize; use mstore::Mstore; -use pc::Pc; -use pop::Pop; use selfbalance::Selfbalance; use sload::Sload; use stackonlyop::StackOnlyOpcode; @@ -77,31 +67,31 @@ type FnGenAssociatedOps = fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { match opcode_id { OpcodeId::STOP => Stop::gen_associated_ops, - OpcodeId::ADD => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::MUL => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::SUB => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::DIV => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::SDIV => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::MOD => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::SMOD => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::ADDMOD => StackOnlyOpcode::<3>::gen_associated_ops, - OpcodeId::MULMOD => StackOnlyOpcode::<3>::gen_associated_ops, - OpcodeId::EXP => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::SIGNEXTEND => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::LT => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::GT => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::SLT => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::SGT => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::EQ => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::ISZERO => StackOnlyOpcode::<1>::gen_associated_ops, - OpcodeId::AND => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::OR => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::XOR => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::NOT => StackOnlyOpcode::<1>::gen_associated_ops, - OpcodeId::BYTE => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::SHL => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::SHR => StackOnlyOpcode::<2>::gen_associated_ops, - OpcodeId::SAR => StackOnlyOpcode::<2>::gen_associated_ops, + OpcodeId::ADD => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::MUL => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::SUB => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::DIV => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::SDIV => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::MOD => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::SMOD => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::ADDMOD => StackOnlyOpcode::<3, 1>::gen_associated_ops, + OpcodeId::MULMOD => StackOnlyOpcode::<3, 1>::gen_associated_ops, + OpcodeId::EXP => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::SIGNEXTEND => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::LT => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::GT => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::SLT => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::SGT => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::EQ => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::ISZERO => StackOnlyOpcode::<1, 1>::gen_associated_ops, + OpcodeId::AND => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::OR => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::XOR => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::NOT => StackOnlyOpcode::<1, 1>::gen_associated_ops, + OpcodeId::BYTE => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::SHL => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::SHR => StackOnlyOpcode::<2, 1>::gen_associated_ops, + OpcodeId::SAR => StackOnlyOpcode::<2, 1>::gen_associated_ops, // OpcodeId::SHA3 => {}, // OpcodeId::ADDRESS => {}, // OpcodeId::BALANCE => {}, @@ -109,7 +99,7 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { OpcodeId::CALLER => Caller::gen_associated_ops, OpcodeId::CALLVALUE => Callvalue::gen_associated_ops, OpcodeId::CALLDATASIZE => Calldatasize::gen_associated_ops, - OpcodeId::CALLDATALOAD => StackOnlyOpcode::<1>::gen_associated_ops, + OpcodeId::CALLDATALOAD => StackOnlyOpcode::<1, 1>::gen_associated_ops, // OpcodeId::CALLDATACOPY => {}, // OpcodeId::CODESIZE => {}, // OpcodeId::CODECOPY => {}, @@ -120,58 +110,58 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { // OpcodeId::RETURNDATACOPY => {}, // OpcodeId::EXTCODEHASH => {}, // OpcodeId::BLOCKHASH => {}, - OpcodeId::COINBASE => StackOnlyOpcode::<0>::gen_associated_ops, - OpcodeId::TIMESTAMP => StackOnlyOpcode::<0>::gen_associated_ops, - OpcodeId::NUMBER => StackOnlyOpcode::<0>::gen_associated_ops, + OpcodeId::COINBASE => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::TIMESTAMP => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::NUMBER => StackOnlyOpcode::<0, 1>::gen_associated_ops, // OpcodeId::DIFFICULTY => {}, // OpcodeId::GASLIMIT => {}, // OpcodeId::CHAINID => {}, OpcodeId::SELFBALANCE => Selfbalance::gen_associated_ops, // OpcodeId::BASEFEE => {}, - OpcodeId::POP => Pop::gen_associated_ops, + OpcodeId::POP => StackOnlyOpcode::<1, 0>::gen_associated_ops, OpcodeId::MLOAD => Mload::gen_associated_ops, OpcodeId::MSTORE => Mstore::::gen_associated_ops, OpcodeId::MSTORE8 => Mstore::::gen_associated_ops, OpcodeId::SLOAD => Sload::gen_associated_ops, // OpcodeId::SSTORE => {}, - OpcodeId::JUMP => Jump::gen_associated_ops, - OpcodeId::JUMPI => Jumpi::gen_associated_ops, - OpcodeId::PC => Pc::gen_associated_ops, - OpcodeId::MSIZE => Msize::gen_associated_ops, - OpcodeId::GAS => Gas::gen_associated_ops, - OpcodeId::JUMPDEST => Jumpdest::gen_associated_ops, - OpcodeId::PUSH1 => Push::<1>::gen_associated_ops, - OpcodeId::PUSH2 => Push::<2>::gen_associated_ops, - OpcodeId::PUSH3 => Push::<3>::gen_associated_ops, - OpcodeId::PUSH4 => Push::<4>::gen_associated_ops, - OpcodeId::PUSH5 => Push::<5>::gen_associated_ops, - OpcodeId::PUSH6 => Push::<6>::gen_associated_ops, - OpcodeId::PUSH7 => Push::<7>::gen_associated_ops, - OpcodeId::PUSH8 => Push::<8>::gen_associated_ops, - OpcodeId::PUSH9 => Push::<9>::gen_associated_ops, - OpcodeId::PUSH10 => Push::<10>::gen_associated_ops, - OpcodeId::PUSH11 => Push::<11>::gen_associated_ops, - OpcodeId::PUSH12 => Push::<12>::gen_associated_ops, - OpcodeId::PUSH13 => Push::<13>::gen_associated_ops, - OpcodeId::PUSH14 => Push::<14>::gen_associated_ops, - OpcodeId::PUSH15 => Push::<15>::gen_associated_ops, - OpcodeId::PUSH16 => Push::<16>::gen_associated_ops, - OpcodeId::PUSH17 => Push::<17>::gen_associated_ops, - OpcodeId::PUSH18 => Push::<18>::gen_associated_ops, - OpcodeId::PUSH19 => Push::<19>::gen_associated_ops, - OpcodeId::PUSH20 => Push::<20>::gen_associated_ops, - OpcodeId::PUSH21 => Push::<21>::gen_associated_ops, - OpcodeId::PUSH22 => Push::<22>::gen_associated_ops, - OpcodeId::PUSH23 => Push::<23>::gen_associated_ops, - OpcodeId::PUSH24 => Push::<24>::gen_associated_ops, - OpcodeId::PUSH25 => Push::<25>::gen_associated_ops, - OpcodeId::PUSH26 => Push::<26>::gen_associated_ops, - OpcodeId::PUSH27 => Push::<27>::gen_associated_ops, - OpcodeId::PUSH28 => Push::<28>::gen_associated_ops, - OpcodeId::PUSH29 => Push::<29>::gen_associated_ops, - OpcodeId::PUSH30 => Push::<30>::gen_associated_ops, - OpcodeId::PUSH31 => Push::<31>::gen_associated_ops, - OpcodeId::PUSH32 => Push::<32>::gen_associated_ops, + OpcodeId::JUMP => StackOnlyOpcode::<1, 0>::gen_associated_ops, + OpcodeId::JUMPI => StackOnlyOpcode::<2, 0>::gen_associated_ops, + OpcodeId::PC => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::MSIZE => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::GAS => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::JUMPDEST => dummy_gen_associated_ops, + OpcodeId::PUSH1 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH2 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH3 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH4 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH5 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH6 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH7 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH8 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH9 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH10 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH11 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH12 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH13 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH14 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH15 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH16 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH17 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH18 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH19 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH20 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH21 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH22 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH23 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH24 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH25 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH26 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH27 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH28 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH29 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH30 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH31 => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::PUSH32 => StackOnlyOpcode::<0, 1>::gen_associated_ops, OpcodeId::DUP1 => Dup::<1>::gen_associated_ops, OpcodeId::DUP2 => Dup::<2>::gen_associated_ops, OpcodeId::DUP3 => Dup::<3>::gen_associated_ops, @@ -212,11 +202,13 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { // OpcodeId::CREATE => {}, // OpcodeId::CALL => {}, // OpcodeId::CALLCODE => {}, - // OpcodeId::RETURN => {}, + // TODO: Handle RETURN by its own gen_associated_ops. + OpcodeId::RETURN => Stop::gen_associated_ops, // OpcodeId::DELEGATECALL => {}, // OpcodeId::CREATE2 => {}, // OpcodeId::STATICCALL => {}, - // OpcodeId::REVERT => {}, + // TODO: Handle REVERT by its own gen_associated_ops. + OpcodeId::REVERT => Stop::gen_associated_ops, // OpcodeId::SELFDESTRUCT => {}, // _ => panic!("Opcode {:?} gen_associated_ops not implemented", // self), @@ -237,3 +229,220 @@ pub fn gen_associated_ops( let fn_gen_associated_ops = fn_gen_associated_ops(opcode_id); fn_gen_associated_ops(state, next_steps) } + +pub fn gen_begin_tx_ops(state: &mut CircuitInputStateRef) -> Result<(), Error> { + let call = state.call()?.clone(); + + for (field, value) in [ + (CallContextField::TxId, state.tx_ctx.id().into()), + ( + CallContextField::RwCounterEndOfReversion, + call.rw_counter_end_of_reversion.into(), + ), + ( + CallContextField::IsPersistent, + (call.is_persistent as usize).into(), + ), + ] { + state.push_op( + RW::READ, + CallContextOp { + call_id: call.call_id, + field, + value, + }, + ); + } + + let caller_address = call.caller_address; + let nonce_prev = state.sdb.increase_nonce(&caller_address); + state.push_op( + RW::WRITE, + AccountOp { + address: caller_address, + field: AccountField::Nonce, + value: (nonce_prev + 1).into(), + value_prev: (nonce_prev).into(), + }, + ); + + for address in [call.caller_address, call.address] { + state.sdb.add_account_to_access_list(address); + state.push_op( + RW::WRITE, + TxAccessListAccountOp { + tx_id: state.tx_ctx.id(), + address, + value: true, + value_prev: false, + }, + ); + } + + let call_data_gas_cost = state + .tx + .input + .iter() + .fold(0, |acc, byte| acc + if *byte == 0 { 4 } else { 16 }); + let intrinsic_gas_cost = if state.tx.is_create() { + GasCost::CREATION_TX.as_u64() + } else { + GasCost::TX.as_u64() + } + call_data_gas_cost; + state.step.gas_cost = GasCost(intrinsic_gas_cost); + + let (found, caller_account) = state.sdb.get_account_mut(&call.caller_address); + if !found { + return Err(Error::AccountNotFound(call.caller_address)); + } + let caller_balance_prev = caller_account.balance; + let caller_balance = caller_account.balance - call.value - state.tx.gas_price * state.tx.gas; + state.push_op_reversible( + RW::WRITE, + AccountOp { + address: call.caller_address, + field: AccountField::Balance, + value: caller_balance, + value_prev: caller_balance_prev, + }, + )?; + + let (found, callee_account) = state.sdb.get_account_mut(&call.address); + if !found { + return Err(Error::AccountNotFound(call.address)); + } + let callee_balance_prev = callee_account.balance; + let callee_balance = callee_account.balance + call.value; + let code_hash = callee_account.code_hash; + state.push_op_reversible( + RW::WRITE, + AccountOp { + address: call.address, + field: AccountField::Balance, + value: callee_balance, + value_prev: callee_balance_prev, + }, + )?; + + if call.is_create() { + unimplemented!("Creation transaction is not yet implemented") + } else if state.is_precompiled(&call.address) { + unimplemented!("Call to precompiled is not yet implemented") + } else { + state.push_op( + RW::READ, + AccountOp { + address: call.address, + field: AccountField::CodeHash, + value: code_hash.to_word(), + value_prev: code_hash.to_word(), + }, + ); + } + + for (field, value) in [ + (CallContextField::Depth, call.depth.into()), + ( + CallContextField::CallerAddress, + call.caller_address.to_word(), + ), + (CallContextField::CalleeAddress, call.address.to_word()), + ( + CallContextField::CallDataOffset, + call.call_data_offset.into(), + ), + ( + CallContextField::CallDataLength, + call.call_data_length.into(), + ), + (CallContextField::Value, call.value), + (CallContextField::IsStatic, (call.is_static as usize).into()), + (CallContextField::LastCalleeId, 0.into()), + (CallContextField::LastCalleeReturnDataOffset, 0.into()), + (CallContextField::LastCalleeReturnDataLength, 0.into()), + ] { + state.push_op( + RW::READ, + CallContextOp { + call_id: call.call_id, + field, + value, + }, + ); + } + + Ok(()) +} + +pub fn gen_end_tx_ops(state: &mut CircuitInputStateRef) -> Result<(), Error> { + let call = state.tx.calls()[0].clone(); + + state.push_op( + RW::READ, + CallContextOp { + call_id: call.call_id, + field: CallContextField::TxId, + value: state.tx_ctx.id().into(), + }, + ); + + let refund = state.sdb.refund(); + state.push_op( + RW::READ, + TxRefundOp { + tx_id: state.tx_ctx.id(), + value: refund, + value_prev: refund, + }, + ); + + let effective_refund = + refund.min((state.tx.gas - state.step.gas_left.0) / MAX_REFUND_QUOTIENT_OF_GAS_USED as u64); + let (found, caller_account) = state.sdb.get_account_mut(&call.caller_address); + if !found { + return Err(Error::AccountNotFound(call.caller_address)); + } + let caller_balance_prev = caller_account.balance; + let caller_balance = + caller_account.balance + state.tx.gas_price * (state.step.gas_left.0 + effective_refund); + state.push_op( + RW::WRITE, + AccountOp { + address: call.caller_address, + field: AccountField::Balance, + value: caller_balance, + value_prev: caller_balance_prev, + }, + ); + + let effective_tip = state.tx.gas_price - state.block.base_fee; + let (found, coinbase_account) = state.sdb.get_account_mut(&state.block.coinbase); + if !found { + return Err(Error::AccountNotFound(state.block.coinbase)); + } + let coinbase_balance_prev = coinbase_account.balance; + let coinbase_balance = + coinbase_account.balance + effective_tip * (state.tx.gas - state.step.gas_left.0); + state.push_op( + RW::WRITE, + AccountOp { + address: state.block.coinbase, + field: AccountField::Balance, + value: coinbase_balance, + value_prev: coinbase_balance_prev, + }, + ); + + if !state.tx_ctx.is_last_tx() { + state.push_op( + RW::READ, + CallContextOp { + call_id: state.block_ctx.rwc.0 + 1, + field: CallContextField::TxId, + value: (state.tx_ctx.id() + 1).into(), + }, + ); + } + + Ok(()) +} diff --git a/bus-mapping/src/evm/opcodes/calldatasize.rs b/bus-mapping/src/evm/opcodes/calldatasize.rs index c950f0d1cf..32487d0e12 100644 --- a/bus-mapping/src/evm/opcodes/calldatasize.rs +++ b/bus-mapping/src/evm/opcodes/calldatasize.rs @@ -21,91 +21,72 @@ impl Opcode for Calldatasize { state.push_op( RW::READ, CallContextOp { - call_id: state.call().call_id, + call_id: state.call()?.call_id, field: CallContextField::CallDataLength, value, }, ); - state.push_stack_op(RW::WRITE, step.stack.last_filled().map(|a| a - 1), value); + state.push_stack_op(RW::WRITE, step.stack.last_filled().map(|a| a - 1), value)?; Ok(()) } } #[cfg(test)] mod calldatasize_tests { - use crate::{ - circuit_input_builder::{ExecStep, TransactionContext}, - mock::BlockData, - operation::{CallContextField, CallContextOp, RW}, - Error, - }; - use eth_types::bytecode; - use eth_types::evm_types::StackAddress; - use mock::new_single_tx_trace_code_at_start; + use crate::operation::{CallContextField, CallContextOp, StackOp, RW}; + use eth_types::{bytecode, evm_types::OpcodeId, evm_types::StackAddress}; use pretty_assertions::assert_eq; #[test] - fn calldatasize_opcode_impl() -> Result<(), Error> { + fn calldatasize_opcode_impl() { let code = bytecode! { - #[start] CALLDATASIZE STOP }; // Get the execution steps from the external tracer - let block = - BlockData::new_from_geth_data(new_single_tx_trace_code_at_start(&code).unwrap()); + let block = crate::mock::BlockData::new_from_geth_data( + mock::new_single_tx_trace_code(&code).unwrap(), + ); let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) + builder + .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - // Generate step corresponding to CALLDATASIZE - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - // Get calldatasize from eth tx. - let call_data_size = block.eth_tx.input.to_vec().len(); + let step = builder.block.txs()[0] + .steps() + .iter() + .find(|step| step.op == OpcodeId::CALLDATASIZE) + .unwrap(); - // Add the read operation. - state_ref.push_op( - RW::READ, - CallContextOp { - call_id: state_ref.call().call_id, - field: CallContextField::CallDataLength, - value: eth_types::U256::from(call_data_size), + let call_id = builder.block.txs()[0].calls()[0].call_id; + let call_data_size = block.eth_block.transactions[0].input.as_ref().len().into(); + assert_eq!( + { + let operation = + &builder.block.container.call_context[step.bus_mapping_instance[0].as_usize()]; + (operation.rw(), operation.op()) }, + ( + RW::READ, + &CallContextOp { + call_id, + field: CallContextField::CallDataLength, + value: call_data_size, + } + ) ); - - // Add the stack write. - state_ref.push_stack_op( - RW::WRITE, - StackAddress::from(1024 - 1), - eth_types::U256::from(call_data_size), - ); - - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); - - // Compare first step bus mapping instance assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, + { + let operation = + &builder.block.container.stack[step.bus_mapping_instance[1].as_usize()]; + (operation.rw(), operation.op()) + }, + ( + RW::WRITE, + &StackOp::new(1, StackAddress::from(1023), call_data_size) + ) ); - - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) } } diff --git a/bus-mapping/src/evm/opcodes/caller.rs b/bus-mapping/src/evm/opcodes/caller.rs index ada767bbf0..a01ea619dc 100644 --- a/bus-mapping/src/evm/opcodes/caller.rs +++ b/bus-mapping/src/evm/opcodes/caller.rs @@ -21,13 +21,13 @@ impl Opcode for Caller { state.push_op( RW::READ, CallContextOp { - call_id: state.call().call_id, + call_id: state.call()?.call_id, field: CallContextField::CallerAddress, value, }, ); // Stack write of the caller_address - state.push_stack_op(RW::WRITE, step.stack.last_filled().map(|a| a - 1), value); + state.push_stack_op(RW::WRITE, step.stack.last_filled().map(|a| a - 1), value)?; Ok(()) } @@ -35,68 +35,60 @@ impl Opcode for Caller { #[cfg(test)] mod caller_tests { - use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; - use eth_types::{bytecode, evm_types::StackAddress, ToWord}; + use crate::operation::{CallContextField, CallContextOp, StackOp, RW}; + use eth_types::{bytecode, evm_types::OpcodeId, evm_types::StackAddress, ToWord}; use pretty_assertions::assert_eq; #[test] - fn caller_opcode_impl() -> Result<(), Error> { + fn caller_opcode_impl() { let code = bytecode! { - #[start] CALLER STOP }; // Get the execution steps from the external tracer let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), + mock::new_single_tx_trace_code(&code).unwrap(), ); let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) + builder + .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - - // Generate step corresponding to CALLER - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - let caller_address = block.eth_tx.from.to_word(); + let step = builder.block.txs()[0] + .steps() + .iter() + .find(|step| step.op == OpcodeId::CALLER) + .unwrap(); - // Add the CallContext read - state_ref.push_op( - RW::READ, - CallContextOp { - call_id: state_ref.call().call_id, - field: CallContextField::CallerAddress, - value: caller_address, + let call_id = builder.block.txs()[0].calls()[0].call_id; + let caller_address = block.eth_block.transactions[0].from.to_word(); + assert_eq!( + { + let operation = + &builder.block.container.call_context[step.bus_mapping_instance[0].as_usize()]; + (operation.rw(), operation.op()) }, + ( + RW::READ, + &CallContextOp { + call_id, + field: CallContextField::CallerAddress, + value: caller_address, + } + ) ); - // Add the Stack write - state_ref.push_stack_op(RW::WRITE, StackAddress::from(1024 - 1), caller_address); - - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); - - // Compare first step bus mapping instance assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, + { + let operation = + &builder.block.container.stack[step.bus_mapping_instance[1].as_usize()]; + (operation.rw(), operation.op()) + }, + ( + RW::WRITE, + &StackOp::new(1, StackAddress::from(1023), caller_address) + ) ); - - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) } } diff --git a/bus-mapping/src/evm/opcodes/callvalue.rs b/bus-mapping/src/evm/opcodes/callvalue.rs index 0708766870..c9901c7fe6 100644 --- a/bus-mapping/src/evm/opcodes/callvalue.rs +++ b/bus-mapping/src/evm/opcodes/callvalue.rs @@ -21,13 +21,13 @@ impl Opcode for Callvalue { state.push_op( RW::READ, CallContextOp { - call_id: state.call().call_id, + call_id: state.call()?.call_id, field: CallContextField::Value, value, }, ); // Stack write of the call_value - state.push_stack_op(RW::WRITE, step.stack.last_filled().map(|a| a - 1), value); + state.push_stack_op(RW::WRITE, step.stack.last_filled().map(|a| a - 1), value)?; Ok(()) } @@ -35,68 +35,60 @@ impl Opcode for Callvalue { #[cfg(test)] mod callvalue_tests { - use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; - use eth_types::{bytecode, evm_types::StackAddress}; + use crate::operation::{CallContextField, CallContextOp, StackOp, RW}; + use eth_types::{bytecode, evm_types::OpcodeId, evm_types::StackAddress}; use pretty_assertions::assert_eq; #[test] - fn callvalue_opcode_impl() -> Result<(), Error> { + fn callvalue_opcode_impl() { let code = bytecode! { - #[start] CALLVALUE STOP }; // Get the execution steps from the external tracer let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), + mock::new_single_tx_trace_code(&code).unwrap(), ); let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) + builder + .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - - // Generate step corresponding to CALLVALUE - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - let call_value = block.eth_tx.value; + let step = builder.block.txs()[0] + .steps() + .iter() + .find(|step| step.op == OpcodeId::CALLVALUE) + .unwrap(); - // Add the CallContext read - state_ref.push_op( - RW::READ, - CallContextOp { - call_id: state_ref.call().call_id, - field: CallContextField::Value, - value: call_value, + let call_id = builder.block.txs()[0].calls()[0].call_id; + let call_value = block.eth_block.transactions[0].value; + assert_eq!( + { + let operation = + &builder.block.container.call_context[step.bus_mapping_instance[0].as_usize()]; + (operation.rw(), operation.op()) }, + ( + RW::READ, + &CallContextOp { + call_id, + field: CallContextField::Value, + value: call_value, + } + ) ); - // Add the Stack write - state_ref.push_stack_op(RW::WRITE, StackAddress::from(1024 - 1), call_value); - - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); - - // Compare first step bus mapping instance assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, + { + let operation = + &builder.block.container.stack[step.bus_mapping_instance[1].as_usize()]; + (operation.rw(), operation.op()) + }, + ( + RW::WRITE, + &StackOp::new(1, StackAddress::from(1023), call_value) + ) ); - - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) } } diff --git a/bus-mapping/src/evm/opcodes/coinbase.rs b/bus-mapping/src/evm/opcodes/coinbase.rs deleted file mode 100644 index 94ada0e232..0000000000 --- a/bus-mapping/src/evm/opcodes/coinbase.rs +++ /dev/null @@ -1,65 +0,0 @@ -#[cfg(test)] -mod coinbase_tests { - use crate::{ - circuit_input_builder::{ExecStep, TransactionContext}, - mock::BlockData, - operation::RW, - Error, - }; - use eth_types::evm_types::StackAddress; - use eth_types::{bytecode, ToWord}; - use mock::new_single_tx_trace_code_at_start; - use pretty_assertions::assert_eq; - - #[test] - fn coinbase_opcode_impl() -> Result<(), Error> { - let code = bytecode! { - #[start] - COINBASE - STOP - }; - - // Get the execution steps from the external tracer - let block = - BlockData::new_from_geth_data(new_single_tx_trace_code_at_start(&code).unwrap()); - - let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) - .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - - // Generate step corresponding to COINBASE - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - // Add the last Stack write - state_ref.push_stack_op( - RW::WRITE, - StackAddress::from(1024 - 1), - block.eth_block.author.to_word(), - ); - - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); - - // Compare first step bus mapping instance - assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, - ); - - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) - } -} diff --git a/bus-mapping/src/evm/opcodes/dup.rs b/bus-mapping/src/evm/opcodes/dup.rs index 61c04e1cd6..d06792369d 100644 --- a/bus-mapping/src/evm/opcodes/dup.rs +++ b/bus-mapping/src/evm/opcodes/dup.rs @@ -17,13 +17,13 @@ impl Opcode for Dup { let stack_value_read = step.stack.nth_last(N - 1)?; let stack_position = step.stack.nth_last_filled(N - 1); - state.push_stack_op(RW::READ, stack_position, stack_value_read); + state.push_stack_op(RW::READ, stack_position, stack_value_read)?; state.push_stack_op( RW::WRITE, step.stack.last_filled().map(|a| a - 1), stack_value_read, - ); + )?; Ok(()) } @@ -32,70 +32,62 @@ impl Opcode for Dup { #[cfg(test)] mod dup_tests { use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; + use crate::operation::StackOp; + use eth_types::bytecode; use eth_types::evm_types::StackAddress; - use eth_types::{bytecode, word}; + use eth_types::word; + use itertools::Itertools; use pretty_assertions::assert_eq; #[test] - fn dup_opcode_impl() -> Result<(), Error> { + fn dup_opcode_impl() { let code = bytecode! { PUSH1(0x1) PUSH1(0x2) - PUSH1(0x3) - #[start] // [1,2,3] - DUP1 // [1,2,3,3] - DUP3 // [1,2,3,3,2] - DUP5 // [1,2,3,3,2,1] + PUSH1(0x3) // [1,2,3] + DUP1 // [1,2,3,3] + DUP3 // [1,2,3,3,2] + DUP5 // [1,2,3,3,2,1] STOP }; // Get the execution steps from the external tracer let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), + mock::new_single_tx_trace_code(&code).unwrap(), ); let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) + builder + .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate steps corresponding to DUP1, DUP3, DUP5 for (i, word) in [word!("0x3"), word!("0x2"), word!("0x1")] .iter() .enumerate() { - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[i], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - state_ref.push_stack_op(RW::READ, StackAddress(1024 - 3 + i), *word); + let step = builder.block.txs()[0] + .steps() + .iter() + .filter(|step| step.op.is_dup()) + .collect_vec()[i]; - state_ref.push_stack_op(RW::WRITE, StackAddress(1024 - 4 - i), *word); - - tx.steps_mut().push(step); - } - - test_builder.block.txs_mut().push(tx); - - // Compare first 3 steps bus mapping instance - for i in 0..3 { assert_eq!( - builder.block.txs()[0].steps()[i].bus_mapping_instance, - test_builder.block.txs()[0].steps()[i].bus_mapping_instance - ); + [0, 1] + .map(|idx| &builder.block.container.stack + [step.bus_mapping_instance[idx].as_usize()]) + .map(|operation| (operation.rw(), operation.op())), + [ + ( + RW::READ, + &StackOp::new(1, StackAddress(1024 - 3 + i), *word) + ), + ( + RW::WRITE, + &StackOp::new(1, StackAddress(1024 - 4 - i), *word) + ) + ] + ) } - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) } } diff --git a/bus-mapping/src/evm/opcodes/gas.rs b/bus-mapping/src/evm/opcodes/gas.rs deleted file mode 100644 index 85e1b1c6cd..0000000000 --- a/bus-mapping/src/evm/opcodes/gas.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::{circuit_input_builder::CircuitInputStateRef, operation::RW, Error}; -use eth_types::GethExecStep; - -use super::Opcode; - -#[derive(Clone, Copy, Debug)] -pub(crate) struct Gas; - -impl Opcode for Gas { - fn gen_associated_ops( - state: &mut CircuitInputStateRef, - next_steps: &[GethExecStep], - ) -> Result<(), Error> { - let step = &next_steps[0]; - // Get value result from next step and do stack write - let value = next_steps[1].stack.last()?; - state.push_stack_op(RW::WRITE, step.stack.last_filled().map(|a| a - 1), value); - Ok(()) - } -} - -#[cfg(test)] -mod gas_tests { - use crate::{ - circuit_input_builder::{ExecStep, TransactionContext}, - evm::OpcodeId, - mock::BlockData, - operation::StackAddress, - }; - use eth_types::{bytecode, bytecode::Bytecode, evm_types::GasCost, Word}; - use mock::new_single_tx_trace_code_at_start; - - use super::*; - - fn test_ok(code: Bytecode, gas_left: u64) -> Result<(), Error> { - let block = BlockData::new_from_geth_data(new_single_tx_trace_code_at_start(&code)?); - - let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace)?; - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder.new_tx(&block.eth_tx, !block.geth_trace.failed)?; - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace)?; - - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - state_ref.push_stack_op(RW::WRITE, StackAddress::from(1022), Word::from(gas_left)); - - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); - - assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, - ); - - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) - } - - #[test] - fn gas_opcode_impl() -> Result<(), Error> { - const GAS_LIMIT: u64 = 1_000_000; - - const GAS_COST: u64 = GasCost::TX.as_u64() - + OpcodeId::PUSH1.constant_gas_cost().as_u64() - + OpcodeId::PUSH1.constant_gas_cost().as_u64() - + OpcodeId::GAS.constant_gas_cost().as_u64(); - - let test_scenarios: [(Bytecode, u64); 4] = [ - ( - bytecode! { - PUSH1(0x1) - PUSH1(0x1) - POP - #[start] - GAS - STOP - }, - GAS_LIMIT.saturating_sub( - GAS_COST + OpcodeId::POP.constant_gas_cost().as_u64(), - ), - ), - ( - bytecode! { - PUSH1(0x1) - PUSH1(0x1) - ADD - #[start] - GAS - STOP - }, - GAS_LIMIT.saturating_sub( - GAS_COST + OpcodeId::ADD.constant_gas_cost().as_u64(), - ), - ), - ( - bytecode! { - PUSH1(0x1) - PUSH1(0x1) - DIV - #[start] - GAS - STOP - }, - GAS_LIMIT.saturating_sub( - GAS_COST + OpcodeId::DIV.constant_gas_cost().as_u64(), - ), - ), - ( - bytecode! { - PUSH1(0x1) - PUSH1(0x1) - EXP - #[start] - GAS - STOP - }, - GAS_LIMIT.saturating_sub( - GAS_COST - + OpcodeId::EXP.constant_gas_cost().as_u64() - + 50u64, // dynamic gas cost - ), - ), - ]; - - for t in test_scenarios { - test_ok(t.0, t.1)?; - } - - Ok(()) - } -} diff --git a/bus-mapping/src/evm/opcodes/jump.rs b/bus-mapping/src/evm/opcodes/jump.rs deleted file mode 100644 index 4b4af4a321..0000000000 --- a/bus-mapping/src/evm/opcodes/jump.rs +++ /dev/null @@ -1,95 +0,0 @@ -use super::Opcode; -use crate::circuit_input_builder::CircuitInputStateRef; -use crate::{operation::RW, Error}; -use eth_types::GethExecStep; - -/// Placeholder structure used to implement [`Opcode`] trait over it -/// corresponding to the [`OpcodeId::JUMP`](crate::evm::OpcodeId::JUMP) -/// `OpcodeId`. -#[derive(Debug, Copy, Clone)] -pub(crate) struct Jump; - -impl Opcode for Jump { - fn gen_associated_ops( - state: &mut CircuitInputStateRef, - steps: &[GethExecStep], - ) -> Result<(), Error> { - let step = &steps[0]; - - // `JUMP` needs only one read operation - state.push_stack_op( - RW::READ, - step.stack.nth_last_filled(0), - step.stack.nth_last(0)?, - ); - - Ok(()) - } -} - -#[cfg(test)] -mod jump_tests { - use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; - use eth_types::evm_types::StackAddress; - use eth_types::{bytecode, Word}; - use pretty_assertions::assert_eq; - - #[test] - fn jump_opcode_impl() -> Result<(), Error> { - let destination = 35; - - let mut code = bytecode! { - PUSH32(destination) - #[start] - JUMP - }; - for _ in 0..(destination - 34) { - code.write(0); - } - code.append(&bytecode! { - JUMPDEST - STOP - }); - - // Get the execution steps from the external tracer - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), - ); - - let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) - .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - - // Generate step corresponding to JUMP - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - // Add the last Stack read - state_ref.push_stack_op(RW::READ, StackAddress::from(1023), Word::from(destination)); - - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); - - // Compare first step bus mapping instance - assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, - ); - - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) - } -} diff --git a/bus-mapping/src/evm/opcodes/jumpdest.rs b/bus-mapping/src/evm/opcodes/jumpdest.rs deleted file mode 100644 index 36571ead93..0000000000 --- a/bus-mapping/src/evm/opcodes/jumpdest.rs +++ /dev/null @@ -1,20 +0,0 @@ -use super::Opcode; -use crate::circuit_input_builder::CircuitInputStateRef; -use crate::Error; -use eth_types::GethExecStep; - -/// Placeholder structure used to implement [`Opcode`] trait over it -/// corresponding to the [`OpcodeId::JUMPDEST`](crate::evm::OpcodeId::JUMPDEST) -/// `OpcodeId`. -#[derive(Debug, Copy, Clone)] -pub(crate) struct Jumpdest; - -impl Opcode for Jumpdest { - fn gen_associated_ops( - _state: &mut CircuitInputStateRef, - _steps: &[GethExecStep], - ) -> Result<(), Error> { - // Jumpdest does not generate any operations - Ok(()) - } -} diff --git a/bus-mapping/src/evm/opcodes/jumpi.rs b/bus-mapping/src/evm/opcodes/jumpi.rs deleted file mode 100644 index 12051952ab..0000000000 --- a/bus-mapping/src/evm/opcodes/jumpi.rs +++ /dev/null @@ -1,103 +0,0 @@ -use super::Opcode; -use crate::circuit_input_builder::CircuitInputStateRef; -use crate::{operation::RW, Error}; -use eth_types::GethExecStep; - -/// Placeholder structure used to implement [`Opcode`] trait over it -/// corresponding to the [`OpcodeId::JUMPI`](crate::evm::OpcodeId::JUMPI) -/// `OpcodeId`. -#[derive(Debug, Copy, Clone)] -pub(crate) struct Jumpi; - -impl Opcode for Jumpi { - fn gen_associated_ops( - state: &mut CircuitInputStateRef, - steps: &[GethExecStep], - ) -> Result<(), Error> { - let step = &steps[0]; - // `JUMPI` needs two read operation - state.push_stack_op( - RW::READ, - step.stack.nth_last_filled(0), - step.stack.nth_last(0)?, - ); - state.push_stack_op( - RW::READ, - step.stack.nth_last_filled(1), - step.stack.nth_last(1)?, - ); - - Ok(()) - } -} - -#[cfg(test)] -mod jumpi_tests { - use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; - use eth_types::evm_types::StackAddress; - use eth_types::{bytecode, Word}; - use pretty_assertions::assert_eq; - - #[test] - fn jumpi_opcode_impl() -> Result<(), Error> { - let condition = 1; - let destination = 69; - - let mut code = bytecode! { - PUSH32(condition) - PUSH32(destination) - #[start] - JUMPI - STOP - }; - for _ in 0..(destination - 68) { - code.write(0); - } - code.append(&bytecode! { - JUMPDEST - STOP - }); - - // Get the execution steps from the external tracer - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), - ); - - let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) - .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - - // Generate step corresponding to JUMP - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - // Add the last 2 Stack reads - state_ref.push_stack_op(RW::READ, StackAddress::from(1022), Word::from(destination)); - state_ref.push_stack_op(RW::READ, StackAddress::from(1023), Word::from(condition)); - - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); - - // Compare first step bus mapping instance - assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, - ); - - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) - } -} diff --git a/bus-mapping/src/evm/opcodes/mload.rs b/bus-mapping/src/evm/opcodes/mload.rs index 65a6dd7d47..c4de071d66 100644 --- a/bus-mapping/src/evm/opcodes/mload.rs +++ b/bus-mapping/src/evm/opcodes/mload.rs @@ -26,7 +26,7 @@ impl Opcode for Mload { let stack_position = step.stack.last_filled(); // Manage first stack read at latest stack position - state.push_stack_op(RW::READ, stack_position, stack_value_read); + state.push_stack_op(RW::READ, stack_position, stack_value_read)?; // Read the memory let mut mem_read_addr: MemoryAddress = stack_value_read.try_into()?; @@ -40,18 +40,17 @@ impl Opcode for Mload { // // First stack write // - state.push_stack_op(RW::WRITE, stack_position, mem_read_value); + state.push_stack_op(RW::WRITE, stack_position, mem_read_value)?; // // First mem read -> 32 MemoryOp generated. // - let bytes = mem_read_value.to_be_bytes(); - bytes.iter().for_each(|value_byte| { - state.push_memory_op(RW::READ, mem_read_addr, *value_byte); + for byte in mem_read_value.to_be_bytes() { + state.push_memory_op(RW::READ, mem_read_addr, byte)?; // Update mem_read_addr to next byte's one mem_read_addr += MemoryAddress::from(1); - }); + } Ok(()) } @@ -60,74 +59,67 @@ impl Opcode for Mload { #[cfg(test)] mod mload_tests { use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; - use eth_types::evm_types::StackAddress; - use eth_types::{bytecode, Word}; + use crate::operation::{MemoryOp, StackOp}; + use eth_types::bytecode; + use eth_types::evm_types::{OpcodeId, StackAddress}; + use eth_types::Word; + use itertools::Itertools; use pretty_assertions::assert_eq; #[test] - fn mload_opcode_impl() -> Result<(), Error> { + fn mload_opcode_impl() { let code = bytecode! { .setup_state() PUSH1(0x40u64) - #[start] MLOAD STOP }; // Get the execution steps from the external tracer let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), + mock::new_single_tx_trace_code(&code).unwrap(), ); let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) + builder + .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - - // Generate step corresponding to MLOAD - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - // Add StackOp associated to the 0x40 read from the latest Stack pos. - state_ref.push_stack_op(RW::READ, StackAddress::from(1023), Word::from(0x40)); - // Add the last Stack write - state_ref.push_stack_op(RW::WRITE, StackAddress::from(1023), Word::from(0x80)); - - // Add the 32 MemoryOp generated from the Memory read at addr - // 0x40<->0x80 for each byte. - Word::from(0x80) - .to_be_bytes() + let step = builder.block.txs()[0] + .steps() .iter() - .enumerate() - .map(|(idx, byte)| (idx + 0x40, byte)) - .for_each(|(idx, byte)| { - state_ref.push_memory_op(RW::READ, idx.into(), *byte); - }); - - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); + .find(|step| step.op == OpcodeId::MLOAD) + .unwrap(); - // Compare first step bus mapping instance assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, + [0, 1] + .map(|idx| &builder.block.container.stack[step.bus_mapping_instance[idx].as_usize()]) + .map(|operation| (operation.rw(), operation.op())), + [ + ( + RW::READ, + &StackOp::new(1, StackAddress::from(1023), Word::from(0x40)) + ), + ( + RW::WRITE, + &StackOp::new(1, StackAddress::from(1023), Word::from(0x80)) + ) + ] ); - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) + assert_eq!( + (2..34) + .map(|idx| &builder.block.container.memory + [step.bus_mapping_instance[idx].as_usize()]) + .map(|operation| (operation.rw(), operation.op().clone())) + .collect_vec(), + Word::from(0x80) + .to_be_bytes() + .into_iter() + .enumerate() + .map(|(idx, byte)| (RW::READ, MemoryOp::new(1, MemoryAddress(idx + 0x40), byte))) + .collect_vec() + ) } } diff --git a/bus-mapping/src/evm/opcodes/msize.rs b/bus-mapping/src/evm/opcodes/msize.rs deleted file mode 100644 index ba16bf7846..0000000000 --- a/bus-mapping/src/evm/opcodes/msize.rs +++ /dev/null @@ -1,88 +0,0 @@ -use super::Opcode; -use crate::circuit_input_builder::CircuitInputStateRef; -use crate::{operation::RW, Error}; -use eth_types::GethExecStep; - -/// Placeholder structure used to implement [`Opcode`] trait over it -/// corresponding to the [`OpcodeId::MSIZE`](crate::evm::OpcodeId::MSIZE) -/// `OpcodeId`. -#[derive(Debug, Copy, Clone)] -pub(crate) struct Msize; - -impl Opcode for Msize { - fn gen_associated_ops( - state: &mut CircuitInputStateRef, - steps: &[GethExecStep], - ) -> Result<(), Error> { - let step = &steps[0]; - - state.push_stack_op( - RW::WRITE, - step.stack.last_filled().map(|a| a - 1), - steps[1].stack.last()?, - ); - - Ok(()) - } -} - -#[cfg(test)] -mod msize_tests { - use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; - use eth_types::evm_types::StackAddress; - use eth_types::{bytecode, Word}; - - #[test] - fn msize_opcode_impl() -> Result<(), Error> { - let code = bytecode! { - .setup_state() - - #[start] - MSIZE - STOP - }; - - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), - ); - - let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) - .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - // Add StackOp WRITE to the latest Stack pos. - state_ref.push_stack_op( - RW::WRITE, - StackAddress::from(1023), - Word::from(96), // 3 words, 96 bytes - ); - - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); - - // Compare first step bus mapping instance - assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, - ); - - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) - } -} diff --git a/bus-mapping/src/evm/opcodes/mstore.rs b/bus-mapping/src/evm/opcodes/mstore.rs index bfdd6caa16..7d8d7de05b 100644 --- a/bus-mapping/src/evm/opcodes/mstore.rs +++ b/bus-mapping/src/evm/opcodes/mstore.rs @@ -20,12 +20,12 @@ impl Opcode for Mstore { // First stack read (offset) let offset = step.stack.nth_last(0)?; let offset_pos = step.stack.nth_last_filled(0); - state.push_stack_op(RW::READ, offset_pos, offset); + state.push_stack_op(RW::READ, offset_pos, offset)?; // Second stack read (value) let value = step.stack.nth_last(1)?; let value_pos = step.stack.nth_last_filled(1); - state.push_stack_op(RW::READ, value_pos, value); + state.push_stack_op(RW::READ, value_pos, value)?; // First mem write -> 32 MemoryOp generated. let offset_addr: MemoryAddress = offset.try_into()?; @@ -37,13 +37,13 @@ impl Opcode for Mstore { RW::WRITE, offset_addr, *value.to_le_bytes().first().unwrap(), - ); + )?; } false => { // stack write each byte for mstore let bytes = value.to_be_bytes(); for (i, byte) in bytes.iter().enumerate() { - state.push_memory_op(RW::WRITE, offset_addr.map(|a| a + i), *byte); + state.push_memory_op(RW::WRITE, offset_addr.map(|a| a + i), *byte)?; } } } @@ -55,125 +55,120 @@ impl Opcode for Mstore { #[cfg(test)] mod mstore_tests { use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; - use eth_types::evm_types::{MemoryAddress, StackAddress}; - use eth_types::{bytecode, Word}; + use crate::operation::{MemoryOp, StackOp}; + use eth_types::bytecode; + use eth_types::evm_types::{MemoryAddress, OpcodeId, StackAddress}; + use eth_types::Word; + use itertools::Itertools; use pretty_assertions::assert_eq; #[test] - fn mstore_opcode_impl() -> Result<(), Error> { + fn mstore_opcode_impl() { let code = bytecode! { .setup_state() PUSH2(0x1234) PUSH2(0x100) - #[start] MSTORE STOP }; // Get the execution steps from the external tracer let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), + mock::new_single_tx_trace_code(&code).unwrap(), ); let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) + builder + .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - - // Generate step corresponding to MSTORE - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - // Add StackOps associated to the 0x100, 0x1234 reads starting from last - // stack position. - state_ref.push_stack_op(RW::READ, StackAddress::from(1022), Word::from(0x100)); - state_ref.push_stack_op(RW::READ, StackAddress::from(1023), Word::from(0x1234)); - - // Add the 32 MemoryOp generated from the Memory write at addr - // 0x100..0x120 for each byte. - for (i, byte) in Word::from(0x1234).to_be_bytes().iter().enumerate() { - state_ref.push_memory_op(RW::WRITE, MemoryAddress(0x100 + i), *byte); - } - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); + let step = builder.block.txs()[0] + .steps() + .iter() + .filter(|step| step.op == OpcodeId::MSTORE) + .nth(1) + .unwrap(); - // Compare first step bus mapping instance assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, + [0, 1] + .map(|idx| &builder.block.container.stack[step.bus_mapping_instance[idx].as_usize()]) + .map(|operation| (operation.rw(), operation.op())), + [ + ( + RW::READ, + &StackOp::new(1, StackAddress::from(1022), Word::from(0x100)) + ), + ( + RW::READ, + &StackOp::new(1, StackAddress::from(1023), Word::from(0x1234)) + ) + ] ); - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) + assert_eq!( + (2..34) + .map(|idx| &builder.block.container.memory + [step.bus_mapping_instance[idx].as_usize()]) + .map(|operation| (operation.rw(), operation.op().clone())) + .collect_vec(), + Word::from(0x1234) + .to_be_bytes() + .into_iter() + .enumerate() + .map(|(idx, byte)| ( + RW::WRITE, + MemoryOp::new(1, MemoryAddress(idx + 0x100), byte) + )) + .collect_vec() + ) } #[test] - fn mstore8_opcode_impl() -> Result<(), Error> { + fn mstore8_opcode_impl() { let code = bytecode! { .setup_state() PUSH2(0x1234) PUSH2(0x100) - #[start] MSTORE8 STOP }; // Get the execution steps from the external tracer let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), + mock::new_single_tx_trace_code(&code).unwrap(), ); let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) + builder + .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - - // Generate step corresponding to MSTORE - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - // Add StackOps associated to the 0x100, 0x12 reads starting from last - // stack position. - state_ref.push_stack_op(RW::READ, StackAddress::from(1022), Word::from(0x100)); - state_ref.push_stack_op(RW::READ, StackAddress::from(1023), Word::from(0x1234)); - - // Add 1 MemoryOp generated from the Memory write at addr 0x100. - state_ref.push_memory_op(RW::WRITE, MemoryAddress(0x100), 0x34); - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); + let step = builder.block.txs()[0] + .steps() + .iter() + .find(|step| step.op == OpcodeId::MSTORE8) + .unwrap(); - // Compare first step bus mapping instance assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, + [0, 1] + .map(|idx| &builder.block.container.stack[step.bus_mapping_instance[idx].as_usize()]) + .map(|operation| (operation.rw(), operation.op())), + [ + ( + RW::READ, + &StackOp::new(1, StackAddress::from(1022), Word::from(0x100)) + ), + ( + RW::READ, + &StackOp::new(1, StackAddress::from(1023), Word::from(0x1234)) + ) + ] ); - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) + let memory_op = &builder.block.container.memory[step.bus_mapping_instance[2].as_usize()]; + assert_eq!( + (memory_op.rw(), memory_op.op()), + (RW::WRITE, &MemoryOp::new(1, MemoryAddress(0x100), 0x34)) + ) } } diff --git a/bus-mapping/src/evm/opcodes/pc.rs b/bus-mapping/src/evm/opcodes/pc.rs deleted file mode 100644 index 761574826b..0000000000 --- a/bus-mapping/src/evm/opcodes/pc.rs +++ /dev/null @@ -1,83 +0,0 @@ -use super::Opcode; -use crate::circuit_input_builder::CircuitInputStateRef; -use crate::{operation::RW, Error}; -use eth_types::GethExecStep; - -/// Placeholder structure used to implement [`Opcode`] trait over it -/// corresponding to the [`OpcodeId::PC`](crate::evm::OpcodeId::PC) `OpcodeId`. -#[derive(Debug, Copy, Clone)] -pub(crate) struct Pc; - -impl Opcode for Pc { - fn gen_associated_ops( - state: &mut CircuitInputStateRef, - steps: &[GethExecStep], - ) -> Result<(), Error> { - let step = &steps[0]; - // Get value result from next step and do stack write - let value = steps[1].stack.last()?; - state.push_stack_op(RW::WRITE, step.stack.last_filled().map(|a| a - 1), value); - - Ok(()) - } -} - -#[cfg(test)] -mod pc_tests { - use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; - use eth_types::evm_types::StackAddress; - use eth_types::{bytecode, Word}; - use pretty_assertions::assert_eq; - - #[test] - fn pc_opcode_impl() -> Result<(), Error> { - let code = bytecode! { - PUSH1(0x1) - PUSH1(0x2) - #[start] - PC - STOP - }; - - // Get the execution steps from the external tracer - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), - ); - - let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) - .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - - // Generate step corresponding to MLOAD - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - // Add the last Stack write - state_ref.push_stack_op(RW::WRITE, StackAddress::from(1024 - 3), Word::from(0x4)); - - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); - - // Compare first step bus mapping instance - assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, - ); - - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) - } -} diff --git a/bus-mapping/src/evm/opcodes/pop.rs b/bus-mapping/src/evm/opcodes/pop.rs deleted file mode 100644 index 5f6a078b6f..0000000000 --- a/bus-mapping/src/evm/opcodes/pop.rs +++ /dev/null @@ -1,83 +0,0 @@ -use super::Opcode; -use crate::circuit_input_builder::CircuitInputStateRef; -use crate::{operation::RW, Error}; -use eth_types::GethExecStep; - -/// Placeholder structure used to implement [`Opcode`] trait over it -/// corresponding to the POP stack operation -#[derive(Debug, Copy, Clone)] -pub(crate) struct Pop; - -impl Opcode for Pop { - fn gen_associated_ops( - state: &mut CircuitInputStateRef, - steps: &[GethExecStep], - ) -> Result<(), Error> { - let step = &steps[0]; - // `POP` needs only one read operation - state.push_stack_op( - RW::READ, - step.stack.nth_last_filled(0), - step.stack.nth_last(0)?, - ); - - Ok(()) - } -} - -#[cfg(test)] -mod pop_tests { - use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; - use eth_types::evm_types::StackAddress; - use eth_types::{bytecode, Word}; - use pretty_assertions::assert_eq; - - #[test] - fn pop_opcode_impl() -> Result<(), Error> { - let code = bytecode! { - PUSH1(0x80) - #[start] - POP - STOP - }; - - // Get the execution steps from the external tracer - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), - ); - - let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) - .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - - // Generate step corresponding to POP - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - // Add StackOp associated to the stack pop. - state_ref.push_stack_op(RW::READ, StackAddress::from(1023), Word::from(0x80u32)); - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); - - // Compare first step bus mapping instance - assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance - ); - - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) - } -} diff --git a/bus-mapping/src/evm/opcodes/push.rs b/bus-mapping/src/evm/opcodes/push.rs deleted file mode 100644 index ef8adabd04..0000000000 --- a/bus-mapping/src/evm/opcodes/push.rs +++ /dev/null @@ -1,101 +0,0 @@ -use super::Opcode; -use crate::circuit_input_builder::CircuitInputStateRef; -use crate::{operation::RW, Error}; -use eth_types::GethExecStep; - -/// Placeholder structure used to implement [`Opcode`] trait over it -/// corresponding to the `OpcodeId::PUSH*` `OpcodeId`. -/// This is responsible of generating all of the associated -/// [`crate::operation::StackOp`]s and place them inside the trace's -/// [`OperationContainer`](crate::operation::OperationContainer). -#[derive(Debug, Copy, Clone)] -pub(crate) struct Push; - -impl Opcode for Push { - fn gen_associated_ops( - state: &mut CircuitInputStateRef, - steps: &[GethExecStep], - ) -> Result<(), Error> { - let step = &steps[0]; - state.push_stack_op( - RW::WRITE, - // Get the value and addr from the next step. Being the last - // position filled with an element in the stack - step.stack.last_filled().map(|a| a - 1), - steps[1].stack.last()?, - ); - - Ok(()) - } -} - -#[cfg(test)] -mod push_tests { - use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; - use eth_types::evm_types::StackAddress; - use eth_types::{bytecode, word}; - use pretty_assertions::assert_eq; - - #[test] - fn push_opcode_impl() -> Result<(), Error> { - let code = bytecode! { - #[start] - PUSH1(0x80) - PUSH2(0x1234) - PUSH16(word!("0x00112233445566778899aabbccddeeff")) - STOP - }; - - // Get the execution steps from the external tracer - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), - ); - - let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) - .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - - // Generate steps corresponding to PUSH1 80, PUSH2 1234, - // PUSH16 0x00112233445566778899aabbccddeeff - for (i, word) in [ - word!("0x80"), - word!("0x1234"), - word!("0x00112233445566778899aabbccddeeff"), - ] - .iter() - .enumerate() - { - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[i], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - // Add StackOp associated to the push at the latest Stack pos. - state_ref.push_stack_op(RW::WRITE, StackAddress::from(1023 - i), *word); - tx.steps_mut().push(step); - } - - test_builder.block.txs_mut().push(tx); - - // Compare first 3 steps bus mapping instance - for i in 0..3 { - assert_eq!( - builder.block.txs()[0].steps()[i].bus_mapping_instance, - test_builder.block.txs()[0].steps()[i].bus_mapping_instance - ); - } - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) - } -} diff --git a/bus-mapping/src/evm/opcodes/selfbalance.rs b/bus-mapping/src/evm/opcodes/selfbalance.rs index 308e451b1f..f7769a3f4e 100644 --- a/bus-mapping/src/evm/opcodes/selfbalance.rs +++ b/bus-mapping/src/evm/opcodes/selfbalance.rs @@ -14,13 +14,13 @@ impl Opcode for Selfbalance { ) -> Result<(), Error> { let step = &steps[0]; let self_balance = steps[1].stack.last()?; - let callee_address = state.call().address; + let callee_address = state.call()?.address; // CallContext read of the callee_address state.push_op( RW::READ, CallContextOp { - call_id: state.call().call_id, + call_id: state.call()?.call_id, field: CallContextField::CalleeAddress, value: callee_address.to_word(), }, @@ -42,7 +42,7 @@ impl Opcode for Selfbalance { RW::WRITE, step.stack.last_filled().map(|a| a - 1), self_balance, - ); + )?; Ok(()) } @@ -51,82 +51,78 @@ impl Opcode for Selfbalance { #[cfg(test)] mod selfbalance_tests { use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; - use eth_types::{bytecode, evm_types::StackAddress, ToWord}; + use crate::operation::{CallContextField, CallContextOp, StackOp, RW}; + use eth_types::{bytecode, evm_types::OpcodeId, evm_types::StackAddress}; use pretty_assertions::assert_eq; #[test] - fn selfbalance_opcode_impl() -> Result<(), Error> { + fn selfbalance_opcode_impl() { let code = bytecode! { - #[start] SELFBALANCE STOP }; - let mut geth_data = mock::new_single_tx_trace_code(&code)?; - geth_data.geth_trace.struct_logs = - geth_data.geth_trace.struct_logs[code.get_pos("start")..].to_vec(); - // Get the execution steps from the external tracer - let block = crate::mock::BlockData::new_from_geth_data(geth_data); + let block = crate::mock::BlockData::new_from_geth_data( + mock::new_single_tx_trace_code(&code).unwrap(), + ); let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) + builder + .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - // Generate step corresponding to SELFBALANCE - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); + let step = builder.block.txs()[0] + .steps() + .iter() + .find(|step| step.op == OpcodeId::SELFBALANCE) + .unwrap(); - let callee_address = block.eth_tx.to.unwrap(); - let self_balance = state_ref.sdb.get_account(&callee_address).1.balance; + let call_id = builder.block.txs()[0].calls()[0].call_id; + let callee_address = builder.block.txs()[0].to; + let self_balance = builder.sdb.get_account(&callee_address).1.balance; - // CallContext read for callee_address - state_ref.push_op( - RW::READ, - CallContextOp { - call_id: state_ref.call().call_id, - field: CallContextField::CalleeAddress, - value: callee_address.to_word(), + assert_eq!( + { + let operation = + &builder.block.container.call_context[step.bus_mapping_instance[0].as_usize()]; + (operation.rw(), operation.op()) }, + ( + RW::READ, + &CallContextOp { + call_id, + field: CallContextField::CalleeAddress, + value: callee_address.to_word(), + } + ) ); - - // Account read for balance of callee_address - state_ref.push_op( - RW::READ, - AccountOp { - address: callee_address, - field: AccountField::Balance, - value: self_balance, - value_prev: self_balance, + assert_eq!( + { + let operation = + &builder.block.container.account[step.bus_mapping_instance[1].as_usize()]; + (operation.rw(), operation.op()) }, + ( + RW::READ, + &AccountOp { + address: callee_address, + field: AccountField::Balance, + value: self_balance, + value_prev: self_balance, + } + ) ); - - // Add the Stack write - state_ref.push_stack_op(RW::WRITE, StackAddress::from(1024 - 1), self_balance); - - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); - - // Compare first step bus mapping instance assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, + { + let operation = + &builder.block.container.stack[step.bus_mapping_instance[2].as_usize()]; + (operation.rw(), operation.op()) + }, + ( + RW::WRITE, + &StackOp::new(1, StackAddress::from(1023), self_balance) + ) ); - - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) } } diff --git a/bus-mapping/src/evm/opcodes/sload.rs b/bus-mapping/src/evm/opcodes/sload.rs index 76b65a869b..455d199c3a 100644 --- a/bus-mapping/src/evm/opcodes/sload.rs +++ b/bus-mapping/src/evm/opcodes/sload.rs @@ -24,14 +24,14 @@ impl Opcode for Sload { let stack_position = step.stack.last_filled(); // Manage first stack read at latest stack position - state.push_stack_op(RW::READ, stack_position, stack_value_read); + state.push_stack_op(RW::READ, stack_position, stack_value_read)?; // Storage read let storage_value_read = step.storage.get_or_err(&stack_value_read)?; state.push_op( RW::READ, StorageOp::new( - state.call().address, + state.call()?.address, stack_value_read, storage_value_read, storage_value_read, @@ -41,7 +41,7 @@ impl Opcode for Sload { ); // First stack write - state.push_stack_op(RW::WRITE, stack_position, storage_value_read); + state.push_stack_op(RW::WRITE, stack_position, storage_value_read)?; Ok(()) } @@ -50,13 +50,14 @@ impl Opcode for Sload { #[cfg(test)] mod sload_tests { use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; - use eth_types::evm_types::StackAddress; - use eth_types::{bytecode, Address, Word}; + use crate::operation::StackOp; + use eth_types::bytecode; + use eth_types::evm_types::{OpcodeId, StackAddress}; + use eth_types::{Address, Word}; use pretty_assertions::assert_eq; #[test] - fn sload_opcode_impl() -> Result<(), Error> { + fn sload_opcode_impl() { let code = bytecode! { // Write 0x6f to storage slot 0 PUSH1(0x6fu64) @@ -65,58 +66,56 @@ mod sload_tests { // Load storage slot 0 PUSH1(0x00u64) - #[start] SLOAD STOP }; // Get the execution steps from the external tracer let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), + mock::new_single_tx_trace_code(&code).unwrap(), ); let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) + builder + .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - // Generate step corresponding to SLOAD - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - // Add StackOp associated to the stack pop. - state_ref.push_stack_op(RW::READ, StackAddress::from(1023), Word::from(0x0u32)); - // Add StorageOp associated to the storage read. - state_ref.push_op( - RW::READ, - StorageOp::new( - Address::from([0u8; 20]), - Word::from(0x0u32), - Word::from(0x6fu32), - Word::from(0x6fu32), - 1usize, - Word::from(0x6fu32), - ), - ); - // Add StackOp associated to the stack push. - state_ref.push_stack_op(RW::WRITE, StackAddress::from(1023), Word::from(0x6fu32)); - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); + let step = builder.block.txs()[0] + .steps() + .iter() + .find(|step| step.op == OpcodeId::SLOAD) + .unwrap(); assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance + [0, 2] + .map(|idx| &builder.block.container.stack[step.bus_mapping_instance[idx].as_usize()]) + .map(|operation| (operation.rw(), operation.op())), + [ + ( + RW::READ, + &StackOp::new(1, StackAddress::from(1023), Word::from(0x0u32)) + ), + ( + RW::WRITE, + &StackOp::new(1, StackAddress::from(1023), Word::from(0x6fu32)) + ) + ] ); - assert_eq!(builder.block.container, test_builder.block.container); - Ok(()) + let storage_op = &builder.block.container.storage[step.bus_mapping_instance[1].as_usize()]; + assert_eq!( + (storage_op.rw(), storage_op.op()), + ( + RW::READ, + &StorageOp::new( + Address::from([0u8; 20]), + Word::from(0x0u32), + Word::from(0x6fu32), + Word::from(0x6fu32), + 1, + Word::from(0x6fu32), + ) + ) + ) } } diff --git a/bus-mapping/src/evm/opcodes/stackonlyop.rs b/bus-mapping/src/evm/opcodes/stackonlyop.rs index 741e1a58ef..f0b3aeeaf8 100644 --- a/bus-mapping/src/evm/opcodes/stackonlyop.rs +++ b/bus-mapping/src/evm/opcodes/stackonlyop.rs @@ -10,30 +10,32 @@ use eth_types::GethExecStep; /// - N = 2: BinaryOpcode /// - N = 3: TernaryOpcode #[derive(Debug, Copy, Clone)] -pub(crate) struct StackOnlyOpcode; +pub(crate) struct StackOnlyOpcode; -impl Opcode for StackOnlyOpcode { +impl Opcode for StackOnlyOpcode { fn gen_associated_ops( state: &mut CircuitInputStateRef, steps: &[GethExecStep], ) -> Result<(), Error> { let step = &steps[0]; - // N stack reads - for i in 0..N { + + // N_POP stack reads + for i in 0..N_POP { state.push_stack_op( RW::READ, step.stack.nth_last_filled(i), step.stack.nth_last(i)?, - ); + )?; } - // Get operator result from next step and do stack write - let result_value = steps[1].stack.last()?; - state.push_stack_op( - RW::WRITE, - step.stack.last_filled().map(|a| a - 1 + N), - result_value, - ); + // N_PUSH stack writes + for i in 0..N_PUSH { + state.push_stack_op( + RW::WRITE, + steps[1].stack.nth_last_filled(N_PUSH - 1 - i), + steps[1].stack.nth_last(N_PUSH - 1 - i)?, + )?; + } Ok(()) } @@ -42,189 +44,120 @@ impl Opcode for StackOnlyOpcode { #[cfg(test)] mod stackonlyop_tests { use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; - use eth_types::evm_types::StackAddress; - use eth_types::{bytecode, word, Word}; + use crate::operation::StackOp; + use eth_types::bytecode; + use eth_types::evm_types::{OpcodeId, StackAddress}; + use eth_types::{bytecode::Bytecode, word, Word}; + use itertools::Itertools; use pretty_assertions::assert_eq; - #[test] - fn not_opcode_impl() -> Result<(), Error> { - let code = bytecode! { - PUSH32(word!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")) - #[start] - NOT - STOP - }; - + fn stack_only_opcode_impl( + opcode: OpcodeId, + code: Bytecode, + pops: Vec, + pushes: Vec, + ) { // Get the execution steps from the external tracer let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), + mock::new_single_tx_trace_code(&code).unwrap(), ); let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) + builder + .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - // Generate step corresponding to NOT - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - // Read a - state_ref.push_stack_op( - RW::READ, - StackAddress(1024 - 1), - word!("0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), - ); + let step = builder.block.txs()[0] + .steps() + .iter() + .find(|step| step.op == opcode) + .unwrap(); - // Write ~a - state_ref.push_stack_op( - RW::WRITE, - StackAddress(1024 - 1), - word!("0xfffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0"), + assert_eq!( + (0..N_POP) + .map(|idx| { + &builder.block.container.stack[step.bus_mapping_instance[idx].as_usize()] + }) + .map(|operation| (operation.rw(), operation.op().clone())) + .collect_vec(), + pops.into_iter().map(|pop| (RW::READ, pop)).collect_vec() ); - - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); - - // Compare first step bus mapping instance assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, + (0..N_PUSH) + .map(|idx| { + &builder.block.container.stack + [step.bus_mapping_instance[N_POP + idx].as_usize()] + }) + .map(|operation| (operation.rw(), operation.op().clone())) + .collect_vec(), + pushes + .into_iter() + .map(|push| (RW::WRITE, push)) + .collect_vec() ); - - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) } #[test] - fn add_opcode_impl() -> Result<(), Error> { - let code = bytecode! { - PUSH1(0x80u64) - PUSH1(0x80u64) - #[start] - ADD - STOP - }; - - // Get the execution steps from the external tracer - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), - ); - - let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) - .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - - // Generate step corresponding to ADD - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, + fn not_opcode_impl() { + stack_only_opcode_impl::<1, 1>( + OpcodeId::NOT, + bytecode! { + PUSH32(word!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")) + NOT + STOP + }, + vec![StackOp::new( + 1, + StackAddress(1023), + word!("0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"), + )], + vec![StackOp::new( + 1, + StackAddress(1023), + word!("0xfffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0"), + )], ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - let last_stack_pointer = StackAddress(1022); - let second_last_stack_pointer = StackAddress(1023); - let stack_value_a = Word::from(0x80); - let stack_value_b = Word::from(0x80); - let sum = Word::from(0x100); - - // Manage first stack read at latest stack position - state_ref.push_stack_op(RW::READ, last_stack_pointer, stack_value_a); - - // Manage second stack read at second latest stack position - state_ref.push_stack_op(RW::READ, second_last_stack_pointer, stack_value_b); - - // Add StackOp associated to the 0x80 push at the latest Stack pos. - state_ref.push_stack_op(RW::WRITE, second_last_stack_pointer, sum); - - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); - - // Compare first step bus mapping instance - assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, - ); - - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) } #[test] - fn addmod_opcode_impl() -> Result<(), Error> { - let code = bytecode! { - PUSH3(0xbcdef) - PUSH3(0x6789a) - PUSH3(0x12345) - #[start] - ADDMOD - STOP - }; - - // Get the execution steps from the external tracer - let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), + fn add_opcode_impl() { + stack_only_opcode_impl::<2, 1>( + OpcodeId::ADD, + bytecode! { + PUSH1(0x80u64) + PUSH1(0x60u64) + ADD + STOP + }, + vec![ + StackOp::new(1, StackAddress(1022), Word::from(0x60)), + StackOp::new(1, StackAddress(1023), Word::from(0x80)), + ], + vec![StackOp::new( + 1, + StackAddress(1023), + Word::from(0x60) + Word::from(0x80), + )], ); + } - let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) - .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - - // Generate step corresponding to ADDMOD - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - // Read a, b, n - state_ref.push_stack_op(RW::READ, StackAddress(1024 - 3), Word::from(0x12345)); - state_ref.push_stack_op(RW::READ, StackAddress(1024 - 2), Word::from(0x6789a)); - state_ref.push_stack_op(RW::READ, StackAddress(1024 - 1), Word::from(0xbcdef)); - - // Write a + b % n - state_ref.push_stack_op(RW::WRITE, StackAddress(1024 - 1), Word::from(0x79bdf)); - - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); - - // Compare first step bus mapping instance - assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, + #[test] + fn addmod_opcode_impl() { + stack_only_opcode_impl::<3, 1>( + OpcodeId::ADDMOD, + bytecode! { + PUSH3(0xbcdef) + PUSH3(0x6789a) + PUSH3(0x12345) + ADDMOD + STOP + }, + vec![ + StackOp::new(1, StackAddress(1021), Word::from(0x12345)), + StackOp::new(1, StackAddress(1022), Word::from(0x6789a)), + StackOp::new(1, StackAddress(1023), Word::from(0xbcdef)), + ], + vec![StackOp::new(1, StackAddress(1023), Word::from(0x79bdf))], ); - - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) } } diff --git a/bus-mapping/src/evm/opcodes/swap.rs b/bus-mapping/src/evm/opcodes/swap.rs index b02f733b49..fc288983c9 100644 --- a/bus-mapping/src/evm/opcodes/swap.rs +++ b/bus-mapping/src/evm/opcodes/swap.rs @@ -18,14 +18,14 @@ impl Opcode for Swap { // Peek b and a let stack_b_value_read = step.stack.nth_last(N)?; let stack_b_position = step.stack.nth_last_filled(N); - state.push_stack_op(RW::READ, stack_b_position, stack_b_value_read); + state.push_stack_op(RW::READ, stack_b_position, stack_b_value_read)?; let stack_a_value_read = step.stack.last()?; let stack_a_position = step.stack.last_filled(); - state.push_stack_op(RW::READ, stack_a_position, stack_a_value_read); + state.push_stack_op(RW::READ, stack_a_position, stack_a_value_read)?; // Write a into b_position, write b into a_position - state.push_stack_op(RW::WRITE, stack_b_position, stack_a_value_read); - state.push_stack_op(RW::WRITE, stack_a_position, stack_b_value_read); + state.push_stack_op(RW::WRITE, stack_b_position, stack_a_value_read)?; + state.push_stack_op(RW::WRITE, stack_a_position, stack_b_value_read)?; Ok(()) } @@ -34,76 +34,63 @@ impl Opcode for Swap { #[cfg(test)] mod swap_tests { use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; + use crate::operation::StackOp; + use eth_types::bytecode; use eth_types::evm_types::StackAddress; - use eth_types::{bytecode, Word}; + use eth_types::Word; + use itertools::Itertools; use pretty_assertions::assert_eq; #[test] - fn swap_opcode_impl() -> Result<(), Error> { + fn swap_opcode_impl() { let code = bytecode! { PUSH1(0x1) PUSH1(0x2) PUSH1(0x3) PUSH1(0x4) PUSH1(0x5) - PUSH1(0x6) - #[start] // [1,2,3,4,5,6] - SWAP1 // [1,2,3,4,6,5] - SWAP3 // [1,2,5,4,6,3] - SWAP5 // [3,2,5,4,6,1] + PUSH1(0x6) // [1,2,3,4,5,6] + SWAP1 // [1,2,3,4,6,5] + SWAP3 // [1,2,5,4,6,3] + SWAP5 // [3,2,5,4,6,1] STOP }; // Get the execution steps from the external tracer let block = crate::mock::BlockData::new_from_geth_data( - mock::new_single_tx_trace_code_at_start(&code).unwrap(), + mock::new_single_tx_trace_code(&code).unwrap(), ); let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) + builder + .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); // Generate steps corresponding to DUP1, DUP3, DUP5 for (i, (a, b)) in [(6, 5), (5, 3), (3, 1)].iter().enumerate() { - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[i], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); + let step = builder.block.txs()[0] + .steps() + .iter() + .filter(|step| step.op.is_swap()) + .collect_vec()[i]; let a_pos = StackAddress(1024 - 6); let b_pos = StackAddress(1024 - 5 + i * 2); let a_val = Word::from(*a); let b_val = Word::from(*b); - state_ref.push_stack_op(RW::READ, b_pos, b_val); - state_ref.push_stack_op(RW::READ, a_pos, a_val); - state_ref.push_stack_op(RW::WRITE, b_pos, a_val); - state_ref.push_stack_op(RW::WRITE, a_pos, b_val); - - tx.steps_mut().push(step); - } - - test_builder.block.txs_mut().push(tx); - - // Compare first 3 steps bus mapping instance - for i in 0..3 { assert_eq!( - builder.block.txs()[0].steps()[i].bus_mapping_instance, - test_builder.block.txs()[0].steps()[i].bus_mapping_instance + [0, 1, 2, 3] + .map(|idx| &builder.block.container.stack + [step.bus_mapping_instance[idx].as_usize()]) + .map(|operation| (operation.rw(), operation.op())), + [ + (RW::READ, &StackOp::new(1, b_pos, b_val)), + (RW::READ, &StackOp::new(1, a_pos, a_val)), + (RW::WRITE, &StackOp::new(1, b_pos, a_val)), + (RW::WRITE, &StackOp::new(1, a_pos, b_val)), + ] ); } - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) } } diff --git a/bus-mapping/src/evm/opcodes/timestamp.rs b/bus-mapping/src/evm/opcodes/timestamp.rs deleted file mode 100644 index 69a0ebce1d..0000000000 --- a/bus-mapping/src/evm/opcodes/timestamp.rs +++ /dev/null @@ -1,64 +0,0 @@ -#[cfg(test)] -mod timestamp_tests { - use crate::{ - circuit_input_builder::{ExecStep, TransactionContext}, - mock::BlockData, - operation::RW, - Error, - }; - use eth_types::{bytecode, evm_types::StackAddress}; - use mock::new_single_tx_trace_code_at_start; - use pretty_assertions::assert_eq; - - #[test] - fn timestamp_opcode_impl() -> Result<(), Error> { - let code = bytecode! { - #[start] - TIMESTAMP - STOP - }; - - // Get the execution steps from the external tracer - let block = - BlockData::new_from_geth_data(new_single_tx_trace_code_at_start(&code).unwrap()); - - let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - - let mut test_builder = block.new_circuit_input_builder(); - let mut tx = test_builder - .new_tx(&block.eth_tx, !block.geth_trace.failed) - .unwrap(); - let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap(); - - // Generate step corresponding to TIMESTAMP - let mut step = ExecStep::new( - &block.geth_trace.struct_logs[0], - 0, - test_builder.block_ctx.rwc, - 0, - ); - let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step); - - // Add the last Stack write - state_ref.push_stack_op( - RW::WRITE, - StackAddress::from(1024 - 1), - block.eth_block.timestamp, - ); - - tx.steps_mut().push(step); - test_builder.block.txs_mut().push(tx); - - // Compare first step bus mapping instance - assert_eq!( - builder.block.txs()[0].steps()[0].bus_mapping_instance, - test_builder.block.txs()[0].steps()[0].bus_mapping_instance, - ); - - // Compare containers - assert_eq!(builder.block.container, test_builder.block.container); - - Ok(()) - } -} diff --git a/bus-mapping/src/lib.rs b/bus-mapping/src/lib.rs index 458b7ad0b0..d071f4575a 100644 --- a/bus-mapping/src/lib.rs +++ b/bus-mapping/src/lib.rs @@ -106,8 +106,9 @@ //! "#; //! //! // We use some mock data as context for the trace -//! let eth_block = mock::new_block(); +//! let mut eth_block = mock::new_block(); //! let eth_tx = mock::new_tx(ð_block); +//! eth_block.transactions.push(eth_tx.clone()); //! let mut sdb = StateDB::new(); //! sdb.set_account(ð_tx.from, state_db::Account::zero()); //! sdb.set_account(&Address::zero(), state_db::Account::zero()); @@ -115,7 +116,7 @@ //! let mut builder = CircuitInputBuilder::new( //! sdb, //! CodeDB::new(), -//! Block::new::<()>(0.into(), Vec::new(), ð_block).unwrap(), +//! Block::new(0.into(), Vec::new(), ð_block).unwrap(), //! ); //! //! let geth_steps: Vec = serde_json::from_str(input_trace).unwrap(); @@ -125,7 +126,7 @@ //! struct_logs: geth_steps, //! }; //! // Here we update the circuit input with the data from the transaction trace. -//! builder.handle_tx(ð_tx, &geth_trace).unwrap(); +//! builder.handle_block(ð_block, &[geth_trace]).unwrap(); //! //! // Get an ordered vector with all of the Stack operations of this trace. //! let stack_ops = builder.block.container.sorted_stack(); diff --git a/bus-mapping/src/mock.rs b/bus-mapping/src/mock.rs index 81d8eda56f..cdd1b33572 100644 --- a/bus-mapping/src/mock.rs +++ b/bus-mapping/src/mock.rs @@ -20,11 +20,9 @@ pub struct BlockData { /// the lastest one is at history_hashes[history_hashes.len() - 1]. pub history_hashes: Vec, /// Block from geth - pub eth_block: eth_types::Block<()>, - /// Transaction from geth - pub eth_tx: eth_types::Transaction, + pub eth_block: eth_types::Block, /// Execution Trace from geth - pub geth_trace: eth_types::GethExecTrace, + pub geth_traces: Vec, } impl BlockData { @@ -44,7 +42,12 @@ impl BlockData { let mut code_db = CodeDB::new(); sdb.set_account(&geth_data.eth_block.author, state_db::Account::zero()); - sdb.set_account(&geth_data.eth_tx.from, state_db::Account::zero()); + for tx in geth_data.eth_block.transactions.iter() { + sdb.set_account(&tx.from, state_db::Account::zero()); + if let Some(to) = tx.to.as_ref() { + sdb.set_account(to, state_db::Account::zero()); + } + } for account in geth_data.accounts { let code_hash = code_db.insert(account.code.to_vec()); @@ -58,14 +61,14 @@ impl BlockData { }, ); } + Self { sdb, code_db, chain_id: geth_data.chain_id, history_hashes: geth_data.history_hashes, eth_block: geth_data.eth_block, - eth_tx: geth_data.eth_tx, - geth_trace: geth_data.geth_trace, + geth_traces: geth_data.geth_traces, } } } diff --git a/bus-mapping/src/operation.rs b/bus-mapping/src/operation.rs index b833c4d089..56e92180be 100644 --- a/bus-mapping/src/operation.rs +++ b/bus-mapping/src/operation.rs @@ -179,7 +179,7 @@ impl Op for MemoryOp { } fn reverse(&self) -> Self { - unreachable!() + unreachable!("MemoryOp can't be reverted") } } @@ -256,7 +256,7 @@ impl Op for StackOp { } fn reverse(&self) -> Self { - unreachable!() + unreachable!("StackOp can't be reverted") } } @@ -481,10 +481,10 @@ impl Op for TxAccessListAccountStorageOp { pub struct TxRefundOp { /// Transaction ID: Transaction index in the block starting at 1. pub tx_id: usize, - /// Refund Value after the operation - pub value: Word, - /// Refund Value before the operation - pub value_prev: Word, + /// Refund of gas after the operation + pub value: u64, + /// Refund of gas before the operation + pub value_prev: u64, } impl fmt::Debug for TxRefundOp { @@ -729,7 +729,7 @@ impl Op for CallContextOp { } fn reverse(&self) -> Self { - unreachable!() + unreachable!("CallContextOp can't be reverted") } } diff --git a/bus-mapping/src/state_db.rs b/bus-mapping/src/state_db.rs index bf7ebc26bf..cc7a5bc91e 100644 --- a/bus-mapping/src/state_db.rs +++ b/bus-mapping/src/state_db.rs @@ -73,8 +73,10 @@ impl Account { #[derive(Debug, Clone)] pub struct StateDB { state: HashMap, + // Fields with transaction lifespan, will be clear in `clear_access_list_and_refund`. access_list_account: HashSet
, access_list_account_storage: HashSet<(Address, U256)>, + refund: u64, } impl Default for StateDB { @@ -90,6 +92,7 @@ impl StateDB { state: HashMap::new(), access_list_account: HashSet::new(), access_list_account_storage: HashSet::new(), + refund: 0, } } @@ -148,6 +151,14 @@ impl StateDB { (found, acc.storage.get_mut(key).expect("key not inserted")) } + /// Increase nonce of account with `addr` and return the previous value. + pub fn increase_nonce(&mut self, addr: &Address) -> u64 { + let (_, account) = self.get_account_mut(addr); + let nonce = account.nonce.as_u64(); + account.nonce = account.nonce + 1; + nonce + } + /// Add `addr` into account access list. Returns `true` if it's not in the /// access list before. pub fn add_account_to_access_list(&mut self, addr: Address) -> bool { @@ -156,7 +167,7 @@ impl StateDB { /// Remove `addr` from account access list. pub fn remove_account_from_access_list(&mut self, addr: &Address) { - assert!(self.access_list_account.remove(addr)); + debug_assert!(self.access_list_account.remove(addr)); } /// Add `(addr, key)` into account storage access list. Returns `true` if @@ -167,7 +178,20 @@ impl StateDB { /// Remove `(addr, key)` from account storage access list. pub fn remove_account_storage_from_access_list(&mut self, pair: &(Address, Word)) { - assert!(self.access_list_account_storage.remove(pair)); + debug_assert!(self.access_list_account_storage.remove(pair)); + } + + /// Retrieve refund. + pub fn refund(&self) -> u64 { + self.refund + } + + /// Clear access list and refund. It should be invoked before processing + /// with new transaction with the same [`StateDB`]. + pub fn clear_access_list_and_refund(&mut self) { + self.access_list_account = HashSet::new(); + self.access_list_account_storage = HashSet::new(); + self.refund = 0; } } diff --git a/eth-types/src/evm_types.rs b/eth-types/src/evm_types.rs index 29ffa3272d..8a9ffdaaf5 100644 --- a/eth-types/src/evm_types.rs +++ b/eth-types/src/evm_types.rs @@ -1,12 +1,13 @@ //! Evm types needed for parsing instruction sets as well +use serde::{Deserialize, Serialize}; +use std::fmt; + pub mod memory; pub mod opcode_ids; pub mod stack; pub mod storage; -use serde::{Deserialize, Serialize}; -use std::fmt; pub use { memory::{Memory, MemoryAddress}, opcode_ids::OpcodeId, @@ -60,6 +61,9 @@ impl fmt::Debug for Gas { } } +/// Quotient for max refund of gas used +pub const MAX_REFUND_QUOTIENT_OF_GAS_USED: usize = 5; + /// Defines the gas consumption. #[derive(Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub struct GasCost(pub u64); diff --git a/eth-types/src/geth_types.rs b/eth-types/src/geth_types.rs index 81ee241f23..1c1d573849 100644 --- a/eth-types/src/geth_types.rs +++ b/eth-types/src/geth_types.rs @@ -125,11 +125,9 @@ pub struct GethData { /// the lastest one is at history_hashes[history_hashes.len() - 1]. pub history_hashes: Vec, /// Block from geth - pub eth_block: Block<()>, - /// Transaction from geth - pub eth_tx: crate::Transaction, + pub eth_block: Block, /// Execution Trace from geth - pub geth_trace: GethExecTrace, + pub geth_traces: Vec, /// Accounts pub accounts: Vec, } diff --git a/external-tracer/src/lib.rs b/external-tracer/src/lib.rs index 8c7618cf8c..8a97c5da18 100644 --- a/external-tracer/src/lib.rs +++ b/external-tracer/src/lib.rs @@ -20,11 +20,11 @@ pub struct TraceConfig { /// accounts pub accounts: HashMap, /// transaction - pub transaction: Transaction, + pub transactions: Vec, } /// Creates a trace for the specified config -pub fn trace(config: &TraceConfig) -> Result { +pub fn trace(config: &TraceConfig) -> Result, Error> { // Get the trace let trace_string = geth_utils::trace(&serde_json::to_string(config).unwrap()).map_err( |error| match error { diff --git a/geth-utils/example/add_sub.go b/geth-utils/example/add_sub.go index bb01649bf7..d58a3e033a 100644 --- a/geth-utils/example/add_sub.go +++ b/geth-utils/example/add_sub.go @@ -17,12 +17,12 @@ func main() { accounts := map[common.Address]gethutil.Account{address: {Code: assembly.Bytecode()}} tx := gethutil.Transaction{To: &address, GasLimit: 21100} - result, err := gethutil.TraceTx(gethutil.TraceConfig{Accounts: accounts, Transaction: tx}) + result, err := gethutil.Trace(gethutil.TraceConfig{Accounts: accounts, Transactions: []gethutil.Transaction{tx}}) if err != nil { fmt.Fprintf(os.Stderr, "failed to trace tx, err: %v\n", err) } - bytes, err := json.MarshalIndent(result.StructLogs, "", " ") + bytes, err := json.MarshalIndent(result[0].StructLogs, "", " ") if err != nil { fmt.Fprintf(os.Stderr, "failed to marshal logs, err: %v\n", err) } diff --git a/geth-utils/example/msize.go b/geth-utils/example/msize.go index f220550265..9c13f85d76 100644 --- a/geth-utils/example/msize.go +++ b/geth-utils/example/msize.go @@ -17,12 +17,12 @@ func main() { accounts := map[common.Address]gethutil.Account{address: {Code: assembly.Bytecode()}} tx := gethutil.Transaction{To: &address, GasLimit: 21100} - result, err := gethutil.TraceTx(gethutil.TraceConfig{Accounts: accounts, Transaction: tx}) + result, err := gethutil.Trace(gethutil.TraceConfig{Accounts: accounts, Transactions: []gethutil.Transaction{tx}}) if err != nil { fmt.Fprintf(os.Stderr, "failed to trace tx, err: %v\n", err) } - bytes, err := json.MarshalIndent(result.StructLogs, "", " ") + bytes, err := json.MarshalIndent(result[0].StructLogs, "", " ") if err != nil { fmt.Fprintf(os.Stderr, "failed to marshal logs, err: %v\n", err) } diff --git a/geth-utils/example/mstore_mload.go b/geth-utils/example/mstore_mload.go index 786c859107..ecfc6d91b5 100644 --- a/geth-utils/example/mstore_mload.go +++ b/geth-utils/example/mstore_mload.go @@ -17,12 +17,12 @@ func main() { accounts := map[common.Address]gethutil.Account{address: {Code: assembly.Bytecode()}} tx := gethutil.Transaction{To: &address, GasLimit: 21100} - result, err := gethutil.TraceTx(gethutil.TraceConfig{Accounts: accounts, Transaction: tx}) + result, err := gethutil.Trace(gethutil.TraceConfig{Accounts: accounts, Transactions: []gethutil.Transaction{tx}}) if err != nil { fmt.Fprintf(os.Stderr, "failed to trace tx, err: %v\n", err) } - bytes, err := json.MarshalIndent(result.StructLogs, "", " ") + bytes, err := json.MarshalIndent(result[0].StructLogs, "", " ") if err != nil { fmt.Fprintf(os.Stderr, "failed to marshal logs, err: %v\n", err) } diff --git a/geth-utils/example/sstore_sload.go b/geth-utils/example/sstore_sload.go index 1546abfb22..b3bc8a672e 100644 --- a/geth-utils/example/sstore_sload.go +++ b/geth-utils/example/sstore_sload.go @@ -17,12 +17,12 @@ func main() { accounts := map[common.Address]gethutil.Account{address: {Code: assembly.Bytecode()}} tx := gethutil.Transaction{To: &address, GasLimit: 46000} - result, err := gethutil.TraceTx(gethutil.TraceConfig{Accounts: accounts, Transaction: tx}) + result, err := gethutil.Trace(gethutil.TraceConfig{Accounts: accounts, Transactions: []gethutil.Transaction{tx}}) if err != nil { fmt.Fprintf(os.Stderr, "failed to trace tx, err: %v\n", err) } - bytes, err := json.MarshalIndent(result.StructLogs, "", " ") + bytes, err := json.MarshalIndent(result[0].StructLogs, "", " ") if err != nil { fmt.Fprintf(os.Stderr, "failed to marshal logs, err: %v\n", err) } diff --git a/geth-utils/gethutil/trace.go b/geth-utils/gethutil/trace.go index 3acce4d66a..e698dde4d2 100644 --- a/geth-utils/gethutil/trace.go +++ b/geth-utils/gethutil/trace.go @@ -118,10 +118,10 @@ type TraceConfig struct { HistoryHashes []*hexutil.Big `json:"history_hashes"` Block Block `json:"block_constants"` Accounts map[common.Address]Account `json:"accounts"` - Transaction Transaction `json:"transaction"` + Transactions []Transaction `json:"transactions"` } -func TraceTx(config TraceConfig) (*ExecutionResult, error) { +func Trace(config TraceConfig) ([]*ExecutionResult, error) { chainConfig := params.ChainConfig{ ChainID: toBigInt(config.ChainID), HomesteadBlock: big.NewInt(0), @@ -140,11 +140,35 @@ func TraceTx(config TraceConfig) (*ExecutionResult, error) { LondonBlock: big.NewInt(0), } - // If gas price is specified directly, the tx is treated as legacy one - if config.Transaction.GasPrice != nil { - config.Block.BaseFee = new(hexutil.Big) - config.Transaction.GasFeeCap = config.Transaction.GasPrice - config.Transaction.GasTipCap = config.Transaction.GasPrice + var blockGasLimit uint64 + messages := make([]types.Message, len(config.Transactions)) + for i, tx := range config.Transactions { + // If gas price is specified directly, the tx is treated as legacy type. + if tx.GasPrice != nil { + tx.GasFeeCap = tx.GasPrice + tx.GasTipCap = tx.GasPrice + } + + txAccessList := make(types.AccessList, len(tx.AccessList)) + for i, accessList := range tx.AccessList { + txAccessList[i].Address = accessList.Address + txAccessList[i].StorageKeys = accessList.StorageKeys + } + messages[i] = types.NewMessage( + tx.From, + tx.To, + uint64(tx.Nonce), + toBigInt(tx.Value), + uint64(tx.GasLimit), + toBigInt(tx.GasPrice), + toBigInt(tx.GasFeeCap), + toBigInt(tx.GasTipCap), + tx.CallData, + txAccessList, + false, + ) + + blockGasLimit += uint64(tx.GasLimit) } blockCtx := vm.BlockContext{ @@ -163,28 +187,8 @@ func TraceTx(config TraceConfig) (*ExecutionResult, error) { Time: toBigInt(config.Block.Timestamp), Difficulty: toBigInt(config.Block.Difficulty), BaseFee: toBigInt(config.Block.BaseFee), - GasLimit: toBigInt(config.Block.GasLimit).Uint64(), - } - - txAccessList := make(types.AccessList, len(config.Transaction.AccessList)) - for i, accessList := range config.Transaction.AccessList { - txAccessList[i].Address = accessList.Address - txAccessList[i].StorageKeys = accessList.StorageKeys + GasLimit: blockGasLimit, } - message := types.NewMessage( - config.Transaction.From, - config.Transaction.To, - uint64(config.Transaction.Nonce), - toBigInt(config.Transaction.Value), - uint64(config.Transaction.GasLimit), - toBigInt(config.Transaction.GasPrice), - toBigInt(config.Transaction.GasFeeCap), - toBigInt(config.Transaction.GasTipCap), - config.Transaction.CallData, - txAccessList, - false, - ) - txContext := core.NewEVMTxContext(message) // Setup state db with accounts from argument stateDB, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) @@ -198,20 +202,27 @@ func TraceTx(config TraceConfig) (*ExecutionResult, error) { stateDB.SetState(address, key, value) } } - stateDB.Finalise(chainConfig.IsByzantium(blockCtx.BlockNumber)) - - // Run the transaction with tracing enabled. - tracer := logger.NewStructLogger(&logger.Config{EnableMemory: true}) - evm := vm.NewEVM(blockCtx, txContext, stateDB, &chainConfig, vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true}) - result, err := core.ApplyMessage(evm, message, new(core.GasPool).AddGas(message.Gas())) - if err != nil { - return nil, err + stateDB.Finalise(true) + + // Run the transactions with tracing enabled. + executionResults := make([]*ExecutionResult, len(config.Transactions)) + for i, message := range messages { + tracer := logger.NewStructLogger(&logger.Config{EnableMemory: true}) + evm := vm.NewEVM(blockCtx, core.NewEVMTxContext(message), stateDB, &chainConfig, vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true}) + + result, err := core.ApplyMessage(evm, message, new(core.GasPool).AddGas(message.Gas())) + if err != nil { + return nil, fmt.Errorf("Failed to apply config.Transactions[%d]: %w", i, err) + } + stateDB.Finalise(true) + + executionResults[i] = &ExecutionResult{ + Gas: result.UsedGas, + Failed: result.Failed(), + ReturnValue: fmt.Sprintf("%x", result.ReturnData), + StructLogs: FormatLogs(tracer.StructLogs()), + } } - return &ExecutionResult{ - Gas: result.UsedGas, - Failed: result.Failed(), - ReturnValue: fmt.Sprintf("%x", result.ReturnData), - StructLogs: FormatLogs(tracer.StructLogs()), - }, nil + return executionResults, nil } diff --git a/geth-utils/lib/lib.go b/geth-utils/lib/lib.go index 634d4f7fc6..5f428c3583 100644 --- a/geth-utils/lib/lib.go +++ b/geth-utils/lib/lib.go @@ -21,14 +21,14 @@ func CreateTrace(configStr *C.char) *C.char { return C.CString(fmt.Sprintf("Failed to unmarshal config, err: %v", err)) } - executionResult, err := gethutil.TraceTx(config) + executionResults, err := gethutil.Trace(config) if err != nil { - return C.CString(fmt.Sprintf("Failed to trace tx, err: %v", err)) + return C.CString(fmt.Sprintf("Failed to run Trace, err: %v", err)) } - bytes, err := json.MarshalIndent(executionResult, "", " ") + bytes, err := json.MarshalIndent(executionResults, "", " ") if err != nil { - return C.CString(fmt.Sprintf("Failed to marshal ExecutionResult, err: %v", err)) + return C.CString(fmt.Sprintf("Failed to marshal []ExecutionResult, err: %v", err)) } return C.CString(string(bytes)) diff --git a/geth-utils/src/lib.rs b/geth-utils/src/lib.rs index 30268ed162..81d8ae6f31 100644 --- a/geth-utils/src/lib.rs +++ b/geth-utils/src/lib.rs @@ -57,18 +57,22 @@ mod test { for config in [ // Minimal call tx with gas_limit = 21000 r#"{ - "transaction": { - "from": "0x00000000000000000000000000000000000000fe", - "to": "0x00000000000000000000000000000000000000ff", - "gas_limit": "0x5208" - } + "transactions": [ + { + "from": "0x00000000000000000000000000000000000000fe", + "to": "0x00000000000000000000000000000000000000ff", + "gas_limit": "0x5208" + } + ] }"#, // Minimal creation tx with gas_limit = 53000 r#"{ - "transaction": { - "from": "0x00000000000000000000000000000000000000fe", - "gas_limit": "0xcf08" - } + "transactions": [ + { + "from": "0x00000000000000000000000000000000000000fe", + "gas_limit": "0xcf08" + } + ] }"#, // Normal call tx with gas_limit = 21000 and gas_price = 2 Gwei r#"{ @@ -77,12 +81,14 @@ mod test { "balance": "0x2632e314a000" } }, - "transaction": { - "from": "0x00000000000000000000000000000000000000fe", - "to": "0x00000000000000000000000000000000000000ff", - "gas_limit": "0x5208", - "gas_price": "0x77359400" - } + "transactions": [ + { + "from": "0x00000000000000000000000000000000000000fe", + "to": "0x00000000000000000000000000000000000000ff", + "gas_limit": "0x5208", + "gas_price": "0x77359400" + } + ] }"#, ] { assert!(trace(config).is_ok()); @@ -94,28 +100,34 @@ mod test { for config in [ // Insufficient gas for intrinsic usage r#"{ - "transaction": { - "from": "0x00000000000000000000000000000000000000fe", - "to": "0x00000000000000000000000000000000000000ff" - } + "transactions": [ + { + "from": "0x00000000000000000000000000000000000000fe", + "to": "0x00000000000000000000000000000000000000ff" + } + ] }"#, // Insufficient balance to buy gas r#"{ - "transaction": { - "from": "0x00000000000000000000000000000000000000fe", - "to": "0x00000000000000000000000000000000000000ff", - "gas_limit": "0x5208", - "gas_price": "0x1111" - } + "transactions": [ + { + "from": "0x00000000000000000000000000000000000000fe", + "to": "0x00000000000000000000000000000000000000ff", + "gas_limit": "0x5208", + "gas_price": "0x1111" + } + ] }"#, // Insufficient balance to do the first transfer r#"{ - "transaction": { - "from": "0x00000000000000000000000000000000000000fe", - "to": "0x00000000000000000000000000000000000000ff", - "value": "0x100", - "gas_limit": "0x5208" - } + "transactions": [ + { + "from": "0x00000000000000000000000000000000000000fe", + "to": "0x00000000000000000000000000000000000000ff", + "value": "0x100", + "gas_limit": "0x5208" + } + ] }"#, ] { assert!(trace(config).is_err()) diff --git a/mock/src/lib.rs b/mock/src/lib.rs index 8d4242595f..701d362cdd 100644 --- a/mock/src/lib.rs +++ b/mock/src/lib.rs @@ -19,18 +19,16 @@ lazy_static! { address!("0x00000000000000000000000000000000c014ba5e"); } -/// Create a new block with a single tx that executes the code found in the -/// account with address 0x0 (which can call code in the other accounts), -/// with the given gas limit. -/// The trace will be generated automatically with the external_tracer -/// from the accounts code. -pub fn new_single_tx_trace_accounts_gas( +/// Create a new block with txs. +pub fn new( accounts: Vec, - gas: Gas, + eth_txs: Vec, ) -> Result { - let eth_block = new_block(); - let mut eth_tx = new_tx(ð_block); - eth_tx.gas = Word::from(gas.0); + let mut eth_block = new_block(); + eth_block.transactions = eth_txs; + for (idx, tx) in eth_block.transactions.iter_mut().enumerate() { + tx.transaction_index = Some(idx.into()) + } let trace_config = TraceConfig { chain_id: MOCK_CHAIN_ID.into(), @@ -41,20 +39,37 @@ pub fn new_single_tx_trace_accounts_gas( .iter() .map(|account| (account.address, account.clone())) .collect(), - transaction: Transaction::from_eth_tx(ð_tx), + transactions: eth_block + .transactions + .iter() + .map(Transaction::from_eth_tx) + .collect(), }; - let geth_trace = trace(&trace_config)?; + let geth_traces = trace(&trace_config)?; Ok(GethData { chain_id: trace_config.chain_id, history_hashes: trace_config.history_hashes, eth_block, - eth_tx, - geth_trace, + geth_traces, accounts, }) } +/// Create a new block with a single tx that executes the code found in the +/// account with address 0x0 (which can call code in the other accounts), +/// with the given gas limit. +/// The trace will be generated automatically with the external_tracer +/// from the accounts code. +pub fn new_single_tx_trace_accounts_gas( + accounts: Vec, + gas: Gas, +) -> Result { + let mut eth_tx = new_tx(&new_block()); + eth_tx.gas = Word::from(gas.0); + new(accounts, vec![eth_tx]) +} + /// Create a new block with a single tx that executes the code found in the /// account with address 0x0 (which can call code in the other accounts). /// The trace will be generated automatically with the external_tracer @@ -89,19 +104,8 @@ pub fn new_single_tx_trace_code_2(code_a: &Bytecode, code_b: &Bytecode) -> Resul new_single_tx_trace_accounts(vec![tracer_account_a, tracer_account_b]) } -/// Create a new block with a single tx that executes the code passed by -/// argument. The trace will be generated automatically with the -/// external_tracer from the code. The trace steps will start at the -/// "start" position as tagged in the code. -pub fn new_single_tx_trace_code_at_start(code: &Bytecode) -> Result { - let mut geth_data = new_single_tx_trace_code(code)?; - geth_data.geth_trace.struct_logs = - geth_data.geth_trace.struct_logs[code.get_pos("start")..].to_vec(); - Ok(geth_data) -} - /// Generate a new mock block with preloaded data, useful for tests. -pub fn new_block() -> Block<()> { +pub fn new_block() -> Block { eth_types::Block { hash: Some(Hash::zero()), parent_hash: Hash::zero(), @@ -113,7 +117,7 @@ pub fn new_block() -> Block<()> { 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)), + base_fee_per_gas: Some(Word::zero()), extra_data: Bytes::default(), logs_bloom: None, timestamp: Word::from(1633398551u64), diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 45c469623e..eaa29a080f 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -430,6 +430,7 @@ pub mod test { run_test_circuit( block, vec![ + FixedTableTag::Range5, FixedTableTag::Range16, FixedTableTag::Range32, FixedTableTag::Range256, diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 75b0246a6e..2c7992f388 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -15,7 +15,7 @@ use halo2_proofs::{ plonk::{Column, ConstraintSystem, Error, Expression, Fixed, Selector}, poly::Rotation, }; -use std::collections::HashMap; +use std::{collections::HashMap, iter}; mod add; mod begin_tx; @@ -29,7 +29,9 @@ mod callvalue; mod coinbase; mod comparator; mod dup; -mod error_oog_pure_memory; +mod end_block; +mod end_tx; +mod error_oog_static_memory; mod gas; mod jump; mod jumpdest; @@ -62,7 +64,9 @@ use callvalue::CallValueGadget; use coinbase::CoinbaseGadget; use comparator::ComparatorGadget; use dup::DupGadget; -use error_oog_pure_memory::ErrorOOGPureMemoryGadget; +use end_block::EndBlockGadget; +use end_tx::EndTxGadget; +use error_oog_static_memory::ErrorOOGStaticMemoryGadget; use gas::GasGadget; use jump::JumpGadget; use jumpdest::JumpdestGadget; @@ -105,6 +109,7 @@ pub(crate) trait ExecutionGadget { pub(crate) struct ExecutionConfig { q_step: Selector, q_step_first: Selector, + q_step_last: Selector, step: Step, presets_map: HashMap>>, add_gadget: AddGadget, @@ -119,7 +124,9 @@ pub(crate) struct ExecutionConfig { call_value_gadget: CallValueGadget, comparator_gadget: ComparatorGadget, dup_gadget: DupGadget, - error_oog_pure_memory_gadget: ErrorOOGPureMemoryGadget, + end_block_gadget: EndBlockGadget, + end_tx_gadget: EndTxGadget, + error_oog_static_memory_gadget: ErrorOOGStaticMemoryGadget, jump_gadget: JumpGadget, jumpdest_gadget: JumpdestGadget, jumpi_gadget: JumpiGadget, @@ -159,6 +166,7 @@ impl ExecutionConfig { { let q_step = meta.complex_selector(); let q_step_first = meta.complex_selector(); + let q_step_last = meta.complex_selector(); let qs_byte_lookup = meta.advice_column(); let advices = [(); STEP_WIDTH].map(|_| meta.advice_column()); @@ -170,6 +178,7 @@ impl ExecutionConfig { meta.create_gate("Constrain execution state", |meta| { let q_step = meta.query_selector(q_step); let q_step_first = meta.query_selector(q_step_first); + let q_step_last = meta.query_selector(q_step_last); // Only one of execution_state should be enabled let sum_to_one = ( @@ -189,21 +198,93 @@ impl ExecutionConfig { ) }); - // TODO: ExecutionState transition needs to be constraint to avoid - // transition from non-terminator to BeginTx. + // ExecutionState transition should be correct. + let execution_state_transition = { + let q_step_last = q_step_last.clone(); + iter::empty() + .chain( + [ + ( + "EndTx can only transit to BeginTx or EndBlock", + ExecutionState::EndTx, + vec![ExecutionState::BeginTx, ExecutionState::EndBlock], + ), + ( + "EndBlock can only transit to EndBlock", + ExecutionState::EndBlock, + vec![ExecutionState::EndBlock], + ), + ] + .map(|(name, from, to)| { + ( + name, + step_curr.execution_state_selector([from]) + * (1.expr() - step_next.execution_state_selector(to)), + ) + }), + ) + .chain( + [ + ( + "Only EndTx can transit to BeginTx", + ExecutionState::BeginTx, + vec![ExecutionState::EndTx], + ), + ( + "Only ExecutionState which halts or BeginTx can transit to EndTx", + ExecutionState::EndTx, + ExecutionState::iterator() + .filter(ExecutionState::halts) + .chain(iter::once(ExecutionState::BeginTx)) + .collect(), + ), + ( + "Only EndTx or EndBlock can transit to EndBlock", + ExecutionState::EndBlock, + vec![ExecutionState::EndTx, ExecutionState::EndBlock], + ), + ( + "Only ExecutionState which copies memory to memory can transit to CopyToMemory", + ExecutionState::CopyToMemory, + vec![ExecutionState::CopyToMemory, ExecutionState::CALLDATACOPY], + ), + ] + .map(|(name, to, from)| { + ( + name, + step_next.execution_state_selector([to]) + * (1.expr() - step_curr.execution_state_selector(from)), + ) + }), + ) + .map(move |(name, poly)| (name, (1.expr() - q_step_last.clone()) * poly)) + }; - let first_step_check = { - let begin_tx_selector = step_curr.execution_state_selector(ExecutionState::BeginTx); - std::iter::once(( + let _first_step_check = { + let begin_tx_selector = + step_curr.execution_state_selector([ExecutionState::BeginTx]); + iter::once(( "First step should be BeginTx", - q_step_first * (begin_tx_selector - 1u64.expr()), + q_step_first * (1.expr() - begin_tx_selector), + )) + }; + + let _last_step_check = { + let end_block_selector = + step_curr.execution_state_selector([ExecutionState::EndBlock]); + iter::once(( + "Last step should be EndBlock", + q_step_last * (1.expr() - end_block_selector), )) }; - std::iter::once(sum_to_one) + iter::once(sum_to_one) .chain(bool_checks) + .chain(execution_state_transition) .map(move |(name, poly)| (name, q_step.clone() * poly)) - .chain(first_step_check) + // TODO: Enable these after test of CALLDATACOPY is complete. + // .chain(first_step_check) + // .chain(last_step_check) }); // Use qs_byte_lookup as selector to do byte range lookup on each advice @@ -244,6 +325,7 @@ impl ExecutionConfig { let config = Self { q_step, q_step_first, + q_step_last, add_gadget: configure_gadget!(), mul_gadget: configure_gadget!(), bitwise_gadget: configure_gadget!(), @@ -256,7 +338,9 @@ impl ExecutionConfig { call_value_gadget: configure_gadget!(), comparator_gadget: configure_gadget!(), dup_gadget: configure_gadget!(), - error_oog_pure_memory_gadget: configure_gadget!(), + end_block_gadget: configure_gadget!(), + end_tx_gadget: configure_gadget!(), + error_oog_static_memory_gadget: configure_gadget!(), jump_gadget: configure_gadget!(), jumpdest_gadget: configure_gadget!(), jumpi_gadget: configure_gadget!(), @@ -405,7 +489,7 @@ impl ExecutionConfig { }; } - lookup!(Table::Fixed, fixed_table, "fixed table"); + lookup!(Table::Fixed, fixed_table, "Fixed table"); lookup!(Table::Tx, tx_table, "Tx table"); lookup!(Table::Rw, rw_table, "RW table"); lookup!(Table::Bytecode, bytecode_table, "Bytecode table"); @@ -421,15 +505,14 @@ impl ExecutionConfig { || "Execution step", |mut region| { let mut offset = 0; + + self.q_step_first.enable(&mut region, offset)?; + for transaction in &block.txs { for step in &transaction.steps { let call = &transaction.calls[step.call_index]; self.q_step.enable(&mut region, offset)?; - if offset == 0 { - self.q_step_first.enable(&mut region, offset)?; - } - self.assign_exec_step(&mut region, offset, block, transaction, call, step)?; offset += STEP_HEIGHT; @@ -440,6 +523,7 @@ impl ExecutionConfig { )?; // TODO: Pad leftover region to the desired capacity + // TODO: Enable q_step_last Ok(()) } @@ -454,6 +538,9 @@ impl ExecutionConfig { || "Execution step", |mut region| { let mut offset = 0; + + self.q_step_first.enable(&mut region, offset)?; + for transaction in &block.txs { for step in &transaction.steps { let call = &transaction.calls[step.call_index]; @@ -464,6 +551,9 @@ impl ExecutionConfig { offset += STEP_HEIGHT; } } + + self.q_step_last.enable(&mut region, offset - STEP_HEIGHT)?; + Ok(()) }, ) @@ -497,6 +587,10 @@ impl ExecutionConfig { match step.execution_state { ExecutionState::BeginTx => assign_exec_step!(self.begin_tx_gadget), + ExecutionState::EndTx => assign_exec_step!(self.end_tx_gadget), + ExecutionState::EndBlock => { + assign_exec_step!(self.end_block_gadget) + } ExecutionState::STOP => assign_exec_step!(self.stop_gadget), ExecutionState::ADD => assign_exec_step!(self.add_gadget), ExecutionState::MUL => assign_exec_step!(self.mul_gadget), @@ -544,8 +638,8 @@ impl ExecutionConfig { ExecutionState::CALLDATALOAD => { assign_exec_step!(self.calldataload_gadget) } - ExecutionState::ErrorOutOfGasPureMemory => { - assign_exec_step!(self.error_oog_pure_memory_gadget) + ExecutionState::ErrorOutOfGasStaticMemoryExpansion => { + assign_exec_step!(self.error_oog_static_memory_gadget) } ExecutionState::CALLDATASIZE => { assign_exec_step!(self.calldatasize_gadget) diff --git a/zkevm-circuits/src/evm_circuit/execution/add.rs b/zkevm-circuits/src/evm_circuit/execution/add.rs index 538e18fcef..fd56a5194d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/add.rs +++ b/zkevm-circuits/src/evm_circuit/execution/add.rs @@ -23,7 +23,7 @@ use halo2_proofs::{circuit::Region, plonk::Error}; #[derive(Clone, Debug)] pub(crate) struct AddGadget { same_context: SameContextGadget, - add_words: AddWordsGadget, + add_words: AddWordsGadget, is_sub: PairSelectGadget, } @@ -37,8 +37,8 @@ impl ExecutionGadget for AddGadget { let a = cb.query_word(); let b = cb.query_word(); - let add_words = AddWordsGadget::construct(cb, [a.clone(), b.clone()]); - let c = add_words.sum(); + let c = cb.query_word(); + let add_words = AddWordsGadget::construct(cb, [a.clone(), b.clone()], c.clone()); // Swap a and c if opcode is SUB let is_sub = PairSelectGadget::construct( @@ -59,9 +59,10 @@ impl ExecutionGadget for AddGadget { rw_counter: Delta(3.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta(1.expr()), - ..Default::default() + gas_left: Delta(-OpcodeId::ADD.constant_gas_cost().expr()), + ..StepStateTransition::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -112,7 +113,6 @@ mod test { let bytecode = bytecode! { PUSH32(a) PUSH32(b) - #[start] .write_op(opcode) STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs index 581fe9fab2..233e4baedb 100644 --- a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs @@ -1,7 +1,7 @@ use crate::{ evm_circuit::{ execution::ExecutionGadget, - param::{N_BYTES_GAS, STACK_CAPACITY}, + param::N_BYTES_GAS, step::ExecutionState, table::{AccountFieldTag, CallContextFieldTag, TxContextFieldTag}, util::{ @@ -86,13 +86,14 @@ impl ExecutionGadget for BeginTxGadget { AccountFieldTag::Nonce, tx_nonce.expr() + 1.expr(), tx_nonce.expr(), + None, ); // TODO: Implement EIP 1559 (currently it only supports legacy // transaction format) // Calculate transaction gas fee let mul_gas_fee_by_gas = - MulWordByU64Gadget::construct(cb, tx_gas_price.clone(), tx_gas.clone(), true); + MulWordByU64Gadget::construct(cb, tx_gas_price.clone(), tx_gas.expr()); // TODO: Take gas cost of access list (EIP 2930) into consideration. // Use intrinsic gas @@ -107,8 +108,20 @@ impl ExecutionGadget for BeginTxGadget { let sufficient_gas_left = RangeCheckGadget::construct(cb, gas_left.clone()); // Prepare access list of caller and callee - cb.account_access_list_write(tx_id.expr(), tx_caller_address.expr(), 1.expr(), 0.expr()); - cb.account_access_list_write(tx_id.expr(), tx_callee_address.expr(), 1.expr(), 0.expr()); + cb.account_access_list_write( + tx_id.expr(), + tx_caller_address.expr(), + 1.expr(), + 0.expr(), + None, + ); + cb.account_access_list_write( + tx_id.expr(), + tx_callee_address.expr(), + 1.expr(), + 0.expr(), + None, + ); // Transfer value from caller to callee let transfer_with_gas_fee = TransferWithGasFeeGadget::construct( @@ -177,8 +190,6 @@ impl ExecutionGadget for BeginTxGadget { is_root: To(true.expr()), is_create: To(false.expr()), code_source: To(code_hash.expr()), - program_counter: To(0.expr()), - stack_pointer: To(STACK_CAPACITY.expr()), gas_left: To(gas_left), state_write_counter: To(2.expr()), ..StepStateTransition::new_context() @@ -272,297 +283,52 @@ impl ExecutionGadget for BeginTxGadget { #[cfg(test)] mod test { use crate::evm_circuit::{ - param::STACK_CAPACITY, - step::ExecutionState, - table::{AccountFieldTag, CallContextFieldTag, RwTableTag}, - test::{rand_bytes, rand_fp, rand_range, run_test_circuit_incomplete_fixed_table}, - witness::{Block, Bytecode, Call, CodeSource, ExecStep, Rw, RwMap, Transaction}, - }; - use eth_types::{ - self, address, - evm_types::{GasCost, OpcodeId}, - Address, ToWord, Word, + test::{rand_bytes, rand_range, run_test_circuit_incomplete_fixed_table}, + witness::block_convert, }; - use std::convert::TryInto; + use bus_mapping::evm::OpcodeId; + use eth_types::{self, address, bytecode, evm_types::GasCost, geth_types::Account, Word}; fn test_ok(tx: eth_types::Transaction, is_success: bool) { - let rw_counter_end_of_reversion = if is_success { 0 } else { 23 }; - - let gas_fee = tx.gas * tx.gas_price.unwrap_or_else(Word::zero); - let call_data_gas_cost = tx - .input - .0 - .iter() - .fold(0, |acc, byte| acc + if *byte == 0 { 4 } else { 16 }); - let intrinsic_gas_cost = if tx.to.is_none() { - GasCost::CREATION_TX.as_u64() - } else { - GasCost::TX.as_u64() - } + call_data_gas_cost; - - let from_balance_prev = Word::from(10_i32).pow(20_i32.into()); - let to_balance_prev = Word::zero(); - let from_balance = from_balance_prev - tx.value - gas_fee; - let to_balance = to_balance_prev + tx.value; - - let randomness = rand_fp(); - let bytecode = Bytecode::new(vec![OpcodeId::STOP.as_u8()]); - let block = Block { - randomness, - txs: vec![Transaction { - id: 1, - nonce: tx.nonce.try_into().unwrap(), - gas: tx.gas.try_into().unwrap(), - gas_price: tx.gas_price.unwrap_or_else(Word::zero), - caller_address: tx.from, - callee_address: tx.to.unwrap_or_else(Address::zero), - is_create: tx.to.is_none(), - value: tx.value, - call_data: tx.input.to_vec(), - call_data_length: tx.input.0.len(), - call_data_gas_cost, - calls: vec![Call { - id: 1, - is_root: true, - is_create: false, - code_source: CodeSource::Account(bytecode.hash), - rw_counter_end_of_reversion, - is_persistent: is_success, - is_success, - ..Default::default() - }], - steps: vec![ - ExecStep { - rw_indices: [ - vec![ - (RwTableTag::CallContext, 0), - (RwTableTag::CallContext, 1), - (RwTableTag::CallContext, 2), - (RwTableTag::Account, 0), - (RwTableTag::TxAccessListAccount, 0), - (RwTableTag::TxAccessListAccount, 1), - (RwTableTag::Account, 1), - (RwTableTag::Account, 2), - (RwTableTag::Account, 3), - (RwTableTag::CallContext, 3), - (RwTableTag::CallContext, 4), - (RwTableTag::CallContext, 5), - (RwTableTag::CallContext, 6), - (RwTableTag::CallContext, 7), - (RwTableTag::CallContext, 8), - (RwTableTag::CallContext, 9), - ], - if is_success { - vec![] - } else { - vec![(RwTableTag::Account, 4), (RwTableTag::Account, 5)] - }, - ] - .concat(), - execution_state: ExecutionState::BeginTx, - rw_counter: 1, - gas_cost: intrinsic_gas_cost, + let block_data = bus_mapping::mock::BlockData::new_from_geth_data( + mock::new( + vec![ + Account { + address: tx.from, + balance: Word::from(10).pow(20.into()), ..Default::default() }, - ExecStep { - execution_state: ExecutionState::STOP, - rw_counter: 20, - program_counter: 0, - stack_pointer: STACK_CAPACITY, - gas_left: 0, - opcode: Some(OpcodeId::STOP), - state_write_counter: 2, + Account { + address: tx.to.unwrap_or_default(), + code: if is_success { + bytecode! { + PUSH1(0) + PUSH1(0) + RETURN + } + .to_vec() + .into() + } else { + bytecode! { + PUSH1(0) + PUSH1(0) + REVERT + } + .to_vec() + .into() + }, ..Default::default() }, ], - }], - rws: RwMap( - [ - ( - RwTableTag::TxAccessListAccount, - vec![ - Rw::TxAccessListAccount { - rw_counter: 5, - is_write: true, - tx_id: 1, - account_address: tx.from, - value: true, - value_prev: false, - }, - Rw::TxAccessListAccount { - rw_counter: 6, - is_write: true, - tx_id: 1, - account_address: tx.to.unwrap(), - value: true, - value_prev: false, - }, - ], - ), - ( - RwTableTag::Account, - [ - vec![ - Rw::Account { - rw_counter: 4, - is_write: true, - account_address: tx.from, - field_tag: AccountFieldTag::Nonce, - value: tx.nonce + Word::one(), - value_prev: tx.nonce, - }, - Rw::Account { - rw_counter: 7, - is_write: true, - account_address: tx.from, - field_tag: AccountFieldTag::Balance, - value: from_balance, - value_prev: from_balance_prev, - }, - Rw::Account { - rw_counter: 8, - is_write: true, - account_address: tx.to.unwrap_or_else(Address::zero), - field_tag: AccountFieldTag::Balance, - value: to_balance, - value_prev: to_balance_prev, - }, - Rw::Account { - rw_counter: 9, - is_write: false, - account_address: tx.to.unwrap_or_else(Address::zero), - field_tag: AccountFieldTag::CodeHash, - value: bytecode.hash, - value_prev: bytecode.hash, - }, - ], - if is_success { - vec![] - } else { - vec![ - Rw::Account { - rw_counter: rw_counter_end_of_reversion - 1, - is_write: true, - account_address: tx.to.unwrap_or_else(Address::zero), - field_tag: AccountFieldTag::Balance, - value: to_balance_prev, - value_prev: to_balance, - }, - Rw::Account { - rw_counter: rw_counter_end_of_reversion, - is_write: true, - account_address: tx.from, - field_tag: AccountFieldTag::Balance, - value: from_balance_prev, - value_prev: from_balance, - }, - ] - }, - ] - .concat(), - ), - ( - RwTableTag::CallContext, - vec![ - Rw::CallContext { - rw_counter: 1, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::TxId, - value: Word::one(), - }, - Rw::CallContext { - rw_counter: 2, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::RwCounterEndOfReversion, - value: Word::from(rw_counter_end_of_reversion), - }, - Rw::CallContext { - rw_counter: 3, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::IsPersistent, - value: Word::from(is_success as u64), - }, - Rw::CallContext { - rw_counter: 10, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::Depth, - value: Word::one(), - }, - Rw::CallContext { - rw_counter: 11, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::CallerAddress, - value: tx.from.to_word(), - }, - Rw::CallContext { - rw_counter: 12, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::CalleeAddress, - value: tx.to.unwrap_or_else(Address::zero).to_word(), - }, - Rw::CallContext { - rw_counter: 13, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::CallDataOffset, - value: Word::zero(), - }, - Rw::CallContext { - rw_counter: 14, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::CallDataLength, - value: tx.input.0.len().into(), - }, - Rw::CallContext { - rw_counter: 15, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::Value, - value: tx.value, - }, - Rw::CallContext { - rw_counter: 16, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::IsStatic, - value: Word::zero(), - }, - Rw::CallContext { - rw_counter: 17, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::LastCalleeId, - value: Word::zero(), - }, - Rw::CallContext { - rw_counter: 18, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::LastCalleeReturnDataOffset, - value: Word::zero(), - }, - Rw::CallContext { - rw_counter: 19, - is_write: false, - call_id: 1, - field_tag: CallContextFieldTag::LastCalleeReturnDataLength, - value: Word::zero(), - }, - ], - ), - ] - .into(), - ), - bytecodes: vec![bytecode], - ..Default::default() - }; + vec![tx], + ) + .unwrap(), + ); + let mut builder = block_data.new_circuit_input_builder(); + builder + .handle_block(&block_data.eth_block, &block_data.geth_traces) + .unwrap(); + let block = block_convert(&builder.block, &builder.code_db); assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); } @@ -574,7 +340,8 @@ mod test { ) -> eth_types::Transaction { let from = address!("0x00000000000000000000000000000000000000fe"); let to = address!("0x00000000000000000000000000000000000000ff"); - let minimal_gas = Word::from(GasCost::TX.as_u64()); + let minimal_gas = + Word::from(GasCost::TX.as_u64() + 2 * OpcodeId::PUSH32.constant_gas_cost().as_u64()); let one_ether = Word::from(10).pow(18.into()); let two_gwei = Word::from(2_000_000_000); eth_types::Transaction { @@ -598,7 +365,7 @@ mod test { // Transfer nothing with some calldata test_ok( - mock_tx(None, Some(21080), None, vec![1, 2, 3, 4, 0, 0, 0, 0]), + mock_tx(None, Some(21086), None, vec![1, 2, 3, 4, 0, 0, 0, 0]), false, ); } @@ -623,7 +390,7 @@ mod test { mock_tx( None, None, - Some(Word::from(rand_range(0..42857142857143u64))), + Some(Word::from(rand_range(0..4712939160239931u64))), vec![], ), true, @@ -645,7 +412,7 @@ mod test { mock_tx( None, None, - Some(Word::from(rand_range(0..42857142857143u64))), + Some(Word::from(rand_range(0..4712939160239931u64))), vec![], ), false, diff --git a/zkevm-circuits/src/evm_circuit/execution/bitwise.rs b/zkevm-circuits/src/evm_circuit/execution/bitwise.rs index a67638dc3b..fc62a757f1 100644 --- a/zkevm-circuits/src/evm_circuit/execution/bitwise.rs +++ b/zkevm-circuits/src/evm_circuit/execution/bitwise.rs @@ -65,9 +65,10 @@ impl ExecutionGadget for BitwiseGadget { rw_counter: Delta(3.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta(1.expr()), + gas_left: Delta(-OpcodeId::AND.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -116,7 +117,6 @@ mod test { PUSH32(a) PUSH32(b) PUSH32(a) - #[start] AND POP OR diff --git a/zkevm-circuits/src/evm_circuit/execution/byte.rs b/zkevm-circuits/src/evm_circuit/execution/byte.rs index 838e9520af..be71a6440f 100644 --- a/zkevm-circuits/src/evm_circuit/execution/byte.rs +++ b/zkevm-circuits/src/evm_circuit/execution/byte.rs @@ -13,6 +13,7 @@ use crate::{ util::Expr, }; use array_init::array_init; +use bus_mapping::evm::OpcodeId; use eth_types::Field; use eth_types::ToLittleEndian; use halo2_proofs::{circuit::Region, plonk::Error}; @@ -74,10 +75,11 @@ impl ExecutionGadget for ByteGadget { rw_counter: Delta(3.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta(1.expr()), + gas_left: Delta(-OpcodeId::BYTE.constant_gas_cost().expr()), ..Default::default() }; let opcode = cb.query_cell(); - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -132,7 +134,6 @@ mod test { let bytecode = bytecode! { PUSH32(value) PUSH32(index) - #[start] BYTE STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs b/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs index 5c8fe80570..bf5ab2f996 100644 --- a/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs @@ -18,6 +18,7 @@ use crate::{ }, util::Expr, }; +use bus_mapping::evm::OpcodeId; use eth_types::Field; use eth_types::ToLittleEndian; use halo2_proofs::{circuit::Region, plonk::Error}; @@ -145,15 +146,13 @@ impl ExecutionGadget for CallDataCopyGadget { rw_counter: Delta(cb.rw_counter_offset()), program_counter: Delta(1.expr()), stack_pointer: Delta(3.expr()), + gas_left: Delta( + -(OpcodeId::CALLDATACOPY.constant_gas_cost().expr() + memory_copier_gas.gas_cost()), + ), memory_word_size: To(memory_expansion.next_memory_word_size()), ..Default::default() }; - let same_context = SameContextGadget::construct( - cb, - opcode, - step_state_transition, - Some(memory_copier_gas.gas_cost()), - ); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, diff --git a/zkevm-circuits/src/evm_circuit/execution/calldataload.rs b/zkevm-circuits/src/evm_circuit/execution/calldataload.rs index 743c89c44d..4e7829d4b5 100644 --- a/zkevm-circuits/src/evm_circuit/execution/calldataload.rs +++ b/zkevm-circuits/src/evm_circuit/execution/calldataload.rs @@ -1,5 +1,6 @@ use std::convert::TryInto; +use bus_mapping::evm::OpcodeId; use eth_types::{Field, ToLittleEndian}; use halo2_proofs::{ circuit::Region, @@ -13,7 +14,7 @@ use crate::{ table::{CallContextFieldTag, TxContextFieldTag}, util::{ common_gadget::SameContextGadget, - constraint_builder::{ConstraintBuilder, StepStateTransition, Transition}, + constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, memory_gadget::BufferReaderGadget, Cell, MemoryAddress, RandomLinearCombination, }, @@ -151,13 +152,14 @@ impl ExecutionGadget for CallDataLoadGadget { )); let step_state_transition = StepStateTransition { - rw_counter: Transition::Delta(cb.rw_counter_offset()), - program_counter: Transition::Delta(1.expr()), - stack_pointer: Transition::Delta(0.expr()), + rw_counter: Delta(cb.rw_counter_offset()), + program_counter: Delta(1.expr()), + stack_pointer: Delta(0.expr()), + gas_left: Delta(-OpcodeId::CALLDATALOAD.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, diff --git a/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs b/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs index d86b344693..a0cddf913f 100644 --- a/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs +++ b/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs @@ -1,23 +1,22 @@ -use eth_types::{Field, ToLittleEndian}; -use halo2_proofs::{circuit::Region, plonk::Error}; -use std::convert::TryInto; - use crate::{ evm_circuit::{ + execution::ExecutionGadget, param::N_BYTES_CALLDATASIZE, step::ExecutionState, table::CallContextFieldTag, util::{ common_gadget::SameContextGadget, - constraint_builder::{ConstraintBuilder, StepStateTransition, Transition}, + constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, from_bytes, RandomLinearCombination, }, witness::{Block, Call, ExecStep, Transaction}, }, util::Expr, }; - -use super::ExecutionGadget; +use bus_mapping::evm::OpcodeId; +use eth_types::{Field, ToLittleEndian}; +use halo2_proofs::{circuit::Region, plonk::Error}; +use std::convert::TryInto; #[derive(Clone, Debug)] pub(crate) struct CallDataSizeGadget { @@ -46,13 +45,14 @@ impl ExecutionGadget for CallDataSizeGadget { cb.stack_push(call_data_size.expr()); let step_state_transition = StepStateTransition { - rw_counter: Transition::Delta(2.expr()), - program_counter: Transition::Delta(1.expr()), - stack_pointer: Transition::Delta((-1).expr()), + rw_counter: Delta(2.expr()), + program_counter: Delta(1.expr()), + stack_pointer: Delta((-1).expr()), + gas_left: Delta(-OpcodeId::CALLDATASIZE.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -106,7 +106,6 @@ mod test { fn test_ok(call_data_size: usize, is_root: bool) { let randomness = Fr::rand(); let bytecode = bytecode! { - #[start] CALLDATASIZE STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/caller.rs b/zkevm-circuits/src/evm_circuit/execution/caller.rs index 5fd2f2c7d1..e9cf3a2202 100644 --- a/zkevm-circuits/src/evm_circuit/execution/caller.rs +++ b/zkevm-circuits/src/evm_circuit/execution/caller.rs @@ -13,8 +13,8 @@ use crate::{ }, util::Expr, }; -use eth_types::Field; -use eth_types::ToLittleEndian; +use bus_mapping::evm::OpcodeId; +use eth_types::{Field, ToLittleEndian}; use halo2_proofs::{circuit::Region, plonk::Error}; use std::convert::TryInto; @@ -50,9 +50,10 @@ impl ExecutionGadget for CallerGadget { rw_counter: Delta(2.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta((-1).expr()), + gas_left: Delta(-OpcodeId::CALLER.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -94,7 +95,6 @@ mod test { fn test_ok() { let bytecode = bytecode! { - #[start] CALLER STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/callvalue.rs b/zkevm-circuits/src/evm_circuit/execution/callvalue.rs index 379140ad62..65b54e5790 100644 --- a/zkevm-circuits/src/evm_circuit/execution/callvalue.rs +++ b/zkevm-circuits/src/evm_circuit/execution/callvalue.rs @@ -12,8 +12,8 @@ use crate::{ }, util::Expr, }; -use eth_types::Field; -use eth_types::ToLittleEndian; +use bus_mapping::evm::OpcodeId; +use eth_types::{Field, ToLittleEndian}; use halo2_proofs::{circuit::Region, plonk::Error}; #[derive(Clone, Debug)] @@ -49,9 +49,10 @@ impl ExecutionGadget for CallValueGadget { rw_counter: Delta(2.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta((-1).expr()), + gas_left: Delta(-OpcodeId::CALLVALUE.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -92,7 +93,6 @@ mod test { fn test_ok() { let bytecode = bytecode! { - #[start] CALLVALUE STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/coinbase.rs b/zkevm-circuits/src/evm_circuit/execution/coinbase.rs index e0febd11c7..96458ddc03 100644 --- a/zkevm-circuits/src/evm_circuit/execution/coinbase.rs +++ b/zkevm-circuits/src/evm_circuit/execution/coinbase.rs @@ -13,6 +13,7 @@ use crate::{ }, util::Expr, }; +use bus_mapping::evm::OpcodeId; use eth_types::Field; use eth_types::ToLittleEndian; use halo2_proofs::{circuit::Region, plonk::Error}; @@ -48,9 +49,10 @@ impl ExecutionGadget for CoinbaseGadget { rw_counter: Delta(1.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta((-1).expr()), + gas_left: Delta(-OpcodeId::COINBASE.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -92,7 +94,6 @@ mod test { fn test_ok() { let bytecode = bytecode! { - #[start] COINBASE STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/comparator.rs b/zkevm-circuits/src/evm_circuit/execution/comparator.rs index e61d352204..acd95d397a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/comparator.rs +++ b/zkevm-circuits/src/evm_circuit/execution/comparator.rs @@ -87,9 +87,10 @@ impl ExecutionGadget for ComparatorGadget { rw_counter: Delta(3.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta(1.expr()), + gas_left: Delta(-OpcodeId::LT.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -175,7 +176,6 @@ mod test { let bytecode = bytecode! { PUSH32(b) PUSH32(a) - #[start] .write_op(opcode) STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/dup.rs b/zkevm-circuits/src/evm_circuit/execution/dup.rs index d18df3ae87..dd3a9a76c2 100644 --- a/zkevm-circuits/src/evm_circuit/execution/dup.rs +++ b/zkevm-circuits/src/evm_circuit/execution/dup.rs @@ -43,9 +43,10 @@ impl ExecutionGadget for DupGadget { rw_counter: Delta(2.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta((-1).expr()), + gas_left: Delta(-OpcodeId::DUP1.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -94,7 +95,6 @@ mod test { bytecode.write_op(OpcodeId::DUP1); } bytecode.append(&bytecode! { - #[start] .write_op(opcode) STOP }); diff --git a/zkevm-circuits/src/evm_circuit/execution/end_block.rs b/zkevm-circuits/src/evm_circuit/execution/end_block.rs new file mode 100644 index 0000000000..85e0c7a949 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/end_block.rs @@ -0,0 +1,44 @@ +use crate::evm_circuit::{ + execution::ExecutionGadget, + step::ExecutionState, + util::constraint_builder::ConstraintBuilder, + witness::{Block, Call, ExecStep, Transaction}, +}; +use eth_types::Field; +use halo2_proofs::{circuit::Region, plonk::Error}; +use std::marker::PhantomData; + +#[derive(Clone, Debug)] +pub(crate) struct EndBlockGadget { + _marker: PhantomData, +} + +impl ExecutionGadget for EndBlockGadget { + const NAME: &'static str = "EndBlock"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::EndBlock; + + fn configure(_: &mut ConstraintBuilder) -> Self { + // TODO: For last step, constrain: + // - tx_id is equal to total_tx + // - rw_counter is equal to public input one + + // TODO: For the rest steps, propagate the rw_counter and call_id to next step. + + Self { + _marker: PhantomData, + } + } + + fn assign_exec_step( + &self, + _region: &mut Region<'_, F>, + _offset: usize, + _: &Block, + _: &Transaction, + _: &Call, + _step: &ExecStep, + ) -> Result<(), Error> { + Ok(()) + } +} diff --git a/zkevm-circuits/src/evm_circuit/execution/end_tx.rs b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs new file mode 100644 index 0000000000..4b52dc76ff --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs @@ -0,0 +1,277 @@ +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + param::N_BYTES_GAS, + step::ExecutionState, + table::{BlockContextFieldTag, CallContextFieldTag, TxContextFieldTag}, + util::{ + common_gadget::UpdateBalanceGadget, + constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, + math_gadget::{ + AddWordsGadget, ConstantDivisionGadget, MinMaxGadget, MulWordByU64Gadget, + }, + Cell, + }, + witness::{Block, Call, ExecStep, Transaction}, + }, + util::Expr, +}; +use eth_types::{evm_types::MAX_REFUND_QUOTIENT_OF_GAS_USED, Field, ToScalar}; +use halo2_proofs::{circuit::Region, plonk::Error}; + +#[derive(Clone, Debug)] +pub(crate) struct EndTxGadget { + tx_id: Cell, + tx_gas: Cell, + max_refund: ConstantDivisionGadget, + refund: Cell, + effective_refund: MinMaxGadget, + mul_gas_price_by_refund: MulWordByU64Gadget, + tx_caller_address: Cell, + gas_fee_refund: UpdateBalanceGadget, + sub_gas_price_by_base_fee: AddWordsGadget, + mul_effective_tip_by_gas_used: MulWordByU64Gadget, + coinbase: Cell, + coinbase_reward: UpdateBalanceGadget, +} + +impl ExecutionGadget for EndTxGadget { + const NAME: &'static str = "EndTx"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::EndTx; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let tx_id = cb.call_context(None, CallContextFieldTag::TxId); + + let [tx_gas, tx_caller_address] = + [TxContextFieldTag::Gas, TxContextFieldTag::CallerAddress] + .map(|field_tag| cb.tx_context(tx_id.expr(), field_tag, None)); + let tx_gas_price = cb.tx_context_as_word(tx_id.expr(), TxContextFieldTag::GasPrice, None); + + // Calculate effective gas to refund + let gas_used = tx_gas.expr() - cb.curr.state.gas_left.expr(); + let max_refund = ConstantDivisionGadget::construct( + cb, + gas_used.clone(), + MAX_REFUND_QUOTIENT_OF_GAS_USED as u64, + ); + let refund = cb.query_cell(); + cb.tx_refund_read(tx_id.expr(), refund.expr()); + let effective_refund = MinMaxGadget::construct(cb, max_refund.quotient(), refund.expr()); + + // Add effective_refund * tx_gas_price back to caller's balance + let mul_gas_price_by_refund = MulWordByU64Gadget::construct( + cb, + tx_gas_price.clone(), + effective_refund.min() + cb.curr.state.gas_left.expr(), + ); + let gas_fee_refund = UpdateBalanceGadget::construct( + cb, + tx_caller_address.expr(), + vec![mul_gas_price_by_refund.product().clone()], + None, + ); + + // Add gas_used * effective_tip to coinbase's balance + let coinbase = cb.query_cell(); + let base_fee = cb.query_word(); + for (tag, value) in [ + (BlockContextFieldTag::Coinbase, coinbase.expr()), + (BlockContextFieldTag::BaseFee, base_fee.expr()), + ] { + cb.block_lookup(tag.expr(), None, value); + } + let effective_tip = cb.query_word(); + let sub_gas_price_by_base_fee = + AddWordsGadget::construct(cb, [effective_tip.clone(), base_fee], tx_gas_price); + let mul_effective_tip_by_gas_used = + MulWordByU64Gadget::construct(cb, effective_tip, gas_used); + let coinbase_reward = UpdateBalanceGadget::construct( + cb, + coinbase.expr(), + vec![mul_effective_tip_by_gas_used.product().clone()], + None, + ); + + cb.condition( + cb.next.execution_state_selector([ExecutionState::BeginTx]), + |cb| { + cb.call_context_lookup( + false.expr(), + Some(cb.next.state.rw_counter.expr()), + CallContextFieldTag::TxId, + tx_id.expr() + 1.expr(), + ); + + cb.require_step_state_transition(StepStateTransition { + rw_counter: Delta(5.expr()), + ..StepStateTransition::any() + }); + }, + ); + + cb.condition( + cb.next.execution_state_selector([ExecutionState::EndBlock]), + |cb| { + cb.require_step_state_transition(StepStateTransition { + rw_counter: Delta(4.expr()), + ..StepStateTransition::any() + }); + }, + ); + + Self { + tx_id, + tx_gas, + max_refund, + refund, + effective_refund, + mul_gas_price_by_refund, + tx_caller_address, + gas_fee_refund, + sub_gas_price_by_base_fee, + mul_effective_tip_by_gas_used, + coinbase, + coinbase_reward, + } + } + + fn assign_exec_step( + &self, + region: &mut Region<'_, F>, + offset: usize, + block: &Block, + tx: &Transaction, + _: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + let gas_used = tx.gas - step.gas_left; + let refund = block.rws[step.rw_indices[1]].tx_refund_value(); + let [caller_balance_pair, coinbase_balance_pair] = + [step.rw_indices[2], step.rw_indices[3]].map(|idx| block.rws[idx].account_value_pair()); + + self.tx_id + .assign(region, offset, Some(F::from(tx.id as u64)))?; + self.tx_gas.assign(region, offset, Some(F::from(tx.gas)))?; + let (max_refund, _) = self.max_refund.assign(region, offset, gas_used as u128)?; + self.refund.assign(region, offset, Some(F::from(refund)))?; + self.effective_refund.assign( + region, + offset, + F::from(max_refund as u64), + F::from(refund), + )?; + let effective_refund = refund.min(max_refund as u64); + let gas_fee_refund = tx.gas_price * (effective_refund + step.gas_left); + self.mul_gas_price_by_refund.assign( + region, + offset, + tx.gas_price, + effective_refund + step.gas_left, + gas_fee_refund, + )?; + self.tx_caller_address + .assign(region, offset, tx.caller_address.to_scalar())?; + self.gas_fee_refund.assign( + region, + offset, + vec![caller_balance_pair.1, gas_fee_refund], + caller_balance_pair.0, + )?; + let effective_tip = tx.gas_price - block.context.base_fee; + self.sub_gas_price_by_base_fee.assign( + region, + offset, + [effective_tip, block.context.base_fee], + tx.gas_price, + )?; + self.mul_effective_tip_by_gas_used.assign( + region, + offset, + effective_tip, + gas_used, + effective_tip * gas_used, + )?; + self.coinbase + .assign(region, offset, block.context.coinbase.to_scalar())?; + self.coinbase_reward.assign( + region, + offset, + vec![coinbase_balance_pair.1, effective_tip * gas_used], + coinbase_balance_pair.0, + )?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::evm_circuit::{ + test::run_test_circuit_incomplete_fixed_table, witness::block_convert, + }; + use eth_types::{self, address, geth_types::Account, Address, Word}; + + fn test_ok(txs: Vec) { + let accounts = txs + .iter() + .map(|tx| Account { + address: tx.from, + balance: Word::from(10).pow(20.into()), + ..Default::default() + }) + .collect::>(); + let block_data = + bus_mapping::mock::BlockData::new_from_geth_data(mock::new(accounts, txs).unwrap()); + let mut builder = block_data.new_circuit_input_builder(); + builder + .handle_block(&block_data.eth_block, &block_data.geth_traces) + .unwrap(); + let block = block_convert(&builder.block, &builder.code_db); + + assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); + } + + fn mock_tx(from: Address, gas: Option, gas_price: Option) -> eth_types::Transaction { + let to = address!("0x00000000000000000000000000000000000000ff"); + let minimal_gas = Word::from(21000); + let two_gwei = Word::from(2_000_000_000); + eth_types::Transaction { + from, + to: Some(to), + gas: gas.map(Word::from).unwrap_or(minimal_gas), + gas_price: gas_price.or(Some(two_gwei)), + ..Default::default() + } + } + + #[test] + fn end_tx_gadget_simple() { + // TODO: Enable this with respective code when SSTORE is implemented. + // Tx with non-capped refund + // test_ok(vec![mock_tx( + // address!("0x00000000000000000000000000000000000000fe"), + // Some(27000), + // None, + // )]); + // Tx with capped refund + // test_ok(vec![mock_tx( + // address!("0x00000000000000000000000000000000000000fe"), + // Some(65000), + // None, + // )]); + // Multiple txs + test_ok(vec![ + mock_tx( + address!("0x00000000000000000000000000000000000000fd"), + None, + None, + ), + mock_tx( + address!("0x00000000000000000000000000000000000000fe"), + None, + None, + ), + ]); + } +} diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_pure_memory.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_static_memory.rs similarity index 95% rename from zkevm-circuits/src/evm_circuit/execution/error_oog_pure_memory.rs rename to zkevm-circuits/src/evm_circuit/execution/error_oog_static_memory.rs index a5fd7d336e..935a0c4ddc 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_pure_memory.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_static_memory.rs @@ -17,7 +17,7 @@ use eth_types::{evm_types::OpcodeId, Field, ToLittleEndian}; use halo2_proofs::{circuit::Region, plonk::Error}; #[derive(Clone, Debug)] -pub(crate) struct ErrorOOGPureMemoryGadget { +pub(crate) struct ErrorOOGStaticMemoryGadget { opcode: Cell, address: Word, address_in_range: IsZeroGadget, @@ -35,10 +35,10 @@ pub(crate) struct ErrorOOGPureMemoryGadget { is_mstore8: IsEqualGadget, } -impl ExecutionGadget for ErrorOOGPureMemoryGadget { - const NAME: &'static str = "ErrorOutOfGasPureMemory"; +impl ExecutionGadget for ErrorOOGStaticMemoryGadget { + const NAME: &'static str = "ErrorOutOfGasStaticMemoryExpansion"; - const EXECUTION_STATE: ExecutionState = ExecutionState::ErrorOutOfGasPureMemory; + const EXECUTION_STATE: ExecutionState = ExecutionState::ErrorOutOfGasStaticMemoryExpansion; // Support other OOG due to pure memory including CREATE, RETURN and REVERT fn configure(cb: &mut ConstraintBuilder) -> Self { diff --git a/zkevm-circuits/src/evm_circuit/execution/gas.rs b/zkevm-circuits/src/evm_circuit/execution/gas.rs index bdd8d2c695..40be9b035d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/gas.rs +++ b/zkevm-circuits/src/evm_circuit/execution/gas.rs @@ -45,10 +45,11 @@ impl ExecutionGadget for GasGadget { rw_counter: Delta(1.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta((-1).expr()), + gas_left: Delta(-OpcodeId::GAS.constant_gas_cost().expr()), ..Default::default() }; let opcode = cb.query_cell(); - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -95,7 +96,6 @@ mod test { fn test_ok() { let bytecode = bytecode! { - #[start] GAS STOP }; @@ -110,7 +110,6 @@ mod test { #[test] fn gas_gadget_incorrect_deduction() { let bytecode = bytecode! { - #[start] GAS STOP }; @@ -121,7 +120,7 @@ mod test { ); let mut builder = block_trace.new_circuit_input_builder(); builder - .handle_tx(&block_trace.eth_tx, &block_trace.geth_trace) + .handle_block(&block_trace.eth_block, &block_trace.geth_traces) .expect("could not handle block tx"); let mut block = block_convert(&builder.block, &builder.code_db); @@ -129,8 +128,8 @@ mod test { // wrong `gas_left` value for the second step, to assert that // the circuit verification fails for this scenario. assert_eq!(block.txs.len(), 1); - assert_eq!(block.txs[0].steps.len(), 2); - block.txs[0].steps[1].gas_left -= 1; + assert_eq!(block.txs[0].steps.len(), 5); + block.txs[0].steps[2].gas_left -= 1; assert!(run_test_circuit(block, config.evm_circuit_lookup_tags).is_err()); } diff --git a/zkevm-circuits/src/evm_circuit/execution/jump.rs b/zkevm-circuits/src/evm_circuit/execution/jump.rs index 66f27d11fc..e7ca28fe22 100644 --- a/zkevm-circuits/src/evm_circuit/execution/jump.rs +++ b/zkevm-circuits/src/evm_circuit/execution/jump.rs @@ -49,9 +49,10 @@ impl ExecutionGadget for JumpGadget { rw_counter: Delta(1.expr()), program_counter: To(from_bytes::expr(&destination.cells)), stack_pointer: Delta(1.expr()), + gas_left: Delta(-OpcodeId::JUMP.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -95,7 +96,6 @@ mod test { let mut bytecode = bytecode! { PUSH32(destination) - #[start] JUMP }; for _ in 0..(destination - 34) { diff --git a/zkevm-circuits/src/evm_circuit/execution/jumpdest.rs b/zkevm-circuits/src/evm_circuit/execution/jumpdest.rs index 704ca9c50f..4f6fceb175 100644 --- a/zkevm-circuits/src/evm_circuit/execution/jumpdest.rs +++ b/zkevm-circuits/src/evm_circuit/execution/jumpdest.rs @@ -10,6 +10,7 @@ use crate::{ }, util::Expr, }; +use bus_mapping::evm::OpcodeId; use eth_types::Field; use halo2_proofs::{circuit::Region, plonk::Error}; @@ -27,10 +28,11 @@ impl ExecutionGadget for JumpdestGadget { // State transition let step_state_transition = StepStateTransition { program_counter: Delta(1.expr()), + gas_left: Delta(-OpcodeId::JUMPDEST.constant_gas_cost().expr()), ..Default::default() }; let opcode = cb.query_cell(); - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context } } @@ -55,7 +57,6 @@ mod test { fn test_ok() { let bytecode = bytecode! { - #[start] JUMPDEST STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/jumpi.rs b/zkevm-circuits/src/evm_circuit/execution/jumpi.rs index 51c869705e..daf0dda9e8 100644 --- a/zkevm-circuits/src/evm_circuit/execution/jumpi.rs +++ b/zkevm-circuits/src/evm_circuit/execution/jumpi.rs @@ -69,9 +69,10 @@ impl ExecutionGadget for JumpiGadget { rw_counter: Delta(2.expr()), program_counter: To(next_program_counter), stack_pointer: Delta(2.expr()), + gas_left: Delta(-OpcodeId::JUMPI.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -126,7 +127,6 @@ mod test { let mut bytecode = bytecode! { PUSH32(condition) PUSH32(destination) - #[start] JUMPI STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/memory.rs b/zkevm-circuits/src/evm_circuit/execution/memory.rs index 51b62716e6..31c4050830 100644 --- a/zkevm-circuits/src/evm_circuit/execution/memory.rs +++ b/zkevm-circuits/src/evm_circuit/execution/memory.rs @@ -112,19 +112,16 @@ impl ExecutionGadget for MemoryGadget { // - `stack_pointer` needs to be increased by 2 when is_store, otherwise to be // same // - `memory_size` needs to be set to `next_memory_size` + let gas_cost = OpcodeId::MLOAD.constant_gas_cost().expr() + memory_expansion.gas_cost(); let step_state_transition = StepStateTransition { rw_counter: Delta(34.expr() - is_mstore8.expr() * 31.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta(is_store * 2.expr()), + gas_left: Delta(-gas_cost), memory_word_size: To(memory_expansion.next_memory_word_size()), ..Default::default() }; - let same_context = SameContextGadget::construct( - cb, - opcode, - step_state_transition, - Some(memory_expansion.gas_cost()), - ); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -206,7 +203,6 @@ mod test { let bytecode = bytecode! { PUSH32(value) PUSH32(address) - #[start] .write_op(opcode) STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/msize.rs b/zkevm-circuits/src/evm_circuit/execution/msize.rs index b229cd3a01..a3de581cec 100644 --- a/zkevm-circuits/src/evm_circuit/execution/msize.rs +++ b/zkevm-circuits/src/evm_circuit/execution/msize.rs @@ -12,6 +12,7 @@ use crate::{ }, util::Expr, }; +use bus_mapping::evm::OpcodeId; use eth_types::Field; use halo2_proofs::{circuit::Region, plonk::Error}; @@ -44,10 +45,11 @@ impl ExecutionGadget for MsizeGadget { rw_counter: Delta(1.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta((-1).expr()), + gas_left: Delta(-OpcodeId::MSIZE.constant_gas_cost().expr()), ..Default::default() }; let opcode = cb.query_cell(); - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -88,7 +90,6 @@ mod test { PUSH32(value) PUSH32(address) MSTORE - #[start] MSIZE STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/mul.rs b/zkevm-circuits/src/evm_circuit/execution/mul.rs index 8c2a7d60c6..e75393dece 100644 --- a/zkevm-circuits/src/evm_circuit/execution/mul.rs +++ b/zkevm-circuits/src/evm_circuit/execution/mul.rs @@ -11,6 +11,7 @@ use crate::{ }, util::Expr, }; +use bus_mapping::evm::OpcodeId; use eth_types::Field; use halo2_proofs::{circuit::Region, plonk::Error}; @@ -43,12 +44,10 @@ impl ExecutionGadget for MulGadget { rw_counter: Delta(3.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta(1.expr()), - // Setting gas_left as default (SAME), SameContextGadget would - // deduce the gas cost from OPCODE automatically - // gas_left: Delta(-GasCost::FAST.as_usize().expr()), + gas_left: Delta(-OpcodeId::MUL.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -82,7 +81,6 @@ mod test { let bytecode = bytecode! { PUSH32(a) PUSH32(b) - #[start] .write_op(opcode) STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/number.rs b/zkevm-circuits/src/evm_circuit/execution/number.rs index e2249bfdfb..e6562c6d57 100644 --- a/zkevm-circuits/src/evm_circuit/execution/number.rs +++ b/zkevm-circuits/src/evm_circuit/execution/number.rs @@ -13,6 +13,7 @@ use crate::{ }, util::Expr, }; +use bus_mapping::evm::OpcodeId; use eth_types::Field; use halo2_proofs::{circuit::Region, plonk::Error}; use std::convert::TryFrom; @@ -47,9 +48,10 @@ impl ExecutionGadget for NumberGadget { rw_counter: Delta(1.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta((-1).expr()), + gas_left: Delta(-OpcodeId::NUMBER.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, diff --git a/zkevm-circuits/src/evm_circuit/execution/pc.rs b/zkevm-circuits/src/evm_circuit/execution/pc.rs index 379c0ebd32..61d8aefbee 100644 --- a/zkevm-circuits/src/evm_circuit/execution/pc.rs +++ b/zkevm-circuits/src/evm_circuit/execution/pc.rs @@ -12,6 +12,7 @@ use crate::{ }, util::Expr, }; +use bus_mapping::evm::OpcodeId; use eth_types::Field; use halo2_proofs::{circuit::Region, plonk::Error}; @@ -44,10 +45,11 @@ impl ExecutionGadget for PcGadget { rw_counter: Delta(1.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta((-1).expr()), + gas_left: Delta(-OpcodeId::PC.constant_gas_cost().expr()), ..Default::default() }; let opcode = cb.query_cell(); - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -81,7 +83,6 @@ mod test { fn test_ok() { let bytecode = bytecode! { PUSH32(0) - #[start] PC STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/pop.rs b/zkevm-circuits/src/evm_circuit/execution/pop.rs index 39802ed77a..b41177725b 100644 --- a/zkevm-circuits/src/evm_circuit/execution/pop.rs +++ b/zkevm-circuits/src/evm_circuit/execution/pop.rs @@ -11,6 +11,7 @@ use crate::{ }, util::Expr, }; +use bus_mapping::evm::OpcodeId; use eth_types::{Field, ToLittleEndian}; use halo2_proofs::{circuit::Region, plonk::Error}; @@ -36,10 +37,11 @@ impl ExecutionGadget for PopGadget { rw_counter: Delta(1.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta(1.expr()), + gas_left: Delta(-OpcodeId::POP.constant_gas_cost().expr()), ..Default::default() }; let opcode = cb.query_cell(); - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -80,7 +82,6 @@ mod test { fn test_ok(value: Word) { let bytecode = bytecode! { PUSH32(value) - #[start] POP STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/push.rs b/zkevm-circuits/src/evm_circuit/execution/push.rs index fbec27cac8..19e431a4b4 100644 --- a/zkevm-circuits/src/evm_circuit/execution/push.rs +++ b/zkevm-circuits/src/evm_circuit/execution/push.rs @@ -100,9 +100,10 @@ impl ExecutionGadget for PushGadget { rw_counter: Delta(1.expr()), program_counter: Delta(opcode.expr() - (OpcodeId::PUSH1.as_u64() - 2).expr()), stack_pointer: Delta((-1).expr()), + gas_left: Delta(-OpcodeId::PUSH1.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -151,7 +152,6 @@ mod test { assert!(bytes.len() as u8 == opcode.as_u8() - OpcodeId::PUSH1.as_u8() + 1,); let mut bytecode = bytecode! { - #[start] .write_op(opcode) }; for b in bytes { diff --git a/zkevm-circuits/src/evm_circuit/execution/selfbalance.rs b/zkevm-circuits/src/evm_circuit/execution/selfbalance.rs index b44a3ca5d1..7a00c82c62 100644 --- a/zkevm-circuits/src/evm_circuit/execution/selfbalance.rs +++ b/zkevm-circuits/src/evm_circuit/execution/selfbalance.rs @@ -12,6 +12,7 @@ use crate::{ }, util::Expr, }; +use bus_mapping::evm::OpcodeId; use eth_types::{Field, ToLittleEndian, ToScalar}; use halo2_proofs::{circuit::Region, plonk::Error}; @@ -44,9 +45,10 @@ impl ExecutionGadget for SelfbalanceGadget { rw_counter: Delta(3.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta((-1).expr()), + gas_left: Delta(-OpcodeId::SELFBALANCE.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -91,7 +93,6 @@ mod test { #[test] fn selfbalance_gadget_test() { let bytecode = bytecode! { - #[start] SELFBALANCE STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/signed_comparator.rs b/zkevm-circuits/src/evm_circuit/execution/signed_comparator.rs index 0a83fd9e78..bff64b730f 100644 --- a/zkevm-circuits/src/evm_circuit/execution/signed_comparator.rs +++ b/zkevm-circuits/src/evm_circuit/execution/signed_comparator.rs @@ -4,7 +4,7 @@ use crate::{ step::ExecutionState, util::{ common_gadget::SameContextGadget, - constraint_builder::{ConstraintBuilder, StepStateTransition, Transition}, + constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, from_bytes, math_gadget::{ComparisonGadget, IsEqualGadget, LtGadget}, select, Cell, Word, @@ -121,12 +121,13 @@ impl ExecutionGadget for SignedComparatorGadget { // and the since the stack now has one less word, the stack pointer also // shifts by one. let step_state_transition = StepStateTransition { - rw_counter: Transition::Delta(3.expr()), - program_counter: Transition::Delta(1.expr()), - stack_pointer: Transition::Delta(1.expr()), + rw_counter: Delta(3.expr()), + program_counter: Delta(1.expr()), + stack_pointer: Delta(1.expr()), + gas_left: Delta(-OpcodeId::SLT.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -224,7 +225,7 @@ mod test { use crate::{evm_circuit::test::rand_word, test_util::run_test_circuits}; fn test_ok(pairs: Vec<(OpcodeId, Word, Word)>) { - let mut bytecode = bytecode! { #[start] }; + let mut bytecode = bytecode! {}; for (opcode, a, b) in pairs { bytecode.push(32, b); bytecode.push(32, a); diff --git a/zkevm-circuits/src/evm_circuit/execution/signextend.rs b/zkevm-circuits/src/evm_circuit/execution/signextend.rs index de0d875b54..4bbd307a79 100644 --- a/zkevm-circuits/src/evm_circuit/execution/signextend.rs +++ b/zkevm-circuits/src/evm_circuit/execution/signextend.rs @@ -15,6 +15,7 @@ use crate::{ util::Expr, }; use array_init::array_init; +use bus_mapping::evm::OpcodeId; use eth_types::{Field, ToLittleEndian}; use halo2_proofs::{circuit::Region, plonk::Error}; @@ -133,10 +134,11 @@ impl ExecutionGadget for SignextendGadget { rw_counter: Delta(3.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta(1.expr()), + gas_left: Delta(-OpcodeId::SIGNEXTEND.constant_gas_cost().expr()), ..Default::default() }; let opcode = cb.query_cell(); - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -210,7 +212,6 @@ mod test { let bytecode = bytecode! { PUSH32(value) PUSH32(index) - #[start] SIGNEXTEND STOP }; diff --git a/zkevm-circuits/src/evm_circuit/execution/sload.rs b/zkevm-circuits/src/evm_circuit/execution/sload.rs index 05051bc2e1..e7f1850b3c 100644 --- a/zkevm-circuits/src/evm_circuit/execution/sload.rs +++ b/zkevm-circuits/src/evm_circuit/execution/sload.rs @@ -67,25 +67,30 @@ impl ExecutionGadget for SloadGadget { cb.stack_push(value.expr()); let is_warm = cb.query_bool(); - cb.account_storage_access_list_write_with_reversion( + cb.account_storage_access_list_write( tx_id.expr(), callee_address.expr(), key.expr(), true.expr(), is_warm.expr(), - is_persistent.expr(), - rw_counter_end_of_reversion.expr(), + Some( + ( + &is_persistent, + rw_counter_end_of_reversion.expr() - cb.curr.state.state_write_counter.expr(), + ) + .into(), + ), ); + let gas_cost = SloadGasGadget::construct(cb, is_warm.expr()).expr(); let step_state_transition = StepStateTransition { rw_counter: Delta(8.expr()), program_counter: Delta(1.expr()), state_write_counter: To(1.expr()), + gas_left: Delta(-gas_cost), ..Default::default() }; - let gas_cost = SloadGasGadget::construct(cb, is_warm.expr()); - let same_context = - SameContextGadget::construct(cb, opcode, step_state_transition, Some(gas_cost.expr())); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, diff --git a/zkevm-circuits/src/evm_circuit/execution/swap.rs b/zkevm-circuits/src/evm_circuit/execution/swap.rs index ca6f112daf..0a8540ebfc 100644 --- a/zkevm-circuits/src/evm_circuit/execution/swap.rs +++ b/zkevm-circuits/src/evm_circuit/execution/swap.rs @@ -47,9 +47,10 @@ impl ExecutionGadget for SwapGadget { let step_state_transition = StepStateTransition { rw_counter: Delta(4.expr()), program_counter: Delta(1.expr()), + gas_left: Delta(-OpcodeId::SWAP1.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -104,7 +105,6 @@ mod test { } bytecode.append(&bytecode! { PUSH32(rhs) - #[start] .write_op(opcode) STOP }); diff --git a/zkevm-circuits/src/evm_circuit/execution/timestamp.rs b/zkevm-circuits/src/evm_circuit/execution/timestamp.rs index 271b205d6d..bb8dbe4ddb 100644 --- a/zkevm-circuits/src/evm_circuit/execution/timestamp.rs +++ b/zkevm-circuits/src/evm_circuit/execution/timestamp.rs @@ -13,6 +13,7 @@ use crate::{ }, util::Expr, }; +use bus_mapping::evm::OpcodeId; use eth_types::Field; use halo2_proofs::{circuit::Region, plonk::Error}; use std::convert::TryFrom; @@ -45,9 +46,10 @@ impl ExecutionGadget for TimestampGadget { rw_counter: Delta(1.expr()), program_counter: Delta(1.expr()), stack_pointer: Delta((-1).expr()), + gas_left: Delta(-OpcodeId::TIMESTAMP.constant_gas_cost().expr()), ..Default::default() }; - let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); Self { same_context, @@ -85,7 +87,6 @@ mod test { fn test_ok() { let bytecode = bytecode! { - #[start] TIMESTAMP STOP }; diff --git a/zkevm-circuits/src/evm_circuit/param.rs b/zkevm-circuits/src/evm_circuit/param.rs index 8901f21b04..0b745b6068 100644 --- a/zkevm-circuits/src/evm_circuit/param.rs +++ b/zkevm-circuits/src/evm_circuit/param.rs @@ -1,7 +1,7 @@ // Step dimension pub(crate) const STEP_WIDTH: usize = 32; /// Step height -pub const STEP_HEIGHT: usize = 14; +pub const STEP_HEIGHT: usize = 16; pub(crate) const N_CELLS_STEP_STATE: usize = 10; /// Maximum number of bytes that an integer can fit in field without wrapping diff --git a/zkevm-circuits/src/evm_circuit/step.rs b/zkevm-circuits/src/evm_circuit/step.rs index f9d375d9e6..ebf5343779 100644 --- a/zkevm-circuits/src/evm_circuit/step.rs +++ b/zkevm-circuits/src/evm_circuit/step.rs @@ -19,6 +19,8 @@ use std::collections::VecDeque; pub enum ExecutionState { // Internal state BeginTx, + EndTx, + EndBlock, CopyToMemory, // Opcode successful cases STOP, @@ -98,25 +100,28 @@ pub enum ExecutionState { ErrorDepth, ErrorInsufficientBalance, ErrorContractAddressCollision, - ErrorMaxCodeSizeExceeded, ErrorInvalidCreationCode, - ErrorReverted, + ErrorMaxCodeSizeExceeded, ErrorInvalidJump, ErrorReturnDataOutOfBound, ErrorOutOfGasConstant, - ErrorOutOfGasPureMemory, + ErrorOutOfGasStaticMemoryExpansion, + ErrorOutOfGasDynamicMemoryExpansion, + ErrorOutOfGasMemoryCopy, + ErrorOutOfGasAccountAccess, ErrorOutOfGasCodeStore, + ErrorOutOfGasLOG, + ErrorOutOfGasEXP, ErrorOutOfGasSHA3, - ErrorOutOfGasCALLDATACOPY, - ErrorOutOfGasCODECOPY, ErrorOutOfGasEXTCODECOPY, - ErrorOutOfGasRETURNDATACOPY, - ErrorOutOfGasLOG, + ErrorOutOfGasSLOAD, + ErrorOutOfGasSSTORE, ErrorOutOfGasCALL, ErrorOutOfGasCALLCODE, ErrorOutOfGasDELEGATECALL, ErrorOutOfGasCREATE2, ErrorOutOfGasSTATICCALL, + ErrorOutOfGasSELFDESTRUCT, } impl Default for ExecutionState { @@ -133,6 +138,8 @@ impl ExecutionState { pub(crate) fn iterator() -> impl Iterator { [ Self::BeginTx, + Self::EndTx, + Self::EndBlock, Self::CopyToMemory, Self::STOP, Self::ADD, @@ -210,25 +217,28 @@ impl ExecutionState { Self::ErrorDepth, Self::ErrorInsufficientBalance, Self::ErrorContractAddressCollision, - Self::ErrorMaxCodeSizeExceeded, Self::ErrorInvalidCreationCode, - Self::ErrorReverted, + Self::ErrorMaxCodeSizeExceeded, Self::ErrorInvalidJump, Self::ErrorReturnDataOutOfBound, Self::ErrorOutOfGasConstant, - Self::ErrorOutOfGasPureMemory, + Self::ErrorOutOfGasStaticMemoryExpansion, + Self::ErrorOutOfGasDynamicMemoryExpansion, + Self::ErrorOutOfGasMemoryCopy, + Self::ErrorOutOfGasAccountAccess, Self::ErrorOutOfGasCodeStore, + Self::ErrorOutOfGasLOG, + Self::ErrorOutOfGasEXP, Self::ErrorOutOfGasSHA3, - Self::ErrorOutOfGasCALLDATACOPY, - Self::ErrorOutOfGasCODECOPY, Self::ErrorOutOfGasEXTCODECOPY, - Self::ErrorOutOfGasRETURNDATACOPY, - Self::ErrorOutOfGasLOG, + Self::ErrorOutOfGasSLOAD, + Self::ErrorOutOfGasSSTORE, Self::ErrorOutOfGasCALL, Self::ErrorOutOfGasCALLCODE, Self::ErrorOutOfGasDELEGATECALL, Self::ErrorOutOfGasCREATE2, Self::ErrorOutOfGasSTATICCALL, + Self::ErrorOutOfGasSELFDESTRUCT, ] .iter() .copied() @@ -238,6 +248,45 @@ impl ExecutionState { Self::iterator().count() } + pub(crate) fn halts(&self) -> bool { + matches!( + self, + Self::STOP + | Self::RETURN + | Self::REVERT + | Self::SELFDESTRUCT + | Self::ErrorInvalidOpcode + | Self::ErrorStackOverflow + | Self::ErrorStackUnderflow + | Self::ErrorWriteProtection + | Self::ErrorDepth + | Self::ErrorInsufficientBalance + | Self::ErrorContractAddressCollision + | Self::ErrorInvalidCreationCode + | Self::ErrorMaxCodeSizeExceeded + | Self::ErrorInvalidJump + | Self::ErrorReturnDataOutOfBound + | Self::ErrorOutOfGasConstant + | Self::ErrorOutOfGasStaticMemoryExpansion + | Self::ErrorOutOfGasDynamicMemoryExpansion + | Self::ErrorOutOfGasMemoryCopy + | Self::ErrorOutOfGasAccountAccess + | Self::ErrorOutOfGasCodeStore + | Self::ErrorOutOfGasLOG + | Self::ErrorOutOfGasEXP + | Self::ErrorOutOfGasSHA3 + | Self::ErrorOutOfGasEXTCODECOPY + | Self::ErrorOutOfGasSLOAD + | Self::ErrorOutOfGasSSTORE + | Self::ErrorOutOfGasCALL + | Self::ErrorOutOfGasCALLCODE + | Self::ErrorOutOfGasDELEGATECALL + | Self::ErrorOutOfGasCREATE2 + | Self::ErrorOutOfGasSTATICCALL + | Self::ErrorOutOfGasSELFDESTRUCT + ) + } + pub(crate) fn responsible_opcodes(&self) -> Vec { match self { Self::STOP => vec![OpcodeId::STOP], @@ -491,9 +540,13 @@ impl Step { pub(crate) fn execution_state_selector( &self, - execution_state: ExecutionState, + execution_states: impl IntoIterator, ) -> Expression { - self.state.execution_state[execution_state as usize].expr() + execution_states + .into_iter() + .map(|execution_state| self.state.execution_state[execution_state as usize].expr()) + .reduce(|acc, expr| acc + expr) + .expect("Select some ExecutionStates") } pub(crate) fn assign_exec_step( diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index 618cec2952..f067dae2e9 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -23,7 +23,8 @@ impl LookupTable for [Column; W] { #[derive(Clone, Copy, Debug)] pub enum FixedTableTag { - Range16 = 1, + Range5 = 1, + Range16, Range32, Range256, Range512, @@ -37,6 +38,7 @@ pub enum FixedTableTag { impl FixedTableTag { pub fn iterator() -> impl Iterator { [ + Self::Range5, Self::Range16, Self::Range32, Self::Range256, @@ -54,6 +56,9 @@ impl FixedTableTag { pub fn build(&self) -> Box> { let tag = F::from(*self as u64); match self { + Self::Range5 => { + Box::new((0..5).map(move |value| [tag, F::from(value), F::zero(), F::zero()])) + } Self::Range16 => { Box::new((0..16).map(move |value| [tag, F::from(value), F::zero(), F::zero()])) } diff --git a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs index 98f830c786..fd11371056 100644 --- a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs @@ -3,7 +3,7 @@ use crate::{ param::N_BYTES_GAS, table::{AccountFieldTag, FixedTableTag, Lookup}, util::{ - constraint_builder::{ConstraintBuilder, StepStateTransition, Transition}, + constraint_builder::{ConstraintBuilder, ReversionInfo, StepStateTransition}, math_gadget::{AddWordsGadget, RangeCheckGadget}, Cell, Word, }, @@ -16,6 +16,7 @@ use halo2_proofs::{ circuit::Region, plonk::{Error, Expression}, }; +use std::convert::TryInto; /// Construction of execution state that stays in the same call context, which /// lookups the opcode and verifies the execution state is responsible for it, @@ -30,8 +31,7 @@ impl SameContextGadget { pub(crate) fn construct( cb: &mut ConstraintBuilder, opcode: Cell, - mut step_state_transition: StepStateTransition, - dynamic_gas_cost: Option>, + step_state_transition: StepStateTransition, ) -> Self { cb.opcode_lookup(opcode.expr(), 1.expr()); cb.add_lookup( @@ -46,29 +46,8 @@ impl SameContextGadget { }, ); - let mut gas_cost = cb - .execution_state() - .responsible_opcodes() - .first() - .expect("Execution state in SameContextGadget should be responsible to some opcodes") - .constant_gas_cost() - .as_u64() - .expr(); - - if let Some(dynamic_gas_cost) = dynamic_gas_cost { - gas_cost = gas_cost + dynamic_gas_cost; - } - // Check gas_left is sufficient - let sufficient_gas_left = - RangeCheckGadget::construct(cb, cb.curr.state.gas_left.expr() - gas_cost.clone()); - - // Set state transition of gas_left if it's default value - if matches!(step_state_transition.gas_left, Transition::Same) - && !matches!(gas_cost, Expression::Constant(constant) if constant.is_zero_vartime()) - { - step_state_transition.gas_left = Transition::Delta(-gas_cost); - } + let sufficient_gas_left = RangeCheckGadget::construct(cb, cb.next.state.gas_left.expr()); // State transition cb.require_step_state_transition(step_state_transition); @@ -99,10 +78,70 @@ impl SameContextGadget { } } +#[derive(Clone, Debug)] +pub(crate) struct UpdateBalanceGadget { + add_words: AddWordsGadget, +} + +impl + UpdateBalanceGadget +{ + pub(crate) fn construct( + cb: &mut ConstraintBuilder, + address: Expression, + updates: Vec>, + reversion_info: Option>, + ) -> Self { + debug_assert!(updates.len() == N_ADDENDS - 1); + + let balance_addend = cb.query_word(); + let balance_sum = cb.query_word(); + + let [value, value_prev] = if INCREASE { + [balance_sum.expr(), balance_addend.expr()] + } else { + [balance_addend.expr(), balance_sum.expr()] + }; + + let add_words = AddWordsGadget::construct( + cb, + std::iter::once(balance_addend) + .chain(updates.to_vec()) + .collect::>() + .try_into() + .unwrap(), + balance_sum, + ); + + cb.account_write( + address, + AccountFieldTag::Balance, + value, + value_prev, + reversion_info, + ); + + Self { add_words } + } + + pub(crate) fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + addends: Vec, + sum: U256, + ) -> Result<(), Error> { + debug_assert!(addends.len() == N_ADDENDS); + + self.add_words + .assign(region, offset, addends.try_into().unwrap(), sum) + } +} + #[derive(Clone, Debug)] pub(crate) struct TransferWithGasFeeGadget { - sub_sender_balance: AddWordsGadget, - add_receiver_balance: AddWordsGadget, + sender: UpdateBalanceGadget, + receiver: UpdateBalanceGadget, } impl TransferWithGasFeeGadget { @@ -115,47 +154,20 @@ impl TransferWithGasFeeGadget { is_persistent: Expression, rw_counter_end_of_reversion: Expression, ) -> Self { - let sender_balance = cb.query_word(); - let receiver_balance_prev = cb.query_word(); - - // Subtract sender balance by value and gas_fee - let sub_sender_balance = - AddWordsGadget::construct(cb, [sender_balance.clone(), value.clone(), gas_fee]); - cb.require_zero( - "Sender has sufficient balance", - sub_sender_balance.carry().expr(), + let sender = UpdateBalanceGadget::construct( + cb, + sender_address, + vec![value.clone(), gas_fee], + Some((&is_persistent, &rw_counter_end_of_reversion).into()), ); - - // Add receiver balance by value - let add_receiver_balance = - AddWordsGadget::construct(cb, [receiver_balance_prev.clone(), value]); - cb.require_zero( - "Receiver has too much balance", - add_receiver_balance.carry().expr(), + let receiver = UpdateBalanceGadget::construct( + cb, + receiver_address, + vec![value], + Some((is_persistent, rw_counter_end_of_reversion - 1.expr()).into()), ); - let sender_balance_prev = sub_sender_balance.sum(); - let receiver_balance = add_receiver_balance.sum(); - - // Write with possible reversion - for (address, balance, balance_prev) in [ - (sender_address, &sender_balance, sender_balance_prev), - (receiver_address, receiver_balance, &receiver_balance_prev), - ] { - cb.account_write_with_reversion( - address, - AccountFieldTag::Balance, - balance.expr(), - balance_prev.expr(), - is_persistent.clone(), - rw_counter_end_of_reversion.clone(), - ); - } - - Self { - sub_sender_balance, - add_receiver_balance, - } + Self { sender, receiver } } pub(crate) fn assign( @@ -167,16 +179,16 @@ impl TransferWithGasFeeGadget { value: U256, gas_fee: U256, ) -> Result<(), Error> { - self.sub_sender_balance.assign( + self.sender.assign( region, offset, - [sender_balance, value, gas_fee], + vec![sender_balance, value, gas_fee], sender_balance_prev, )?; - self.add_receiver_balance.assign( + self.receiver.assign( region, offset, - [receiver_balance_prev, value], + vec![receiver_balance_prev, value], receiver_balance, )?; Ok(()) diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index aec6e4070f..6b957e76b8 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -1,5 +1,6 @@ use crate::{ evm_circuit::{ + param::STACK_CAPACITY, step::{ExecutionState, Preset, Step}, table::{ AccountFieldTag, CallContextFieldTag, FixedTableTag, Lookup, RwTableTag, @@ -31,6 +32,7 @@ pub(crate) enum Transition { Same, Delta(T), To(T), + Any, } impl Default for Transition { @@ -57,9 +59,39 @@ impl StepStateTransition { pub(crate) fn new_context() -> Self { Self { program_counter: Transition::To(0.expr()), - stack_pointer: Transition::To(1024.expr()), + stack_pointer: Transition::To(STACK_CAPACITY.expr()), memory_word_size: Transition::To(0.expr()), - ..Self::default() + ..Default::default() + } + } + + pub(crate) fn any() -> Self { + Self { + rw_counter: Transition::Any, + call_id: Transition::Any, + is_root: Transition::Any, + is_create: Transition::Any, + code_source: Transition::Any, + program_counter: Transition::Any, + stack_pointer: Transition::Any, + gas_left: Transition::Any, + memory_word_size: Transition::Any, + state_write_counter: Transition::Any, + } + } +} + +#[derive(Clone)] +pub(crate) struct ReversionInfo { + is_persistent: Expression, + rw_counter: Expression, +} + +impl, E2: Expr> From<(E1, E2)> for ReversionInfo { + fn from((is_persistent, rw_counter): (E1, E2)) -> Self { + Self { + is_persistent: is_persistent.expr(), + rw_counter: rw_counter.expr(), } } } @@ -174,8 +206,8 @@ pub(crate) struct ConstraintBuilder<'a, F> { rw_counter_offset: Expression, program_counter_offset: usize, stack_pointer_offset: i32, - state_write_counter_offset: usize, in_next_step: bool, + condition: Option>, } impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { @@ -198,8 +230,8 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { rw_counter_offset: 0.expr(), program_counter_offset: 0, stack_pointer_offset: 0, - state_write_counter_offset: 0, in_next_step: false, + condition: None, } } @@ -235,7 +267,7 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { )); } - let execution_state_selector = self.curr.execution_state_selector(self.execution_state); + let execution_state_selector = self.curr.execution_state_selector([self.execution_state]); ( constraints @@ -377,8 +409,8 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { self.cb.require_in_set(name, value, set); } - pub(crate) fn require_next_state(&mut self, exec_state: ExecutionState) { - let next_state = self.next.execution_state_selector(exec_state); + pub(crate) fn require_next_state(&mut self, execution_state: ExecutionState) { + let next_state = self.next.execution_state_selector([execution_state]); self.add_constraint( "Constrain next execution state", 1.expr() - next_state.expr(), @@ -389,82 +421,46 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { &mut self, step_state_transition: StepStateTransition, ) { - for (name, curr, next, transition) in vec![ - ( - "State transition constrain of rw_counter", - &self.curr.state.rw_counter, - &self.next.state.rw_counter, - step_state_transition.rw_counter, - ), - ( - "State transition constrain of call_id", - &self.curr.state.call_id, - &self.next.state.call_id, - step_state_transition.call_id, - ), - ( - "State transition constrain of is_root", - &self.curr.state.is_root, - &self.next.state.is_root, - step_state_transition.is_root, - ), - ( - "State transition constrain of is_create", - &self.curr.state.is_create, - &self.next.state.is_create, - step_state_transition.is_create, - ), - ( - "State transition constrain of code_source", - &self.curr.state.code_source, - &self.next.state.code_source, - step_state_transition.code_source, - ), - ( - "State transition constrain of program_counter", - &self.curr.state.program_counter, - &self.next.state.program_counter, - step_state_transition.program_counter, - ), - ( - "State transition constrain of stack_pointer", - &self.curr.state.stack_pointer, - &self.next.state.stack_pointer, - step_state_transition.stack_pointer, - ), - ( - "State transition constrain of gas_left", - &self.curr.state.gas_left, - &self.next.state.gas_left, - step_state_transition.gas_left, - ), - ( - "State transition constrain of memory_word_size", - &self.curr.state.memory_word_size, - &self.next.state.memory_word_size, - step_state_transition.memory_word_size, - ), - ( - "State transition constrain of state_write_counter", - &self.curr.state.state_write_counter, - &self.next.state.state_write_counter, - step_state_transition.state_write_counter, - ), - ] { - match transition { - Transition::Same => self.require_equal(name, next.expr(), curr.expr()), - Transition::Delta(delta) => { - self.require_equal(name, next.expr(), curr.expr() + delta) + macro_rules! constrain { + ($name:tt) => { + match step_state_transition.$name { + Transition::Same => self.require_equal( + concat!("State transition constraint of ", stringify!($name)), + self.next.state.$name.expr(), + self.curr.state.$name.expr(), + ), + Transition::Delta(delta) => self.require_equal( + concat!("State transition constraint of ", stringify!($name)), + self.next.state.$name.expr(), + self.curr.state.$name.expr() + delta, + ), + Transition::To(to) => self.require_equal( + concat!("State transition constraint of ", stringify!($name)), + self.next.state.$name.expr(), + to, + ), + _ => {} } - Transition::To(to) => self.require_equal(name, next.expr(), to), - } + }; } + + constrain!(rw_counter); + constrain!(call_id); + constrain!(is_root); + constrain!(is_create); + constrain!(code_source); + constrain!(program_counter); + constrain!(stack_pointer); + constrain!(gas_left); + constrain!(memory_word_size); + constrain!(state_write_counter); } // Fixed pub(crate) fn range_lookup(&mut self, value: Expression, range: u64) { let (name, tag) = match range { + 5 => ("Range5", FixedTableTag::Range5), 16 => ("Range16", FixedTableTag::Range16), 32 => ("Range32", FixedTableTag::Range32), 256 => ("Range256", FixedTableTag::Range256), @@ -616,37 +612,30 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { self.rw_counter_offset.clone() + self.cb.condition.clone().unwrap_or_else(|| 1.expr()); } - fn state_write_with_reversion( + fn state_write( &mut self, name: &'static str, tag: RwTableTag, mut values: [Expression; 8], - is_persistent: Expression, - rw_counter_end_of_reversion: Expression, + reversion_info: Option>, ) { - self.rw_lookup(name, true.expr(), tag, values.clone()); - - // Revert if is_persistent is 0 - self.condition(1.expr() - is_persistent, |cb| { - // Calculate state_write_counter so far - let state_write_counter = - cb.curr.state.state_write_counter.expr() + cb.state_write_counter_offset.expr(); - - // Swap value and value_prev respect to tag - if tag.is_reversible() { - values.swap(4, 5) - }; + debug_assert!(tag.is_reversible(), "Only reversible tags are state write"); - cb.rw_lookup_with_counter( - name, - rw_counter_end_of_reversion - state_write_counter, - true.expr(), - tag, - values, - ) - }); + self.rw_lookup(name, true.expr(), tag, values.clone()); - self.state_write_counter_offset += 1; + if let Some(ReversionInfo { + is_persistent, + rw_counter, + }) = reversion_info + { + // Revert if is_persistent is 0 + self.condition(1.expr() - is_persistent, |cb| { + // Swap value and value_prev + values.swap(4, 5); + + cb.rw_lookup_with_counter(name, rw_counter, true.expr(), tag, values) + }); + } } // Access list @@ -657,10 +646,10 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { account_address: Expression, value: Expression, value_prev: Expression, + reversion_info: Option>, ) -> Expression { - self.rw_lookup( + self.state_write( "TxAccessListAccount write", - true.expr(), RwTableTag::TxAccessListAccount, [ tx_id, @@ -672,56 +661,51 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { 0.expr(), 0.expr(), ], + reversion_info, ); value - value_prev } - #[allow(clippy::too_many_arguments)] - pub(crate) fn account_storage_access_list_write_with_reversion( + pub(crate) fn account_storage_access_list_write( &mut self, tx_id: Expression, account_address: Expression, - key: Expression, + storage_key: Expression, value: Expression, value_prev: Expression, - is_persistent: Expression, - rw_counter_end_of_reversion: Expression, - ) { - self.state_write_with_reversion( - "account_storage_access_list_write_with_reversion", + reversion_info: Option>, + ) -> Expression { + self.state_write( + "TxAccessListAccountStorage write", RwTableTag::TxAccessListAccountStorage, [ tx_id, account_address, 0.expr(), - key, - value, - value_prev, + storage_key, + value.clone(), + value_prev.clone(), 0.expr(), 0.expr(), ], - is_persistent, - rw_counter_end_of_reversion, + reversion_info, ); + + value - value_prev } - // Account + // Tx Refund - pub(crate) fn account_read( - &mut self, - account_address: Expression, - field_tag: AccountFieldTag, - value: Expression, - ) { + pub(crate) fn tx_refund_read(&mut self, tx_id: Expression, value: Expression) { self.rw_lookup( - "Account read", + "TxRefund read", false.expr(), - RwTableTag::Account, + RwTableTag::TxRefund, [ + tx_id, + 0.expr(), 0.expr(), - account_address, - field_tag.expr(), 0.expr(), value.clone(), value, @@ -731,40 +715,40 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { ); } - pub(crate) fn account_write( + // Account + + pub(crate) fn account_read( &mut self, account_address: Expression, field_tag: AccountFieldTag, value: Expression, - value_prev: Expression, ) { self.rw_lookup( - "Account write", - true.expr(), + "Account read", + false.expr(), RwTableTag::Account, [ 0.expr(), account_address, field_tag.expr(), 0.expr(), + value.clone(), value, - value_prev, 0.expr(), 0.expr(), ], ); } - pub(crate) fn account_write_with_reversion( + pub(crate) fn account_write( &mut self, account_address: Expression, field_tag: AccountFieldTag, value: Expression, value_prev: Expression, - is_persistent: Expression, - rw_counter_end_of_reversion: Expression, + reversion_info: Option>, ) { - self.state_write_with_reversion( + self.state_write( "Account write with reversion", RwTableTag::Account, [ @@ -777,8 +761,7 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { 0.expr(), 0.expr(), ], - is_persistent, - rw_counter_end_of_reversion, + reversion_info, ); } diff --git a/zkevm-circuits/src/evm_circuit/util/math_gadget.rs b/zkevm-circuits/src/evm_circuit/util/math_gadget.rs index 4b3c5daec6..bf0ba7442b 100644 --- a/zkevm-circuits/src/evm_circuit/util/math_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/math_gadget.rs @@ -90,18 +90,27 @@ impl IsEqualGadget { /// Construction of 2 256-bit words addition and result, which is useful for /// opcode ADD, SUB and balance operation #[derive(Clone, Debug)] -pub(crate) struct AddWordsGadget { - addends: [util::Word; N], +pub(crate) struct AddWordsGadget { + addends: [util::Word; N_ADDENDS], sum: util::Word, carry_lo: Cell, - carry_hi: Cell, + carry_hi: Option>, } -impl AddWordsGadget { - pub(crate) fn construct(cb: &mut ConstraintBuilder, addends: [util::Word; N]) -> Self { - let sum = cb.query_word(); +impl + AddWordsGadget +{ + pub(crate) fn construct( + cb: &mut ConstraintBuilder, + addends: [util::Word; N_ADDENDS], + sum: util::Word, + ) -> Self { let carry_lo = cb.query_cell(); - let carry_hi = cb.query_cell(); + let carry_hi = if CHECK_OVREFLOW { + None + } else { + Some(cb.query_cell()) + }; let addends_lo = &addends .iter() @@ -120,16 +129,28 @@ impl AddWordsGadget { sum_lo + carry_lo.expr() * pow_of_two_expr(128), ); cb.require_equal( - "sum(addends_hi) + carry_lo == sum_hi + carry_hi ⋅ 2^128", + if CHECK_OVREFLOW { + "sum(addends_hi) + carry_lo == sum_hi" + } else { + "sum(addends_hi) + carry_lo == sum_hi + carry_hi ⋅ 2^128" + }, sum::expr(addends_hi) + carry_lo.expr(), - sum_hi + carry_hi.expr() * pow_of_two_expr(128), + if CHECK_OVREFLOW { + sum_hi + } else { + sum_hi + carry_hi.as_ref().unwrap().expr() * pow_of_two_expr(128) + }, ); - for carry in [&carry_lo, &carry_hi] { + for carry in if CHECK_OVREFLOW { + vec![&carry_lo] + } else { + vec![&carry_lo, carry_hi.as_ref().unwrap()] + } { cb.require_in_set( - "carry_lo in 0..N", + "carry_lo in 0..N_ADDENDS", carry.expr(), - (0..N).map(|idx| idx.expr()).collect(), + (0..N_ADDENDS).map(|idx| idx.expr()).collect(), ); } @@ -145,7 +166,7 @@ impl AddWordsGadget { &self, region: &mut Region<'_, F>, offset: usize, - addends: [Word; N], + addends: [Word; N_ADDENDS], sum: Word, ) -> Result<(), Error> { for (word, value) in self.addends.iter().zip(addends.iter()) { @@ -164,9 +185,15 @@ impl AddWordsGadget { .fold(Word::zero(), |acc, addend_hi| acc + addend_hi); let carry_lo = (sum_of_addends_lo - sum_lo) >> 128; - let carry_hi = (sum_of_addends_hi + carry_lo - sum_hi) >> 128; self.carry_lo.assign(region, offset, carry_lo.to_scalar())?; - self.carry_hi.assign(region, offset, carry_hi.to_scalar())?; + + if !CHECK_OVREFLOW { + let carry_hi = (sum_of_addends_hi + carry_lo - sum_hi) >> 128; + self.carry_hi + .as_ref() + .unwrap() + .assign(region, offset, carry_hi.to_scalar())?; + } Ok(()) } @@ -175,7 +202,7 @@ impl AddWordsGadget { &self.sum } - pub(crate) fn carry(&self) -> &util::Cell { + pub(crate) fn carry(&self) -> &Option> { &self.carry_hi } } @@ -364,29 +391,25 @@ impl MulWordsGadget { } } -/// Construction of 256-bit product by 256-bit multiplicand * 64-bit multiplier. +/// Construction of 256-bit product by 256-bit multiplicand * 64-bit multiplier, +/// which disallows overflow. #[derive(Clone, Debug)] pub(crate) struct MulWordByU64Gadget { multiplicand: util::Word, - multiplier: util::Cell, product: util::Word, carry_lo: [util::Cell; 8], - carry_hi: [util::Cell; 8], } impl MulWordByU64Gadget { pub(crate) fn construct( cb: &mut ConstraintBuilder, multiplicand: util::Word, - multiplier: util::Cell, - check_overflow: bool, + multiplier: Expression, ) -> Self { let gadget = Self { multiplicand, - multiplier, product: cb.query_word(), carry_lo: cb.query_bytes(), - carry_hi: cb.query_bytes(), }; let multiplicand_lo = from_bytes::expr(&gadget.multiplicand.cells[..16]); @@ -396,24 +419,19 @@ impl MulWordByU64Gadget { let product_hi = from_bytes::expr(&gadget.product.cells[16..]); let carry_lo = from_bytes::expr(&gadget.carry_lo[..8]); - let carry_hi = from_bytes::expr(&gadget.carry_hi[8..]); cb.require_equal( "multiplicand_lo ⋅ multiplier == carry_lo ⋅ 2^128 + product_lo", - multiplicand_lo * gadget.multiplier.expr(), + multiplicand_lo * multiplier.expr(), carry_lo.clone() * pow_of_two_expr(128) + product_lo, ); cb.require_equal( - "multiplicand_hi ⋅ multiplier + carry_lo == carry_hi ⋅ 2^128 + product_hi", - multiplicand_hi * gadget.multiplier.expr() + carry_lo, - carry_hi.clone() * pow_of_two_expr(128) + product_hi, + "multiplicand_hi ⋅ multiplier + carry_lo == product_hi", + multiplicand_hi * multiplier.expr() + carry_lo, + product_hi, ); - if check_overflow { - cb.require_zero("carry_hi == 0", carry_hi); - } - gadget } @@ -429,28 +447,19 @@ impl MulWordByU64Gadget { .assign(region, offset, Some(multiplicand.to_le_bytes()))?; self.product .assign(region, offset, Some(product.to_le_bytes()))?; - self.multiplier - .assign(region, offset, Some(multiplier.into()))?; - - let (multiplicand_lo, multiplicand_hi) = split_u256(&multiplicand); - let (product_lo, product_hi) = split_u256(&product); - - let mut assign_quotient = |cells: &[Cell], value: Word| -> Result<(), Error> { - for (cell, byte) in cells.iter().zip( - u64::try_from(value) - .map_err(|_| Error::Synthesis)? - .to_le_bytes() - .iter(), - ) { - cell.assign(region, offset, Some(F::from(*byte as u64)))?; - } - Ok(()) - }; + + let (multiplicand_lo, _) = split_u256(&multiplicand); + let (product_lo, _) = split_u256(&product); let carry_lo = (multiplicand_lo * multiplier - product_lo) >> 128; - let carry_hi = (multiplicand_hi * multiplier - product_hi + carry_lo) >> 128; - assign_quotient(&self.carry_lo, carry_lo)?; - assign_quotient(&self.carry_hi, carry_hi)?; + for (cell, byte) in self.carry_lo.iter().zip( + u64::try_from(carry_lo) + .map_err(|_| Error::Synthesis)? + .to_le_bytes() + .iter(), + ) { + cell.assign(region, offset, Some(F::from(*byte as u64)))?; + } Ok(()) } @@ -458,10 +467,6 @@ impl MulWordByU64Gadget { pub(crate) fn product(&self) -> &util::Word { &self.product } - - pub(crate) fn carry(&self) -> &[util::Cell; 8] { - &self.carry_hi - } } /// Requires that the passed in value is within the specified range. diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index 5e7357bbf0..3eef652900 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -14,7 +14,7 @@ use eth_types::{Address, Field, ToLittleEndian, ToScalar, ToWord, Word}; use halo2_proofs::arithmetic::{BaseExt, FieldExt}; use pairing::bn256::Fr as Fp; use sha3::{Digest, Keccak256}; -use std::{collections::HashMap, convert::TryInto}; +use std::{collections::HashMap, convert::TryInto, iter}; #[derive(Debug, Default, Clone)] pub struct Block { @@ -462,8 +462,8 @@ pub enum Rw { rw_counter: usize, is_write: bool, tx_id: usize, - value: Word, - value_prev: Word, + value: u64, + value_prev: u64, }, Account { rw_counter: usize, @@ -559,6 +559,13 @@ impl Rw { } } + pub fn tx_refund_value(&self) -> u64 { + match self { + Self::TxRefund { value, .. } => *value, + _ => unreachable!(), + } + } + pub fn account_value_pair(&self) -> (Word, Word) { match self { Self::Account { @@ -648,6 +655,26 @@ impl Rw { F::zero(), ] .into(), + Self::TxRefund { + rw_counter, + is_write, + tx_id, + value, + value_prev, + } => [ + F::from(*rw_counter as u64), + F::from(*is_write as u64), + F::from(RwTableTag::TxRefund as u64), + F::from(*tx_id as u64), + F::zero(), + F::zero(), + F::zero(), + F::from(*value), + F::from(*value_prev), + F::zero(), + F::zero(), + ] + .into(), Self::Account { rw_counter, is_write, @@ -699,7 +726,9 @@ impl Rw { randomness, ) } - CallContextFieldTag::IsSuccess => value.to_scalar().unwrap(), + CallContextFieldTag::CallerAddress + | CallContextFieldTag::CalleeAddress + | CallContextFieldTag::IsSuccess => value.to_scalar().unwrap(), _ => F::from(value.low_u64()), }, F::zero(), @@ -984,7 +1013,6 @@ impl From<&operation::OperationContainer> for RwMap { impl From<&ExecError> for ExecutionState { fn from(error: &ExecError) -> Self { match error { - ExecError::Reverted => ExecutionState::ErrorReverted, ExecError::InvalidOpcode => ExecutionState::ErrorInvalidOpcode, ExecError::StackOverflow => ExecutionState::ErrorStackOverflow, ExecError::StackUnderflow => ExecutionState::ErrorStackUnderflow, @@ -999,18 +1027,27 @@ impl From<&ExecError> for ExecutionState { ExecError::MaxCodeSizeExceeded => ExecutionState::ErrorMaxCodeSizeExceeded, ExecError::OutOfGas(oog_error) => match oog_error { OogError::Constant => ExecutionState::ErrorOutOfGasConstant, - OogError::PureMemory => ExecutionState::ErrorOutOfGasPureMemory, + OogError::StaticMemoryExpansion => { + ExecutionState::ErrorOutOfGasStaticMemoryExpansion + } + OogError::DynamicMemoryExpansion => { + ExecutionState::ErrorOutOfGasDynamicMemoryExpansion + } + OogError::MemoryCopy => ExecutionState::ErrorOutOfGasMemoryCopy, + OogError::AccountAccess => ExecutionState::ErrorOutOfGasAccountAccess, + OogError::CodeStore => ExecutionState::ErrorOutOfGasCodeStore, + OogError::Log => ExecutionState::ErrorOutOfGasLOG, + OogError::Exp => ExecutionState::ErrorOutOfGasEXP, OogError::Sha3 => ExecutionState::ErrorOutOfGasSHA3, - OogError::CallDataCopy => ExecutionState::ErrorOutOfGasCALLDATACOPY, - OogError::CodeCopy => ExecutionState::ErrorOutOfGasCODECOPY, OogError::ExtCodeCopy => ExecutionState::ErrorOutOfGasEXTCODECOPY, - OogError::ReturnDataCopy => ExecutionState::ErrorOutOfGasRETURNDATACOPY, - OogError::Log => ExecutionState::ErrorOutOfGasLOG, + OogError::Sload => ExecutionState::ErrorOutOfGasSLOAD, + OogError::Sstore => ExecutionState::ErrorOutOfGasSSTORE, OogError::Call => ExecutionState::ErrorOutOfGasCALL, OogError::CallCode => ExecutionState::ErrorOutOfGasCALLCODE, OogError::DelegateCall => ExecutionState::ErrorOutOfGasDELEGATECALL, OogError::Create2 => ExecutionState::ErrorOutOfGasCREATE2, OogError::StaticCall => ExecutionState::ErrorOutOfGasSTATICCALL, + OogError::SelfDestruct => ExecutionState::ErrorOutOfGasSELFDESTRUCT, }, } } @@ -1037,7 +1074,8 @@ impl From<&bus_mapping::circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::EQ | OpcodeId::LT | OpcodeId::GT => ExecutionState::CMP, OpcodeId::SLT | OpcodeId::SGT => ExecutionState::SCMP, OpcodeId::SIGNEXTEND => ExecutionState::SIGNEXTEND, - OpcodeId::STOP => ExecutionState::STOP, + // TODO: Convert REVERT and RETURN to their own ExecutionState. + OpcodeId::STOP | OpcodeId::RETURN | OpcodeId::REVERT => ExecutionState::STOP, OpcodeId::AND => ExecutionState::BITWISE, OpcodeId::XOR => ExecutionState::BITWISE, OpcodeId::OR => ExecutionState::BITWISE, @@ -1060,6 +1098,13 @@ impl From<&bus_mapping::circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::GAS => ExecutionState::GAS, OpcodeId::SELFBALANCE => ExecutionState::SELFBALANCE, OpcodeId::SLOAD => ExecutionState::SLOAD, + // TODO: Use better way to convert BeginTx and EndTx. + OpcodeId::INVALID(_) if [19, 21].contains(&step.bus_mapping_instance.len()) => { + ExecutionState::BeginTx + } + OpcodeId::INVALID(_) if [4, 5].contains(&step.bus_mapping_instance.len()) => { + ExecutionState::EndTx + } _ => unimplemented!("unimplemented opcode {:?}", step.op), } } @@ -1107,9 +1152,9 @@ fn step_convert(step: &circuit_input_builder::ExecStep) -> ExecStep { } } -fn tx_convert(tx: &circuit_input_builder::Transaction) -> Transaction { +fn tx_convert(tx: &circuit_input_builder::Transaction, id: usize, is_last_tx: bool) -> Transaction { Transaction { - id: 1, + id, nonce: tx.nonce, gas: tx.gas, gas_price: tx.gas_price, @@ -1151,7 +1196,24 @@ fn tx_convert(tx: &circuit_input_builder::Transaction) -> Transaction { is_static: call.is_static, }) .collect(), - steps: tx.steps().iter().map(step_convert).collect(), + steps: tx + .steps() + .iter() + .map(step_convert) + .chain( + (if is_last_tx { + Some(iter::once(ExecStep { + rw_counter: tx.steps().last().unwrap().rwc.0 + 4, + execution_state: ExecutionState::EndBlock, + ..Default::default() + })) + } else { + None + }) + .into_iter() + .flatten(), + ) + .collect(), } } pub fn block_convert( @@ -1162,7 +1224,12 @@ pub fn block_convert( randomness: Fp::rand(), context: block.into(), rws: RwMap::from(&block.container), - txs: block.txs().iter().map(tx_convert).collect(), + txs: block + .txs() + .iter() + .enumerate() + .map(|(idx, tx)| tx_convert(tx, idx + 1, idx + 1 == block.txs().len())) + .collect(), bytecodes: block .txs() .iter() diff --git a/zkevm-circuits/src/state_circuit/state.rs b/zkevm-circuits/src/state_circuit/state.rs index 1d8bd8ad3d..979ffcb049 100644 --- a/zkevm-circuits/src/state_circuit/state.rs +++ b/zkevm-circuits/src/state_circuit/state.rs @@ -1388,7 +1388,6 @@ mod tests { PUSH1(0x80) PUSH1(0x40) MSTORE - #[start] PUSH1(0x40) MLOAD STOP @@ -1397,7 +1396,9 @@ mod tests { mock::new_single_tx_trace_code(&bytecode).unwrap(), ); let mut builder = block.new_circuit_input_builder(); - builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); let stack_ops = builder.block.container.sorted_stack(); let memory_ops = builder.block.container.sorted_memory(); diff --git a/zkevm-circuits/src/test_util.rs b/zkevm-circuits/src/test_util.rs index 49fee5c810..ed955b3f03 100644 --- a/zkevm-circuits/src/test_util.rs +++ b/zkevm-circuits/src/test_util.rs @@ -15,6 +15,7 @@ pub fn get_fixed_table(conf: FixedTableConfig) -> Vec { match conf { FixedTableConfig::Incomplete => { vec![ + FixedTableTag::Range5, FixedTableTag::Range16, FixedTableTag::Range32, FixedTableTag::Range256, @@ -59,7 +60,7 @@ pub fn test_circuits_using_bytecode( ); let mut builder = block_trace.new_circuit_input_builder(); builder - .handle_tx(&block_trace.eth_tx, &block_trace.geth_trace) + .handle_block(&block_trace.eth_block, &block_trace.geth_traces) .unwrap(); // build a witness block from trace result