use crate::{
evm_circuit::{
execution::ExecutionGadget,
step::ExecutionState,
util::{
common_gadget::SameContextGadget,
constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta},
CachedRegion,
},
witness::{Block, Call, Chunk, ExecStep, Transaction},
},
table::CallContextFieldTag,
util::{
word::{WordExpr, WordLoHiCell},
Expr,
},
};
use bus_mapping::evm::OpcodeId;
use eth_types::Field;
use halo2_proofs::plonk::Error;
#[derive(Clone, Debug)]
pub(crate) struct CallDataSizeGadget<F> {
same_context: SameContextGadget<F>,
call_data_size: WordLoHiCell<F>,
}
impl<F: Field> ExecutionGadget<F> for CallDataSizeGadget<F> {
const NAME: &'static str = "CALLDATASIZE";
const EXECUTION_STATE: ExecutionState = ExecutionState::CALLDATASIZE;
fn configure(cb: &mut EVMConstraintBuilder<F>) -> Self {
let opcode = cb.query_cell();
let call_data_size = cb.query_word_unchecked();
cb.call_context_lookup_read(
None,
CallContextFieldTag::CallDataLength,
call_data_size.to_word(),
);
cb.stack_push(call_data_size.to_word());
let step_state_transition = StepStateTransition {
rw_counter: Delta(2.expr()),
program_counter: Delta(1.expr()),
stack_pointer: Delta((-1).expr()),
gas_left: Delta(-OpcodeId::CALLDATASIZE.constant_gas_cost().expr()),
..Default::default()
};
let same_context = SameContextGadget::construct(cb, opcode, step_state_transition);
Self {
same_context,
call_data_size,
}
}
fn assign_exec_step(
&self,
region: &mut CachedRegion<'_, '_, F>,
offset: usize,
block: &Block<F>,
_chunk: &Chunk<F>,
_tx: &Transaction,
_call: &Call,
step: &ExecStep,
) -> Result<(), Error> {
self.same_context.assign_exec_step(region, offset, step)?;
let call_data_size = block.get_rws(step, 1).stack_value();
self.call_data_size
.assign_u64(region, offset, call_data_size.as_u64())?;
Ok(())
}
}
#[cfg(test)]
mod test {
use crate::{evm_circuit::test::rand_bytes, test_util::CircuitTestBuilder};
use bus_mapping::circuit_input_builder::FixedCParams;
use eth_types::{address, bytecode, Word};
use itertools::Itertools;
use mock::TestContext;
fn test_ok(call_data_size: usize, is_root: bool) {
let bytecode = bytecode! {
CALLDATASIZE
STOP
};
if is_root {
let ctx = TestContext::<2, 1>::new(
None,
|accs| {
accs[0]
.address(address!("0x0000000000000000000000000000000000000123"))
.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)
.input(rand_bytes(call_data_size).into())
.gas(Word::from(40000));
},
|block, _tx| block.number(0xcafeu64),
)
.unwrap();
CircuitTestBuilder::new_from_test_ctx(ctx)
.params(FixedCParams {
max_calldata: 1200,
..FixedCParams::default()
})
.run();
} else {
let ctx = TestContext::<3, 1>::new(
None,
|accs| {
accs[0]
.address(address!("0x0000000000000000000000000000000000000123"))
.balance(Word::from(1u64 << 30));
accs[1]
.address(address!("0x0000000000000000000000000000000000000010"))
.balance(Word::from(1u64 << 20))
.code(bytecode! {
PUSH1(0)
PUSH1(0)
PUSH32(call_data_size)
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();
CircuitTestBuilder::new_from_test_ctx(ctx)
.params(FixedCParams {
max_calldata: 600,
..FixedCParams::default()
})
.run();
};
}
#[test]
fn calldatasize_gadget_root() {
for (call_data_size, is_root) in vec![32, 64, 96, 128, 256, 512, 1024]
.into_iter()
.cartesian_product([true, false])
{
test_ok(call_data_size, is_root);
}
}
}