diff --git a/Cargo.lock b/Cargo.lock index a10902b7cc..76b382bd7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,6 +346,7 @@ dependencies = [ "mock", "pairing_bn256", "pretty_assertions", + "rand", "serde", "serde_json", "tokio", diff --git a/bus-mapping/Cargo.toml b/bus-mapping/Cargo.toml index d1fecd29ed..e777b32621 100644 --- a/bus-mapping/Cargo.toml +++ b/bus-mapping/Cargo.toml @@ -20,5 +20,6 @@ serde_json = "1.0.66" hex = "0.4.3" mock = { path = "../mock" } pretty_assertions = "1.0.0" +rand = "0.8" tokio = { version = "1.13", features = ["macros"] } url = "2.2.2" diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index c4f924eb48..810ff4664a 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -18,6 +18,7 @@ use log::warn; mod call; mod calldatacopy; +mod calldataload; mod calldatasize; mod caller; mod callvalue; @@ -39,6 +40,7 @@ mod swap; use call::Call; use calldatacopy::Calldatacopy; +use calldataload::Calldataload; use calldatasize::Calldatasize; use caller::Caller; use callvalue::Callvalue; @@ -118,7 +120,7 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { OpcodeId::CALLER => Caller::gen_associated_ops, OpcodeId::CALLVALUE => Callvalue::gen_associated_ops, OpcodeId::CALLDATASIZE => Calldatasize::gen_associated_ops, - OpcodeId::CALLDATALOAD => StackOnlyOpcode::<1, 1>::gen_associated_ops, + OpcodeId::CALLDATALOAD => Calldataload::gen_associated_ops, OpcodeId::CALLDATACOPY => Calldatacopy::gen_associated_ops, // OpcodeId::CODESIZE => {}, OpcodeId::GASPRICE => GasPrice::gen_associated_ops, diff --git a/bus-mapping/src/evm/opcodes/calldatacopy.rs b/bus-mapping/src/evm/opcodes/calldatacopy.rs index 17aa358769..f650698dbd 100644 --- a/bus-mapping/src/evm/opcodes/calldatacopy.rs +++ b/bus-mapping/src/evm/opcodes/calldatacopy.rs @@ -188,10 +188,10 @@ mod calldatacopy_tests { operation::{CallContextField, CallContextOp, MemoryOp, StackOp, RW}, }; use eth_types::{ - address, bytecode, + bytecode, evm_types::{OpcodeId, StackAddress}, geth_types::GethData, - Address, ToWord, Word, + ToWord, Word, }; use mock::test_ctx::{helpers::*, TestContext}; @@ -199,8 +199,7 @@ mod calldatacopy_tests { #[test] fn calldatacopy_opcode_internal() { - let addr_a = address!("0x000000000000000000000000000000000cafe00a"); - let addr_b = address!("0x000000000000000000000000000000000cafe00b"); + let (addr_a, addr_b) = (mock::MOCK_ACCOUNTS[0], mock::MOCK_ACCOUNTS[1]); // code B gets called by code A, so the call is an internal call. let dst_offset = 0x00usize; @@ -246,13 +245,13 @@ mod calldatacopy_tests { accs[0].address(addr_b).code(code_b); accs[1].address(addr_a).code(code_a); accs[2] - .address(Address::random()) + .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.number(0xcafeu64), + |block, _tx| block, ) .unwrap() .into(); @@ -384,7 +383,7 @@ mod calldatacopy_tests { None, account_0_code_account_1_no_code(code), tx_from_1_to_0, - |block, _tx| block.number(0xcafeu64), + |block, _tx| block, ) .unwrap() .into(); diff --git a/bus-mapping/src/evm/opcodes/calldataload.rs b/bus-mapping/src/evm/opcodes/calldataload.rs new file mode 100644 index 0000000000..5d3c5b29da --- /dev/null +++ b/bus-mapping/src/evm/opcodes/calldataload.rs @@ -0,0 +1,410 @@ +use crate::{ + circuit_input_builder::{CircuitInputStateRef, ExecStep}, + operation::{CallContextField, CallContextOp, MemoryOp, RW}, + Error, +}; +use eth_types::{GethExecStep, U256}; + +use super::Opcode; + +#[derive(Clone, Copy, Debug)] +pub(crate) struct Calldataload; + +impl Opcode for Calldataload { + 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)?; + + // fetch the top of the stack, i.e. offset in calldata to start reading 32-bytes + // from. + let offset = geth_step.stack.nth_last(0)?; + + state.push_stack_op( + &mut exec_step, + RW::READ, + geth_step.stack.nth_last_filled(0), + offset, + )?; + + let is_root = state.call()?.is_root; + if is_root { + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: state.call()?.call_id, + field: CallContextField::TxId, + value: state.tx_ctx.id().into(), + }, + ); + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: state.call()?.call_id, + field: CallContextField::CallDataLength, + value: state.call()?.call_data_length.into(), + }, + ); + } else { + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: state.call()?.call_id, + field: CallContextField::CallerId, + value: state.call()?.caller_id.into(), + }, + ); + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: state.call()?.call_id, + field: CallContextField::CallDataLength, + value: state.call()?.call_data_length.into(), + }, + ); + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: state.call()?.call_id, + field: CallContextField::CallDataOffset, + value: state.call()?.call_data_offset.into(), + }, + ); + } + + let call = state.call()?.clone(); + let (src_addr, src_addr_end, caller_id, call_data) = ( + call.call_data_offset as usize + offset.as_usize(), + call.call_data_offset as usize + call.call_data_length as usize, + call.caller_id, + state.call_ctx()?.call_data.to_vec(), + ); + let calldata_word = (0..32) + .map(|idx| { + let addr = src_addr + idx; + if addr < src_addr_end { + let byte = call_data[addr - call.call_data_offset as usize]; + if !is_root { + state.push_op( + &mut exec_step, + RW::READ, + MemoryOp::new(caller_id, (src_addr + idx).into(), byte), + ); + } + byte + } else { + 0 + } + }) + .collect::>(); + + state.push_stack_op( + &mut exec_step, + RW::WRITE, + geth_step.stack.last_filled(), + U256::from_big_endian(&calldata_word), + )?; + + Ok(vec![exec_step]) + } +} + +#[cfg(test)] +mod calldataload_tests { + use eth_types::{ + bytecode, + evm_types::{OpcodeId, StackAddress}, + geth_types::GethData, + ToWord, Word, + }; + use mock::{test_ctx::helpers::account_0_code_account_1_no_code, TestContext}; + use rand::random; + + use crate::{circuit_input_builder::ExecState, mock::BlockData, operation::StackOp}; + + use super::*; + + fn rand_bytes(size: usize) -> Vec { + (0..size).map(|_| random()).collect::>() + } + + fn test_internal_ok( + call_data_length: usize, + call_data_offset: usize, + offset: usize, + pushdata: Vec, + call_data_word: Word, + ) { + let (addr_a, addr_b) = (mock::MOCK_ACCOUNTS[0], mock::MOCK_ACCOUNTS[1]); + + // code B gets called by code A, so the call is an internal call. + let code_b = bytecode! { + PUSH32(offset) + CALLDATALOAD + STOP + }; + + let mut memory_a = std::iter::repeat(0) + .take(32 - pushdata.len() - call_data_offset) + .chain(pushdata.clone()) + .collect::>(); + if memory_a.len() < call_data_length { + memory_a.resize(call_data_length, 0); + } + let code_a = bytecode! { + // populate memory in A's context. + PUSH32(Word::from_big_endian(&pushdata)) + PUSH1(0x00) // offset + MSTORE + // call addr_b + PUSH1(0x00) // retLength + PUSH1(0x00) // retOffset + PUSH1(call_data_length) // argsLength + PUSH1(call_data_offset) // argsOffset + PUSH1(0x00) // value + PUSH32(addr_b.to_word()) // addr + PUSH32(0x1_0000) // gas + CALL + STOP + }; + + // Get the execution steps from the external tracer + let block: GethData = 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() + .into(); + + let mut builder = BlockData::new_from_geth_data(block.clone()).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::CALLDATALOAD)) + .unwrap(); + + let call_id = builder.block.txs()[0].calls()[step.call_index].call_id; + let caller_id = builder.block.txs()[0].calls()[step.call_index].caller_id; + + // 1 stack read, 3 call context reads, 32 memory reads and 1 stack write. + assert_eq!(step.bus_mapping_instance.len(), 37); + + // stack read and write. + assert_eq!( + [0, 36] + .map(|idx| &builder.block.container.stack[step.bus_mapping_instance[idx].as_usize()]) + .map(|op| (op.rw(), op.op())), + [ + ( + RW::READ, + &StackOp::new(call_id, StackAddress::from(1023), Word::from(offset)), + ), + ( + RW::WRITE, + &StackOp::new(call_id, StackAddress::from(1023), call_data_word), + ), + ] + ); + + // call context reads. + assert_eq!( + [1, 2, 3] + .map(|idx| &builder.block.container.call_context + [step.bus_mapping_instance[idx].as_usize()]) + .map(|op| (op.rw(), op.op())), + [ + ( + RW::READ, + &CallContextOp { + call_id, + field: CallContextField::CallerId, + value: Word::from(caller_id), + }, + ), + ( + RW::READ, + &CallContextOp { + call_id, + field: CallContextField::CallDataLength, + value: Word::from(call_data_length), + }, + ), + ( + RW::READ, + &CallContextOp { + call_id, + field: CallContextField::CallDataOffset, + value: Word::from(call_data_offset), + } + ), + ], + ); + + // 32 memory reads from caller memory + assert_eq!( + (0..32) + .map(|idx| &builder.block.container.memory + [step.bus_mapping_instance[4 + idx].as_usize()]) + .map(|op| (op.rw(), op.op().clone())) + .collect::>(), + (0..32) + .map(|idx| { + ( + RW::READ, + MemoryOp::new( + caller_id, + (call_data_offset + offset + idx).into(), + memory_a[offset + idx], + ), + ) + }) + .collect::>(), + ); + } + + fn test_root_ok(offset: u64, calldata: Vec, calldata_word: Word) { + let code = bytecode! { + PUSH32(offset) + CALLDATALOAD + STOP + }; + + let block: GethData = TestContext::<2, 1>::new( + None, + account_0_code_account_1_no_code(code), + |mut txs, accs| { + txs[0] + .to(accs[0].address) + .from(accs[1].address) + .input(calldata.clone().into()); + }, + |block, _tx| block, + ) + .unwrap() + .into(); + + let mut builder = BlockData::new_from_geth_data(block.clone()).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::CALLDATALOAD)) + .unwrap(); + + let call_id = builder.block.txs()[0].calls()[0].call_id; + + // 1 stack read, 2 call context reads and 1 stack write. + assert_eq!(step.bus_mapping_instance.len(), 4); + + // stack read and write. + assert_eq!( + [0, 3] + .map(|idx| &builder.block.container.stack[step.bus_mapping_instance[idx].as_usize()]) + .map(|op| (op.rw(), op.op())), + [ + ( + RW::READ, + &StackOp::new(call_id, StackAddress::from(1023), Word::from(offset)), + ), + ( + RW::WRITE, + &StackOp::new(call_id, StackAddress::from(1023), calldata_word), + ), + ] + ); + + // call context reads. + assert_eq!( + [1, 2] + .map(|idx| &builder.block.container.call_context + [step.bus_mapping_instance[idx].as_usize()]) + .map(|op| (op.rw(), op.op())), + [ + ( + RW::READ, + &CallContextOp { + call_id, + field: CallContextField::TxId, + value: Word::from(1), + } + ), + ( + RW::READ, + &CallContextOp { + call_id, + field: CallContextField::CallDataLength, + value: Word::from(calldata.len()), + }, + ) + ], + ); + } + + #[test] + fn calldataload_opcode_root() { + // 1. should be right padded + test_root_ok(0u64, vec![1u8, 2u8], { + let mut v = vec![0u8; 32]; + v[0] = 1u8; + v[1] = 2u8; + Word::from_big_endian(&v) + }); + + // 2. exactly 32 bytes + let calldata = rand_bytes(32); + test_root_ok(0u64, calldata.clone(), Word::from_big_endian(&calldata)); + + // 3. out-of-bounds: take only 32 bytes + let calldata = rand_bytes(64); + test_root_ok( + 12u64, + calldata.clone(), + Word::from_big_endian(&calldata[12..44]), + ); + } + + #[test] + fn calldataload_opcode_internal() { + let pushdata = rand_bytes(0x08); + let expected = std::iter::repeat(0) + .take(0x20 - pushdata.len()) + .chain(pushdata.clone()) + .collect::>(); + test_internal_ok( + 0x20, // call data length + 0x00, // call data offset + 0x00, // offset + pushdata, + Word::from_big_endian(&expected), + ); + + let pushdata = rand_bytes(0x10); + let mut expected = pushdata.clone(); + expected.resize(0x20, 0); + test_internal_ok(0x20, 0x10, 0x00, pushdata, Word::from_big_endian(&expected)); + } +} diff --git a/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs b/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs index 67a8d98088..6e3fc3aeb7 100644 --- a/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs @@ -236,7 +236,7 @@ impl ExecutionGadget for CallDataCopyGadget { #[cfg(test)] mod test { use crate::{evm_circuit::test::rand_bytes, test_util::run_test_circuits}; - use eth_types::{address, bytecode, Address, ToWord, Word}; + use eth_types::{bytecode, ToWord, Word}; use mock::test_ctx::{helpers::*, TestContext}; fn test_ok_root( @@ -279,8 +279,7 @@ mod test { offset: usize, copy_size: usize, ) { - let addr_a = address!("0x000000000000000000000000000000000cafe00a"); - let addr_b = address!("0x000000000000000000000000000000000cafe00b"); + let (addr_a, addr_b) = (mock::MOCK_ACCOUNTS[0], mock::MOCK_ACCOUNTS[1]); // code B gets called by code A, so the call is an internal call. let code_b = bytecode! { @@ -292,7 +291,7 @@ mod test { }; // code A calls code B. - let pushdata = hex::decode("1234567890abcdef").unwrap(); + let pushdata = rand_bytes(8); let code_a = bytecode! { // populate memory in A's context. PUSH8(Word::from_big_endian(&pushdata)) @@ -316,13 +315,13 @@ mod test { accs[0].address(addr_b).code(code_b); accs[1].address(addr_a).code(code_a); accs[2] - .address(Address::random()) + .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.number(0xcafeu64), + |block, _tx| block, ) .unwrap(); diff --git a/zkevm-circuits/src/evm_circuit/execution/calldataload.rs b/zkevm-circuits/src/evm_circuit/execution/calldataload.rs index 1d05fbb908..5e647e553e 100644 --- a/zkevm-circuits/src/evm_circuit/execution/calldataload.rs +++ b/zkevm-circuits/src/evm_circuit/execution/calldataload.rs @@ -26,25 +26,24 @@ use crate::{ use super::ExecutionGadget; // The offset in the RW indices that mark the start of memory lookups. -const OFFSET_RW_MEMORY_INDICES: usize = 5usize; +const OFFSET_RW_MEMORY_INDICES: usize = 4usize; #[derive(Clone, Debug)] pub(crate) struct CallDataLoadGadget { /// Gadget to constrain the same context. same_context: SameContextGadget, - /// Transaction id from the tx context. - tx_id: Cell, + /// Source of data, this is transaction ID for a root call and caller ID for + /// an internal call. + src_id: Cell, /// The bytes offset in calldata, from which we load a 32-bytes word. offset: MemoryAddress, /// The size of the call's data (tx input for a root call or calldata length /// of an internal call). - calldata_length: Cell, + call_data_length: Cell, /// The offset from where call data begins. This is 0 for a root call since /// tx data starts at the first byte, but can be non-zero offset for an /// internal call. - calldata_offset: Cell, - /// Parent call ID that makes a call to this internal call. - caller_id: Cell, + call_data_offset: Cell, /// Gadget to read from tx calldata, which we validate against the word /// pushed to stack. buffer_reader: BufferReaderGadget, @@ -64,25 +63,24 @@ impl ExecutionGadget for CallDataLoadGadget { cb.stack_pop(offset.expr()); // Add a lookup constrain for TxId in the RW table. - let tx_id = cb.call_context(None, CallContextFieldTag::TxId); + let src_id = cb.query_cell(); + let call_data_length = cb.query_cell(); + let call_data_offset = cb.query_cell(); - let calldata_length = cb.query_cell(); - let calldata_offset = cb.query_cell(); - let caller_id = cb.query_cell(); - - let src_addr = offset.expr() + calldata_offset.expr(); - let src_addr_end = calldata_length.expr() + calldata_offset.expr(); + let src_addr = offset.expr() + call_data_offset.expr(); + let src_addr_end = call_data_length.expr() + call_data_offset.expr(); cb.condition(cb.curr.state.is_root.expr(), |cb| { - cb.tx_context_lookup( - tx_id.expr(), - TxContextFieldTag::CallDataLength, + cb.call_context_lookup(false.expr(), None, CallContextFieldTag::TxId, src_id.expr()); + cb.call_context_lookup( + false.expr(), None, - calldata_length.expr(), + CallContextFieldTag::CallDataLength, + call_data_length.expr(), ); cb.require_equal( - "if is_root then calldata_offset == 0", - calldata_offset.expr(), + "if is_root then call_data_offset == 0", + call_data_offset.expr(), 0.expr(), ); }); @@ -90,20 +88,20 @@ impl ExecutionGadget for CallDataLoadGadget { cb.call_context_lookup( false.expr(), None, - CallContextFieldTag::CallDataLength, - calldata_length.expr(), + CallContextFieldTag::CallerId, + src_id.expr(), ); cb.call_context_lookup( false.expr(), None, - CallContextFieldTag::CallDataOffset, - calldata_offset.expr(), + CallContextFieldTag::CallDataLength, + call_data_length.expr(), ); cb.call_context_lookup( false.expr(), None, - CallContextFieldTag::CallerId, - caller_id.expr(), + CallContextFieldTag::CallDataOffset, + call_data_offset.expr(), ); }); @@ -116,7 +114,7 @@ impl ExecutionGadget for CallDataLoadGadget { cb.curr.state.is_root.expr() * buffer_reader.read_flag(idx), |cb| { cb.tx_context_lookup( - tx_id.expr(), + src_id.expr(), TxContextFieldTag::CallData, Some(src_addr.expr() + idx.expr()), buffer_reader.byte(idx), @@ -131,7 +129,7 @@ impl ExecutionGadget for CallDataLoadGadget { 0.expr(), src_addr.expr() + idx.expr(), buffer_reader.byte(idx), - Some(caller_id.expr()), + Some(src_id.expr()), ); }, ); @@ -164,10 +162,9 @@ impl ExecutionGadget for CallDataLoadGadget { Self { same_context, offset, - calldata_length, - calldata_offset, - caller_id, - tx_id, + src_id, + call_data_length, + call_data_offset, buffer_reader, } } @@ -198,13 +195,9 @@ impl ExecutionGadget for CallDataLoadGadget { ), )?; - // assign the tx id. - self.tx_id - .assign(region, offset, Some(F::from(tx.id as u64)))?; - // assign to the buffer reader gadget. - let (calldata_length, calldata_offset, caller_id) = if call.is_root { - (tx.call_data_length as u64, 0u64, 0u64) + let (calldata_length, calldata_offset, src_id) = if call.is_root { + (tx.call_data_length as u64, 0u64, tx.id as u64) } else { ( call.call_data_length, @@ -212,12 +205,11 @@ impl ExecutionGadget for CallDataLoadGadget { call.caller_id as u64, ) }; - self.calldata_length + self.src_id.assign(region, offset, Some(F::from(src_id)))?; + self.call_data_length .assign(region, offset, Some(F::from(calldata_length)))?; - self.calldata_offset + self.call_data_offset .assign(region, offset, Some(F::from(calldata_offset)))?; - self.caller_id - .assign(region, offset, Some(F::from(caller_id)))?; let mut calldata_bytes = vec![0u8; N_BYTES_WORD]; let (src_addr, src_addr_end) = ( @@ -253,254 +245,85 @@ impl ExecutionGadget for CallDataLoadGadget { #[cfg(test)] mod test { - use std::collections::HashMap; - - use bus_mapping::evm::OpcodeId; - use eth_types::{bytecode, Word}; - use halo2_proofs::arithmetic::BaseExt; - use pairing::bn256::Fr; + use eth_types::{bytecode, ToWord, Word}; + use mock::TestContext; - use crate::evm_circuit::{ - execution::calldataload::OFFSET_RW_MEMORY_INDICES, - param::N_BYTES_WORD, - step::ExecutionState, - table::{CallContextFieldTag, RwTableTag}, - test::run_test_circuit_incomplete_fixed_table, - witness::{Block, Bytecode, Call, CodeSource, ExecStep, Rw, RwMap, Transaction}, - }; - - fn bytes_from_hex(s: &str) -> Vec { - hex::decode(s).expect("invalid hex") - } + use crate::{evm_circuit::test::rand_bytes, test_util::run_test_circuits}; - fn word_from_hex(s: &str) -> Word { - Word::from_big_endian(&bytes_from_hex(s)) - } + fn test_root_ok(offset: usize) { + let bytecode = bytecode! { + PUSH32(Word::from(offset)) + CALLDATALOAD + STOP + }; - fn test_data() -> Vec<(Vec, usize, Word)> { - vec![ - ( - bytes_from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEE"), - 0, - word_from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEE"), - ), - ( - bytes_from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), - 31, - word_from_hex("FF00000000000000000000000000000000000000000000000000000000000000"), - ), - ( - bytes_from_hex("a1bacf5488bfafc33bad736db41f06866eaeb35e1c1dd81dfc268357ec98563f"), - 16, - word_from_hex("6eaeb35e1c1dd81dfc268357ec98563f00000000000000000000000000000000"), + assert_eq!( + run_test_circuits( + TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode).unwrap(), + None ), - ] + Ok(()) + ); } - fn test_ok(call_data: Vec, offset: Word, expected: Word, call_data_offset: Option) { - let randomness = Fr::rand(); - let bytecode = bytecode! { - #[start] - PUSH32(offset) + fn test_internal_ok(call_data_length: usize, call_data_offset: usize, offset: usize) { + let (addr_a, addr_b) = (mock::MOCK_ACCOUNTS[0], mock::MOCK_ACCOUNTS[1]); + + // code B gets called by code A, so the call is an internal call. + let code_b = bytecode! { + PUSH32(Word::from(offset)) CALLDATALOAD STOP }; - let bytecode = Bytecode::new(bytecode.to_vec()); - let tx_id = 1; - let (call_id, parent_call_id) = if call_data_offset.is_some() { - (1, 0) - } else { - (2, 1) + + let pushdata = rand_bytes(32); + let code_a = bytecode! { + // populate memory in A's context. + PUSH32(Word::from_big_endian(&pushdata)) + PUSH1(0x00) // offset + MSTORE + // call addr_b + PUSH1(0x00) // retLength + PUSH1(0x00) // retOffset + PUSH1(call_data_length) // argsLength + PUSH1(call_data_offset) // argsOffset + PUSH1(0x00) // value + PUSH32(addr_b.to_word()) // addr + PUSH32(0x1_0000) // gas + CALL + STOP }; - let call_data_length = call_data.len(); - - let mut rws_stack = vec![ - Rw::Stack { - rw_counter: 1, - is_write: true, - call_id, - stack_pointer: 1023, - value: offset, - }, - Rw::Stack { - rw_counter: 2, - is_write: false, - call_id, - stack_pointer: 1023, - value: offset, - }, - ]; - let mut rws_call_context = vec![Rw::CallContext { - rw_counter: 3, - is_write: false, - call_id, - field_tag: CallContextFieldTag::TxId, - value: Word::one(), - }]; - let mut rw_indices = vec![(RwTableTag::Stack, 1), (RwTableTag::CallContext, 0)]; - - // if not root call, add calldata to memory. - let mut rws_map = HashMap::new(); - let mut rw_counter = 4; - // if call data offset is provided, then it is an internal call. - if let Some(call_data_offset) = call_data_offset { - let src_addr = offset.as_usize() + call_data_offset as usize; - // handle call context rws. - rws_call_context.append(&mut vec![ - Rw::CallContext { - is_write: false, - call_id, - rw_counter, - field_tag: CallContextFieldTag::CallDataLength, - value: Word::from(call_data_length as u64), - }, - Rw::CallContext { - is_write: false, - call_id, - rw_counter: rw_counter + 1, - field_tag: CallContextFieldTag::CallDataOffset, - value: Word::from(call_data_offset), - }, - Rw::CallContext { - is_write: false, - call_id, - rw_counter: rw_counter + 2, - field_tag: CallContextFieldTag::CallerId, - value: Word::from(parent_call_id as u64), - }, - ]); - rw_indices.append(&mut vec![ - (RwTableTag::CallContext, 1), - (RwTableTag::CallContext, 2), - (RwTableTag::CallContext, 3), - ]); - rw_counter += 3; - - assert_eq!(rw_indices.len(), OFFSET_RW_MEMORY_INDICES); - - // handle memory rws. - let rws_memory = call_data - .iter() - .skip(src_addr) - .take(N_BYTES_WORD) - .enumerate() - .map(|(idx, byte)| Rw::Memory { - call_id: parent_call_id, - memory_address: (src_addr + idx) as u64, - is_write: false, - byte: *byte, - rw_counter: rw_counter + idx, - }) - .collect::>(); - rw_counter += rws_memory.len(); - let mut extra_indices = (0..rws_memory.len()) - .map(|i| (RwTableTag::Memory, i)) - .collect(); - rw_indices.append(&mut extra_indices); - rws_map.insert(RwTableTag::Memory, rws_memory); - } - rw_indices.push((RwTableTag::Stack, 2)); - rws_stack.push(Rw::Stack { - rw_counter, - is_write: true, - call_id, - stack_pointer: 1023, - value: expected, - }); - rws_map.insert(RwTableTag::Stack, rws_stack); - rws_map.insert(RwTableTag::CallContext, rws_call_context); - - let gas_left = vec![OpcodeId::PUSH32, OpcodeId::CALLDATALOAD, OpcodeId::STOP] - .iter() - .map(|o| o.constant_gas_cost().as_u64()) - .sum(); - let steps = vec![ - ExecStep { - execution_state: ExecutionState::PUSH, - rw_indices: vec![(RwTableTag::Stack, 0)], - rw_counter: 1, - program_counter: 0, - stack_pointer: 1024, - gas_left, - gas_cost: OpcodeId::PUSH32.constant_gas_cost().as_u64(), - opcode: Some(OpcodeId::PUSH32), - ..Default::default() - }, - ExecStep { - execution_state: ExecutionState::CALLDATALOAD, - rw_indices, - rw_counter: 2, - program_counter: 33, - stack_pointer: 1023, - gas_left: gas_left - OpcodeId::PUSH32.constant_gas_cost().as_u64(), - gas_cost: OpcodeId::CALLDATALOAD.constant_gas_cost().as_u64(), - opcode: Some(OpcodeId::CALLDATALOAD), - ..Default::default() + + 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)); }, - ExecStep { - execution_state: ExecutionState::STOP, - rw_counter: rw_counter + 1, - program_counter: 34, - stack_pointer: 1023, - gas_left: 0, - opcode: Some(OpcodeId::STOP), - ..Default::default() + |mut txs, accs| { + txs[0].to(accs[1].address).from(accs[2].address); }, - ]; - - let block = Block { - randomness, - txs: vec![Transaction { - id: tx_id, - call_data_length: if call_data_offset.is_none() { - call_data.len() - } else { - 0 - }, - call_data: if call_data_offset.is_none() { - call_data - } else { - vec![] - }, - steps, - calls: vec![Call { - id: call_id, - is_root: call_data_offset.is_none(), - is_create: false, - call_data_length: call_data_length as u64, - call_data_offset: call_data_offset.unwrap_or(0), - code_source: CodeSource::Account(bytecode.hash), - caller_id: parent_call_id, - ..Default::default() - }], - ..Default::default() - }], - rws: RwMap(rws_map), - bytecodes: vec![bytecode], - ..Default::default() - }; + |block, _tx| block, + ) + .unwrap(); - assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); + assert_eq!(run_test_circuits(ctx, None), Ok(())); } #[test] fn calldataload_gadget_root() { - test_data() - .iter() - .for_each(|t| test_ok(t.0.clone(), Word::from(t.1), t.2, None)); + test_root_ok(0x00); + test_root_ok(0x08); + test_root_ok(0x10); } #[test] fn calldataload_gadget_internal() { - test_data() - .iter() - .for_each(|t| test_ok(t.0.clone(), Word::from(t.1), t.2, Some(0u64))); - - test_ok( - bytes_from_hex("73ccaaba64c27c285a0ada6ffb1804dc959a99b99a5c0cba8a2bd5bd3937be6a3ef6f6ae8dac116faf671072c9d5958a"), - Word::from(4u64), - word_from_hex("04dc959a99b99a5c0cba8a2bd5bd3937be6a3ef6f6ae8dac116faf671072c9d5"), - Some(10u64), - ); + test_internal_ok(0x20, 0x00, 0x00); + test_internal_ok(0x20, 0x10, 0x10); + test_internal_ok(0x40, 0x20, 0x08); } } diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index e527bf9aaf..5e8026bb41 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -1120,6 +1120,7 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::CALL => ExecutionState::CALL, OpcodeId::ORIGIN => ExecutionState::ORIGIN, OpcodeId::CODECOPY => ExecutionState::CODECOPY, + OpcodeId::CALLDATALOAD => ExecutionState::CALLDATALOAD, _ => unimplemented!("unimplemented opcode {:?}", op), } }