diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index 52ddcb1785..2b7435cf3d 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -1,4 +1,5 @@ //! Definition of each opcode of the EVM. +mod calldatasize; mod coinbase; mod dup; mod gas; @@ -99,7 +100,7 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { // OpcodeId::CALLER => {}, // OpcodeId::CALLVALUE => {}, // OpcodeId::CALLDATALOAD => {}, - // OpcodeId::CALLDATASIZE => {}, + OpcodeId::CALLDATASIZE => StackOnlyOpcode::<0>::gen_associated_ops, // OpcodeId::CALLDATACOPY => {}, // OpcodeId::CODESIZE => {}, // OpcodeId::CODECOPY => {}, diff --git a/bus-mapping/src/evm/opcodes/calldatasize.rs b/bus-mapping/src/evm/opcodes/calldatasize.rs new file mode 100644 index 0000000000..4b07ae4aee --- /dev/null +++ b/bus-mapping/src/evm/opcodes/calldatasize.rs @@ -0,0 +1,63 @@ +#[cfg(test)] +mod calldatasize_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 calldatasize_opcode_impl() -> Result<(), Error> { + 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 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 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.b_constant.coinbase.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/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 4db5031865..83130188d4 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -20,6 +20,7 @@ mod add; mod begin_tx; mod bitwise; mod byte; +mod calldatasize; mod coinbase; mod comparator; mod dup; @@ -44,6 +45,7 @@ use add::AddGadget; use begin_tx::BeginTxGadget; use bitwise::BitwiseGadget; use byte::ByteGadget; +use calldatasize::CallDataSizeGadget; use coinbase::CoinbaseGadget; use comparator::ComparatorGadget; use dup::DupGadget; @@ -93,6 +95,7 @@ pub(crate) struct ExecutionConfig { bitwise_gadget: BitwiseGadget, begin_tx_gadget: BeginTxGadget, byte_gadget: ByteGadget, + calldatasize_gadget: CallDataSizeGadget, comparator_gadget: ComparatorGadget, dup_gadget: DupGadget, error_oog_pure_memory_gadget: ErrorOOGPureMemoryGadget, @@ -221,6 +224,7 @@ impl ExecutionConfig { bitwise_gadget: configure_gadget!(), begin_tx_gadget: configure_gadget!(), byte_gadget: configure_gadget!(), + calldatasize_gadget: configure_gadget!(), comparator_gadget: configure_gadget!(), dup_gadget: configure_gadget!(), error_oog_pure_memory_gadget: configure_gadget!(), @@ -492,6 +496,9 @@ impl ExecutionConfig { ExecutionState::ErrorOutOfGasPureMemory => { assign_exec_step!(self.error_oog_pure_memory_gadget) } + ExecutionState::CALLDATASIZE => { + assign_exec_step!(self.calldatasize_gadget) + } _ => unimplemented!(), } diff --git a/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs b/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs new file mode 100644 index 0000000000..5bc82f1425 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs @@ -0,0 +1,179 @@ +use halo2::{arithmetic::FieldExt, circuit::Region, plonk::Error}; + +use crate::{ + evm_circuit::{ + step::ExecutionState, + table::CallContextFieldTag, + util::{ + common_gadget::SameContextGadget, + constraint_builder::{ConstraintBuilder, StepStateTransition, Transition}, + Cell, + }, + witness::{Block, Call, ExecStep, Transaction}, + }, + util::Expr, +}; + +use super::ExecutionGadget; + +#[derive(Clone, Debug)] +pub(crate) struct CallDataSizeGadget { + same_context: SameContextGadget, + call_data_size: Cell, +} + +impl ExecutionGadget for CallDataSizeGadget { + const NAME: &'static str = "CALLDATASIZE"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::CALLDATASIZE; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let opcode = cb.query_cell(); + + // Calldatasize can be looked up in the above tx_id's context. + let call_data_size = cb.query_cell(); + + // Add lookup constraint in the call context for the calldatasize field. + cb.call_context_lookup( + None, + CallContextFieldTag::CallDataLength, + call_data_size.expr(), + ); + + // The calldatasize should be pushed to the top of the stack. + 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()), + ..Default::default() + }; + + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None); + + Self { + same_context, + call_data_size, + } + } + + fn assign_exec_step( + &self, + region: &mut Region<'_, F>, + offset: usize, + _block: &Block, + _tx: &Transaction, + call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + self.same_context.assign_exec_step(region, offset, step)?; + + self.call_data_size + .assign(region, offset, Some(F::from(call.call_data_length as u64)))?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use bus_mapping::evm::OpcodeId; + use eth_types::{bytecode, ToLittleEndian, Word}; + use halo2::arithmetic::BaseExt; + use pairing::bn256::Fr; + + use crate::evm_circuit::{ + step::ExecutionState, + table::CallContextFieldTag, + test::{rand_bytes, run_test_circuit_incomplete_fixed_table}, + util::RandomLinearCombination, + witness::{Block, Bytecode, Call, ExecStep, Rw, Transaction}, + }; + + fn test_ok(call_data_size: usize, is_root: bool) { + let randomness = Fr::rand(); + let bytecode = bytecode! { + #[start] + CALLDATASIZE + STOP + }; + let bytecode = Bytecode::new(bytecode.to_vec()); + let call_id = 1; + let call_data = rand_bytes(call_data_size); + + let rws = vec![ + Rw::CallContext { + rw_counter: 9, + is_write: false, + call_id, + field_tag: CallContextFieldTag::CallDataLength, + value: Word::from(call_data_size), + }, + Rw::Stack { + rw_counter: 10, + is_write: true, + call_id, + stack_pointer: 1023, + value: Word::from(call_data_size), + }, + ]; + + let steps = vec![ + ExecStep { + execution_state: ExecutionState::CALLDATASIZE, + rw_indices: vec![0, 1], + rw_counter: 9, + program_counter: 0, + stack_pointer: 1024, + gas_left: OpcodeId::CALLDATASIZE.constant_gas_cost().as_u64(), + gas_cost: OpcodeId::CALLDATASIZE.constant_gas_cost().as_u64(), + opcode: Some(OpcodeId::CALLDATASIZE), + ..Default::default() + }, + ExecStep { + execution_state: ExecutionState::STOP, + rw_counter: 11, + program_counter: 1, + stack_pointer: 1023, + gas_left: 0, + opcode: Some(OpcodeId::STOP), + ..Default::default() + }, + ]; + + let block = Block { + randomness, + txs: vec![Transaction { + id: 1, + call_data, + call_data_length: call_data_size, + steps, + calls: vec![Call { + id: call_id, + is_root, + is_create: false, + call_data_length: call_data_size, + opcode_source: RandomLinearCombination::random_linear_combine( + bytecode.hash.to_le_bytes(), + randomness, + ), + ..Default::default() + }], + ..Default::default() + }], + rws, + bytecodes: vec![bytecode], + ..Default::default() + }; + + assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); + } + + #[test] + fn calldatasize_gadget_root() { + test_ok(32, true); + test_ok(64, true); + test_ok(96, true); + } +}