Skip to content
This repository was archived by the owner on Apr 18, 2025. It is now read-only.
4 changes: 3 additions & 1 deletion bus-mapping/src/evm/opcodes.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Definition of each opcode of the EVM.
mod calldatasize;
mod coinbase;
mod dup;
mod jump;
Expand All @@ -23,6 +24,7 @@ use eth_types::GethExecStep;
use log::warn;

use self::push::Push;
use calldatasize::CallDataSize;
use coinbase::Coinbase;
use dup::Dup;
use jump::Jump;
Expand Down Expand Up @@ -100,7 +102,7 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps {
// OpcodeId::CALLER => {},
// OpcodeId::CALLVALUE => {},
// OpcodeId::CALLDATALOAD => {},
// OpcodeId::CALLDATASIZE => {},
OpcodeId::CALLDATASIZE => CallDataSize::gen_associated_ops,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can now use StackOnlyOpcode as TIMESTAMP opcode is already merged.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@icemelon rebasing was a mess (because of the merge commit that got through). I'm closing this PR and opening a new one with all the changes from before as well as the changes suggested in the reviews.

// OpcodeId::CALLDATACOPY => {},
// OpcodeId::CODESIZE => {},
// OpcodeId::CODECOPY => {},
Expand Down
91 changes: 91 additions & 0 deletions bus-mapping/src/evm/opcodes/calldatasize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use crate::{
circuit_input_builder::CircuitInputStateRef, operation::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,
next_steps: &[GethExecStep],
) -> Result<(), Error> {
let current_step = &next_steps[0];
let call_data_size = next_steps[1].stack.last()?;

state.push_stack_op(
RW::WRITE,
current_step.stack.last_filled().map(|a| a - 1),
call_data_size,
);

Ok(())
}
}

#[cfg(test)]
mod calldatasize_tests {
use super::*;
use crate::{
bytecode,
circuit_input_builder::{ExecStep, TransactionContext},
mock,
};
use eth_types::evm_types::StackAddress;
use eth_types::ToWord;
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 =
mock::BlockData::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(())
}
}
7 changes: 7 additions & 0 deletions zkevm-circuits/src/evm_circuit/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod add;
mod begin_tx;
mod bitwise;
mod byte;
mod calldatasize;
mod coinbase;
mod comparator;
mod dup;
Expand All @@ -42,6 +43,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;
Expand Down Expand Up @@ -89,6 +91,7 @@ pub(crate) struct ExecutionConfig<F> {
bitwise_gadget: BitwiseGadget<F>,
begin_tx_gadget: BeginTxGadget<F>,
byte_gadget: ByteGadget<F>,
calldatasize_gadget: CallDataSizeGadget<F>,
comparator_gadget: ComparatorGadget<F>,
dup_gadget: DupGadget<F>,
error_oog_pure_memory_gadget: ErrorOOGPureMemoryGadget<F>,
Expand Down Expand Up @@ -218,6 +221,7 @@ impl<F: FieldExt> ExecutionConfig<F> {
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!(),
Expand Down Expand Up @@ -513,6 +517,9 @@ impl<F: FieldExt> ExecutionConfig<F> {
ExecutionState::ErrorOutOfGasPureMemory => {
assign_exec_step!(self.error_oog_pure_memory_gadget)
}
ExecutionState::CALLDATASIZE => {
assign_exec_step!(self.calldatasize_gadget)
}
_ => unimplemented!(),
}

Expand Down
190 changes: 190 additions & 0 deletions zkevm-circuits/src/evm_circuit/execution/calldatasize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
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<F> {
same_context: SameContextGadget<F>,
call_data_size: Cell<F>,
}

impl<F: FieldExt> ExecutionGadget<F> for CallDataSizeGadget<F> {
const NAME: &'static str = "CALLDATASIZE";

const EXECUTION_STATE: ExecutionState = ExecutionState::CALLDATASIZE;

fn configure(cb: &mut ConstraintBuilder<F>) -> 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<F>,
_tx: &Transaction<F>,
call: &Call<F>,
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::{bytecode, evm::OpcodeId};
use eth_types::{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);
}
}