From bad3680e0147ba359e995f5b795899a7cf04e260 Mon Sep 17 00:00:00 2001 From: Eduard S Date: Tue, 30 Aug 2022 17:49:46 +0200 Subject: [PATCH] Add test function to report opcode gas cost in state circuit Add memory expansion for call-like dummy opcodes --- .../circuit_input_builder/input_state_ref.rs | 29 ++++ bus-mapping/src/evm/opcodes.rs | 18 +++ bus-mapping/src/evm/opcodes/call.rs | 17 +-- zkevm-circuits/src/evm_circuit.rs | 4 +- zkevm-circuits/src/state_circuit.rs | 131 ++++++++++++++++++ 5 files changed, 181 insertions(+), 18 deletions(-) diff --git a/bus-mapping/src/circuit_input_builder/input_state_ref.rs b/bus-mapping/src/circuit_input_builder/input_state_ref.rs index fb5217300a..36b8cc32de 100644 --- a/bus-mapping/src/circuit_input_builder/input_state_ref.rs +++ b/bus-mapping/src/circuit_input_builder/input_state_ref.rs @@ -20,6 +20,7 @@ use eth_types::{ Address, GethExecStep, ToAddress, ToBigEndian, Word, H256, }; use ethers_core::utils::{get_contract_address, get_create2_address}; +use std::cmp::max; /// Reference to the internal state of the CircuitInputBuilder in a particular /// [`ExecStep`]. @@ -994,4 +995,32 @@ impl<'a> CircuitInputStateRef<'a> { Ok(None) } + + /// Expand memory of the call context when entering a new call context in + /// case the call arguments or return arguments go beyond the call + /// context current memory. + pub(crate) fn call_expand_memory( + &mut self, + args_offset: usize, + args_length: usize, + ret_offset: usize, + ret_length: usize, + ) -> Result<(), Error> { + let call_ctx = self.call_ctx_mut()?; + let args_minimal = if args_length != 0 { + args_offset + args_length + } else { + 0 + }; + let ret_minimal = if ret_length != 0 { + ret_offset + ret_length + } else { + 0 + }; + if args_minimal != 0 || ret_minimal != 0 { + let minimal_length = max(args_minimal, ret_minimal); + call_ctx.memory.extend_at_least(minimal_length); + } + Ok(()) + } } diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index 95ad33de17..45f9d7880d 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -560,6 +560,24 @@ fn dummy_gen_call_ops( let geth_step = &geth_steps[0]; let mut exec_step = state.new_step(geth_step)?; + let (args_offset, args_length, ret_offset, ret_length) = { + // CALLCODE (gas, addr, value, argsOffset, argsLength, retOffset, retLength) + // DELEGATECALL(gas, addr, argsOffset, argsLength, retOffset, retLength) + // STATICCALL (gas, addr, argsOffset, argsLength, retOffset, retLength) + let pos = match geth_step.op { + OpcodeId::CALLCODE => (3, 4, 5, 6), + OpcodeId::DELEGATECALL | OpcodeId::STATICCALL => (2, 3, 4, 5), + _ => unreachable!("opcode is not of call type"), + }; + ( + geth_step.stack.nth_last(pos.0)?.as_usize(), + geth_step.stack.nth_last(pos.1)?.as_usize(), + geth_step.stack.nth_last(pos.2)?.as_usize(), + geth_step.stack.nth_last(pos.3)?.as_usize(), + ) + }; + state.call_expand_memory(args_offset, args_length, ret_offset, ret_length)?; + let tx_id = state.tx_ctx.id(); let call = state.parse_call(geth_step)?; diff --git a/bus-mapping/src/evm/opcodes/call.rs b/bus-mapping/src/evm/opcodes/call.rs index b621034b69..489f7f6263 100644 --- a/bus-mapping/src/evm/opcodes/call.rs +++ b/bus-mapping/src/evm/opcodes/call.rs @@ -13,7 +13,6 @@ use eth_types::{ }; use keccak256::EMPTY_HASH; use log::warn; -use std::cmp::max; /// Placeholder structure used to implement [`Opcode`] trait over it /// corresponding to the `OpcodeId::CALL` `OpcodeId`. @@ -34,21 +33,7 @@ impl Opcode for Call { let ret_length = geth_step.stack.nth_last(6)?.as_usize(); // we need to keep the memory until parse_call complete - let call_ctx = state.call_ctx_mut()?; - let args_minimal = if args_length != 0 { - args_offset + args_length - } else { - 0 - }; - let ret_minimal = if ret_length != 0 { - ret_offset + ret_length - } else { - 0 - }; - if args_minimal != 0 || ret_minimal != 0 { - let minimal_length = max(args_minimal, ret_minimal); - call_ctx.memory.extend_at_least(minimal_length); - } + state.call_expand_memory(args_offset, args_length, ret_offset, ret_length)?; let tx_id = state.tx_ctx.id(); let call = state.parse_call(geth_step)?; diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 48bb79d0d9..a03c169d04 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -26,7 +26,7 @@ use witness::Block; pub struct EvmCircuit { fixed_table: [Column; 4], byte_table: [Column; 1], - execution: Box>, + pub(crate) execution: Box>, } impl EvmCircuit { @@ -388,7 +388,7 @@ mod evm_circuit_stats { /// This function prints to stdout a table with all the implemented states /// and their responsible opcodes with the following stats: - /// - height: number of rows used by the execution state + /// - height: number of rows in the EVM circuit used by the execution state /// - gas: gas value used for the opcode execution /// - height/gas: ratio between circuit cost and gas cost /// diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index 2c76dddf1f..e51f5464af 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -370,3 +370,134 @@ fn queries(meta: &mut VirtualCells<'_, F>, c: &StateCircuitConfig) * meta.query_advice(first_different_limb.bits[3], Rotation::cur()), } } + +#[cfg(test)] +mod state_circuit_stats { + use crate::evm_circuit::step::ExecutionState; + use crate::evm_circuit::test::TestCircuit; + use bus_mapping::{circuit_input_builder::ExecState, mock::BlockData}; + use eth_types::{bytecode, evm_types::OpcodeId, geth_types::GethData, Address}; + use halo2_proofs::halo2curves::bn256::Fr; + use halo2_proofs::plonk::{Circuit, ConstraintSystem}; + use mock::{eth, test_ctx::TestContext, MOCK_ACCOUNTS}; + use strum::IntoEnumIterator; + + /// This function prints to stdout a table with all the implemented states + /// and their responsible opcodes with the following stats: + /// - height: number of rows in the State circuit used by the execution + /// state + /// - gas: gas value used for the opcode execution + /// - height/gas: ratio between circuit cost and gas cost + /// + /// Run with: + /// `cargo test -p zkevm-circuits --release get_state_states_stats -- + /// --nocapture --ignored` + #[ignore] + #[test] + pub fn get_state_states_stats() { + // Get the list of implemented execution states by configuring the EVM Circuit + // and querying the step height for each possible execution state (only those + // implemented will return a Some value). + let mut meta = ConstraintSystem::::default(); + let circuit = TestCircuit::configure(&mut meta); + + let mut implemented_states = Vec::new(); + for state in ExecutionState::iter() { + let height = circuit.evm_circuit.execution.get_step_height_option(state); + if height.is_some() { + implemented_states.push(state); + } + } + + let mut stats = Vec::new(); + for state in implemented_states { + for opcode in state.responsible_opcodes() { + let mut code = bytecode! { + PUSH2(0x100) + MLOAD // Expand memory a bit + PUSH2(0x00) + EXTCODESIZE // Warm up 0x0 address + PUSH2(0x8000) + PUSH2(0x00) + PUSH2(0x10) + PUSH2(0x20) + PUSH2(0x30) + }; + // Make sure that opcodes that take an address as argument use addres 0x0, which + // will exist in the test. + match opcode { + OpcodeId::BALANCE + | OpcodeId::EXTCODESIZE + | OpcodeId::EXTCODECOPY + | OpcodeId::SELFDESTRUCT + | OpcodeId::EXTCODEHASH => code.append(&bytecode! { + PUSH2(0x40) + PUSH2(0x00) + }), + OpcodeId::CALL + | OpcodeId::CALLCODE + | OpcodeId::DELEGATECALL + | OpcodeId::STATICCALL => code.append(&bytecode! { + PUSH2(0x00) + PUSH2(0x50) + }), + _ => code.append(&bytecode! { + PUSH2(0x40) + PUSH2(0x50) + }), + }; + code.write_op(opcode); + code.write_op(OpcodeId::STOP); + let block: GethData = TestContext::<3, 1>::new( + None, + |accs| { + accs[0] + .address(MOCK_ACCOUNTS[0]) + .balance(eth(10)) + .code(code.clone()); + accs[1].address(MOCK_ACCOUNTS[1]).balance(eth(10)); + accs[2].address(Address::zero()).balance(eth(10)).code(code); + }, + |mut txs, accs| { + txs[0] + .from(accs[1].address) + .to(accs[0].address) + .input(vec![1, 2, 3, 4, 5, 6, 7].into()); + }, + |block, _tx| block.number(0xcafeu64), + ) + .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_index = 1 + 11; // 1 is for the BeginTx, 11 for the bytecode opcodes. + let step = &builder.block.txs[0].steps()[step_index]; + let step_next = &builder.block.txs[0].steps()[step_index + 1]; + assert_eq!(ExecState::Op(opcode), step.exec_state); + let h = step_next.rwc.0 - step.rwc.0; + + let gas_cost = block.geth_traces[0].struct_logs[11].gas_cost.0; + stats.push((state, opcode, h, gas_cost)); + } + } + + println!( + "| {: <14} | {: <14} | {: <2} | {: >6} | {: <5} |", + "state", "opcode", "h", "g", "h/g" + ); + println!("| --- | --- | ---| --- | --- |"); + for (state, opcode, height, gas_cost) in stats { + println!( + "| {: <14?} | {: <14?} | {: >2} | {: >6} | {: >1.3} |", + state, + opcode, + height, + gas_cost, + height as f64 / gas_cost as f64 + ); + } + } +}