diff --git a/bus-mapping/src/circuit_input_builder/input_state_ref.rs b/bus-mapping/src/circuit_input_builder/input_state_ref.rs index 32d146796a..babcbebb62 100644 --- a/bus-mapping/src/circuit_input_builder/input_state_ref.rs +++ b/bus-mapping/src/circuit_input_builder/input_state_ref.rs @@ -370,6 +370,13 @@ impl<'a> CircuitInputStateRef<'a> { .ok_or(Error::CodeNotFound(code_hash)) } + /// Reference to the caller's Call + pub fn caller(&self) -> Result<&Call, Error> { + self.tx_ctx + .caller_index() + .map(|caller_idx| &self.tx.calls()[caller_idx]) + } + /// Reference to the current Call pub fn call(&self) -> Result<&Call, Error> { self.tx_ctx @@ -384,6 +391,11 @@ impl<'a> CircuitInputStateRef<'a> { .map(|call_idx| &mut self.tx.calls_mut()[call_idx]) } + /// Reference to the current CallContext + pub fn caller_ctx(&self) -> Result<&CallContext, Error> { + self.tx_ctx.caller_ctx() + } + /// Reference to the current CallContext pub fn call_ctx(&self) -> Result<&CallContext, Error> { self.tx_ctx.call_ctx() diff --git a/bus-mapping/src/circuit_input_builder/transaction.rs b/bus-mapping/src/circuit_input_builder/transaction.rs index 09f5d55ba0..d260471a87 100644 --- a/bus-mapping/src/circuit_input_builder/transaction.rs +++ b/bus-mapping/src/circuit_input_builder/transaction.rs @@ -96,14 +96,24 @@ impl TransactionContext { &self.calls } + /// Return the index of the caller (the second last call in the call stack). + pub(crate) fn caller_index(&self) -> Result { + self.caller_ctx().map(|call| call.index) + } + /// Return the index of the current call (the last call in the call stack). pub(crate) fn call_index(&self) -> Result { + self.call_ctx().map(|call| call.index) + } + + pub(crate) fn caller_ctx(&self) -> Result<&CallContext, Error> { self.calls - .last() + .len() + .checked_sub(2) + .map(|idx| &self.calls[idx]) .ok_or(Error::InvalidGethExecTrace( "Call stack is empty but call is used", )) - .map(|call| call.index) } pub(crate) fn call_ctx(&self) -> Result<&CallContext, Error> { diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index 24b24d5c1f..d1dbc47cbe 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -34,6 +34,7 @@ mod mload; mod mstore; mod number; mod origin; +mod r#return; mod selfbalance; mod sload; mod sstore; @@ -56,6 +57,7 @@ use logs::Log; use mload::Mload; use mstore::Mstore; use origin::Origin; +use r#return::Return; use selfbalance::Selfbalance; use sload::Sload; use sstore::Sstore; @@ -200,13 +202,15 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { // OpcodeId::CREATE => {}, OpcodeId::CALL => Call::gen_associated_ops, // OpcodeId::CALLCODE => {}, - // TODO: Handle RETURN by its own gen_associated_ops. - OpcodeId::RETURN => Stop::gen_associated_ops, + // OpcodeId::RETURN => {}, // OpcodeId::DELEGATECALL => {}, // OpcodeId::CREATE2 => {}, // OpcodeId::STATICCALL => {}, - // TODO: Handle REVERT by its own gen_associated_ops. - OpcodeId::REVERT => Stop::gen_associated_ops, + // OpcodeId::REVERT => {}, + OpcodeId::REVERT | OpcodeId::RETURN => { + warn!("Using dummy gen_associated_ops for opcode {:?}", opcode_id); + Return::gen_associated_ops + } OpcodeId::SELFDESTRUCT => { warn!("Using dummy gen_selfdestruct_ops for opcode SELFDESTRUCT"); dummy_gen_selfdestruct_ops @@ -375,7 +379,7 @@ pub fn gen_begin_tx_ops(state: &mut CircuitInputStateRef) -> Result Result, Error> { + let geth_step = &geth_steps[0]; + let exec_step = state.new_step(geth_step)?; + + // TODO: Generate associated operations of RETURN + + state.handle_return(geth_step)?; + Ok(vec![exec_step]) + } +} diff --git a/bus-mapping/src/evm/opcodes/stop.rs b/bus-mapping/src/evm/opcodes/stop.rs index 0a08ffab9e..7b4723d116 100644 --- a/bus-mapping/src/evm/opcodes/stop.rs +++ b/bus-mapping/src/evm/opcodes/stop.rs @@ -1,7 +1,10 @@ use super::Opcode; -use crate::circuit_input_builder::{CircuitInputStateRef, ExecStep}; -use crate::Error; -use eth_types::GethExecStep; +use crate::{ + circuit_input_builder::{CircuitInputStateRef, ExecStep}, + operation::{CallContextField, CallContextOp, RW}, + Error, +}; +use eth_types::{GethExecStep, ToWord}; /// Placeholder structure used to implement [`Opcode`] trait over it /// corresponding to the [`OpcodeId::STOP`](crate::evm::OpcodeId::STOP) @@ -18,8 +21,100 @@ impl Opcode for Stop { geth_steps: &[GethExecStep], ) -> Result, Error> { let geth_step = &geth_steps[0]; - let exec_step = state.new_step(geth_step)?; + let mut exec_step = state.new_step(geth_step)?; + + let call = state.call()?.clone(); + + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: call.call_id, + field: CallContextField::IsSuccess, + value: 1.into(), + }, + ); + + if call.is_root { + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: call.call_id, + field: CallContextField::IsPersistent, + value: 1.into(), + }, + ); + } else { + // The following part corresponds to + // Instruction.step_state_transition_to_restored_context + // in python spec, and should be reusable among all expected halting opcodes or + // exceptions. TODO: Refactor it as a helper function. + let caller = state.caller()?.clone(); + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: call.call_id, + field: CallContextField::CallerId, + value: caller.call_id.into(), + }, + ); + + let geth_step_next = &geth_steps[1]; + let caller_gas_left = geth_step_next.gas.0 - geth_step.gas.0; + for (field, value) in [ + (CallContextField::IsRoot, (caller.is_root as u64).into()), + ( + CallContextField::IsCreate, + (caller.is_create() as u64).into(), + ), + (CallContextField::CodeHash, caller.code_hash.to_word()), + (CallContextField::ProgramCounter, geth_step_next.pc.0.into()), + ( + CallContextField::StackPointer, + geth_step_next.stack.stack_pointer().0.into(), + ), + (CallContextField::GasLeft, caller_gas_left.into()), + ( + CallContextField::MemorySize, + geth_step_next.memory.word_size().into(), + ), + ( + CallContextField::ReversibleWriteCounter, + state.caller_ctx()?.reversible_write_counter.into(), + ), + ] { + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: caller.call_id, + field, + value, + }, + ); + } + + for (field, value) in [ + (CallContextField::LastCalleeId, call.call_id.into()), + (CallContextField::LastCalleeReturnDataOffset, 0.into()), + (CallContextField::LastCalleeReturnDataLength, 0.into()), + ] { + state.push_op( + &mut exec_step, + RW::WRITE, + CallContextOp { + call_id: caller.call_id, + field, + value, + }, + ); + } + } + state.handle_return(geth_step)?; + Ok(vec![exec_step]) } } diff --git a/bus-mapping/src/operation.rs b/bus-mapping/src/operation.rs index 6aa0b199b1..840fa63116 100644 --- a/bus-mapping/src/operation.rs +++ b/bus-mapping/src/operation.rs @@ -697,8 +697,8 @@ pub enum CallContextField { IsRoot, /// IsCreate IsCreate, - /// CodeSource - CodeSource, + /// CodeHash + CodeHash, /// ProgramCounter ProgramCounter, /// StackPointer @@ -707,8 +707,8 @@ pub enum CallContextField { GasLeft, /// MemorySize MemorySize, - /// StateWriteCounter - StateWriteCounter, + /// ReversibleWriteCounter + ReversibleWriteCounter, } /// Represents an CallContext read/write operation. diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 70ac7a1f55..3f3b6793f7 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -270,10 +270,10 @@ pub mod test { ) } - fn load_bytecodes( + fn load_bytecodes<'a>( &self, layouter: &mut impl Layouter, - bytecodes: &[Bytecode], + bytecodes: impl IntoIterator + Clone, randomness: F, ) -> Result<(), Error> { layouter.assign_region( @@ -290,7 +290,7 @@ pub mod test { } offset += 1; - for bytecode in bytecodes.iter() { + for bytecode in bytecodes.clone() { for row in bytecode.table_assignments(randomness) { for (column, value) in self.bytecode_table.iter().zip_eq(row) { region.assign_advice( @@ -420,7 +420,11 @@ pub mod test { config.evm_circuit.load_byte_table(&mut layouter)?; config.load_txs(&mut layouter, &self.block.txs, self.block.randomness)?; config.load_rws(&mut layouter, &self.block.rws, self.block.randomness)?; - config.load_bytecodes(&mut layouter, &self.block.bytecodes, self.block.randomness)?; + config.load_bytecodes( + &mut layouter, + self.block.bytecodes.values(), + self.block.randomness, + )?; config.load_block(&mut layouter, &self.block.context, self.block.randomness)?; config .evm_circuit @@ -459,7 +463,7 @@ pub mod test { let k = k.max(log2_ceil( 64 + block .bytecodes - .iter() + .values() .map(|bytecode| bytecode.bytes.len()) .sum::(), )); diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index dc168048a2..c6ec6659f6 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -63,6 +63,7 @@ mod origin; mod pc; mod pop; mod push; +mod r#return; mod selfbalance; mod shr; mod signed_comparator; @@ -113,6 +114,7 @@ use origin::OriginGadget; use pc::PcGadget; use pop::PopGadget; use push::PushGadget; +use r#return::ReturnGadget; use selfbalance::SelfbalanceGadget; use shr::ShrGadget; use signed_comparator::SignedComparatorGadget; @@ -192,6 +194,7 @@ pub(crate) struct ExecutionConfig { pc_gadget: PcGadget, pop_gadget: PopGadget, push_gadget: PushGadget, + return_gadget: ReturnGadget, selfbalance_gadget: SelfbalanceGadget, shr_gadget: ShrGadget, sha3_gadget: DummyGadget, @@ -276,7 +279,7 @@ impl ExecutionConfig { iter::once(sum_to_one) .chain(bool_checks) .map(move |(name, poly)| (name, q_usable.clone() * q_step.clone() * poly)) - // TODO: Enable these after test of CALLDATACOPY is complete. + // TODO: Enable these after incomplete trace is no longer necessary. // .chain(first_step_check) // .chain(last_step_check) }); @@ -387,6 +390,7 @@ impl ExecutionConfig { pc_gadget: configure_gadget!(), pop_gadget: configure_gadget!(), push_gadget: configure_gadget!(), + return_gadget: configure_gadget!(), selfbalance_gadget: configure_gadget!(), sha3_gadget: configure_gadget!(), shr_gadget: configure_gadget!(), @@ -401,7 +405,6 @@ impl ExecutionConfig { block_ctx_u256_gadget: configure_gadget!(), // error gadgets error_oog_static_memory_gadget: configure_gadget!(), - // step and presets step: step_curr, height_map, @@ -664,7 +667,9 @@ impl ExecutionConfig { call, step, height, - steps.peek(), + steps.peek().map(|&(transaction, step)| { + (transaction, &transaction.calls[step.call_index], step) + }), power_of_randomness, )?; @@ -734,7 +739,7 @@ impl ExecutionConfig { call: &Call, step: &ExecStep, height: usize, - next: Option<&(&Transaction, &ExecStep)>, + next: Option<(&Transaction, &Call, &ExecStep)>, power_of_randomness: [F; 31], ) -> Result<(), Error> { // Make the region large enough for the current step and the next step. @@ -753,13 +758,13 @@ impl ExecutionConfig { // These may be used in stored expressions and // so their witness values need to be known to be able // to correctly calculate the intermediate value. - if let Some((transaction_next, step_next)) = next { + if let Some((transaction_next, call_next, step_next)) = next { self.assign_exec_step_int( region, offset + height, block, transaction_next, - call, + call_next, step_next, )?; } @@ -827,6 +832,7 @@ impl ExecutionConfig { ExecutionState::PC => assign_exec_step!(self.pc_gadget), ExecutionState::POP => assign_exec_step!(self.pop_gadget), ExecutionState::PUSH => assign_exec_step!(self.push_gadget), + ExecutionState::RETURN => assign_exec_step!(self.return_gadget), ExecutionState::SCMP => assign_exec_step!(self.signed_comparator_gadget), ExecutionState::BLOCKCTXU64 => assign_exec_step!(self.block_ctx_u64_gadget), ExecutionState::BLOCKCTXU160 => assign_exec_step!(self.block_ctx_u160_gadget), diff --git a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs index 32ad344122..4ba498088b 100644 --- a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs @@ -154,7 +154,7 @@ impl ExecutionGadget for BeginTxGadget { (CallContextFieldTag::LastCalleeReturnDataLength, 0.expr()), (CallContextFieldTag::IsRoot, 1.expr()), (CallContextFieldTag::IsCreate, 0.expr()), - (CallContextFieldTag::CodeSource, code_hash.expr()), + (CallContextFieldTag::CodeHash, code_hash.expr()), ] { cb.call_context_lookup(false.expr(), Some(call_id.expr()), field_tag, value); } diff --git a/zkevm-circuits/src/evm_circuit/execution/call.rs b/zkevm-circuits/src/evm_circuit/execution/call.rs index a360ed3a18..e0e89d9a10 100644 --- a/zkevm-circuits/src/evm_circuit/execution/call.rs +++ b/zkevm-circuits/src/evm_circuit/execution/call.rs @@ -255,7 +255,7 @@ impl ExecutionGadget for CallGadget { memory_expansion.next_memory_word_size(), ), ( - CallContextFieldTag::StateWriteCounter, + CallContextFieldTag::ReversibleWriteCounter, cb.curr.state.reversible_write_counter.expr() + 1.expr(), ), ] { @@ -281,7 +281,7 @@ impl ExecutionGadget for CallGadget { (CallContextFieldTag::LastCalleeReturnDataLength, 0.expr()), (CallContextFieldTag::IsRoot, 0.expr()), (CallContextFieldTag::IsCreate, 0.expr()), - (CallContextFieldTag::CodeSource, callee_code_hash.expr()), + (CallContextFieldTag::CodeHash, callee_code_hash.expr()), ] { cb.call_context_lookup(false.expr(), Some(callee_call_id.expr()), field_tag, value); } diff --git a/zkevm-circuits/src/evm_circuit/execution/codecopy.rs b/zkevm-circuits/src/evm_circuit/execution/codecopy.rs index b7256c7d62..063e4e6eee 100644 --- a/zkevm-circuits/src/evm_circuit/execution/codecopy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/codecopy.rs @@ -15,7 +15,7 @@ use crate::{ memory_gadget::{MemoryAddressGadget, MemoryCopierGasGadget, MemoryExpansionGadget}, CachedRegion, Cell, MemoryAddress, }, - witness::{Block, Call, CodeSource, ExecStep, Transaction}, + witness::{Block, Call, ExecStep, Transaction}, }, util::Expr, }; @@ -177,11 +177,7 @@ impl ExecutionGadget for CodeCopyGadget { let code = block .bytecodes - .iter() - .find(|b| { - let CodeSource::Account(code_hash) = &call.code_source; - b.hash == *code_hash - }) + .get(&call.code_hash) .expect("could not find current environment's bytecode"); self.code_size .assign(region, offset, Some(F::from(code.bytes.len() as u64)))?; diff --git a/zkevm-circuits/src/evm_circuit/execution/copy_code_to_memory.rs b/zkevm-circuits/src/evm_circuit/execution/copy_code_to_memory.rs index ed298cc00e..488b201133 100644 --- a/zkevm-circuits/src/evm_circuit/execution/copy_code_to_memory.rs +++ b/zkevm-circuits/src/evm_circuit/execution/copy_code_to_memory.rs @@ -187,8 +187,7 @@ impl ExecutionGadget for CopyCodeToMemoryGadget { let code = block .bytecodes - .iter() - .find(|b| b.hash == code_hash) + .get(&code_hash) .unwrap_or_else(|| panic!("could not find bytecode with hash={:?}", code_hash)); // Assign to the appropriate cells. self.src_addr @@ -265,7 +264,7 @@ pub(crate) mod test { step::ExecutionState, table::RwTableTag, test::run_test_circuit_incomplete_fixed_table, - witness::{Block, Bytecode, Call, CodeSource, ExecStep, Rw, RwMap, Transaction}, + witness::{Block, Bytecode, Call, ExecStep, Rw, RwMap, Transaction}, }; #[allow(clippy::too_many_arguments)] @@ -393,7 +392,7 @@ pub(crate) mod test { }; let code = Bytecode::new(code.to_vec()); - let dummy_code = Bytecode::new(vec![OpcodeId::STOP.as_u8()]); + let dummy_code = Bytecode::new(vec![OpcodeId::RETURN.as_u8()]); let program_counter = 0; let stack_pointer = 1024; @@ -412,12 +411,12 @@ pub(crate) mod test { ); steps.push(ExecStep { - execution_state: ExecutionState::STOP, + execution_state: ExecutionState::RETURN, rw_counter, program_counter, stack_pointer, memory_size, - opcode: Some(OpcodeId::STOP), + opcode: Some(OpcodeId::RETURN), ..Default::default() }); @@ -429,14 +428,14 @@ pub(crate) mod test { id: call_id, is_root: true, is_create: false, - code_source: CodeSource::Account(dummy_code.hash), + code_hash: dummy_code.hash, ..Default::default() }], steps, ..Default::default() }], rws, - bytecodes: vec![dummy_code, code], + bytecodes: HashMap::from_iter([(dummy_code.hash, dummy_code), (code.hash, code)]), ..Default::default() }; assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); diff --git a/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs b/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs index bd09dac195..d90bba5b9d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs +++ b/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs @@ -233,7 +233,7 @@ pub mod test { step::ExecutionState, table::{RwTableTag, TxLogFieldTag}, test::{rand_bytes, run_test_circuit_incomplete_fixed_table}, - witness::{Block, Bytecode, Call, CodeSource, ExecStep, Rw, RwMap, Transaction}, + witness::{Block, Bytecode, Call, ExecStep, Rw, RwMap, Transaction}, }; use bus_mapping::{ circuit_input_builder::{CopyDetails, StepAuxiliaryData}, @@ -384,7 +384,7 @@ pub mod test { fn test_ok_copy_to_log(src_addr: u64, src_addr_end: u64, length: usize, is_persistent: bool) { let randomness = Fr::rand(); - let bytecode = Bytecode::new(vec![OpcodeId::STOP.as_u8()]); + let bytecode = Bytecode::new(vec![OpcodeId::RETURN.as_u8()]); let call_id = 1; let mut rws = RwMap(Default::default()); let mut rw_counter = 1; @@ -412,12 +412,12 @@ pub mod test { ); steps.push(ExecStep { - execution_state: ExecutionState::STOP, + execution_state: ExecutionState::RETURN, rw_counter, program_counter: 0, stack_pointer: 1023, memory_size, - opcode: Some(OpcodeId::STOP), + opcode: Some(OpcodeId::RETURN), ..Default::default() }); @@ -429,14 +429,14 @@ pub mod test { id: call_id, is_root: false, is_create: false, - code_source: CodeSource::Account(bytecode.hash), + code_hash: bytecode.hash, ..Default::default() }], steps, ..Default::default() }], rws, - bytecodes: vec![bytecode], + bytecodes: HashMap::from_iter([(bytecode.hash, bytecode)]), ..Default::default() }; assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); diff --git a/zkevm-circuits/src/evm_circuit/execution/memory_copy.rs b/zkevm-circuits/src/evm_circuit/execution/memory_copy.rs index b66d099a85..21b071b48a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/memory_copy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/memory_copy.rs @@ -240,7 +240,7 @@ pub mod test { step::ExecutionState, table::RwTableTag, test::{rand_bytes, run_test_circuit_incomplete_fixed_table}, - witness::{Block, Bytecode, Call, CodeSource, ExecStep, Rw, RwMap, Transaction}, + witness::{Block, Bytecode, Call, ExecStep, Rw, RwMap, Transaction}, }; use bus_mapping::circuit_input_builder::{CopyDetails, StepAuxiliaryData}; use eth_types::evm_types::OpcodeId; @@ -364,7 +364,7 @@ pub mod test { fn test_ok_from_memory(src_addr: u64, dst_addr: u64, src_addr_end: u64, length: usize) { let randomness = Fr::rand(); - let bytecode = Bytecode::new(vec![OpcodeId::STOP.as_u8()]); + let bytecode = Bytecode::new(vec![OpcodeId::RETURN.as_u8()]); let mut rws = RwMap(Default::default()); let mut rw_counter = 1; let mut steps = Vec::new(); @@ -387,12 +387,12 @@ pub mod test { ); steps.push(ExecStep { - execution_state: ExecutionState::STOP, + execution_state: ExecutionState::RETURN, rw_counter, program_counter: 0, stack_pointer: 1024, memory_size, - opcode: Some(OpcodeId::STOP), + opcode: Some(OpcodeId::RETURN), ..Default::default() }); @@ -404,7 +404,7 @@ pub mod test { id: CALL_ID, is_root: false, is_create: false, - code_source: CodeSource::Account(bytecode.hash), + code_hash: bytecode.hash, caller_id: CALLER_ID, ..Default::default() }], @@ -412,7 +412,7 @@ pub mod test { ..Default::default() }], rws, - bytecodes: vec![bytecode], + bytecodes: HashMap::from_iter([(bytecode.hash, bytecode)]), ..Default::default() }; assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); @@ -420,7 +420,7 @@ pub mod test { fn test_ok_from_tx(calldata_length: usize, src_addr: u64, dst_addr: u64, length: usize) { let randomness = Fr::rand(); - let bytecode = Bytecode::new(vec![OpcodeId::STOP.as_u8(), OpcodeId::STOP.as_u8()]); + let bytecode = Bytecode::new(vec![OpcodeId::RETURN.as_u8(), OpcodeId::RETURN.as_u8()]); let mut rws = RwMap(Default::default()); let mut rw_counter = 1; let calldata: Vec = rand_bytes(calldata_length); @@ -443,12 +443,12 @@ pub mod test { ); steps.push(ExecStep { - execution_state: ExecutionState::STOP, + execution_state: ExecutionState::RETURN, rw_counter, program_counter: 0, stack_pointer: 1024, memory_size, - opcode: Some(OpcodeId::STOP), + opcode: Some(OpcodeId::RETURN), ..Default::default() }); @@ -462,14 +462,14 @@ pub mod test { id: CALL_ID, is_root: true, is_create: false, - code_source: CodeSource::Account(bytecode.hash), + code_hash: bytecode.hash, ..Default::default() }], steps, ..Default::default() }], rws, - bytecodes: vec![bytecode], + bytecodes: HashMap::from_iter([(bytecode.hash, bytecode)]), ..Default::default() }; assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); diff --git a/zkevm-circuits/src/evm_circuit/execution/return.rs b/zkevm-circuits/src/evm_circuit/execution/return.rs new file mode 100644 index 0000000000..b70f0320ea --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/return.rs @@ -0,0 +1,48 @@ +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + step::ExecutionState, + util::{constraint_builder::ConstraintBuilder, CachedRegion, Cell}, + witness::{Block, Call, ExecStep, Transaction}, + }, + util::Expr, +}; +use eth_types::Field; +use halo2_proofs::plonk::Error; + +#[derive(Clone, Debug)] +pub(crate) struct ReturnGadget { + opcode: Cell, +} + +impl ExecutionGadget for ReturnGadget { + const NAME: &'static str = "RETURN"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::RETURN; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let opcode = cb.query_cell(); + cb.opcode_lookup(opcode.expr(), 1.expr()); + + // TODO: Other constraints are ignored now for RETURN to serve as a + // mocking terminator + + Self { opcode } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + _: &Block, + _: &Transaction, + _: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + let opcode = step.opcode.unwrap(); + self.opcode + .assign(region, offset, Some(F::from(opcode.as_u64())))?; + + Ok(()) + } +} diff --git a/zkevm-circuits/src/evm_circuit/execution/stop.rs b/zkevm-circuits/src/evm_circuit/execution/stop.rs index 642d0ef5e8..a76cd3c28a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/stop.rs +++ b/zkevm-circuits/src/evm_circuit/execution/stop.rs @@ -2,17 +2,30 @@ use crate::{ evm_circuit::{ execution::ExecutionGadget, step::ExecutionState, - util::{constraint_builder::ConstraintBuilder, CachedRegion, Cell}, + table::CallContextFieldTag, + util::{ + common_gadget::RestoreContextGadget, + constraint_builder::{ + ConstraintBuilder, StepStateTransition, + Transition::{Delta, Same}, + }, + math_gadget::IsZeroGadget, + CachedRegion, Cell, + }, witness::{Block, Call, ExecStep, Transaction}, }, util::Expr, }; +use bus_mapping::evm::OpcodeId; use eth_types::Field; use halo2_proofs::plonk::Error; #[derive(Clone, Debug)] pub(crate) struct StopGadget { + code_length: Cell, + is_out_of_range: IsZeroGadget, opcode: Cell, + restore_context: RestoreContextGadget, } impl ExecutionGadget for StopGadget { @@ -21,28 +34,193 @@ impl ExecutionGadget for StopGadget { const EXECUTION_STATE: ExecutionState = ExecutionState::STOP; fn configure(cb: &mut ConstraintBuilder) -> Self { + let code_length = cb.bytecode_length(cb.curr.state.code_hash.expr()); + let is_out_of_range = IsZeroGadget::construct( + cb, + code_length.expr() - cb.curr.state.program_counter.expr(), + ); let opcode = cb.query_cell(); - cb.opcode_lookup(opcode.expr(), 1.expr()); + cb.condition(1.expr() - is_out_of_range.expr(), |cb| { + cb.opcode_lookup(opcode.expr(), 1.expr()); + }); - // Other constraints are ignored now for STOP to serve as a mocking - // terminator + // We do the responsible opcode check explicitly here because we're not using + // the `SameContextGadget` for `STOP`. + cb.require_equal( + "Opcode should be STOP", + opcode.expr(), + OpcodeId::STOP.expr(), + ); - Self { opcode } + // Call ends with STOP must be successful + cb.call_context_lookup(false.expr(), None, CallContextFieldTag::IsSuccess, 1.expr()); + + let is_to_end_tx = cb.next.execution_state_selector([ExecutionState::EndTx]); + cb.require_equal( + "Go to EndTx only when is_root", + cb.curr.state.is_root.expr(), + is_to_end_tx, + ); + + // When it's a root call + cb.condition(cb.curr.state.is_root.expr(), |cb| { + // When a transaction ends with STOP, this call must be persistent + cb.call_context_lookup( + false.expr(), + None, + CallContextFieldTag::IsPersistent, + 1.expr(), + ); + + // Do step state transition + cb.require_step_state_transition(StepStateTransition { + call_id: Same, + rw_counter: Delta(2.expr()), + ..StepStateTransition::any() + }); + }); + + // When it's an internal call + let restore_context = cb.condition(1.expr() - cb.curr.state.is_root.expr(), |cb| { + RestoreContextGadget::construct(cb, 1.expr(), 0.expr(), 0.expr()) + }); + + Self { + code_length, + is_out_of_range, + opcode, + restore_context, + } } fn assign_exec_step( &self, region: &mut CachedRegion<'_, '_, F>, offset: usize, - _: &Block, + block: &Block, _: &Transaction, - _: &Call, + call: &Call, step: &ExecStep, ) -> Result<(), Error> { + let code = block + .bytecodes + .get(&call.code_hash) + .expect("could not find current environment's bytecode"); + self.code_length + .assign(region, offset, Some(F::from(code.bytes.len() as u64)))?; + + self.is_out_of_range.assign( + region, + offset, + F::from(code.bytes.len() as u64) - F::from(step.program_counter), + )?; + let opcode = step.opcode.unwrap(); self.opcode .assign(region, offset, Some(F::from(opcode.as_u64())))?; + self.restore_context + .assign(region, offset, block, call, step)?; + Ok(()) } } + +#[cfg(test)] +mod test { + use crate::evm_circuit::{ + test::run_test_circuit_incomplete_fixed_table, witness::block_convert, + }; + use eth_types::{address, bytecode, Bytecode, Word}; + use itertools::Itertools; + use mock::TestContext; + + fn test_ok(bytecode: Bytecode, is_root: bool) { + let block_data = if is_root { + bus_mapping::mock::BlockData::new_from_geth_data( + TestContext::<2, 1>::new( + None, + |accs| { + accs[0] + .address(address!("0x0000000000000000000000000000000000000000")) + .balance(Word::from(1u64 << 30)); + accs[1] + .address(address!("0x0000000000000000000000000000000000000010")) + .balance(Word::from(1u64 << 20)) + .code(bytecode); + }, + |mut txs, accs| { + txs[0] + .from(accs[0].address) + .to(accs[1].address) + .gas(Word::from(30000)); + }, + |block, _tx| block.number(0xcafeu64), + ) + .unwrap() + .into(), + ) + } else { + bus_mapping::mock::BlockData::new_from_geth_data( + TestContext::<3, 1>::new( + None, + |accs| { + accs[0] + .address(address!("0x0000000000000000000000000000000000000000")) + .balance(Word::from(1u64 << 30)); + accs[1] + .address(address!("0x0000000000000000000000000000000000000010")) + .balance(Word::from(1u64 << 20)) + .code(bytecode! { + PUSH1(0) + PUSH1(0) + PUSH1(0) + PUSH1(0) + PUSH1(0) + PUSH1(0x20) + GAS + CALL + STOP + }); + accs[2] + .address(address!("0x0000000000000000000000000000000000000020")) + .balance(Word::from(1u64 << 20)) + .code(bytecode); + }, + |mut txs, accs| { + txs[0] + .from(accs[0].address) + .to(accs[1].address) + .gas(Word::from(30000)); + }, + |block, _tx| block.number(0xcafeu64), + ) + .unwrap() + .into(), + ) + }; + 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(())); + } + + #[test] + fn stop_gadget_simple() { + let bytecodes = vec![ + bytecode! { + PUSH1(0) + STOP + }, + bytecode! { + PUSH1(0) + }, + ]; + let is_roots = vec![true, false]; + for (bytecode, is_root) in bytecodes.into_iter().cartesian_product(is_roots) { + test_ok(bytecode, is_root); + } + } +} diff --git a/zkevm-circuits/src/evm_circuit/step.rs b/zkevm-circuits/src/evm_circuit/step.rs index f30ca9383f..1a8439c894 100644 --- a/zkevm-circuits/src/evm_circuit/step.rs +++ b/zkevm-circuits/src/evm_circuit/step.rs @@ -3,7 +3,7 @@ use crate::{ evm_circuit::{ param::{MAX_STEP_HEIGHT, STEP_WIDTH}, util::{Cell, RandomLinearCombination}, - witness::{Block, Call, CodeSource, ExecStep, Transaction}, + witness::{Block, Call, ExecStep, Transaction}, }, util::Expr, }; @@ -138,14 +138,14 @@ impl ExecutionState { Self::iter().count() } - pub(crate) fn halts(&self) -> bool { + pub(crate) fn halts_in_success(&self) -> bool { + matches!(self, Self::STOP | Self::RETURN | Self::SELFDESTRUCT) + } + + pub(crate) fn halts_in_exception(&self) -> bool { matches!( self, - Self::STOP - | Self::RETURN - | Self::REVERT - | Self::SELFDESTRUCT - | Self::ErrorInvalidOpcode + Self::ErrorInvalidOpcode | Self::ErrorStackOverflow | Self::ErrorStackUnderflow | Self::ErrorWriteProtection @@ -177,6 +177,10 @@ impl ExecutionState { ) } + pub(crate) fn halts(&self) -> bool { + self.halts_in_success() || self.halts_in_exception() || matches!(self, Self::REVERT) + } + pub(crate) fn responsible_opcodes(&self) -> Vec { match self { Self::STOP => vec![OpcodeId::STOP], @@ -442,18 +446,14 @@ impl Step { self.state .is_create .assign(region, offset, Some(F::from(call.is_create as u64)))?; - match call.code_source { - CodeSource::Account(code_hash) => { - self.state.code_hash.assign( - region, - offset, - Some(RandomLinearCombination::random_linear_combine( - code_hash.to_le_bytes(), - block.randomness, - )), - )?; - } - } + self.state.code_hash.assign( + region, + offset, + Some(RandomLinearCombination::random_linear_combine( + call.code_hash.to_le_bytes(), + block.randomness, + )), + )?; self.state.program_counter.assign( region, offset, diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index 90a3bd30f3..cf79e6a5e8 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -223,12 +223,12 @@ pub enum CallContextFieldTag { IsRoot, IsCreate, - CodeSource, + CodeHash, ProgramCounter, StackPointer, GasLeft, MemorySize, - StateWriteCounter, + ReversibleWriteCounter, } impl_expr!(FixedTableTag); diff --git a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs index d8d33a7bda..9c0d2e2f86 100644 --- a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs @@ -2,17 +2,20 @@ use super::CachedRegion; use crate::{ evm_circuit::{ param::N_BYTES_GAS, - table::{AccountFieldTag, FixedTableTag, Lookup}, + table::{AccountFieldTag, CallContextFieldTag, FixedTableTag, Lookup}, util::{ - constraint_builder::{ConstraintBuilder, ReversionInfo, StepStateTransition}, + constraint_builder::{ + ConstraintBuilder, ReversionInfo, StepStateTransition, + Transition::{Delta, Same, To}, + }, math_gadget::{AddWordsGadget, RangeCheckGadget}, Cell, Word, }, - witness::ExecStep, + witness::{Block, Call, ExecStep}, }, util::Expr, }; -use eth_types::{Field, U256}; +use eth_types::{Field, ToLittleEndian, ToScalar, U256}; use halo2_proofs::plonk::{Error, Expression}; use std::convert::TryInto; @@ -47,7 +50,7 @@ impl SameContextGadget { // Check gas_left is sufficient let sufficient_gas_left = RangeCheckGadget::construct(cb, cb.next.state.gas_left.expr()); - // State transition + // Do step state transition cb.require_step_state_transition(step_state_transition); Self { @@ -76,6 +79,160 @@ impl SameContextGadget { } } +/// Construction of step state transition that restores caller's state. +#[derive(Clone, Debug)] +pub(crate) struct RestoreContextGadget { + caller_id: Cell, + caller_is_root: Cell, + caller_is_create: Cell, + caller_code_hash: Cell, + caller_program_counter: Cell, + caller_stack_pointer: Cell, + caller_gas_left: Cell, + caller_memory_word_size: Cell, + caller_reversible_write_counter: Cell, +} + +impl RestoreContextGadget { + pub(crate) fn construct( + cb: &mut ConstraintBuilder, + rw_counter_delta: Expression, + return_data_offset: Expression, + return_data_length: Expression, + ) -> Self { + // Read caller's context for restore + let caller_id = cb.call_context(None, CallContextFieldTag::CallerId); + let [caller_is_root, caller_is_create, caller_code_hash, caller_program_counter, caller_stack_pointer, caller_gas_left, caller_memory_word_size, caller_reversible_write_counter] = + [ + CallContextFieldTag::IsRoot, + CallContextFieldTag::IsCreate, + CallContextFieldTag::CodeHash, + CallContextFieldTag::ProgramCounter, + CallContextFieldTag::StackPointer, + CallContextFieldTag::GasLeft, + CallContextFieldTag::MemorySize, + CallContextFieldTag::ReversibleWriteCounter, + ] + .map(|field_tag| cb.call_context(Some(caller_id.expr()), field_tag)); + + // Update caller's last callee information + for (field_tag, value) in [ + ( + CallContextFieldTag::LastCalleeId, + cb.curr.state.call_id.expr(), + ), + ( + CallContextFieldTag::LastCalleeReturnDataOffset, + return_data_offset, + ), + ( + CallContextFieldTag::LastCalleeReturnDataLength, + return_data_length, + ), + ] { + cb.call_context_lookup(true.expr(), Some(caller_id.expr()), field_tag, value); + } + + // Consume all gas_left if call halts in exception + let gas_left = if cb.execution_state().halts_in_exception() { + caller_gas_left.expr() + } else { + caller_gas_left.expr() + cb.curr.state.gas_left.expr() + }; + + // Accumulate reversible_write_counter in case this call stack reverts in the + // future even it itself succeeds. Note that when sub-call halts in + // failure, we don't need to accumulate reversible_write_counter because + // what happened in the sub-call has been reverted. + let reversible_write_counter = if cb.execution_state().halts_in_success() { + caller_reversible_write_counter.expr() + cb.curr.state.reversible_write_counter.expr() + } else { + caller_reversible_write_counter.expr() + }; + + // Do step state transition + cb.require_step_state_transition(StepStateTransition { + rw_counter: Delta(rw_counter_delta + 12.expr()), + call_id: To(caller_id.expr()), + is_root: To(caller_is_root.expr()), + is_create: To(caller_is_create.expr()), + code_hash: To(caller_code_hash.expr()), + program_counter: To(caller_program_counter.expr()), + stack_pointer: To(caller_stack_pointer.expr()), + gas_left: To(gas_left.expr()), + memory_word_size: To(caller_memory_word_size.expr()), + reversible_write_counter: To(reversible_write_counter), + log_id: Same, + }); + + Self { + caller_id, + caller_is_root, + caller_is_create, + caller_code_hash, + caller_program_counter, + caller_stack_pointer, + caller_gas_left, + caller_memory_word_size, + caller_reversible_write_counter, + } + } + + pub(crate) fn assign( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + let [caller_id, caller_is_root, caller_is_create, caller_code_hash, caller_program_counter, caller_stack_pointer, caller_gas_left, caller_memory_word_size, caller_reversible_write_counter] = + if call.is_root { + [U256::zero(); 9] + } else { + [ + step.rw_indices[1], + step.rw_indices[2], + step.rw_indices[3], + step.rw_indices[4], + step.rw_indices[5], + step.rw_indices[6], + step.rw_indices[7], + step.rw_indices[8], + step.rw_indices[9], + ] + .map(|idx| block.rws[idx].call_context_value()) + }; + + for (cell, value) in [ + (&self.caller_id, caller_id), + (&self.caller_is_root, caller_is_root), + (&self.caller_is_create, caller_is_create), + (&self.caller_program_counter, caller_program_counter), + (&self.caller_stack_pointer, caller_stack_pointer), + (&self.caller_gas_left, caller_gas_left), + (&self.caller_memory_word_size, caller_memory_word_size), + ( + &self.caller_reversible_write_counter, + caller_reversible_write_counter, + ), + ] { + cell.assign(region, offset, value.to_scalar())?; + } + + self.caller_code_hash.assign( + region, + offset, + Some(Word::random_linear_combine( + caller_code_hash.to_le_bytes(), + block.randomness, + )), + )?; + + Ok(()) + } +} + #[derive(Clone, Debug)] pub(crate) struct UpdateBalanceGadget { add_words: AddWordsGadget, diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index 6a28de64ec..e11e6cebad 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -15,8 +15,8 @@ use bus_mapping::{ operation::{self, AccountField, CallContextField, TxLogField, TxReceiptField}, }; -use eth_types::evm_types::OpcodeId; -use eth_types::{Address, Field, ToLittleEndian, ToScalar, ToWord, Word}; +use eth_types::{evm_types::OpcodeId, ToWord}; +use eth_types::{Address, Field, ToLittleEndian, ToScalar, Word}; use eth_types::{ToAddress, U256}; use halo2_proofs::arithmetic::{BaseExt, FieldExt}; use halo2_proofs::pairing::bn256::Fr; @@ -33,7 +33,7 @@ pub struct Block { /// Read write events in the RwTable pub rws: RwMap, /// Bytecode used in the block - pub bytecodes: Vec, + pub bytecodes: HashMap, /// The block context pub context: BlockContext, } @@ -238,17 +238,6 @@ impl Transaction { } } -#[derive(Debug, Clone)] -pub enum CodeSource { - Account(Word), -} - -impl Default for CodeSource { - fn default() -> Self { - Self::Account(0.into()) - } -} - #[derive(Debug, Default, Clone)] pub struct Call { /// The unique identifier of call in the whole proof, using the @@ -259,7 +248,7 @@ pub struct Call { /// Indicate if the call is a create call pub is_create: bool, /// The identifier of current executed bytecode - pub code_source: CodeSource, + pub code_hash: Word, /// The `rw_counter` at the end of reversion of a call if it has /// `is_persistent == false` pub rw_counter_end_of_reversion: usize, @@ -826,7 +815,7 @@ impl Rw { match field_tag { // Only these two tags have values that may not fit into a scalar, so we need to // RLC. - CallContextFieldTag::CodeSource | CallContextFieldTag::Value => { + CallContextFieldTag::CodeHash | CallContextFieldTag::Value => { RandomLinearCombination::random_linear_combine( value.to_le_bytes(), randomness, @@ -1065,13 +1054,13 @@ impl From<&operation::OperationContainer> for RwMap { } CallContextField::IsRoot => CallContextFieldTag::IsRoot, CallContextField::IsCreate => CallContextFieldTag::IsCreate, - CallContextField::CodeSource => CallContextFieldTag::CodeSource, + CallContextField::CodeHash => CallContextFieldTag::CodeHash, CallContextField::ProgramCounter => CallContextFieldTag::ProgramCounter, CallContextField::StackPointer => CallContextFieldTag::StackPointer, CallContextField::GasLeft => CallContextFieldTag::GasLeft, CallContextField::MemorySize => CallContextFieldTag::MemorySize, - CallContextField::StateWriteCounter => { - CallContextFieldTag::StateWriteCounter + CallContextField::ReversibleWriteCounter => { + CallContextFieldTag::ReversibleWriteCounter } }, value: op.op().value, @@ -1221,8 +1210,7 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::EQ | OpcodeId::LT | OpcodeId::GT => ExecutionState::CMP, OpcodeId::SLT | OpcodeId::SGT => ExecutionState::SCMP, OpcodeId::SIGNEXTEND => ExecutionState::SIGNEXTEND, - // TODO: Convert REVERT and RETURN to their own ExecutionState. - OpcodeId::STOP | OpcodeId::RETURN | OpcodeId::REVERT => ExecutionState::STOP, + OpcodeId::STOP => ExecutionState::STOP, OpcodeId::AND => ExecutionState::BITWISE, OpcodeId::XOR => ExecutionState::BITWISE, OpcodeId::OR => ExecutionState::BITWISE, @@ -1262,6 +1250,7 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::CODECOPY => ExecutionState::CODECOPY, OpcodeId::CALLDATALOAD => ExecutionState::CALLDATALOAD, OpcodeId::CODESIZE => ExecutionState::CODESIZE, + OpcodeId::RETURN | OpcodeId::REVERT => ExecutionState::RETURN, _ => unimplemented!("unimplemented opcode {:?}", op), } } @@ -1345,15 +1334,7 @@ fn tx_convert(tx: &circuit_input_builder::Transaction, id: usize, is_last_tx: bo id: call.call_id, is_root: call.is_root, is_create: call.is_create(), - code_source: match call.code_source { - circuit_input_builder::CodeSource::Address(_) => { - CodeSource::Account(call.code_hash.to_word()) - } - circuit_input_builder::CodeSource::Memory => { - CodeSource::Account(call.code_hash.to_word()) - } - _ => unimplemented!("unimplemented code source {:#?}", call.code_source), - }, + code_hash: call.code_hash.to_word(), rw_counter_end_of_reversion: call.rw_counter_end_of_reversion, caller_id: call.caller_id, depth: call.depth, @@ -1414,7 +1395,10 @@ pub fn block_convert( .map(|call| call.code_hash) .unique() .into_iter() - .map(|code_hash| Bytecode::new(code_db.0.get(&code_hash).unwrap().to_vec())) + .map(|code_hash| { + let bytecode = Bytecode::new(code_db.0.get(&code_hash).unwrap().to_vec()); + (bytecode.hash, bytecode) + }) }) .collect(), }