diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index 863f4894ee..7f6c16b5d4 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -42,6 +42,7 @@ mod number; mod origin; mod r#return; mod returndatacopy; +mod returndatasize; mod selfbalance; mod sha3; mod sload; @@ -76,6 +77,7 @@ use mstore::Mstore; use origin::Origin; use r#return::Return; use returndatacopy::Returndatacopy; +use returndatasize::Returndatasize; use selfbalance::Selfbalance; use sload::Sload; use sstore::Sstore; @@ -161,7 +163,7 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { OpcodeId::CODESIZE => Codesize::gen_associated_ops, OpcodeId::EXTCODESIZE => Extcodesize::gen_associated_ops, OpcodeId::EXTCODECOPY => Extcodecopy::gen_associated_ops, - OpcodeId::RETURNDATASIZE => StackOnlyOpcode::<0, 1>::gen_associated_ops, + OpcodeId::RETURNDATASIZE => Returndatasize::gen_associated_ops, OpcodeId::RETURNDATACOPY => Returndatacopy::gen_associated_ops, OpcodeId::EXTCODEHASH => Extcodehash::gen_associated_ops, OpcodeId::BLOCKHASH => StackOnlyOpcode::<1, 1>::gen_associated_ops, diff --git a/bus-mapping/src/evm/opcodes/returndatasize.rs b/bus-mapping/src/evm/opcodes/returndatasize.rs new file mode 100644 index 0000000000..4fa4cc3f19 --- /dev/null +++ b/bus-mapping/src/evm/opcodes/returndatasize.rs @@ -0,0 +1,163 @@ +use crate::{ + circuit_input_builder::{CircuitInputStateRef, ExecStep}, + operation::CallContextField, + Error, +}; + +use eth_types::GethExecStep; + +use super::Opcode; + +#[derive(Clone, Copy, Debug)] +pub(crate) struct Returndatasize; + +impl Opcode for Returndatasize { + fn gen_associated_ops( + state: &mut CircuitInputStateRef, + geth_steps: &[GethExecStep], + ) -> Result, Error> { + let geth_step = &geth_steps[0]; + let mut exec_step = state.new_step(geth_step)?; + let value = geth_steps[1].stack.last()?; + state.call_context_read( + &mut exec_step, + state.call()?.call_id, + CallContextField::LastCalleeReturnDataLength, + value, + ); + + state.stack_write( + &mut exec_step, + geth_step.stack.last_filled().map(|a| a - 1), + value, + )?; + + Ok(vec![exec_step]) + } +} + +#[cfg(test)] +mod returndatasize_tests { + use crate::circuit_input_builder::CircuitsParams; + use crate::{ + circuit_input_builder::ExecState, + mock::BlockData, + operation::{CallContextField, CallContextOp, StackOp, RW}, + }; + use eth_types::{ + bytecode, + evm_types::{OpcodeId, StackAddress}, + geth_types::GethData, + word, Word, + }; + use mock::test_ctx::{helpers::*, TestContext}; + use pretty_assertions::assert_eq; + + #[test] + fn test_ok() { + let return_data_size = 0x20; + + // // deployed contract + // PUSH1 0x20 + // PUSH1 0 + // PUSH1 0 + // CALLDATACOPY + // PUSH1 0x20 + // PUSH1 0 + // RETURN + // + // bytecode: 0x6020600060003760206000F3 + // + // // constructor + // PUSH12 0x6020600060003760206000F3 + // PUSH1 0 + // MSTORE + // PUSH1 0xC + // PUSH1 0x14 + // RETURN + // + // bytecode: 0x6B6020600060003760206000F3600052600C6014F3 + let code = bytecode! { + PUSH21(word!("6B6020600060003760206000F3600052600C6014F3")) + PUSH1(0) + MSTORE + + PUSH1 (0x15) + PUSH1 (0xB) + PUSH1 (0) + CREATE + + PUSH1 (0x20) + PUSH1 (0x20) + PUSH1 (0x20) + PUSH1 (0) + PUSH1 (0) + DUP6 + PUSH2 (0xFFFF) + CALL + + RETURNDATASIZE + + STOP + }; + // Get the execution steps from the external tracer + let block: GethData = TestContext::<2, 1>::new( + None, + account_0_code_account_1_no_code(code), + tx_from_1_to_0, + |block, _tx| block.number(0xcafeu64), + ) + .unwrap() + .into(); + + let mut builder = BlockData::new_from_geth_data_with_params( + block.clone(), + CircuitsParams { + max_rws: 512, + ..Default::default() + }, + ) + .new_circuit_input_builder(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + + let step = builder.block.txs()[0] + .steps() + .iter() + .find(|step| step.exec_state == ExecState::Op(OpcodeId::RETURNDATASIZE)) + .unwrap(); + + let call_id = builder.block.txs()[0].calls()[0].call_id; + assert_eq!( + { + let operation = + &builder.block.container.call_context[step.bus_mapping_instance[0].as_usize()]; + (operation.rw(), operation.op()) + }, + ( + RW::READ, + &CallContextOp { + call_id, + field: CallContextField::LastCalleeReturnDataLength, + value: Word::from(return_data_size), + } + ) + ); + assert_eq!( + { + let operation = + &builder.block.container.stack[step.bus_mapping_instance[1].as_usize()]; + (operation.rw(), operation.op()) + }, + ( + RW::WRITE, + &StackOp::new( + call_id, + StackAddress::from(1021), + Word::from(return_data_size) + ) + ) + ); + } +} diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 2d487df038..c20fbc3b50 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -70,6 +70,7 @@ mod pc; mod pop; mod push; mod r#return; +mod returndatasize; mod sdiv_smod; mod selfbalance; mod sha3; @@ -123,6 +124,7 @@ use pc::PcGadget; use pop::PopGadget; use push::PushGadget; use r#return::ReturnGadget; +use returndatasize::ReturnDataSizeGadget; use sdiv_smod::SignedDivModGadget; use selfbalance::SelfbalanceGadget; use shl_shr::ShlShrGadget; @@ -220,7 +222,7 @@ pub(crate) struct ExecutionConfig { sar_gadget: DummyGadget, extcodesize_gadget: DummyGadget, extcodecopy_gadget: DummyGadget, - returndatasize_gadget: DummyGadget, + returndatasize_gadget: ReturnDataSizeGadget, returndatacopy_gadget: DummyGadget, create_gadget: DummyGadget, callcode_gadget: DummyGadget, @@ -980,6 +982,7 @@ impl ExecutionConfig { ExecutionState::POP => assign_exec_step!(self.pop_gadget), ExecutionState::PUSH => assign_exec_step!(self.push_gadget), ExecutionState::RETURN => assign_exec_step!(self.return_gadget), + ExecutionState::RETURNDATASIZE => assign_exec_step!(self.returndatasize_gadget), ExecutionState::SCMP => assign_exec_step!(self.signed_comparator_gadget), ExecutionState::SDIV_SMOD => assign_exec_step!(self.sdiv_smod_gadget), ExecutionState::BLOCKCTXU64 => assign_exec_step!(self.block_ctx_u64_gadget), @@ -993,7 +996,6 @@ impl ExecutionConfig { ExecutionState::SAR => assign_exec_step!(self.sar_gadget), ExecutionState::EXTCODESIZE => assign_exec_step!(self.extcodesize_gadget), ExecutionState::EXTCODECOPY => assign_exec_step!(self.extcodecopy_gadget), - ExecutionState::RETURNDATASIZE => assign_exec_step!(self.returndatasize_gadget), ExecutionState::RETURNDATACOPY => assign_exec_step!(self.returndatacopy_gadget), ExecutionState::CREATE => assign_exec_step!(self.create_gadget), ExecutionState::CALLCODE => assign_exec_step!(self.callcode_gadget), diff --git a/zkevm-circuits/src/evm_circuit/execution/returndatasize.rs b/zkevm-circuits/src/evm_circuit/execution/returndatasize.rs new file mode 100644 index 0000000000..9d0732a23f --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/returndatasize.rs @@ -0,0 +1,172 @@ +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + param::N_BYTES_U64, + step::ExecutionState, + util::{ + common_gadget::SameContextGadget, + constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, + from_bytes, CachedRegion, RandomLinearCombination, + }, + witness::{Block, Call, ExecStep, Transaction}, + }, + table::CallContextFieldTag, + util::Expr, +}; +use bus_mapping::evm::OpcodeId; +use eth_types::{Field, ToLittleEndian}; +use halo2_proofs::plonk::Error; + +#[derive(Clone, Debug)] +pub(crate) struct ReturnDataSizeGadget { + same_context: SameContextGadget, + return_data_size: RandomLinearCombination, +} + +impl ExecutionGadget for ReturnDataSizeGadget { + const NAME: &'static str = "RETURNDATASIZE"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::RETURNDATASIZE; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let opcode = cb.query_cell(); + + // Add lookup constraint in the call context for the returndatasize field. + let return_data_size = cb.query_rlc(); + cb.call_context_lookup( + false.expr(), + None, + CallContextFieldTag::LastCalleeReturnDataLength, + from_bytes::expr(&return_data_size.cells), + ); + + // The returndatasize should be pushed to the top of the stack. + cb.stack_push(return_data_size.expr()); + + let step_state_transition = StepStateTransition { + rw_counter: Delta(2.expr()), + program_counter: Delta(1.expr()), + stack_pointer: Delta((-1).expr()), + gas_left: Delta(-OpcodeId::RETURNDATASIZE.constant_gas_cost().expr()), + ..Default::default() + }; + + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); + + Self { + same_context, + return_data_size, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + _tx: &Transaction, + _call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + self.same_context.assign_exec_step(region, offset, step)?; + let return_data_size = block.rws[step.rw_indices[1]].stack_value(); + self.return_data_size.assign( + region, + offset, + Some( + return_data_size.to_le_bytes()[..N_BYTES_U64] + .try_into() + .expect("could not encode return_data_size as byte array in little endian"), + ), + )?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::evm_circuit::test::rand_bytes; + use crate::test_util::run_test_circuits; + use eth_types::{bytecode, ToWord, Word}; + use mock::test_ctx::TestContext; + + fn test_ok_internal(return_data_offset: usize, return_data_size: usize) { + let (addr_a, addr_b) = (mock::MOCK_ACCOUNTS[0], mock::MOCK_ACCOUNTS[1]); + + let pushdata = rand_bytes(32); + let code_b = bytecode! { + PUSH32(Word::from_big_endian(&pushdata)) + PUSH1(0) + MSTORE + + PUSH32(return_data_size) + PUSH1(return_data_offset) + RETURN + STOP + }; + + // code A calls code B. + let code_a = bytecode! { + // call ADDR_B. + PUSH32(return_data_size) // retLength + PUSH1(return_data_offset) // retOffset + PUSH1(0x00) // argsLength + PUSH1(0x00) // argsOffset + PUSH1(0x00) // value + PUSH32(addr_b.to_word()) // addr + PUSH32(0x1_0000) // gas + CALL + RETURNDATASIZE + STOP + }; + + let ctx = TestContext::<3, 1>::new( + None, + |accs| { + accs[0].address(addr_b).code(code_b); + accs[1].address(addr_a).code(code_a); + accs[2] + .address(mock::MOCK_ACCOUNTS[2]) + .balance(Word::from(1u64 << 30)); + }, + |mut txs, accs| { + txs[0].to(accs[1].address).from(accs[2].address); + }, + |block, _tx| block, + ) + .unwrap(); + + assert_eq!(run_test_circuits(ctx, None), Ok(())); + } + + #[test] + fn returndatasize_gadget_simple() { + test_ok_internal(0x00, 0x02); + } + + #[test] + fn returndatasize_gadget_large() { + test_ok_internal(0x00, 0x20); + } + + #[test] + fn returndatasize_gadget_zero_length() { + test_ok_internal(0x00, 0x00); + } + + #[test] + fn test_simple() { + let code = bytecode! { + RETURNDATASIZE + STOP + }; + assert_eq!( + run_test_circuits( + TestContext::<2, 1>::simple_ctx_with_bytecode(code).unwrap(), + None + ), + Ok(()) + ) + } +} diff --git a/zkevm-circuits/src/witness/step.rs b/zkevm-circuits/src/witness/step.rs index 80f37878ac..5b9a7da160 100644 --- a/zkevm-circuits/src/witness/step.rs +++ b/zkevm-circuits/src/witness/step.rs @@ -175,13 +175,13 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::CALLDATALOAD => ExecutionState::CALLDATALOAD, OpcodeId::CODESIZE => ExecutionState::CODESIZE, OpcodeId::RETURN | OpcodeId::REVERT => ExecutionState::RETURN, + OpcodeId::RETURNDATASIZE => ExecutionState::RETURNDATASIZE, // dummy ops OpcodeId::BALANCE => dummy!(ExecutionState::BALANCE), OpcodeId::EXP => dummy!(ExecutionState::EXP), OpcodeId::SAR => dummy!(ExecutionState::SAR), OpcodeId::EXTCODESIZE => dummy!(ExecutionState::EXTCODESIZE), OpcodeId::EXTCODECOPY => dummy!(ExecutionState::EXTCODECOPY), - OpcodeId::RETURNDATASIZE => dummy!(ExecutionState::RETURNDATASIZE), OpcodeId::RETURNDATACOPY => dummy!(ExecutionState::RETURNDATACOPY), OpcodeId::CREATE => dummy!(ExecutionState::CREATE), OpcodeId::CALLCODE => dummy!(ExecutionState::CALLCODE),