diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index eaf9723ee9..b5ceff7b1e 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -4,6 +4,7 @@ use crate::Error; use core::fmt::Debug; use eth_types::GethExecStep; +mod calldatasize; mod caller; mod callvalue; mod coinbase; @@ -27,6 +28,7 @@ use crate::evm::OpcodeId; use log::warn; use self::push::Push; +use calldatasize::Calldatasize; use caller::Caller; use callvalue::Callvalue; use dup::Dup; @@ -104,7 +106,7 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { OpcodeId::CALLER => Caller::gen_associated_ops, OpcodeId::CALLVALUE => Callvalue::gen_associated_ops, // OpcodeId::CALLDATALOAD => {}, - // OpcodeId::CALLDATASIZE => {}, + OpcodeId::CALLDATASIZE => Calldatasize::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..c950f0d1cf --- /dev/null +++ b/bus-mapping/src/evm/opcodes/calldatasize.rs @@ -0,0 +1,111 @@ +use crate::{ + circuit_input_builder::CircuitInputStateRef, + operation::{CallContextField, CallContextOp, RW}, + Error, +}; + +use eth_types::GethExecStep; + +use super::Opcode; + +#[derive(Clone, Copy, Debug)] +pub(crate) struct Calldatasize; + +impl Opcode for Calldatasize { + fn gen_associated_ops( + state: &mut CircuitInputStateRef, + steps: &[GethExecStep], + ) -> Result<(), Error> { + let step = &steps[0]; + let value = steps[1].stack.last()?; + state.push_op( + RW::READ, + CallContextOp { + 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); + 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 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, !block.geth_trace.failed) + .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(); + + // 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), + }, + ); + + // 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, + ); + + // 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 eceaed4a76..ae8ffe8e46 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -21,6 +21,7 @@ mod begin_tx; mod bitwise; mod byte; mod calldatacopy; +mod calldatasize; mod caller; mod callvalue; mod coinbase; @@ -49,6 +50,7 @@ use begin_tx::BeginTxGadget; use bitwise::BitwiseGadget; use byte::ByteGadget; use calldatacopy::CallDataCopyGadget; +use calldatasize::CallDataSizeGadget; use caller::CallerGadget; use callvalue::CallValueGadget; use coinbase::CoinbaseGadget; @@ -102,6 +104,7 @@ pub(crate) struct ExecutionConfig { begin_tx_gadget: BeginTxGadget, byte_gadget: ByteGadget, calldatacopy_gadget: CallDataCopyGadget, + calldatasize_gadget: CallDataSizeGadget, caller_gadget: CallerGadget, call_value_gadget: CallValueGadget, comparator_gadget: ComparatorGadget, @@ -234,6 +237,7 @@ impl ExecutionConfig { begin_tx_gadget: configure_gadget!(), byte_gadget: configure_gadget!(), calldatacopy_gadget: configure_gadget!(), + calldatasize_gadget: configure_gadget!(), caller_gadget: configure_gadget!(), call_value_gadget: configure_gadget!(), comparator_gadget: configure_gadget!(), @@ -518,6 +522,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..f51de46056 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs @@ -0,0 +1,198 @@ +use std::convert::TryInto; + +use eth_types::ToLittleEndian; +use halo2::{arithmetic::FieldExt, circuit::Region, plonk::Error}; + +use crate::{ + evm_circuit::{ + param::N_BYTES_CALLDATASIZE, + step::ExecutionState, + table::CallContextFieldTag, + util::{ + common_gadget::SameContextGadget, + constraint_builder::{ConstraintBuilder, StepStateTransition, Transition}, + from_bytes, RandomLinearCombination, + }, + witness::{Block, Call, ExecStep, Transaction}, + }, + util::Expr, +}; + +use super::ExecutionGadget; + +#[derive(Clone, Debug)] +pub(crate) struct CallDataSizeGadget { + same_context: SameContextGadget, + call_data_size: RandomLinearCombination, +} + +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(); + + // Add lookup constraint in the call context for the calldatasize field. + let call_data_size = cb.query_rlc(); + cb.call_context_lookup( + false.expr(), + None, + CallContextFieldTag::CallDataLength, + from_bytes::expr(&call_data_size.cells), + ); + + // 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)?; + + let call_data_size = block.rws[step.rw_indices[1]].stack_value(); + + self.call_data_size.assign( + region, + offset, + Some( + call_data_size.to_le_bytes()[..N_BYTES_CALLDATASIZE] + .try_into() + .unwrap(), + ), + )?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use std::collections::HashMap; + + use bus_mapping::evm::OpcodeId; + use eth_types::{bytecode, Word}; + use halo2::arithmetic::BaseExt; + use pairing::bn256::Fr; + + use crate::evm_circuit::{ + step::ExecutionState, + table::{CallContextFieldTag, RwTableTag}, + test::{rand_bytes, run_test_circuit_incomplete_fixed_table}, + witness::{Block, Bytecode, Call, CodeSource, ExecStep, Rw, RwMap, 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 mut rw_map = HashMap::new(); + rw_map.insert( + RwTableTag::CallContext, + vec![Rw::CallContext { + rw_counter: 9, + is_write: false, + call_id, + field_tag: CallContextFieldTag::CallDataLength, + value: Word::from(call_data_size), + }], + ); + rw_map.insert( + RwTableTag::Stack, + vec![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![(RwTableTag::CallContext, 0), (RwTableTag::Stack, 0)], + 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 as u64, + code_source: CodeSource::Account(bytecode.hash), + ..Default::default() + }], + ..Default::default() + }], + rws: RwMap(rw_map), + 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); + test_ok(128, true); + test_ok(256, true); + test_ok(512, true); + test_ok(1024, true); + } +} diff --git a/zkevm-circuits/src/evm_circuit/param.rs b/zkevm-circuits/src/evm_circuit/param.rs index 53ba81d181..8901f21b04 100644 --- a/zkevm-circuits/src/evm_circuit/param.rs +++ b/zkevm-circuits/src/evm_circuit/param.rs @@ -33,3 +33,6 @@ pub(crate) const N_BYTES_PROGRAM_COUNTER: usize = N_BYTES_U64; // Number of bytes that will be used for a tx's gas field. pub(crate) const N_BYTES_GAS: usize = N_BYTES_U64; + +// Number of bytes that will be used for call data's size. +pub(crate) const N_BYTES_CALLDATASIZE: usize = N_BYTES_U64;