diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index 6ee9eb07ea..52ddcb1785 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -15,7 +15,7 @@ mod sload; mod stackonlyop; mod stop; mod swap; - +mod timestamp; use crate::circuit_input_builder::CircuitInputStateRef; use crate::evm::OpcodeId; use crate::Error; @@ -24,7 +24,6 @@ use eth_types::GethExecStep; use log::warn; use self::push::Push; -use coinbase::Coinbase; use dup::Dup; use gas::Gas; use jump::Jump; @@ -111,8 +110,8 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { // OpcodeId::RETURNDATACOPY => {}, // OpcodeId::EXTCODEHASH => {}, // OpcodeId::BLOCKHASH => {}, - OpcodeId::COINBASE => Coinbase::gen_associated_ops, - // OpcodeId::TIMESTAMP => {}, + OpcodeId::COINBASE => StackOnlyOpcode::<0>::gen_associated_ops, + OpcodeId::TIMESTAMP => StackOnlyOpcode::<0>::gen_associated_ops, // OpcodeId::NUMBER => {}, // OpcodeId::DIFFICULTY => {}, // OpcodeId::GASLIMIT => {}, diff --git a/bus-mapping/src/evm/opcodes/coinbase.rs b/bus-mapping/src/evm/opcodes/coinbase.rs index ecc42bf332..9e4b0ea314 100644 --- a/bus-mapping/src/evm/opcodes/coinbase.rs +++ b/bus-mapping/src/evm/opcodes/coinbase.rs @@ -1,33 +1,14 @@ -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 Coinbase; - -impl Opcode for Coinbase { - 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 coinbase_tests { - use super::*; - use crate::circuit_input_builder::{ExecStep, TransactionContext}; + 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] @@ -39,9 +20,8 @@ mod coinbase_tests { }; // 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 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(); diff --git a/bus-mapping/src/evm/opcodes/stackonlyop.rs b/bus-mapping/src/evm/opcodes/stackonlyop.rs index 3c34b0e31b..710b8fc378 100644 --- a/bus-mapping/src/evm/opcodes/stackonlyop.rs +++ b/bus-mapping/src/evm/opcodes/stackonlyop.rs @@ -29,7 +29,11 @@ impl Opcode for StackOnlyOpcode { // 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.nth_last_filled(N - 1), result_value); + state.push_stack_op( + RW::WRITE, + step.stack.last_filled().map(|a| a - 1 + N), + result_value, + ); Ok(()) } diff --git a/bus-mapping/src/evm/opcodes/timestamp.rs b/bus-mapping/src/evm/opcodes/timestamp.rs new file mode 100644 index 0000000000..9483f1485c --- /dev/null +++ b/bus-mapping/src/evm/opcodes/timestamp.rs @@ -0,0 +1,62 @@ +#[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).unwrap(); + let mut tx_ctx = TransactionContext::new(&block.eth_tx); + + // 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.b_constant.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/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index b9b424835b..4db5031865 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -38,6 +38,7 @@ mod signed_comparator; mod signextend; mod stop; mod swap; +mod timestamp; use add::AddGadget; use begin_tx::BeginTxGadget; @@ -61,6 +62,7 @@ use signed_comparator::SignedComparatorGadget; use signextend::SignextendGadget; use stop::StopGadget; use swap::SwapGadget; +use timestamp::TimestampGadget; pub(crate) trait ExecutionGadget { const NAME: &'static str; @@ -108,6 +110,7 @@ pub(crate) struct ExecutionConfig { swap_gadget: SwapGadget, msize_gadget: MsizeGadget, coinbase_gadget: CoinbaseGadget, + timestamp_gadget: TimestampGadget, } impl ExecutionConfig { @@ -235,6 +238,7 @@ impl ExecutionConfig { swap_gadget: configure_gadget!(), msize_gadget: configure_gadget!(), coinbase_gadget: configure_gadget!(), + timestamp_gadget: configure_gadget!(), step: step_curr, presets_map, }; @@ -482,6 +486,9 @@ impl ExecutionConfig { ExecutionState::DUP => assign_exec_step!(self.dup_gadget), ExecutionState::SWAP => assign_exec_step!(self.swap_gadget), ExecutionState::COINBASE => assign_exec_step!(self.coinbase_gadget), + ExecutionState::TIMESTAMP => { + assign_exec_step!(self.timestamp_gadget) + } ExecutionState::ErrorOutOfGasPureMemory => { assign_exec_step!(self.error_oog_pure_memory_gadget) } diff --git a/zkevm-circuits/src/evm_circuit/execution/timestamp.rs b/zkevm-circuits/src/evm_circuit/execution/timestamp.rs new file mode 100644 index 0000000000..792226ad6d --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/timestamp.rs @@ -0,0 +1,97 @@ +use crate::evm_circuit::param::N_BYTES_U64; +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + step::ExecutionState, + table::BlockContextFieldTag, + util::{ + common_gadget::SameContextGadget, + constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, + from_bytes, RandomLinearCombination, + }, + witness::{Block, Call, ExecStep, Transaction}, + }, + util::Expr, +}; +use halo2::{arithmetic::FieldExt, circuit::Region, plonk::Error}; +use std::convert::TryFrom; + +#[derive(Clone, Debug)] +pub(crate) struct TimestampGadget { + same_context: SameContextGadget, + timestamp: RandomLinearCombination, +} + +impl ExecutionGadget for TimestampGadget { + const NAME: &'static str = "TIMESTAMP"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::TIMESTAMP; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let timestamp = cb.query_rlc(); + cb.stack_push(timestamp.expr()); + + // Lookup block table with timestamp + cb.block_lookup( + BlockContextFieldTag::Time.expr(), + None, + from_bytes::expr(×tamp.cells), + ); + + // State transition + let opcode = cb.query_cell(); + let step_state_transition = StepStateTransition { + rw_counter: Delta(1.expr()), + program_counter: Delta(1.expr()), + stack_pointer: Delta((-1).expr()), + ..Default::default() + }; + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + + Self { + same_context, + timestamp, + } + } + + fn assign_exec_step( + &self, + region: &mut Region<'_, F>, + offset: usize, + block: &Block, + _: &Transaction, + _: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + self.same_context.assign_exec_step(region, offset, step)?; + + let timestamp = block.rws[step.rw_indices[0]].stack_value(); + + self.timestamp.assign( + region, + offset, + Some(u64::try_from(timestamp).unwrap().to_le_bytes()), + )?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::test_util::run_test_circuits; + use eth_types::bytecode; + + fn test_ok() { + let bytecode = bytecode! { + #[start] + TIMESTAMP + STOP + }; + assert_eq!(run_test_circuits(bytecode), Ok(())); + } + #[test] + fn timestamp_gadget_test() { + test_ok(); + } +} diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index e1669a23cd..8c72f21b72 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -439,7 +439,7 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { // Rw /// Add a Lookup::Rw without increasing the rw_counter_offset, which is - /// useful for state reversion or dummuy lookup. + /// useful for state reversion or dummy lookup. fn rw_lookup_with_counter( &mut self, name: &'static str, diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index fd002e06ea..1b4af57a40 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -594,6 +594,7 @@ impl From<&bus_mapping::circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::PC => ExecutionState::PC, OpcodeId::MSIZE => ExecutionState::MSIZE, OpcodeId::COINBASE => ExecutionState::COINBASE, + OpcodeId::TIMESTAMP => ExecutionState::TIMESTAMP, OpcodeId::GAS => ExecutionState::GAS, _ => unimplemented!("unimplemented opcode {:?}", step.op), } @@ -686,6 +687,7 @@ pub fn block_convert( // converting to block context let context = BlockContext { coinbase: b.block_const.coinbase, + time: b.block_const.timestamp.try_into().unwrap(), ..Default::default() };