diff --git a/Makefile b/Makefile index ea755b7aaa..b491bf821a 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ help: ## Display this help screen clippy: ## Run clippy checks over all workspace members @cargo check --all-features - @cargo clippy --all-features --all-targets -- -D clippy::all + @cargo clippy --all-features --all-targets -- -D warnings doc: ## Generate and tests docs including private items @cargo doc --no-deps --all --document-private-items @@ -20,6 +20,9 @@ test: ## Run tests for all the workspace members test_benches: ## Compiles the benchmarks @cargo test --verbose --release --all-features -p circuit-benchmarks --no-run +test-doc: + @cargo test --release --all --all-features --doc + test-all: fmt doc clippy test_benches test ## Run all the CI checks locally (in your actual toolchain) evm_bench: ## Run Evm Circuit benchmarks diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index be42d2c5e7..32fd173985 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -20,7 +20,8 @@ pub use access::{Access, AccessSet, AccessValue, CodeSource}; pub use block::{Block, BlockContext}; pub use call::{Call, CallContext, CallKind}; use core::fmt::Debug; -use eth_types::{self, Address, GethExecStep, GethExecTrace, Word}; +use eth_types::evm_types::GasCost; +use eth_types::{self, Address, GethExecStep, GethExecTrace, ToWord, Word}; use ethers_providers::JsonRpcClient; pub use execution::{CopyDetails, ExecState, ExecStep, StepAuxiliaryData}; pub use input_state_ref::CircuitInputStateRef; @@ -167,11 +168,21 @@ impl<'a> CircuitInputBuilder { let mut tx = self.new_tx(eth_tx, !geth_trace.failed)?; let mut tx_ctx = TransactionContext::new(eth_tx, geth_trace, is_last_tx)?; + if let Some(al) = ð_tx.access_list { + for item in &al.0 { + self.sdb.add_account_to_access_list(item.address); + for k in &item.storage_keys { + self.sdb + .add_account_storage_to_access_list((item.address, (*k).to_word())); + } + } + } // TODO: Move into gen_associated_steps with // - execution_state: BeginTx // - op: None // Generate BeginTx step - let begin_tx_step = gen_begin_tx_ops(&mut self.state_ref(&mut tx, &mut tx_ctx))?; + let mut begin_tx_step = gen_begin_tx_ops(&mut self.state_ref(&mut tx, &mut tx_ctx))?; + begin_tx_step.gas_cost = GasCost(tx.gas - geth_trace.struct_logs[0].gas.0); tx.steps_mut().push(begin_tx_step); for (index, geth_step) in geth_trace.struct_logs.iter().enumerate() { 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 2d3973642d..a8816a312a 100644 --- a/bus-mapping/src/circuit_input_builder/input_state_ref.rs +++ b/bus-mapping/src/circuit_input_builder/input_state_ref.rs @@ -275,6 +275,13 @@ impl<'a> CircuitInputStateRef<'a> { .ok_or(Error::CodeNotFound(code_hash)) } + /// Reference to the caller's Call + pub fn caller(&self) -> Result<&Call, Error> { + self.tx_ctx + .caller_index() + .map(|caller_idx| &self.tx.calls()[caller_idx]) + } + /// Reference to the current Call pub fn call(&self) -> Result<&Call, Error> { self.tx_ctx @@ -294,6 +301,11 @@ impl<'a> CircuitInputStateRef<'a> { self.tx_ctx.call_ctx() } + /// Reference to the current CallContext + pub fn caller_ctx(&self) -> Result<&CallContext, Error> { + self.tx_ctx.caller_ctx() + } + /// Mutable reference to the call CallContext pub fn call_ctx_mut(&mut self) -> Result<&mut CallContext, Error> { self.tx_ctx.call_ctx_mut() diff --git a/bus-mapping/src/circuit_input_builder/transaction.rs b/bus-mapping/src/circuit_input_builder/transaction.rs index f1d212f296..91327e7554 100644 --- a/bus-mapping/src/circuit_input_builder/transaction.rs +++ b/bus-mapping/src/circuit_input_builder/transaction.rs @@ -96,14 +96,24 @@ impl TransactionContext { &self.calls } + /// Return the index of the caller (the second last call in the call stack). + pub fn caller_index(&self) -> Result { + self.caller_ctx().map(|call| call.index) + } + /// Return the index of the current call (the last call in the call stack). pub(crate) fn call_index(&self) -> Result { + self.call_ctx().map(|call| call.index) + } + + pub(crate) fn caller_ctx(&self) -> Result<&CallContext, Error> { self.calls - .last() + .len() + .checked_sub(2) + .map(|idx| &self.calls[idx]) .ok_or(Error::InvalidGethExecTrace( "Call stack is empty but call is used", )) - .map(|call| call.index) } pub(crate) fn call_ctx(&self) -> Result<&CallContext, Error> { diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs index cd52c27c83..4a7d301b88 100644 --- a/bus-mapping/src/evm/opcodes.rs +++ b/bus-mapping/src/evm/opcodes.rs @@ -32,6 +32,7 @@ mod mload; mod mstore; mod number; mod origin; +mod r#return; mod selfbalance; mod sload; mod sstore; @@ -52,6 +53,7 @@ use gasprice::GasPrice; use mload::Mload; use mstore::Mstore; use origin::Origin; +use r#return::Return; use selfbalance::Selfbalance; use sload::Sload; use sstore::Sstore; @@ -118,7 +120,7 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { OpcodeId::SHL => StackOnlyOpcode::<2, 1>::gen_associated_ops, OpcodeId::SHR => StackOnlyOpcode::<2, 1>::gen_associated_ops, OpcodeId::SAR => StackOnlyOpcode::<2, 1>::gen_associated_ops, - // OpcodeId::SHA3 => {}, + OpcodeId::SHA3 => StackOnlyOpcode::<2, 1>::gen_associated_ops, // OpcodeId::ADDRESS => {}, // OpcodeId::BALANCE => {}, OpcodeId::ORIGIN => Origin::gen_associated_ops, @@ -191,18 +193,18 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps { // OpcodeId::LOG0 => {}, // OpcodeId::LOG1 => {}, // OpcodeId::LOG2 => {}, - // OpcodeId::LOG3 => {}, + OpcodeId::LOG3 => StackOnlyOpcode::<5, 0>::gen_associated_ops, // OpcodeId::LOG4 => {}, // OpcodeId::CREATE => {}, OpcodeId::CALL => Call::gen_associated_ops, // OpcodeId::CALLCODE => {}, - // TODO: Handle RETURN by its own gen_associated_ops. - OpcodeId::RETURN => Stop::gen_associated_ops, + // TODO: Fully implement RETURN. + OpcodeId::RETURN => Return::gen_associated_ops, // OpcodeId::DELEGATECALL => {}, // OpcodeId::CREATE2 => {}, // OpcodeId::STATICCALL => {}, // TODO: Handle REVERT by its own gen_associated_ops. - OpcodeId::REVERT => Stop::gen_associated_ops, + OpcodeId::REVERT => Return::gen_associated_ops, // OpcodeId::SELFDESTRUCT => {}, OpcodeId::CALLCODE | OpcodeId::DELEGATECALL | OpcodeId::STATICCALL => { warn!("Using dummy gen_call_ops for opcode {:?}", opcode_id); diff --git a/bus-mapping/src/evm/opcodes/call.rs b/bus-mapping/src/evm/opcodes/call.rs index d570aaab61..a85f9ca814 100644 --- a/bus-mapping/src/evm/opcodes/call.rs +++ b/bus-mapping/src/evm/opcodes/call.rs @@ -246,7 +246,7 @@ impl Opcode for Call { ), (CallContextField::MemorySize, next_memory_word_size.into()), ( - CallContextField::StateWriteCounter, + CallContextField::ReversibleWriteCounter, (exec_step.reversible_write_counter + 1).into(), ), ] { diff --git a/bus-mapping/src/evm/opcodes/return.rs b/bus-mapping/src/evm/opcodes/return.rs new file mode 100644 index 0000000000..7b6deea9e1 --- /dev/null +++ b/bus-mapping/src/evm/opcodes/return.rs @@ -0,0 +1,20 @@ +use super::Opcode; +use crate::circuit_input_builder::{CircuitInputStateRef, ExecStep}; +use crate::Error; +use eth_types::GethExecStep; + +/// Placeholder structure used to implement [`Opcode`] trait over it +/// corresponding to the [`OpcodeId::RETURN`](crate::evm::OpcodeId::RETURN). +#[derive(Debug, Copy, Clone)] +pub(crate) struct Return; + +impl Opcode for Return { + fn gen_associated_ops( + state: &mut CircuitInputStateRef, + geth_steps: &[GethExecStep], + ) -> Result, Error> { + let exec_step = state.new_step(&geth_steps[0])?; + state.handle_return()?; + Ok(vec![exec_step]) + } +} diff --git a/bus-mapping/src/evm/opcodes/stop.rs b/bus-mapping/src/evm/opcodes/stop.rs index e3f0015ecc..394890c061 100644 --- a/bus-mapping/src/evm/opcodes/stop.rs +++ b/bus-mapping/src/evm/opcodes/stop.rs @@ -1,7 +1,10 @@ use super::Opcode; -use crate::circuit_input_builder::{CircuitInputStateRef, ExecStep}; -use crate::Error; -use eth_types::GethExecStep; +use crate::{ + circuit_input_builder::{CircuitInputStateRef, ExecStep}, + operation::{CallContextField, CallContextOp, RW}, + Error, +}; +use eth_types::{GethExecStep, ToWord}; /// Placeholder structure used to implement [`Opcode`] trait over it /// corresponding to the [`OpcodeId::STOP`](crate::evm::OpcodeId::STOP) @@ -17,8 +20,97 @@ impl Opcode for Stop { state: &mut CircuitInputStateRef, geth_steps: &[GethExecStep], ) -> Result, Error> { - let exec_step = state.new_step(&geth_steps[0])?; + let geth_step = &geth_steps[0]; + let mut exec_step = state.new_step(geth_step)?; + + let call = state.call()?.clone(); + + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: call.call_id, + field: CallContextField::IsSuccess, + value: 1.into(), + }, + ); + + if call.is_root { + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: call.call_id, + field: CallContextField::IsPersistent, + value: 1.into(), + }, + ); + } else { + let caller = state.caller()?.clone(); + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: call.call_id, + field: CallContextField::CallerId, + value: caller.call_id.into(), + }, + ); + + let geth_step_next = &geth_steps[1]; + let caller_gas_left = geth_step_next.gas.0 - geth_step.gas.0; + for (field, value) in [ + (CallContextField::IsRoot, (caller.is_root as u64).into()), + ( + CallContextField::IsCreate, + (caller.is_create() as u64).into(), + ), + (CallContextField::CodeSource, caller.code_hash.to_word()), + (CallContextField::ProgramCounter, geth_step_next.pc.0.into()), + ( + CallContextField::StackPointer, + geth_step_next.stack.stack_pointer().0.into(), + ), + (CallContextField::GasLeft, caller_gas_left.into()), + ( + CallContextField::MemorySize, + geth_step_next.memory.word_size().into(), + ), + ( + CallContextField::ReversibleWriteCounter, + state.caller_ctx()?.reversible_write_counter.into(), + ), + ] { + state.push_op( + &mut exec_step, + RW::READ, + CallContextOp { + call_id: caller.call_id, + field, + value, + }, + ); + } + + for (field, value) in [ + (CallContextField::LastCalleeId, call.call_id.into()), + (CallContextField::LastCalleeReturnDataOffset, 0.into()), + (CallContextField::LastCalleeReturnDataLength, 0.into()), + ] { + state.push_op( + &mut exec_step, + RW::WRITE, + CallContextOp { + call_id: caller.call_id, + field, + value, + }, + ); + } + } + state.handle_return()?; + Ok(vec![exec_step]) } } diff --git a/bus-mapping/src/operation.rs b/bus-mapping/src/operation.rs index 5f3e167e8b..b9c72093fa 100644 --- a/bus-mapping/src/operation.rs +++ b/bus-mapping/src/operation.rs @@ -688,8 +688,8 @@ pub enum CallContextField { GasLeft, /// MemorySize MemorySize, - /// StateWriteCounter - StateWriteCounter, + /// ReversibleWriteCounter + ReversibleWriteCounter, } /// Represents an CallContext read/write operation. diff --git a/circuit-benchmarks/build.rs b/circuit-benchmarks/build.rs index a33f619657..0515aaaf68 100644 --- a/circuit-benchmarks/build.rs +++ b/circuit-benchmarks/build.rs @@ -7,7 +7,7 @@ use std::io::Write; fn main() { let degree: usize = var("DEGREE") - .unwrap_or_else(|_| "11".to_string()) + .unwrap_or_else(|_| "17".to_string()) .parse() .expect("Cannot parse DEGREE env var as usize"); diff --git a/integration-tests/src/bin/gen_blockchain_data.rs b/integration-tests/src/bin/gen_blockchain_data.rs index 591b09491b..976e9de9c7 100644 --- a/integration-tests/src/bin/gen_blockchain_data.rs +++ b/integration-tests/src/bin/gen_blockchain_data.rs @@ -7,6 +7,7 @@ use ethers::{ }, core::utils::WEI_IN_ETHER, middleware::SignerMiddleware, + prelude::NonceManagerMiddleware, providers::{Middleware, PendingTransaction}, signers::Signer, solc::Solc, @@ -54,7 +55,7 @@ where .method::<_, bool>("transfer", (to, amount)) .expect("cannot construct ERC20 transfer call"); // Set gas to avoid `eth_estimateGas` call - let call = call.legacy(); + //let call = call.legacy(); let call = call.gas(100_000); call.tx } @@ -172,7 +173,10 @@ async fn main() { // let mut deployments = HashMap::new(); - let prov_wallet0 = Arc::new(SignerMiddleware::new(get_provider(), wallet0)); + let prov_wallet0 = Arc::new(NonceManagerMiddleware::new( + SignerMiddleware::new(get_provider(), wallet0.clone()), + wallet0.address(), + )); // Greeter let contract = deploy( @@ -194,7 +198,7 @@ async fn main() { contracts .get("OpenZeppelinERC20TestToken") .expect("contract not found"), - prov_wallet0.address(), + wallet0.address(), ) .await; let block_num = prov.get_block_number().await.expect("cannot get block_num"); @@ -325,7 +329,7 @@ async fn main() { for (i, (from_i, to_i)) in [(0, 1), (2, 3), (1, 0), (3, 2)].iter().enumerate() { let amount = U256::from(0x800000000000000 / (i + 1)); let prov_wallet = &wallets[*from_i]; - let tx = erc20_transfer( + let mut tx = erc20_transfer( prov_wallet.clone(), contract_address, contract_abi, @@ -333,6 +337,9 @@ async fn main() { amount, ); + prov_wallet.fill_transaction(&mut tx, None).await.unwrap(); + tx.set_gas(100_000u64); + let pending_tx = prov_wallet .send_transaction(tx, None) .await @@ -345,7 +352,13 @@ async fn main() { let pending_tx = PendingTransaction::new(*tx_hash, wallets[i].inner()); let receipt = pending_tx.confirmations(0usize).await.unwrap().unwrap(); let expected_status = if i % 2 == 0 { 1u64 } else { 0u64 }; - assert_eq!(receipt.status, Some(U64::from(expected_status))); + assert_eq!( + receipt.status, + Some(U64::from(expected_status)), + "failed tx hash: {:?}, receipt: {:#?}", + tx_hash, + receipt + ); } let block_num = prov.get_block_number().await.expect("cannot get block_num"); blocks.insert( diff --git a/integration-tests/tests/circuits.rs b/integration-tests/tests/circuits.rs index 9c7f17feef..bfcfa71580 100644 --- a/integration-tests/tests/circuits.rs +++ b/integration-tests/tests/circuits.rs @@ -17,6 +17,7 @@ lazy_static! { } async fn test_evm_circuit_block(block_num: u64) { + log::info!("test evm circuit, block number: {}", block_num); let cli = get_client(); let cli = BuilderClient::new(cli).await.unwrap(); let builder = cli.gen_inputs(block_num).await.unwrap(); @@ -46,7 +47,7 @@ async fn test_state_circuit_block(block_num: u64) { let rw_map = RwMap::from(&OperationContainer { memory: memory_ops, stack: stack_ops, - storage: storage_ops, + //storage: storage_ops, ..Default::default() }); diff --git a/prover/src/bin/prover_cmd.rs b/prover/src/bin/prover_cmd.rs index bbc75508ba..2c99bf8151 100644 --- a/prover/src/bin/prover_cmd.rs +++ b/prover/src/bin/prover_cmd.rs @@ -1,9 +1,7 @@ use env_logger::Env; -use halo2_proofs::pairing::bn256::G1Affine; +use halo2_proofs::pairing::bn256::{Bn256, G1Affine}; use halo2_proofs::poly::commitment::Params; -use std::env::var; -use std::fs::File; -use std::io::BufReader; +use std::{env::var, fs::File, io::BufReader}; use prover::compute_proof::compute_proof; @@ -24,15 +22,29 @@ async fn main() { .expect("RPC_URL env var") .parse() .expect("Cannot parse RPC_URL env var"); - let params_path: String = var("PARAMS_PATH") - .expect("PARAMS_PATH env var") - .parse() - .expect("Cannot parse PARAMS_PATH env var"); - // load polynomial commitment parameters - let params_fs = File::open(¶ms_path).expect("couldn't open params"); - let params: Params = - Params::read::<_>(&mut BufReader::new(params_fs)).expect("Failed to read params"); + let params_path: String = match var("PARAMS_PATH") { + Ok(path) => path, + Err(e) => { + log::warn!( + "PARAMS_PATH env var is invalid: {:?}. Params will be setup locally.", + e + ); + "".to_string() + } + }; + + let params: Params = if params_path.is_empty() { + let degree = 18; + log::debug!("setup with degree {}", degree); + let params: Params = Params::::unsafe_setup::(degree); + log::debug!("setup done"); + params + } else { + // load polynomial commitment parameters from file + let params_fs = File::open(¶ms_path).expect("couldn't open params"); + Params::read::<_>(&mut BufReader::new(params_fs)).expect("Failed to read params") + }; let result = compute_proof(¶ms, &block_num, &rpc_url) .await diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 66d8cbfa93..b409328d27 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -141,6 +141,9 @@ impl EvmCircuit { #[cfg(any(feature = "test", test))] pub mod test { + + use std::convert::TryInto; + use crate::{ evm_circuit::{ table::FixedTableTag, @@ -148,7 +151,7 @@ pub mod test { EvmCircuit, }, rw_table::RwTable, - util::Expr, + util::DEFAULT_RAND, }; use eth_types::{Field, Word}; use halo2_proofs::{ @@ -156,8 +159,7 @@ pub mod test { circuit::{Layouter, SimpleFloorPlanner}, dev::{MockProver, VerifyFailure}, pairing::bn256::Fr as Fp, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, - poly::Rotation, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Expression}, }; use itertools::Itertools; use rand::{ @@ -381,20 +383,11 @@ pub mod test { let bytecode_table = [(); 5].map(|_| meta.advice_column()); let block_table = [(); 3].map(|_| meta.advice_column()); - let power_of_randomness = { - let columns = [(); 31].map(|_| meta.instance_column()); - let mut power_of_randomness = None; - - meta.create_gate("", |meta| { - power_of_randomness = - Some(columns.map(|column| meta.query_instance(column, Rotation::cur()))); - - [0.expr()] - }); - - power_of_randomness.unwrap() - }; - + let power_of_randomness: [Expression; 31] = (1..32) + .map(|exp| Expression::Constant(F::from_u128(DEFAULT_RAND).pow(&[exp, 0, 0, 0]))) + .collect::>() + .try_into() + .unwrap(); Self::Config { tx_table, rw_table, @@ -424,9 +417,13 @@ pub mod test { config.load_rws(&mut layouter, &self.block.rws, self.block.randomness)?; config.load_bytecodes(&mut layouter, &self.block.bytecodes, self.block.randomness)?; config.load_block(&mut layouter, &self.block.context, self.block.randomness)?; - config - .evm_circuit - .assign_block_exact(&mut layouter, &self.block) + if self.block.step_num_with_pad != 0 { + config.evm_circuit.assign_block(&mut layouter, &self.block) + } else { + config + .evm_circuit + .assign_block_exact(&mut layouter, &self.block) + } } } @@ -468,12 +465,11 @@ pub mod test { let k = k.max(log2_ceil(64 + num_rows_required_for_steps)); log::debug!("evm circuit uses k = {}", k); - let power_of_randomness = (1..32) - .map(|exp| vec![block.randomness.pow(&[exp, 0, 0, 0]); (1 << k) - 64]) - .collect(); let (active_gate_rows, active_lookup_rows) = TestCircuit::get_active_rows(&block); + let block = block; + //block.step_num_with_pad = ((1 << k) - 64) / STEP_HEIGHT; let circuit = TestCircuit::::new(block, fixed_table_tags); - let prover = MockProver::::run(k, &circuit, power_of_randomness).unwrap(); + let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); prover.verify_at_rows(active_gate_rows.into_iter(), active_lookup_rows.into_iter()) } @@ -493,6 +489,8 @@ pub mod test { FixedTableTag::Range1024, FixedTableTag::SignByte, FixedTableTag::ResponsibleOpcode, + FixedTableTag::Bitslevel, + FixedTableTag::Pow64, ], ) } diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index c63266a019..9fcf44de99 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -37,6 +37,7 @@ mod codecopy; mod comparator; mod copy_code_to_memory; mod copy_to_log; +mod dummy; mod dup; mod end_block; mod end_tx; @@ -57,7 +58,10 @@ mod origin; mod pc; mod pop; mod push; +mod r#return; mod selfbalance; +mod shl; +mod shr; mod signed_comparator; mod signextend; mod sload; @@ -81,6 +85,7 @@ use codecopy::CodeCopyGadget; use comparator::ComparatorGadget; use copy_code_to_memory::CopyCodeToMemoryGadget; use copy_to_log::CopyToLogGadget; +use dummy::DummyGadget; use dup::DupGadget; use end_block::EndBlockGadget; use end_tx::EndTxGadget; @@ -92,7 +97,6 @@ use is_zero::IsZeroGadget; use jump::JumpGadget; use jumpdest::JumpdestGadget; use jumpi::JumpiGadget; -use logs::LogGadget; use memory::MemoryGadget; use memory_copy::CopyToMemoryGadget; use msize::MsizeGadget; @@ -101,7 +105,10 @@ use origin::OriginGadget; use pc::PcGadget; use pop::PopGadget; use push::PushGadget; +use r#return::ReturnGadget; use selfbalance::SelfbalanceGadget; +use shl::ShlGadget; +use shr::ShrGadget; use signed_comparator::SignedComparatorGadget; use signextend::SignextendGadget; use sload::SloadGadget; @@ -167,7 +174,7 @@ pub(crate) struct ExecutionConfig { jump_gadget: JumpGadget, jumpdest_gadget: JumpdestGadget, jumpi_gadget: JumpiGadget, - log_gadget: LogGadget, + log_gadget: DummyGadget, memory_gadget: MemoryGadget, msize_gadget: MsizeGadget, mul_div_mod_gadget: MulDivModGadget, @@ -175,7 +182,11 @@ pub(crate) struct ExecutionConfig { pc_gadget: PcGadget, pop_gadget: PopGadget, push_gadget: PushGadget, + return_gadget: ReturnGadget, selfbalance_gadget: SelfbalanceGadget, + sha3_gadget: DummyGadget, + shl_gadget: ShlGadget, + shr_gadget: ShrGadget, signed_comparator_gadget: SignedComparatorGadget, signextend_gadget: SignextendGadget, sload_gadget: SloadGadget, @@ -364,7 +375,11 @@ impl ExecutionConfig { pc_gadget: configure_gadget!(), pop_gadget: configure_gadget!(), push_gadget: configure_gadget!(), + return_gadget: configure_gadget!(), selfbalance_gadget: configure_gadget!(), + sha3_gadget: configure_gadget!(), + shl_gadget: configure_gadget!(), + shr_gadget: configure_gadget!(), signed_comparator_gadget: configure_gadget!(), signextend_gadget: configure_gadget!(), sload_gadget: configure_gadget!(), @@ -798,6 +813,7 @@ impl ExecutionConfig { ExecutionState::PC => assign_exec_step!(self.pc_gadget), 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::SCMP => assign_exec_step!(self.signed_comparator_gadget), ExecutionState::BLOCKCTXU64 => assign_exec_step!(self.block_ctx_u64_gadget), ExecutionState::BLOCKCTXU160 => assign_exec_step!(self.block_ctx_u160_gadget), @@ -805,6 +821,9 @@ impl ExecutionConfig { ExecutionState::SELFBALANCE => assign_exec_step!(self.selfbalance_gadget), ExecutionState::SIGNEXTEND => assign_exec_step!(self.signextend_gadget), ExecutionState::SLOAD => assign_exec_step!(self.sload_gadget), + ExecutionState::SHL => assign_exec_step!(self.shl_gadget), + ExecutionState::SHR => assign_exec_step!(self.shr_gadget), + ExecutionState::SHA3 => assign_exec_step!(self.sha3_gadget), ExecutionState::SSTORE => assign_exec_step!(self.sstore_gadget), ExecutionState::STOP => assign_exec_step!(self.stop_gadget), ExecutionState::SWAP => assign_exec_step!(self.swap_gadget), diff --git a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs index 432cf32623..a9cec0c5a8 100644 --- a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs @@ -11,13 +11,13 @@ use crate::{ Transition::{Delta, To}, }, math_gadget::{MulWordByU64Gadget, RangeCheckGadget}, - select, CachedRegion, Cell, RandomLinearCombination, Word, + CachedRegion, Cell, RandomLinearCombination, Word, }, witness::{Block, Call, ExecStep, Transaction}, }, util::Expr, }; -use eth_types::{evm_types::GasCost, Field, ToLittleEndian, ToScalar}; +use eth_types::{Field, ToLittleEndian, ToScalar}; use halo2_proofs::plonk::Error; #[derive(Clone, Debug)] @@ -34,6 +34,7 @@ pub(crate) struct BeginTxGadget { tx_call_data_length: Cell, tx_call_data_gas_cost: Cell, reversion_info: ReversionInfo, + intrinsic_gas_cost: Cell, sufficient_gas_left: RangeCheckGadget, transfer_with_gas_fee: TransferWithGasFeeGadget, code_hash: Cell, @@ -90,14 +91,16 @@ impl ExecutionGadget for BeginTxGadget { // TODO: Take gas cost of access list (EIP 2930) into consideration. // Use intrinsic gas + /* let intrinsic_gas_cost = select::expr( tx_is_create.expr(), GasCost::CREATION_TX.expr(), GasCost::TX.expr(), ) + tx_call_data_gas_cost.expr(); - + */ // Check gas_left is sufficient - let gas_left = tx_gas.expr() - intrinsic_gas_cost; + let intrinsic_gas_cost = cb.query_cell(); + let gas_left = tx_gas.expr() - intrinsic_gas_cost.expr(); let sufficient_gas_left = RangeCheckGadget::construct(cb, gas_left.clone()); // Prepare access list of caller and callee @@ -207,6 +210,7 @@ impl ExecutionGadget for BeginTxGadget { sufficient_gas_left, transfer_with_gas_fee, code_hash, + intrinsic_gas_cost, } } @@ -252,6 +256,8 @@ impl ExecutionGadget for BeginTxGadget { call.rw_counter_end_of_reversion, call.is_persistent, )?; + self.intrinsic_gas_cost + .assign(region, offset, Some(F::from(step.gas_cost)))?; self.sufficient_gas_left .assign(region, offset, F::from(tx.gas - step.gas_cost))?; self.transfer_with_gas_fee.assign( diff --git a/zkevm-circuits/src/evm_circuit/execution/call.rs b/zkevm-circuits/src/evm_circuit/execution/call.rs index 10295224bc..6105a78e89 100644 --- a/zkevm-circuits/src/evm_circuit/execution/call.rs +++ b/zkevm-circuits/src/evm_circuit/execution/call.rs @@ -255,7 +255,7 @@ impl ExecutionGadget for CallGadget { memory_expansion.next_memory_word_size(), ), ( - CallContextFieldTag::StateWriteCounter, + CallContextFieldTag::ReversibleWriteCounter, cb.curr.state.reversible_write_counter.expr() + 1.expr(), ), ] { diff --git a/zkevm-circuits/src/evm_circuit/execution/codecopy.rs b/zkevm-circuits/src/evm_circuit/execution/codecopy.rs index 9b99e85bb1..2f191efd63 100644 --- a/zkevm-circuits/src/evm_circuit/execution/codecopy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/codecopy.rs @@ -207,13 +207,24 @@ impl ExecutionGadget for CodeCopyGadget { #[cfg(test)] mod tests { + use crate::test_util::run_test_circuits; use eth_types::{bytecode, Word}; use mock::TestContext; - use crate::test_util::run_test_circuits; - fn test_ok(memory_offset: usize, code_offset: usize, size: usize) { let code = bytecode! { + // generate random bytecode longer than `src_addr_end` + PUSH32(Word::from(0x123)) + POP + PUSH32(Word::from(0x213)) + POP + PUSH32(Word::from(0x321)) + POP + PUSH32(Word::from(0x12349AB)) + POP + PUSH32(Word::from(0x1928835)) + POP + PUSH32(Word::from(size)) PUSH32(Word::from(code_offset)) PUSH32(Word::from(memory_offset)) @@ -230,9 +241,31 @@ mod tests { } #[test] - fn codecopy_gadget() { - test_ok(0x00, 0x00, 0x20); - test_ok(0x20, 0x30, 0x30); - test_ok(0x10, 0x20, 0x42); + fn copy_code_to_memory_single_step() { + test_ok( + 0x00, // src_addr + 0x00, // dst_addr + 54, // length + ); + } + + #[test] + fn copy_code_to_memory_multi_step() { + test_ok( + 0x00, // src_addr + 0x40, // dst_addr + 123, // length + ); + } + + #[test] + fn copy_code_to_memory_oob() { + // since the bytecode we construct above is (34 * 5) = 170 bytes long, copying + // 200 bytes means we go out-of-bounds. + test_ok( + 0x10, // src_addr + 0x20, // dst_addr + 200, // length + ); } } diff --git a/zkevm-circuits/src/evm_circuit/execution/copy_code_to_memory.rs b/zkevm-circuits/src/evm_circuit/execution/copy_code_to_memory.rs index 0b31c9cb58..de07d13ec3 100644 --- a/zkevm-circuits/src/evm_circuit/execution/copy_code_to_memory.rs +++ b/zkevm-circuits/src/evm_circuit/execution/copy_code_to_memory.rs @@ -247,227 +247,3 @@ impl ExecutionGadget for CopyCodeToMemoryGadget { Ok(()) } } - -#[cfg(test)] -pub(crate) mod test { - use super::MAX_COPY_BYTES; - use std::collections::HashMap; - - use bus_mapping::{ - circuit_input_builder::{CopyDetails, StepAuxiliaryData}, - evm::OpcodeId, - }; - use eth_types::{bytecode, Word}; - use halo2_proofs::arithmetic::BaseExt; - use halo2_proofs::pairing::bn256::Fr; - - use crate::evm_circuit::{ - step::ExecutionState, - table::RwTableTag, - test::run_test_circuit_incomplete_fixed_table, - witness::{Block, Bytecode, Call, CodeSource, ExecStep, Rw, RwMap, Transaction}, - }; - - #[allow(clippy::too_many_arguments)] - pub(crate) fn make_copy_code_step( - call_id: usize, - src_addr: u64, - dst_addr: u64, - src_addr_end: u64, - bytes_left: usize, - program_counter: u64, - stack_pointer: usize, - memory_size: u64, - rw_counter: usize, - rws: &mut RwMap, - bytes_map: &HashMap, - code: &Bytecode, - ) -> (ExecStep, usize) { - let mut rw_offset = 0usize; - let memory_rws: &mut Vec<_> = rws.0.entry(RwTableTag::Memory).or_insert_with(Vec::new); - let rw_idx_start = memory_rws.len(); - - for idx in 0..std::cmp::min(bytes_left, MAX_COPY_BYTES) { - let addr = src_addr + idx as u64; - let byte = if addr < src_addr_end { - assert!(bytes_map.contains_key(&addr)); - bytes_map[&addr] - } else { - 0 - }; - memory_rws.push(Rw::Memory { - rw_counter: rw_counter + rw_offset, - is_write: true, - call_id, - memory_address: dst_addr + idx as u64, - byte, - }); - rw_offset += 1; - } - - let rw_idx_end = rws.0[&RwTableTag::Memory].len(); - let aux_data = StepAuxiliaryData::new( - src_addr, - dst_addr, - bytes_left as u64, - src_addr_end, - CopyDetails::Code(code.hash), - ); - let step = ExecStep { - execution_state: ExecutionState::CopyCodeToMemory, - rw_indices: (rw_idx_start..rw_idx_end) - .map(|idx| (RwTableTag::Memory, idx)) - .collect(), - rw_counter, - program_counter, - stack_pointer, - memory_size, - gas_cost: 0, - aux_data: Some(aux_data), - ..Default::default() - }; - - (step, rw_offset) - } - - #[allow(clippy::too_many_arguments)] - pub(crate) fn make_copy_code_steps( - call_id: usize, - code: &Bytecode, - src_addr: u64, - dst_addr: u64, - length: usize, - program_counter: u64, - stack_pointer: usize, - memory_size: u64, - rw_counter: &mut usize, - rws: &mut RwMap, - steps: &mut Vec, - ) { - let bytes_map = (0..(code.bytes.len() as u64)) - .zip(code.bytes.iter().copied()) - .collect(); - - let mut copied = 0; - while copied < length { - let (step, rw_offset) = make_copy_code_step( - call_id, - src_addr + copied as u64, - dst_addr + copied as u64, - code.bytes.len() as u64, - length - copied, - program_counter, - stack_pointer, - memory_size, - *rw_counter, - rws, - &bytes_map, - code, - ); - steps.push(step); - *rw_counter += rw_offset; - copied += MAX_COPY_BYTES; - } - } - - fn test_ok(src_addr: u64, dst_addr: u64, length: usize) { - let randomness = Fr::rand(); - let call_id = 1; - let mut rws = RwMap::default(); - let mut rw_counter = 1; - let mut steps = Vec::new(); - let memory_size = (dst_addr + length as u64 + 31) / 32 * 32; - - // generate random bytecode longer than `src_addr_end` - let code = bytecode! { - PUSH32(Word::from(0x123)) - POP - PUSH32(Word::from(0x213)) - POP - PUSH32(Word::from(0x321)) - POP - PUSH32(Word::from(0x12349AB)) - POP - PUSH32(Word::from(0x1928835)) - POP - }; - - let code = Bytecode::new(code.to_vec()); - let dummy_code = Bytecode::new(vec![OpcodeId::STOP.as_u8()]); - - let program_counter = 0; - let stack_pointer = 1024; - make_copy_code_steps( - call_id, - &code, - src_addr, - dst_addr, - length, - program_counter, - stack_pointer, - memory_size, - &mut rw_counter, - &mut rws, - &mut steps, - ); - - steps.push(ExecStep { - execution_state: ExecutionState::STOP, - rw_counter, - program_counter, - stack_pointer, - memory_size, - opcode: Some(OpcodeId::STOP), - ..Default::default() - }); - - let block = Block { - randomness, - txs: vec![Transaction { - id: 1, - calls: vec![Call { - id: call_id, - is_root: true, - is_create: false, - code_source: CodeSource::Account(dummy_code.hash), - ..Default::default() - }], - steps, - ..Default::default() - }], - rws, - bytecodes: vec![dummy_code, code], - ..Default::default() - }; - assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); - } - - #[test] - fn copy_code_to_memory_single_step() { - test_ok( - 0x00, // src_addr - 0x00, // dst_addr - 54, // length - ); - } - - #[test] - fn copy_code_to_memory_multi_step() { - test_ok( - 0x00, // src_addr - 0x40, // dst_addr - 123, // length - ); - } - - #[test] - fn copy_code_to_memory_oob() { - // since the bytecode we construct above is (34 * 5) = 170 bytes long, copying - // 200 bytes means we go out-of-bounds. - test_ok( - 0x10, // src_addr - 0x20, // dst_addr - 200, // length - ); - } -} diff --git a/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs b/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs index b7a244262c..409f33c71f 100644 --- a/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs +++ b/zkevm-circuits/src/evm_circuit/execution/copy_to_log.rs @@ -196,7 +196,7 @@ impl ExecutionGadget for CopyToLogGadget { } } -#[cfg(test)] +#[cfg(feature = "disable")] pub mod test { use crate::evm_circuit::{ step::ExecutionState, diff --git a/zkevm-circuits/src/evm_circuit/execution/dummy.rs b/zkevm-circuits/src/evm_circuit/execution/dummy.rs new file mode 100644 index 0000000000..06043e009c --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/dummy.rs @@ -0,0 +1,63 @@ +use std::marker::PhantomData; + +use crate::evm_circuit::{ + execution::ExecutionGadget, + step::ExecutionState, + util::{constraint_builder::ConstraintBuilder, CachedRegion, Word}, + witness::{Block, Call, ExecStep, Transaction}, +}; +use crate::util::Expr; +use eth_types::Field; +use eth_types::ToLittleEndian; +use halo2_proofs::plonk::Error; + +#[derive(Clone, Debug)] +pub(crate) struct DummyGadget { + pops: [Word; N_POP], + pushes: [Word; N_PUSH], + _marker: PhantomData, +} + +impl ExecutionGadget + for DummyGadget +{ + const NAME: &'static str = "DUMMY"; + + const EXECUTION_STATE: ExecutionState = S; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let pops: [Word; N_POP] = [(); N_POP].map(|_| cb.query_word()); + let pushes: [Word; N_PUSH] = [(); N_PUSH].map(|_| cb.query_word()); + for pop in pops.iter() { + cb.stack_pop(pop.expr()); + } + for push in pushes.iter() { + cb.stack_push(push.expr()); + } + Self { + pops, + pushes, + _marker: PhantomData, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + _: &Transaction, + _: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + for i in 0..N_POP { + let value = block.rws[step.rw_indices[i]].stack_value(); + self.pops[i].assign(region, offset, Some(value.to_le_bytes()))?; + } + for i in 0..N_PUSH { + let value = block.rws[step.rw_indices[N_POP + i]].stack_value(); + self.pushes[i].assign(region, offset, Some(value.to_le_bytes()))?; + } + Ok(()) + } +} diff --git a/zkevm-circuits/src/evm_circuit/execution/logs.rs b/zkevm-circuits/src/evm_circuit/execution/logs.rs index 2bfb12be76..34671deca2 100644 --- a/zkevm-circuits/src/evm_circuit/execution/logs.rs +++ b/zkevm-circuits/src/evm_circuit/execution/logs.rs @@ -241,7 +241,7 @@ impl ExecutionGadget for LogGadget { } } -#[cfg(test)] +#[cfg(feature = "disable")] mod test { use crate::evm_circuit::{ execution::copy_to_log::test::make_log_copy_steps, diff --git a/zkevm-circuits/src/evm_circuit/execution/memory_copy.rs b/zkevm-circuits/src/evm_circuit/execution/memory_copy.rs index b07569e9c3..dccf5caed9 100644 --- a/zkevm-circuits/src/evm_circuit/execution/memory_copy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/memory_copy.rs @@ -233,7 +233,7 @@ impl ExecutionGadget for CopyToMemoryGadget { } } -#[cfg(test)] +#[cfg(feature = "disable")] pub mod test { use crate::evm_circuit::{ execution::memory_copy::MAX_COPY_BYTES, diff --git a/zkevm-circuits/src/evm_circuit/execution/return.rs b/zkevm-circuits/src/evm_circuit/execution/return.rs new file mode 100644 index 0000000000..c10f6135fb --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/return.rs @@ -0,0 +1,48 @@ +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + step::ExecutionState, + util::{constraint_builder::ConstraintBuilder, CachedRegion, Cell}, + witness::{Block, Call, ExecStep, Transaction}, + }, + util::Expr, +}; +use eth_types::Field; +use halo2_proofs::plonk::Error; + +#[derive(Clone, Debug)] +pub(crate) struct ReturnGadget { + opcode: Cell, +} + +impl ExecutionGadget for ReturnGadget { + const NAME: &'static str = "RETURN"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::RETURN; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let opcode = cb.query_cell(); + cb.opcode_lookup(opcode.expr(), 1.expr()); + + // Other constraints are ignored now for RETURN to serve as a mocking + // terminator + + Self { opcode } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + _: &Block, + _: &Transaction, + _: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + let opcode = step.opcode.unwrap(); + self.opcode + .assign(region, offset, Some(F::from(opcode.as_u64())))?; + + Ok(()) + } +} diff --git a/zkevm-circuits/src/evm_circuit/execution/shl.rs b/zkevm-circuits/src/evm_circuit/execution/shl.rs new file mode 100644 index 0000000000..bdb1062f2d --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/shl.rs @@ -0,0 +1,126 @@ +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + step::ExecutionState, + util::{ + common_gadget::SameContextGadget, + constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, + math_gadget::ShlWordsGadget, + CachedRegion, + }, + witness::{Block, Call, ExecStep, Transaction}, + }, + util::Expr, +}; + +use bus_mapping::evm::OpcodeId; +use eth_types::Field; +use halo2_proofs::plonk::Error; + +#[derive(Clone, Debug)] +pub(crate) struct ShlGadget { + same_context: SameContextGadget, + shl_words: ShlWordsGadget, +} + +impl ExecutionGadget for ShlGadget { + const NAME: &'static str = "SHL"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::SHL; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let opcode = cb.query_cell(); + + let a = cb.query_word(); + let shift = cb.query_word(); + + cb.stack_pop(shift.expr()); + cb.stack_pop(a.expr()); + let shl_words = ShlWordsGadget::construct(cb, a, shift); + cb.stack_push(shl_words.b().expr()); + + let step_state_transition = StepStateTransition { + rw_counter: Delta(3.expr()), + program_counter: Delta(1.expr()), + stack_pointer: Delta(1.expr()), + gas_left: Delta(-OpcodeId::SHL.constant_gas_cost().expr()), + ..Default::default() + }; + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); + + Self { + same_context, + shl_words, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + _: &Transaction, + _: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + self.same_context.assign_exec_step(region, offset, step)?; + let indices = [step.rw_indices[0], step.rw_indices[1], step.rw_indices[2]]; + let [shift, a, b] = indices.map(|idx| block.rws[idx].stack_value()); + self.shl_words.assign(region, offset, a, shift, b) + } +} + +#[cfg(test)] +mod test { + use crate::evm_circuit::test::rand_word; + use crate::test_util::run_test_circuits; + use eth_types::evm_types::OpcodeId; + use eth_types::{bytecode, Word}; + use mock::TestContext; + use rand::Rng; + + fn test_ok(opcode: OpcodeId, a: Word, shift: Word) { + let bytecode = bytecode! { + PUSH32(a) + PUSH32(shift) + #[start] + .write_op(opcode) + STOP + }; + assert_eq!( + run_test_circuits( + TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode).unwrap(), + None + ), + Ok(()) + ); + } + + #[test] + fn shl_gadget_simple() { + test_ok(OpcodeId::SHL, 0x02FF.into(), 0x1.into()); + } + + #[test] + fn shl_gadget_rand_normal_shift() { + let a = rand_word(); + let mut rng = rand::thread_rng(); + let shift = rng.gen_range(0..=255); + test_ok(OpcodeId::SHL, a, shift.into()); + } + + #[test] + fn shl_gadget_rand_overflow_shift() { + let a = rand_word(); + let shift = Word::from_big_endian(&[255u8; 32]); + test_ok(OpcodeId::SHL, a, shift); + } + + //this testcase manage to check the split is correct. + #[test] + fn shl_gadget_constant_shift() { + let a = rand_word(); + test_ok(OpcodeId::SHL, a, 8.into()); + test_ok(OpcodeId::SHL, a, 64.into()); + } +} diff --git a/zkevm-circuits/src/evm_circuit/execution/shr.rs b/zkevm-circuits/src/evm_circuit/execution/shr.rs new file mode 100644 index 0000000000..24f0c08936 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/shr.rs @@ -0,0 +1,125 @@ +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + step::ExecutionState, + util::{ + common_gadget::SameContextGadget, + constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, + math_gadget::ShrWordsGadget, + CachedRegion, + }, + witness::{Block, Call, ExecStep, Transaction}, + }, + util::Expr, +}; +use bus_mapping::evm::OpcodeId; +use eth_types::Field; +use halo2_proofs::plonk::Error; + +#[derive(Clone, Debug)] +pub(crate) struct ShrGadget { + same_context: SameContextGadget, + shr_words: ShrWordsGadget, +} + +impl ExecutionGadget for ShrGadget { + const NAME: &'static str = "SHR"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::SHR; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let opcode = cb.query_cell(); + + let a = cb.query_word(); + let shift = cb.query_word(); + + cb.stack_pop(shift.expr()); + cb.stack_pop(a.expr()); + let shr_words = ShrWordsGadget::construct(cb, a, shift); + cb.stack_push(shr_words.b().expr()); + + let step_state_transition = StepStateTransition { + rw_counter: Delta(3.expr()), + program_counter: Delta(1.expr()), + stack_pointer: Delta(1.expr()), + gas_left: Delta(-OpcodeId::SHR.constant_gas_cost().expr()), + ..Default::default() + }; + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); + + Self { + same_context, + shr_words, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + _: &Transaction, + _: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + self.same_context.assign_exec_step(region, offset, step)?; + let indices = [step.rw_indices[0], step.rw_indices[1], step.rw_indices[2]]; + let [shift, a, b] = indices.map(|idx| block.rws[idx].stack_value()); + self.shr_words.assign(region, offset, a, shift, b) + } +} + +#[cfg(test)] +mod test { + use crate::evm_circuit::test::rand_word; + use crate::test_util::run_test_circuits; + use eth_types::evm_types::OpcodeId; + use eth_types::{bytecode, Word}; + use mock::TestContext; + use rand::Rng; + + fn test_ok(opcode: OpcodeId, a: Word, shift: Word) { + let bytecode = bytecode! { + PUSH32(a) + PUSH32(shift) + #[start] + .write_op(opcode) + STOP + }; + assert_eq!( + run_test_circuits( + TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode).unwrap(), + None + ), + Ok(()) + ); + } + + #[test] + fn shr_gadget_simple() { + test_ok(OpcodeId::SHR, 0x02FF.into(), 0x1.into()); + } + + #[test] + fn shr_gadget_rand_normal_shift() { + let a = rand_word(); + let mut rng = rand::thread_rng(); + let shift = rng.gen_range(0..=255); + test_ok(OpcodeId::SHR, a, shift.into()); + } + + #[test] + fn shr_gadget_rand_overflow_shift() { + let a = rand_word(); + let shift = Word::from_big_endian(&[255u8; 32]); + test_ok(OpcodeId::SHR, a, shift); + } + + //this testcase manage to check the split is correct. + #[test] + fn shr_gadget_constant_shift() { + let a = rand_word(); + test_ok(OpcodeId::SHR, a, 8.into()); + test_ok(OpcodeId::SHR, a, 64.into()); + } +} diff --git a/zkevm-circuits/src/evm_circuit/execution/stop.rs b/zkevm-circuits/src/evm_circuit/execution/stop.rs index 642d0ef5e8..7c6821aa3a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/stop.rs +++ b/zkevm-circuits/src/evm_circuit/execution/stop.rs @@ -2,17 +2,30 @@ use crate::{ evm_circuit::{ execution::ExecutionGadget, step::ExecutionState, - util::{constraint_builder::ConstraintBuilder, CachedRegion, Cell}, - witness::{Block, Call, ExecStep, Transaction}, + table::CallContextFieldTag, + util::{ + common_gadget::RestoreContextGadget, + constraint_builder::{ + ConstraintBuilder, StepStateTransition, + Transition::{Delta, Same}, + }, + math_gadget::IsZeroGadget, + CachedRegion, Cell, + }, + witness::{Block, Call, CodeSource, ExecStep, Transaction}, }, util::Expr, }; +use bus_mapping::evm::OpcodeId; use eth_types::Field; use halo2_proofs::plonk::Error; #[derive(Clone, Debug)] pub(crate) struct StopGadget { + code_size: Cell, + is_out_of_range: IsZeroGadget, opcode: Cell, + restore_context: RestoreContextGadget, } impl ExecutionGadget for StopGadget { @@ -21,28 +34,195 @@ impl ExecutionGadget for StopGadget { const EXECUTION_STATE: ExecutionState = ExecutionState::STOP; fn configure(cb: &mut ConstraintBuilder) -> Self { + let code_size = cb.bytecode_length(cb.curr.state.code_source.expr()); + let is_out_of_range = + IsZeroGadget::construct(cb, code_size.expr() - cb.curr.state.program_counter.expr()); let opcode = cb.query_cell(); - cb.opcode_lookup(opcode.expr(), 1.expr()); + cb.condition(1.expr() - is_out_of_range.expr(), |cb| { + cb.opcode_lookup(opcode.expr(), 1.expr()); + }); - // Other constraints are ignored now for STOP to serve as a mocking - // terminator + // We do the responsible opcode check explicitly here because we're not using + // the `SameContextGadget` for `STOP`. + cb.require_equal( + "Opcode should be STOP", + opcode.expr(), + OpcodeId::STOP.expr(), + ); - Self { opcode } + // Call ends with STOP must be successful + cb.call_context_lookup(false.expr(), None, CallContextFieldTag::IsSuccess, 1.expr()); + + let is_to_end_tx = cb.next.execution_state_selector([ExecutionState::EndTx]); + cb.require_equal( + "Go to EndTx only when is_root", + cb.curr.state.is_root.expr() + is_to_end_tx.clone(), + 2.expr() * cb.curr.state.is_root.expr() * is_to_end_tx, + ); + + // When it's a root call + cb.condition(cb.curr.state.is_root.expr(), |cb| { + // When a transaction ends with STOP, this call must be persistent + cb.call_context_lookup( + false.expr(), + None, + CallContextFieldTag::IsPersistent, + 1.expr(), + ); + + // Do step state transition + cb.require_step_state_transition(StepStateTransition { + call_id: Same, + rw_counter: Delta(2.expr()), + ..StepStateTransition::any() + }); + }); + + // When it's an internal call + let restore_context = cb.condition(1.expr() - cb.curr.state.is_root.expr(), |cb| { + RestoreContextGadget::construct(cb, Delta(13.expr()), 0.expr(), 0.expr()) + }); + + Self { + code_size, + is_out_of_range, + opcode, + restore_context, + } } fn assign_exec_step( &self, region: &mut CachedRegion<'_, '_, F>, offset: usize, - _: &Block, + block: &Block, _: &Transaction, - _: &Call, + call: &Call, step: &ExecStep, ) -> Result<(), Error> { + let code = block + .bytecodes + .iter() + .find(|b| { + let CodeSource::Account(code_source) = &call.code_source; + b.hash == *code_source + }) + .expect("could not find current environment's bytecode"); + self.code_size + .assign(region, offset, Some(F::from(code.bytes.len() as u64)))?; + + self.is_out_of_range.assign( + region, + offset, + F::from(code.bytes.len() as u64) - F::from(step.program_counter), + )?; + let opcode = step.opcode.unwrap(); self.opcode .assign(region, offset, Some(F::from(opcode.as_u64())))?; + self.restore_context + .assign(region, offset, block, call, step)?; + Ok(()) } } + +#[cfg(test)] +mod test { + use crate::evm_circuit::{ + test::run_test_circuit_incomplete_fixed_table, witness::block_convert, + }; + use eth_types::{address, bytecode, Bytecode, Word}; + use itertools::Itertools; + use mock::TestContext; + + fn test_ok(bytecode: Bytecode, is_root: bool) { + let block_data = if is_root { + bus_mapping::mock::BlockData::new_from_geth_data( + TestContext::<2, 1>::new( + None, + |accs| { + accs[0] + .address(address!("0x0000000000000000000000000000000000000000")) + .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) + .gas(Word::from(30000)); + }, + |block, _tx| block.number(0xcafeu64), + ) + .unwrap() + .into(), + ) + } else { + bus_mapping::mock::BlockData::new_from_geth_data( + TestContext::<3, 1>::new( + None, + |accs| { + accs[0] + .address(address!("0x0000000000000000000000000000000000000000")) + .balance(Word::from(1u64 << 30)); + accs[1] + .address(address!("0x0000000000000000000000000000000000000010")) + .balance(Word::from(1u64 << 20)) + .code(bytecode! { + PUSH1(0) + PUSH1(0) + PUSH1(0) + 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() + .into(), + ) + }; + let mut builder = block_data.new_circuit_input_builder(); + builder + .handle_block(&block_data.eth_block, &block_data.geth_traces) + .unwrap(); + let block = block_convert(&builder.block, &builder.code_db); + assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); + } + + #[test] + fn stop_gadget_simple() { + let bytecodes = vec![ + bytecode! { + PUSH1(0) + STOP + }, + bytecode! { + PUSH1(0) + }, + ]; + let is_roots = vec![true, false]; + for (bytecode, is_root) in bytecodes.into_iter().cartesian_product(is_roots) { + test_ok(bytecode, is_root); + } + } +} diff --git a/zkevm-circuits/src/evm_circuit/step.rs b/zkevm-circuits/src/evm_circuit/step.rs index 637e6667f4..0bcba7842e 100644 --- a/zkevm-circuits/src/evm_circuit/step.rs +++ b/zkevm-circuits/src/evm_circuit/step.rs @@ -242,14 +242,14 @@ impl ExecutionState { Self::iterator().count() } - pub(crate) fn halts(&self) -> bool { + pub(crate) fn halts_in_success(&self) -> bool { + matches!(self, Self::STOP | Self::RETURN | Self::SELFDESTRUCT) + } + + pub(crate) fn halts_in_exception(&self) -> bool { matches!( self, - Self::STOP - | Self::RETURN - | Self::REVERT - | Self::SELFDESTRUCT - | Self::ErrorInvalidOpcode + Self::ErrorInvalidOpcode | Self::ErrorStackOverflow | Self::ErrorStackUnderflow | Self::ErrorWriteProtection @@ -281,6 +281,10 @@ impl ExecutionState { ) } + pub(crate) fn halts(&self) -> bool { + self.halts_in_success() || self.halts_in_exception() || matches!(self, Self::REVERT) + } + pub(crate) fn responsible_opcodes(&self) -> Vec { match self { Self::STOP => vec![OpcodeId::STOP], diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index 423d4d6d25..5e40d13be8 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -41,6 +41,8 @@ pub enum FixedTableTag { BitwiseOr, BitwiseXor, ResponsibleOpcode, + Bitslevel, + Pow64, } impl FixedTableTag { @@ -101,6 +103,17 @@ impl FixedTableTag { }) })) } + Self::Bitslevel => Box::new((0..9).flat_map(move |level| { + (0..(1 << level)).map(move |idx| [tag, F::from(level), F::from(idx), F::zero()]) + })), + Self::Pow64 => Box::new((0..64).map(move |idx| { + [ + tag, + F::from(idx), + F::from_u128(1u128 << idx), + F::from_u128(1u128 << (64 - idx)), + ] + })), } } } @@ -218,7 +231,7 @@ pub enum CallContextFieldTag { StackPointer, GasLeft, MemorySize, - StateWriteCounter, + ReversibleWriteCounter, } impl_expr!(FixedTableTag); diff --git a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs index d8d33a7bda..a704fd3121 100644 --- a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs @@ -2,17 +2,20 @@ use super::CachedRegion; use crate::{ evm_circuit::{ param::N_BYTES_GAS, - table::{AccountFieldTag, FixedTableTag, Lookup}, + table::{AccountFieldTag, CallContextFieldTag, FixedTableTag, Lookup}, util::{ - constraint_builder::{ConstraintBuilder, ReversionInfo, StepStateTransition}, + constraint_builder::{ + ConstraintBuilder, ReversionInfo, StepStateTransition, + Transition::{self, To}, + }, math_gadget::{AddWordsGadget, RangeCheckGadget}, Cell, Word, }, - witness::ExecStep, + witness::{Block, Call, ExecStep}, }, util::Expr, }; -use eth_types::{Field, U256}; +use eth_types::{Field, ToLittleEndian, ToScalar, U256}; use halo2_proofs::plonk::{Error, Expression}; use std::convert::TryInto; @@ -47,7 +50,7 @@ impl SameContextGadget { // Check gas_left is sufficient let sufficient_gas_left = RangeCheckGadget::construct(cb, cb.next.state.gas_left.expr()); - // State transition + // Do step state transition cb.require_step_state_transition(step_state_transition); Self { @@ -76,6 +79,160 @@ impl SameContextGadget { } } +/// Construction of step state transition that restores caller's state. +#[derive(Clone, Debug)] +pub(crate) struct RestoreContextGadget { + caller_id: Cell, + caller_is_root: Cell, + caller_is_create: Cell, + caller_code_source: Cell, + caller_program_counter: Cell, + caller_stack_pointer: Cell, + caller_gas_left: Cell, + caller_memory_word_size: Cell, + caller_reversible_write_counter: Cell, +} + +impl RestoreContextGadget { + pub(crate) fn construct( + cb: &mut ConstraintBuilder, + rw_counter: Transition>, + return_data_offset: Expression, + return_data_length: Expression, + ) -> Self { + // Read caller's context for restore + let caller_id = cb.call_context(None, CallContextFieldTag::CallerId); + let [caller_is_root, caller_is_create, caller_code_source, caller_program_counter, caller_stack_pointer, caller_gas_left, caller_memory_word_size, caller_reversible_write_counter] = + [ + CallContextFieldTag::IsRoot, + CallContextFieldTag::IsCreate, + CallContextFieldTag::CodeSource, + CallContextFieldTag::ProgramCounter, + CallContextFieldTag::StackPointer, + CallContextFieldTag::GasLeft, + CallContextFieldTag::MemorySize, + CallContextFieldTag::ReversibleWriteCounter, + ] + .map(|field_tag| cb.call_context(Some(caller_id.expr()), field_tag)); + + // Update caller's last callee information + for (field_tag, value) in [ + ( + CallContextFieldTag::LastCalleeId, + cb.curr.state.call_id.expr(), + ), + ( + CallContextFieldTag::LastCalleeReturnDataOffset, + return_data_offset, + ), + ( + CallContextFieldTag::LastCalleeReturnDataLength, + return_data_length, + ), + ] { + cb.call_context_lookup(true.expr(), Some(caller_id.expr()), field_tag, value); + } + + // Consume all gas_left if call halts in exception + let gas_left = if cb.execution_state().halts_in_exception() { + caller_gas_left.expr() + } else { + caller_gas_left.expr() + cb.curr.state.gas_left.expr() + }; + + // Accumulate reversible_write_counter in case this call stack reverts in the + // future even it itself succeeds. Note that when sub-call halts in + // failure, we don't need to accumulate reversible_write_counter because + // what happened in the sub-call has been reverted. + let reversible_write_counter = if cb.execution_state().halts_in_success() { + caller_reversible_write_counter.expr() + cb.curr.state.reversible_write_counter.expr() + } else { + caller_reversible_write_counter.expr() + }; + + // Do step state transition + cb.require_step_state_transition(StepStateTransition { + rw_counter, + call_id: To(caller_id.expr()), + is_root: To(caller_is_root.expr()), + is_create: To(caller_is_create.expr()), + code_source: To(caller_code_source.expr()), + program_counter: To(caller_program_counter.expr()), + stack_pointer: To(caller_stack_pointer.expr()), + gas_left: To(gas_left.expr()), + memory_word_size: To(caller_memory_word_size.expr()), + reversible_write_counter: To(reversible_write_counter), + log_id: To(0.expr()), + }); + + Self { + caller_id, + caller_is_root, + caller_is_create, + caller_code_source, + caller_program_counter, + caller_stack_pointer, + caller_gas_left, + caller_memory_word_size, + caller_reversible_write_counter, + } + } + + pub(crate) fn assign( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + let [caller_id, caller_is_root, caller_is_create, caller_code_source, caller_program_counter, caller_stack_pointer, caller_gas_left, caller_memory_word_size, caller_reversible_write_counter] = + if call.is_root { + [U256::zero(); 9] + } else { + [ + step.rw_indices[1], + step.rw_indices[2], + step.rw_indices[3], + step.rw_indices[4], + step.rw_indices[5], + step.rw_indices[6], + step.rw_indices[7], + step.rw_indices[8], + step.rw_indices[9], + ] + .map(|idx| block.rws[idx].call_context_value()) + }; + + for (cell, value) in [ + (&self.caller_id, caller_id), + (&self.caller_is_root, caller_is_root), + (&self.caller_is_create, caller_is_create), + (&self.caller_program_counter, caller_program_counter), + (&self.caller_stack_pointer, caller_stack_pointer), + (&self.caller_gas_left, caller_gas_left), + (&self.caller_memory_word_size, caller_memory_word_size), + ( + &self.caller_reversible_write_counter, + caller_reversible_write_counter, + ), + ] { + cell.assign(region, offset, value.to_scalar())?; + } + + self.caller_code_source.assign( + region, + offset, + Some(Word::random_linear_combine( + caller_code_source.to_le_bytes(), + block.randomness, + )), + )?; + + Ok(()) + } +} + #[derive(Clone, Debug)] pub(crate) struct UpdateBalanceGadget { add_words: AddWordsGadget, diff --git a/zkevm-circuits/src/evm_circuit/util/math_gadget.rs b/zkevm-circuits/src/evm_circuit/util/math_gadget.rs index 8c1136c593..bf0a3bd08c 100644 --- a/zkevm-circuits/src/evm_circuit/util/math_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/math_gadget.rs @@ -1,8 +1,11 @@ use super::CachedRegion; use crate::{ - evm_circuit::util::{ - self, constraint_builder::ConstraintBuilder, from_bytes, pow_of_two, pow_of_two_expr, - select, split_u256, split_u256_limb64, sum, Cell, + evm_circuit::{ + table::{FixedTableTag, Lookup}, + util::{ + self, constraint_builder::ConstraintBuilder, from_bytes, pow_of_two, pow_of_two_expr, + select, split_u256, split_u256_limb64, sum, Cell, + }, }, util::Expr, }; @@ -729,11 +732,11 @@ impl MinMaxGadget { pub(crate) fn generate_lagrange_base_polynomial< F: Field, Exp: Expr, - R: Iterator, + I: Iterator, >( exp: Exp, val: usize, - range: R, + range: I, ) -> Expression { let mut numerator = 1u64.expr(); let mut denominator = F::from(1); @@ -896,3 +899,667 @@ impl MulAddWordsGadget { self.overflow.clone() } } + +#[derive(Clone, Debug)] +pub struct ShrWordsGadget { + a: util::Word, + shift: util::Word, + b: util::Word, + // slice_hi means the higher part of split digit + // slice_lo means the lower part of the split digit + a_slice_hi: [Cell; 32], + a_slice_lo: [Cell; 32], + // shift_div64, shift_mod64_div8, shift_mod8 + // is used to seperate shift[0] + shift_div64: Cell, + shift_mod64_div8: Cell, + shift_mod64_decpow: Cell, // means 2^(8-shift_mod64) + shift_mod64_pow: Cell, // means 2^shift_mod64 + shift_mod8: Cell, + // is_zero will check combination of shift[1..32] == 0 + is_zero: IsZeroGadget, +} + +impl ShrWordsGadget { + pub(crate) fn construct( + cb: &mut ConstraintBuilder, + a: util::Word, + shift: util::Word, + ) -> Self { + let b = cb.query_word(); + let a_slice_hi = cb.query_bytes(); + let a_slice_lo = cb.query_bytes(); + let shift_div64 = cb.query_cell(); + let shift_mod64_div8 = cb.query_cell(); + let shift_mod64_decpow = cb.query_cell(); + let shift_mod64_pow = cb.query_cell(); + let shift_mod8 = cb.query_cell(); + + // check (combination of shift[1..32] == 0) == 1 - shift_overflow + let mut sum = 0.expr(); + (1..32).for_each(|idx| sum = sum.clone() + shift.cells[idx].expr()); + let is_zero = IsZeroGadget::construct(cb, sum); + // if combination of shift[1..32] == 0 + // shift_overflow will be equal to 0, otherwise 1. + let shift_overflow = 1.expr() - is_zero.expr(); + cb.require_equal( + "shift_overflow == shift > 256 ", + shift_overflow.clone(), + 1.expr() - is_zero.expr(), + ); + + // rename variable: + // shift_div64 :a + // shift_mod64_div8:b + // shift_mod8:c + // we split shift[0] to the equation: + // shift[0] == a * 64 + b * 8 + c + let shift_mod64 = 8.expr() * shift_mod64_div8.expr() + shift_mod8.expr(); + cb.require_equal( + "shift[0] == shift_div64 * 64 + shift_mod64_div8 * 8 + shift_mod8", + shift.cells[0].expr(), + shift_div64.expr() * 64.expr() + shift_mod64.clone(), + ); + + // merge 8 8-bit cell for a 64-bit expression + // for a, a_slice_hi, a_slice_lo, b + let mut a_digits = vec![]; + let mut a_slice_hi_digits = vec![]; + let mut a_slice_lo_digits = vec![]; + let mut b_digits = vec![]; + for virtual_idx in 0..4 { + let now_idx = (virtual_idx * 8) as usize; + a_digits.push(from_bytes::expr(&a.cells[now_idx..now_idx + 8])); + a_slice_lo_digits.push(from_bytes::expr(&a_slice_lo[now_idx..now_idx + 8])); + a_slice_hi_digits.push(from_bytes::expr(&a_slice_hi[now_idx..now_idx + 8])); + b_digits.push(from_bytes::expr(&b.cells[now_idx..now_idx + 8])); + } + + // check combination of a_slice_back_digits and a_slice_front_digits + // == b_digits + let mut shr_constraints = (0..4).map(|_| 0.expr()).collect::>>(); + for transplacement in (0_usize)..(4_usize) { + // generate the polynomial depends on the shift_div64 + let select_transplacement_polynomial = + generate_lagrange_base_polynomial(shift_div64.expr(), transplacement, 0..4); + for idx in 0..(4 - transplacement) { + let tmpidx = idx + transplacement; + let merge_a = if idx + transplacement == (3_usize) { + a_slice_hi_digits[tmpidx].clone() + } else { + a_slice_hi_digits[tmpidx].clone() + + a_slice_lo_digits[tmpidx + 1].clone() * shift_mod64_decpow.expr() + }; + shr_constraints[idx] = shr_constraints[idx].clone() + + select_transplacement_polynomial.clone() + * select::expr( + shift_overflow.clone(), + b_digits[idx].clone(), + merge_a - b_digits[idx].clone(), + ); + } + for idx in (4 - transplacement)..4 { + shr_constraints[idx] = shr_constraints[idx].clone() + + select_transplacement_polynomial.clone() * b_digits[idx].clone(); + } + } + (0..4).for_each(|idx| { + cb.require_zero( + "merge a_slice_lo_digits and a_slice_hi_digits == b_digits", + shr_constraints[idx].clone(), + ) + }); + + // for i in 0..4 + // a_slice_lo_digits[i] + a_slice_hi_digits * shift_mod64_pow + // == a_digits[i] + for idx in 0..4 { + cb.require_equal( + "a[idx] == a_slice_lo[idx] + a_slice_hi[idx] * shift_mod64_pow", + a_slice_lo_digits[idx].clone() + + a_slice_hi_digits[idx].clone() * shift_mod64_pow.expr(), + a_digits[idx].clone(), + ); + } + + // check serveral higher cells == 0 for slice_back and slice_front + let mut equal_to_zero = 0.expr(); + for digit_transplacement in 0..8 { + let select_transplacement_polynomial = generate_lagrange_base_polynomial( + shift_mod64_div8.expr(), + digit_transplacement, + 0..8, + ); + for virtual_idx in 0..4 { + for idx in (digit_transplacement + 1)..8 { + let nowidx = (virtual_idx * 8 + idx) as usize; + equal_to_zero = equal_to_zero + + (select_transplacement_polynomial.clone() * a_slice_lo[nowidx].expr()); + } + for idx in (8 - digit_transplacement)..8 { + let nowidx = (virtual_idx * 8 + idx) as usize; + equal_to_zero = equal_to_zero + + (select_transplacement_polynomial.clone() * a_slice_hi[nowidx].expr()); + } + } + } + + //check the specific 4 cells in 0..(1 << shift_mod8). + //check another specific 4 cells in 0..(1 << (8 - shift_mod8)). + for virtual_idx in 0..4 { + let mut slice_bits_polynomial = vec![0.expr(), 0.expr()]; + for digit_transplacement in 0..8 { + let select_transplacement_polynomial = generate_lagrange_base_polynomial( + shift_mod64_div8.expr(), + digit_transplacement, + 0..8, + ); + let nowidx = (virtual_idx * 8 + digit_transplacement) as usize; + slice_bits_polynomial[0] = slice_bits_polynomial[0].clone() + + select_transplacement_polynomial.clone() * a_slice_lo[nowidx].expr(); + let nowidx = (virtual_idx * 8 + 7 - digit_transplacement) as usize; + slice_bits_polynomial[1] = slice_bits_polynomial[1].clone() + + select_transplacement_polynomial.clone() * a_slice_hi[nowidx].expr(); + } + cb.add_lookup( + "slice_bits range lookup", + Lookup::Fixed { + tag: FixedTableTag::Bitslevel.expr(), + values: [ + shift_mod8.expr(), + slice_bits_polynomial[0].clone(), + 0.expr(), + ], + }, + ); + cb.add_lookup( + "slice_bits range lookup", + Lookup::Fixed { + tag: FixedTableTag::Bitslevel.expr(), + values: [ + 8.expr() - shift_mod8.expr(), + slice_bits_polynomial[1].clone(), + 0.expr(), + ], + }, + ); + } + + // check: + // 2^shift_mod64 == shift_mod64_pow + // 2^(8-shift_mod64) == shift_mod64_decpow + cb.add_lookup( + "pow_of_two lookup", + Lookup::Fixed { + tag: FixedTableTag::Pow64.expr(), + values: [ + shift_mod64, + shift_mod64_pow.expr(), + shift_mod64_decpow.expr(), + ], + }, + ); + + cb.add_lookup( + "shift_div64 range lookup", + Lookup::Fixed { + tag: FixedTableTag::Bitslevel.expr(), + values: [2.expr(), shift_div64.expr(), 0.expr()], + }, + ); + cb.add_lookup( + "shift_mod64_div8 range lookup", + Lookup::Fixed { + tag: FixedTableTag::Bitslevel.expr(), + values: [3.expr(), shift_mod64_div8.expr(), 0.expr()], + }, + ); + cb.add_lookup( + "shift_mod8 range lookup", + Lookup::Fixed { + tag: FixedTableTag::Bitslevel.expr(), + values: [3.expr(), shift_mod8.expr(), 0.expr()], + }, + ); + + Self { + a, + shift, + b, + a_slice_hi, + a_slice_lo, + shift_div64, + shift_mod64_div8, + shift_mod64_decpow, + shift_mod64_pow, + shift_mod8, + is_zero, + } + } + + pub(crate) fn assign( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + a: Word, + shift: Word, + b: Word, + ) -> Result<(), Error> { + self.assign_witness(region, offset, &a, &shift)?; + self.a.assign(region, offset, Some(a.to_le_bytes()))?; + self.shift + .assign(region, offset, Some(shift.to_le_bytes()))?; + self.b.assign(region, offset, Some(b.to_le_bytes()))?; + Ok(()) + } + + pub(crate) fn b(&self) -> &util::Word { + &self.b + } + + fn assign_witness( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + wa: &Word, + wshift: &Word, + ) -> Result<(), Error> { + let a8s = wa.to_le_bytes(); + let shift = wshift.to_le_bytes()[0] as u128; + let shift_div64 = shift / 64; + let shift_mod64_div8 = shift % 64 / 8; + let shift_mod64 = shift % 64; + let shift_mod64_pow = 1u128 << shift_mod64; + let shift_mod64_decpow = (1u128 << 64) / (shift_mod64_pow as u128); + let shift_mod8 = shift % 8; + let mut a_slice_hi = [0u8; 32]; + let mut a_slice_lo = [0u8; 32]; + for virtual_idx in 0..4 { + let mut tmp_a: u64 = 0; + for idx in 0..8 { + let now_idx = virtual_idx * 8 + idx; + tmp_a += (1u64 << (8 * idx)) * (a8s[now_idx] as u64); + } + let mut slice_back = if shift_mod64 == 0 { + 0 + } else { + tmp_a % (1u64 << shift_mod64) + }; + let mut slice_front = if shift_mod64 == 0 { + tmp_a + } else { + tmp_a / (1u64 << shift_mod64) + }; + for idx in 0..8 { + let now_idx = virtual_idx * 8 + idx; + a_slice_lo[now_idx] = (slice_back % (1 << 8)) as u8; + a_slice_hi[now_idx] = (slice_front % (1 << 8)) as u8; + slice_back >>= 8; + slice_front >>= 8; + } + } + a_slice_hi.iter().zip(self.a_slice_hi.iter()).try_for_each( + |(bt, assignee)| -> Result<(), Error> { + assignee.assign(region, offset, Some(F::from(*bt as u64)))?; + Ok(()) + }, + )?; + a_slice_lo.iter().zip(self.a_slice_lo.iter()).try_for_each( + |(bt, assignee)| -> Result<(), Error> { + assignee.assign(region, offset, Some(F::from(*bt as u64)))?; + Ok(()) + }, + )?; + self.shift_div64 + .assign(region, offset, Some(F::from_u128(shift_div64)))?; + self.shift_mod64_div8 + .assign(region, offset, Some(F::from_u128(shift_mod64_div8)))?; + self.shift_mod64_decpow + .assign(region, offset, Some(F::from_u128(shift_mod64_decpow)))?; + self.shift_mod64_pow + .assign(region, offset, Some(F::from_u128(shift_mod64_pow)))?; + self.shift_mod8 + .assign(region, offset, Some(F::from_u128(shift_mod8)))?; + + let mut sum: u128 = 0; + wshift.to_le_bytes().iter().for_each(|v| sum += *v as u128); + sum -= shift as u128; + self.is_zero.assign(region, offset, F::from_u128(sum))?; + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub struct ShlWordsGadget { + a: util::Word, + shift: util::Word, + b: util::Word, + // slice_front means the higher part of split digit + // slice_back means the lower part of the split digit + a_slice_front: [Cell; 32], + a_slice_back: [Cell; 32], + // shift_div64, shift_mod64_div8, shift_mod8 + // is used to seperate shift[0] + shift_div64: Cell, + shift_mod64_div8: Cell, + shift_mod64_decpow: Cell, // means 2^(8-shift_mod64) + shift_mod64_pow: Cell, // means 2^shift_mod64 + shift_mod8: Cell, + // if combination of shift[1..32] == 0 + // shift_overflow will be equal to 0, otherwise 1. + shift_overflow: Cell, + // is_zero will check combination of shift[1..32] == 0 + is_zero: IsZeroGadget, +} +impl ShlWordsGadget { + pub(crate) fn construct( + cb: &mut ConstraintBuilder, + a: util::Word, + shift: util::Word, + ) -> Self { + let b = cb.query_word(); + let a_slice_front = array_init::array_init(|_| cb.query_byte()); + let a_slice_back = array_init::array_init(|_| cb.query_byte()); + let shift_div64 = cb.query_cell(); + let shift_mod64_div8 = cb.query_cell(); + let shift_mod64_decpow = cb.query_cell(); + let shift_mod64_pow = cb.query_cell(); + let shift_mod8 = cb.query_cell(); + let shift_overflow = cb.query_bool(); + + // check (combination of shift[1..32] == 0) == 1 - shift_overflow + let mut sum = 0.expr(); + (1..32).for_each(|idx| sum = sum.clone() + shift.cells[idx].expr()); + let is_zero = IsZeroGadget::construct(cb, sum); + cb.require_equal( + "shift_overflow == shift > 256 ", + shift_overflow.expr(), + 1.expr() - is_zero.expr(), + ); + + // rename variable: + // shift_div64 :a + // shift_mod64_div8:b + // shift_mod8:c + // we split shift[0] to the equation: + // shift[0] == a * 64 + b * 8 + c + let shift_mod64 = 8.expr() * shift_mod64_div8.expr() + shift_mod8.expr(); + cb.require_equal( + "shift[0] == shift_div64 * 64 + shift_mod64_div8 * 8 + shift_mod8", + shift.cells[0].expr(), + shift_div64.expr() * 64.expr() + shift_mod64.clone(), + ); + + // merge 8 8-bit cell for a 64-bit expression + // for a, a_slice_front, a_slice_back, b + let mut a_digits = vec![]; + let mut a_slice_front_digits = vec![]; + let mut a_slice_back_digits = vec![]; + let mut b_digits = vec![]; + for virtual_idx in 0..4 { + let now_idx = (virtual_idx * 8) as usize; + a_digits.push(from_bytes::expr(&a.cells[now_idx..now_idx + 8])); + a_slice_back_digits.push(from_bytes::expr(&a_slice_back[now_idx..now_idx + 8])); + a_slice_front_digits.push(from_bytes::expr(&a_slice_front[now_idx..now_idx + 8])); + b_digits.push(from_bytes::expr(&b.cells[now_idx..now_idx + 8])); + } + + // check combination of a_slice_back_digits and a_slice_front_digits + // == b_digits + let mut shl_constraints = (0..4).map(|_| 0.expr()).collect::>>(); + for transplacement in (0_usize)..(4_usize) { + // generate the polynomial depends on the shift_div64 + let select_transplacement_polynomial = + generate_lagrange_base_polynomial(shift_div64.expr(), transplacement, 0..4); + for idx in 0..(4 - transplacement) { + let tmpidx = idx + transplacement; + let merge_a = if idx == (0_usize) { + a_slice_back_digits[idx].clone() * shift_mod64_pow.expr() + } else { + a_slice_back_digits[idx].clone() * shift_mod64_pow.expr() + + a_slice_front_digits[idx - 1].clone() + }; + shl_constraints[tmpidx] = shl_constraints[tmpidx].clone() + + select_transplacement_polynomial.clone() + * select::expr( + shift_overflow.expr(), + b_digits[tmpidx].clone(), + merge_a - b_digits[tmpidx].clone(), + ); + } + for idx in 0..transplacement { + shl_constraints[idx] = shl_constraints[idx].clone() + + select_transplacement_polynomial.clone() * b_digits[idx].clone(); + } + } + (0..4).for_each(|idx| { + cb.require_zero( + "merge a_slice_back_digits and a_slice_front_digits == b_digits", + shl_constraints[idx].clone(), + ) + }); + + // for i in 0..4 + // a_slice_back_digits[i] + a_slice_front_digits * shift_mod64_decpow + // == a_digits[i] + for idx in 0..4 { + cb.require_equal( + "a[idx] == a_slice_back[idx] + a_slice_front[idx] * shift_mod64_decpow", + a_slice_back_digits[idx].clone() + + a_slice_front_digits[idx].clone() * shift_mod64_decpow.expr(), + a_digits[idx].clone(), + ); + } + + // check serveral higher cells == 0 for slice_back and slice_front + let mut equal_to_zero = 0.expr(); + for digit_transplacement in 0..8 { + let select_transplacement_polynomial = generate_lagrange_base_polynomial( + shift_mod64_div8.clone(), + digit_transplacement, + 0..8, + ); + for virtual_idx in 0..4 { + for idx in (digit_transplacement + 1)..8 { + let nowidx = (virtual_idx * 8 + idx) as usize; + equal_to_zero = equal_to_zero + + (select_transplacement_polynomial.clone() * a_slice_front[nowidx].expr()); + } + for idx in (8 - digit_transplacement)..8 { + let nowidx = (virtual_idx * 8 + idx) as usize; + equal_to_zero = equal_to_zero + + (select_transplacement_polynomial.clone() * a_slice_back[nowidx].expr()); + } + } + } + + //check the specific 4 cells in 0..(1 << shift_mod8). + //check another specific 4 cells in 0..(1 << (8 - shift_mod8)). + for virtual_idx in 0..4 { + let mut slice_bits_polynomial = vec![0.expr(), 0.expr()]; + for digit_transplacement in 0..8 { + let select_transplacement_polynomial = generate_lagrange_base_polynomial( + shift_mod64_div8.clone(), + digit_transplacement, + 0..8, + ); + let nowidx = (virtual_idx * 8 + digit_transplacement) as usize; + slice_bits_polynomial[0] = slice_bits_polynomial[0].clone() + + select_transplacement_polynomial.clone() * a_slice_front[nowidx].expr(); + let nowidx = (virtual_idx * 8 + 7 - digit_transplacement) as usize; + slice_bits_polynomial[1] = slice_bits_polynomial[1].clone() + + select_transplacement_polynomial.clone() * a_slice_back[nowidx].expr(); + } + cb.add_lookup( + "slice_bits range lookup", + Lookup::Fixed { + tag: FixedTableTag::Bitslevel.expr(), + values: [ + shift_mod8.expr(), + slice_bits_polynomial[0].clone(), + 0.expr(), + ], + }, + ); + cb.add_lookup( + "slice_bits range lookup", + Lookup::Fixed { + tag: FixedTableTag::Bitslevel.expr(), + values: [ + 8.expr() - shift_mod8.expr(), + slice_bits_polynomial[1].clone(), + 0.expr(), + ], + }, + ); + } + + // check: + // 2^shift_mod64 == shift_mod64_pow + // 2^(8-shift_mod64) == shift_mod64_decpow + cb.add_lookup( + "pow_of_two lookup", + Lookup::Fixed { + tag: FixedTableTag::Pow64.expr(), + values: [ + shift_mod64, + shift_mod64_pow.expr(), + shift_mod64_decpow.expr(), + ], + }, + ); + + cb.add_lookup( + "shift_div64 range lookup", + Lookup::Fixed { + tag: FixedTableTag::Bitslevel.expr(), + values: [2.expr(), shift_div64.expr(), 0.expr()], + }, + ); + cb.add_lookup( + "shift_mod64_div8 range lookup", + Lookup::Fixed { + tag: FixedTableTag::Bitslevel.expr(), + values: [3.expr(), shift_mod64_div8.expr(), 0.expr()], + }, + ); + cb.add_lookup( + "shift_mod8 range lookup", + Lookup::Fixed { + tag: FixedTableTag::Bitslevel.expr(), + values: [3.expr(), shift_mod8.expr(), 0.expr()], + }, + ); + + Self { + a, + shift, + b, + a_slice_front, + a_slice_back, + shift_div64, + shift_mod64_div8, + shift_mod64_decpow, + shift_mod64_pow, + shift_mod8, + shift_overflow, + is_zero, + } + } + + pub(crate) fn assign( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + a: Word, + shift: Word, + b: Word, + ) -> Result<(), Error> { + self.assign_witness(region, offset, &a, &shift)?; + self.a.assign(region, offset, Some(a.to_le_bytes()))?; + self.shift + .assign(region, offset, Some(shift.to_le_bytes()))?; + self.b.assign(region, offset, Some(b.to_le_bytes()))?; + Ok(()) + } + + pub(crate) fn b(&self) -> &util::Word { + &self.b + } + + fn assign_witness( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + wa: &Word, + wshift: &Word, + ) -> Result<(), Error> { + let a8s = wa.to_le_bytes(); + let shift = wshift.to_le_bytes()[0] as u128; + let shift_div64 = shift / 64; + let shift_mod64_div8 = shift % 64 / 8; + let shift_mod64 = shift % 64; + let shift_mod64_pow = 1u128 << shift_mod64; + let shift_mod64_decpow = (1u128 << 64) / (shift_mod64_pow as u128); + let shift_mod8 = shift % 8; + let mut a_slice_front = [0u8; 32]; + let mut a_slice_back = [0u8; 32]; + for virtual_idx in 0..4 { + let mut tmp_a: u64 = 0; + for idx in 0..8 { + let now_idx = virtual_idx * 8 + idx; + tmp_a += (1u64 << (8 * idx)) * (a8s[now_idx] as u64); + } + let mut slice_back = if shift_mod64 == 0 { + tmp_a + } else { + tmp_a % (1u64 << (64 - shift_mod64)) + }; + let mut slice_front = if shift_mod64 == 0 { + 0 + } else { + tmp_a / (1u64 << (64 - shift_mod64)) + }; + for idx in 0..8 { + let now_idx = virtual_idx * 8 + idx; + a_slice_back[now_idx] = (slice_back % (1 << 8)) as u8; + a_slice_front[now_idx] = (slice_front % (1 << 8)) as u8; + slice_back >>= 8; + slice_front >>= 8; + } + } + a_slice_front + .iter() + .zip(self.a_slice_front.iter()) + .try_for_each(|(bt, assignee)| -> Result<(), Error> { + assignee.assign(region, offset, Some(F::from(*bt as u64)))?; + Ok(()) + })?; + a_slice_back + .iter() + .zip(self.a_slice_back.iter()) + .try_for_each(|(bt, assignee)| -> Result<(), Error> { + assignee.assign(region, offset, Some(F::from(*bt as u64)))?; + Ok(()) + })?; + self.shift_div64 + .assign(region, offset, Some(F::from_u128(shift_div64)))?; + self.shift_mod64_div8 + .assign(region, offset, Some(F::from_u128(shift_mod64_div8)))?; + self.shift_mod64_decpow + .assign(region, offset, Some(F::from_u128(shift_mod64_decpow)))?; + self.shift_mod64_pow + .assign(region, offset, Some(F::from_u128(shift_mod64_pow)))?; + self.shift_mod8 + .assign(region, offset, Some(F::from_u128(shift_mod8)))?; + + let mut sum: u128 = 0; + wshift.to_le_bytes().iter().for_each(|v| sum += *v as u128); + sum -= shift as u128; + let shift_overflow = sum != 0; + self.is_zero.assign(region, offset, F::from_u128(sum))?; + self.shift_overflow + .assign(region, offset, Some(F::from_u128(shift_overflow as u128)))?; + Ok(()) + } +} diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index 83253a286d..6ef749bfe1 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -1,12 +1,15 @@ #![allow(missing_docs)] -use crate::evm_circuit::{ - param::{N_BYTES_WORD, STACK_CAPACITY}, - step::ExecutionState, - table::{ - AccountFieldTag, BlockContextFieldTag, BytecodeFieldTag, CallContextFieldTag, RwTableTag, - TxContextFieldTag, TxLogFieldTag, TxReceiptFieldTag, +use crate::{ + evm_circuit::{ + param::{N_BYTES_WORD, STACK_CAPACITY}, + step::ExecutionState, + table::{ + AccountFieldTag, BlockContextFieldTag, BytecodeFieldTag, CallContextFieldTag, + RwTableTag, TxContextFieldTag, TxLogFieldTag, TxReceiptFieldTag, + }, + util::RandomLinearCombination, }, - util::RandomLinearCombination, + util::DEFAULT_RAND, }; use bus_mapping::{ @@ -18,7 +21,7 @@ use bus_mapping::{ use eth_types::evm_types::OpcodeId; use eth_types::{Address, Field, ToLittleEndian, ToScalar, ToWord, Word}; use eth_types::{ToAddress, U256}; -use halo2_proofs::arithmetic::{BaseExt, FieldExt}; +use halo2_proofs::arithmetic::FieldExt; use halo2_proofs::pairing::bn256::Fr as Fp; use itertools::Itertools; use sha3::{Digest, Keccak256}; @@ -36,6 +39,8 @@ pub struct Block { pub bytecodes: Vec, /// The block context pub context: BlockContext, + /// .. + pub step_num_with_pad: usize, } #[derive(Debug, Default, Clone)] @@ -1018,8 +1023,8 @@ impl From<&operation::OperationContainer> for RwMap { CallContextField::StackPointer => CallContextFieldTag::StackPointer, CallContextField::GasLeft => CallContextFieldTag::GasLeft, CallContextField::MemorySize => CallContextFieldTag::MemorySize, - CallContextField::StateWriteCounter => { - CallContextFieldTag::StateWriteCounter + CallContextField::ReversibleWriteCounter => { + CallContextFieldTag::ReversibleWriteCounter } }, value: op.op().value, @@ -1145,10 +1150,11 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::ADD | OpcodeId::SUB => ExecutionState::ADD_SUB, OpcodeId::MUL | OpcodeId::DIV | OpcodeId::MOD => ExecutionState::MUL_DIV_MOD, OpcodeId::EQ | OpcodeId::LT | OpcodeId::GT => ExecutionState::CMP, + OpcodeId::SHR => ExecutionState::SHR, OpcodeId::SLT | OpcodeId::SGT => ExecutionState::SCMP, + OpcodeId::SHL => ExecutionState::SHL, OpcodeId::SIGNEXTEND => ExecutionState::SIGNEXTEND, - // TODO: Convert REVERT and RETURN to their own ExecutionState. - OpcodeId::STOP | OpcodeId::RETURN | OpcodeId::REVERT => ExecutionState::STOP, + OpcodeId::STOP => ExecutionState::STOP, OpcodeId::AND => ExecutionState::BITWISE, OpcodeId::XOR => ExecutionState::BITWISE, OpcodeId::OR => ExecutionState::BITWISE, @@ -1184,6 +1190,8 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::ORIGIN => ExecutionState::ORIGIN, OpcodeId::CODECOPY => ExecutionState::CODECOPY, OpcodeId::CALLDATALOAD => ExecutionState::CALLDATALOAD, + // TODO: Convert REVERT to its own ExecutionState. + OpcodeId::RETURN | OpcodeId::REVERT => ExecutionState::RETURN, _ => unimplemented!("unimplemented opcode {:?}", op), } } @@ -1313,7 +1321,7 @@ pub fn block_convert( code_db: &bus_mapping::state_db::CodeDB, ) -> Block { Block { - randomness: Fp::rand(), + randomness: Fp::from_u128(DEFAULT_RAND), context: block.into(), rws: RwMap::from(&block.container), txs: block @@ -1334,5 +1342,6 @@ pub fn block_convert( .map(|code_hash| Bytecode::new(code_db.0.get(&code_hash).unwrap().to_vec())) }) .collect(), + step_num_with_pad: 0, } } diff --git a/zkevm-circuits/src/lib.rs b/zkevm-circuits/src/lib.rs index a40a1482f2..113eb2291a 100644 --- a/zkevm-circuits/src/lib.rs +++ b/zkevm-circuits/src/lib.rs @@ -1,5 +1,7 @@ //! # zk_evm +#![allow(incomplete_features)] +#![feature(adt_const_params)] #![cfg_attr(docsrs, feature(doc_cfg))] // Temporary until we have more of the crate implemented. #![allow(dead_code)] diff --git a/zkevm-circuits/src/state_circuit/old_state_tests.rs b/zkevm-circuits/src/state_circuit/old_state_tests.rs new file mode 100644 index 0000000000..6462dfc1ca --- /dev/null +++ b/zkevm-circuits/src/state_circuit/old_state_tests.rs @@ -0,0 +1,788 @@ +// TODO: migrate these over. + +#[cfg(test)] +mod tests { + use super::*; + use bus_mapping::mock::BlockData; + use bus_mapping::operation::{ + MemoryOp, Operation, OperationContainer, RWCounter, StackOp, StorageOp, RW, + }; + use eth_types::{ + address, bytecode, + evm_types::{MemoryAddress, StackAddress}, + geth_types::GethData, + Word, + }; + use halo2_proofs::arithmetic::BaseExt; + use halo2_proofs::dev::MockProver; + use mock::TestContext; + use pairing::bn256::Fr; + + macro_rules! test_state_circuit_ok { + ($k:expr, $rw_counter_max:expr, $memory_rows_max:expr, $memory_address_max:expr, $stack_rows_max:expr, $stack_address_max:expr, $storage_rows_max:expr, $memory_ops:expr, $stack_ops:expr, $storage_ops:expr, $result:expr) => {{ + let rw_map = RwMap::from(&OperationContainer { + memory: $memory_ops, + stack: $stack_ops, + storage: $storage_ops, + ..Default::default() + }); + let circuit = StateCircuit::< + Fr, + true, + $rw_counter_max, + $memory_address_max, + $stack_address_max, + { $memory_rows_max + $stack_rows_max + $storage_rows_max }, + >::new(Fr::rand(), &rw_map); + + let power_of_randomness: Vec<_> = (1..32) + .map(|exp| { + vec![ + circuit.randomness.pow(&[exp, 0, 0, 0]); + { $memory_rows_max + $stack_rows_max + $storage_rows_max } // I think this is the max offset? + ] + }) + .collect(); + + let prover = MockProver::::run($k, &circuit, power_of_randomness).unwrap(); + let verify_result = prover.verify(); + assert!(verify_result.is_ok(), "verify err: {:#?}", verify_result); + }}; + } + + macro_rules! test_state_circuit_error { + ($k:expr, $rw_counter_max:expr, $memory_rows_max:expr, $memory_address_max:expr, $stack_rows_max:expr, $stack_address_max:expr, $storage_rows_max:expr, $memory_ops:expr, $stack_ops:expr, $storage_ops:expr) => {{ + let rw_map = RwMap::from(&OperationContainer { + memory: $memory_ops, + stack: $stack_ops, + storage: $storage_ops, + ..Default::default() + }); + let circuit = StateCircuit::< + Fr, + false, + $rw_counter_max, + $memory_address_max, + $stack_address_max, + { $memory_rows_max + $stack_rows_max + $storage_rows_max }, + >::new(Fr::rand(), &rw_map); + + let power_of_randomness: Vec<_> = (1..32) + .map(|exp| { + vec![ + circuit.randomness.pow(&[exp, 0, 0, 0]); + { $memory_rows_max + $stack_rows_max + $storage_rows_max } // I think this is the max offset? + ] + }) + .collect(); + + let prover = MockProver::::run($k, &circuit, power_of_randomness).unwrap(); + assert!(prover.verify().is_err()); + }}; + } + + #[test] + fn state_circuit_simple() { + let memory_op_0 = Operation::new( + RWCounter::from(12), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + let memory_op_1 = Operation::new( + RWCounter::from(24), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + + let memory_op_2 = Operation::new( + RWCounter::from(17), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(1), 32), + ); + let memory_op_3 = Operation::new( + RWCounter::from(87), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(1), 32), + ); + + let stack_op_0 = Operation::new( + RWCounter::from(17), + RW::WRITE, + StackOp::new(1, StackAddress::from(1), Word::from(32)), + ); + let stack_op_1 = Operation::new( + RWCounter::from(87), + RW::READ, + StackOp::new(1, StackAddress::from(1), Word::from(32)), + ); + + let storage_op_0 = Operation::new( + RWCounter::from(0), + RW::WRITE, + StorageOp::new( + U256::from(100).to_address(), + Word::from(0x40), + Word::from(32), + Word::zero(), + 1usize, + Word::zero(), + ), + ); + let storage_op_1 = Operation::new( + RWCounter::from(18), + RW::WRITE, + StorageOp::new( + U256::from(100).to_address(), + Word::from(0x40), + Word::from(32), + Word::from(32), + 1usize, + Word::from(32), + ), + ); + let storage_op_2 = Operation::new( + RWCounter::from(19), + RW::WRITE, + StorageOp::new( + U256::from(100).to_address(), + Word::from(0x40), + Word::from(32), + Word::from(32), + 1usize, + Word::from(32), + ), + ); + + test_state_circuit_ok!( + 17, + 2000, + 100, + 2, + 100, + 1023, + 1000, + vec![memory_op_0, memory_op_1, memory_op_2, memory_op_3], + vec![stack_op_0, stack_op_1], + vec![storage_op_0, storage_op_1, storage_op_2], + Ok(()) + ); + } + + #[test] + fn no_stack_padding() { + let memory_op_0 = Operation::new( + RWCounter::from(12), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + let memory_op_1 = Operation::new( + RWCounter::from(24), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + + let memory_op_2 = Operation::new( + RWCounter::from(17), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(1), 32), + ); + let memory_op_3 = Operation::new( + RWCounter::from(87), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(1), 32), + ); + + let stack_op_0 = Operation::new( + RWCounter::from(17), + RW::WRITE, + StackOp::new(1, StackAddress::from(1), Word::from(32)), + ); + let stack_op_1 = Operation::new( + RWCounter::from(87), + RW::READ, + StackOp::new(1, StackAddress::from(1), Word::from(32)), + ); + + const STACK_ROWS_MAX: usize = 2; + test_state_circuit_ok!( + 17, + 2000, + 100, + STACK_ROWS_MAX, + 100, + 1023, + 1000, + vec![memory_op_0, memory_op_1, memory_op_2, memory_op_3], + vec![stack_op_0, stack_op_1], + vec![], + Ok(()) + ); + } + + #[test] + fn same_address_read() { + let memory_op_0 = Operation::new( + RWCounter::from(12), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 31), + ); + let memory_op_1 = Operation::new( + RWCounter::from(24), + RW::READ, + MemoryOp::new( + 1, + MemoryAddress::from(0), + 32, + /* This should fail as it not the same value as in previous + * write op */ + ), + ); + + let stack_op_0 = Operation::new( + RWCounter::from(19), + RW::WRITE, + StackOp::new(1, StackAddress::from(0), Word::from(12)), + ); + let stack_op_1 = Operation::new( + RWCounter::from(28), + RW::READ, + StackOp::new( + 1, + StackAddress::from(0), + Word::from(13), + /* This should fail as it not the same value as in previous + * write op */ + ), + ); + + const MEMORY_ROWS_MAX: usize = 7; + test_state_circuit_error!( + 17, + 2000, + MEMORY_ROWS_MAX, + 1000, + 100, + 1023, + 1000, + vec![memory_op_0, memory_op_1], + vec![stack_op_0, stack_op_1], + vec![] + ); + } + + #[test] + fn first_write() { + let stack_op_0 = Operation::new( + RWCounter::from(28), + RW::READ, + StackOp::new(1, StackAddress::from(0), Word::from(13)), + ); + + let storage_op_0 = Operation::new( + RWCounter::from(17), + RW::READ, + StorageOp::new( + /* Fails because the first storage op needs to be + * write. */ + address!("0x0000000000000000000000000000000000000002"), + Word::from(0x40), + Word::from(32), + Word::zero(), + 1usize, + Word::zero(), + ), + ); + let storage_op_1 = Operation::new( + RWCounter::from(18), + RW::READ, + StorageOp::new( + /* Fails because when storage key changes, the op + * needs to be write. */ + address!("0x0000000000000000000000000000000000000002"), + Word::from(0x41), + Word::from(32), + Word::zero(), + 1usize, + Word::zero(), + ), + ); + + let storage_op_2 = Operation::new( + RWCounter::from(19), + RW::READ, + StorageOp::new( + /* Fails because when address changes, the op + * needs to be write. */ + address!("0x0000000000000000000000000000000000000003"), + Word::from(0x40), + /* Intentionally different storage key as the last one in the previous ops to + have two conditions met. */ + Word::from(32), + Word::zero(), + 1usize, + Word::zero(), + ), + ); + + const MEMORY_ROWS_MAX: usize = 2; + const STORAGE_ROWS_MAX: usize = 2; + test_state_circuit_error!( + 17, + 2000, + MEMORY_ROWS_MAX, + 1000, + STORAGE_ROWS_MAX, + 1023, + 1000, + vec![], + vec![stack_op_0], + vec![storage_op_0, storage_op_1, storage_op_2] + ); + } + + #[test] + fn max_values() { + let memory_op_0 = Operation::new( + RWCounter::from(12), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX), 32), + ); + let memory_op_1 = Operation::new( + RWCounter::from(RW_COUNTER_MAX), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX), 32), + ); + let memory_op_2 = Operation::new( + RWCounter::from(RW_COUNTER_MAX + 1), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX), 32), + ); + + let memory_op_3 = Operation::new( + RWCounter::from(12), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX + 1), 32), + ); + let memory_op_4 = Operation::new( + RWCounter::from(24), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(MEMORY_ADDRESS_MAX + 1), 32), + ); + + let stack_op_0 = Operation::new( + RWCounter::from(12), + RW::WRITE, + StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX), Word::from(12)), + ); + let stack_op_1 = Operation::new( + RWCounter::from(24), + RW::READ, + StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX), Word::from(12)), + ); + + let stack_op_2 = Operation::new( + RWCounter::from(17), + RW::WRITE, + StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX + 1), Word::from(12)), + ); + let stack_op_3 = Operation::new( + RWCounter::from(RW_COUNTER_MAX + 1), + RW::WRITE, + StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX + 1), Word::from(12)), + ); + + // Small MEMORY_MAX_ROWS is set to avoid having padded rows (all padded + // rows would fail because of the address they would have - the + // address of the last unused row) + const MEMORY_ROWS_MAX: usize = 7; + const STACK_ROWS_MAX: usize = 7; + const STORAGE_ROWS_MAX: usize = 7; + const RW_COUNTER_MAX: usize = 60000; + const MEMORY_ADDRESS_MAX: usize = 100; + const STACK_ADDRESS_MAX: usize = 1023; + + test_state_circuit_error!( + 18, + RW_COUNTER_MAX, + MEMORY_ROWS_MAX, + MEMORY_ADDRESS_MAX, + STACK_ROWS_MAX, + STACK_ADDRESS_MAX, + STORAGE_ROWS_MAX, + vec![ + memory_op_0, + memory_op_1, + memory_op_2, + memory_op_3, + memory_op_4 + ], + vec![stack_op_0, stack_op_1, stack_op_2, stack_op_3], + vec![] + ); + } + + #[test] + fn max_values_first_row() { + // first row of a target needs to be checked for address to be in range + // too + let memory_op_0 = Operation::new( + RWCounter::from(12), + RW::WRITE, + MemoryOp::new( + 1, + MemoryAddress::from(MEMORY_ADDRESS_MAX + 1), + // This address is not in the allowed range + 32, + ), + ); + + let stack_op_0 = Operation::new( + RWCounter::from(12), + RW::WRITE, + StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX + 1), Word::from(12)), + ); + let stack_op_1 = Operation::new( + RWCounter::from(24), + RW::READ, + StackOp::new(1, StackAddress::from(STACK_ADDRESS_MAX + 1), Word::from(12)), + ); + + // Small MEMORY_MAX_ROWS is set to avoid having padded rows (all padded + // rows would fail because of the address they would have - the + // address of the last unused row) + const MEMORY_ROWS_MAX: usize = 2; + const STACK_ROWS_MAX: usize = 2; + const STORAGE_ROWS_MAX: usize = 2; + const RW_COUNTER_MAX: usize = 60000; + const MEMORY_ADDRESS_MAX: usize = 100; + const STACK_ADDRESS_MAX: usize = 1023; + + test_state_circuit_error!( + 18, + RW_COUNTER_MAX, + MEMORY_ROWS_MAX, + MEMORY_ADDRESS_MAX, + STACK_ROWS_MAX, + STACK_ADDRESS_MAX, + STORAGE_ROWS_MAX, + vec![memory_op_0], + vec![stack_op_0, stack_op_1], + vec![] + ); + } + + #[test] + fn non_monotone_rw_counter() { + let memory_op_0 = Operation::new( + RWCounter::from(1352), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + let memory_op_1 = Operation::new( + RWCounter::from(1255), + RW::READ, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + + // fails because it needs to be strictly monotone + let memory_op_2 = Operation::new( + RWCounter::from(1255), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + + let stack_op_0 = Operation::new( + RWCounter::from(228), + RW::WRITE, + StackOp::new(1, StackAddress::from(1), Word::from(12)), + ); + let stack_op_1 = Operation::new( + RWCounter::from(217), + RW::READ, + StackOp::new(1, StackAddress::from(1), Word::from(12)), + ); + let stack_op_2 = Operation::new( + RWCounter::from(217), + RW::READ, + StackOp::new(1, StackAddress::from(1), Word::from(12)), + ); + + let storage_op_0 = Operation::new( + RWCounter::from(301), + RW::WRITE, + StorageOp::new( + address!("0x0000000000000000000000000000000000000001"), + Word::from(0x40), + Word::from(32), + Word::zero(), + 1usize, + Word::zero(), + ), + ); + let storage_op_1 = Operation::new( + RWCounter::from(302), + RW::READ, + StorageOp::new( + address!("0x0000000000000000000000000000000000000001"), + Word::from(0x40), + Word::from(32), + Word::zero(), + 1usize, + Word::zero(), + ), + ); + let storage_op_2 = Operation::new( + RWCounter::from(302), + RW::READ, + StorageOp::new( + /*fails because the address and + * storage key are the same as in + * the previous row */ + address!("0x0000000000000000000000000000000000000001"), + Word::from(0x40), + Word::from(32), + Word::zero(), + 1usize, + Word::zero(), + ), + ); + let storage_op_3 = Operation::new( + RWCounter::from(297), + RW::WRITE, + StorageOp::new( + // Global counter goes down, but it doesn't fail because + // the storage key is not the same as in the previous row. + address!("0x0000000000000000000000000000000000000001"), + Word::from(0x41), + Word::from(32), + Word::from(32), + 1usize, + Word::from(32), + ), + ); + + let storage_op_4 = Operation::new( + RWCounter::from(296), + RW::WRITE, + StorageOp::new( + // Global counter goes down, but it doesn't fail because the + // address is not the same as in the previous row (while the + // storage key is). + address!("0x0000000000000000000000000000000000000002"), + Word::from(0x41), + Word::from(32), + Word::zero(), + 1usize, + Word::zero(), + ), + ); + + const MEMORY_ROWS_MAX: usize = 100; + const STACK_ROWS_MAX: usize = 100; + test_state_circuit_error!( + 17, + 10000, + MEMORY_ROWS_MAX, + 10000, + STACK_ROWS_MAX, + 1023, + 1000, + vec![memory_op_0, memory_op_1, memory_op_2], + vec![stack_op_0, stack_op_1, stack_op_2], + vec![ + storage_op_0, + storage_op_1, + storage_op_2, + storage_op_3, + storage_op_4 + ] + ); + } + + #[ignore = "disabled temporarily since we sort rws inside the assign method. FIXME later"] + #[test] + fn non_monotone_address() { + let memory_op_0 = Operation::new( + RWCounter::from(1352), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + let memory_op_1 = Operation::new( + RWCounter::from(1255), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(1), 32), + ); + + // fails because it's not monotone + let memory_op_2 = Operation::new( + RWCounter::from(1255), + RW::WRITE, + MemoryOp::new(1, MemoryAddress::from(0), 32), + ); + + let stack_op_0 = Operation::new( + RWCounter::from(228), + RW::WRITE, + StackOp::new(1, StackAddress::from(0), Word::from(12)), + ); + let stack_op_1 = Operation::new( + RWCounter::from(229), + RW::WRITE, + StackOp::new(1, StackAddress::from(1), Word::from(12)), + ); + let stack_op_2 = Operation::new( + RWCounter::from(230), + RW::WRITE, + StackOp::new( + 1, + StackAddress::from(0), /* this fails because the + * address is not + * monotone */ + Word::from(12), + ), + ); + + const MEMORY_ROWS_MAX: usize = 10; + test_state_circuit_error!( + 18, + 10000, + MEMORY_ROWS_MAX, + 10000, + 10, + 1023, + 1000, + vec![memory_op_0, memory_op_1, memory_op_2], + vec![stack_op_0, stack_op_1, stack_op_2], + vec![] + ); + } + + #[test] + fn storage() { + let storage_op_0 = Operation::new( + RWCounter::from(18), + RW::WRITE, + StorageOp::new( + address!("0x0000000000000000000000000000000000000001"), + Word::from(0x40), + Word::from(32), + Word::zero(), + 1usize, + Word::zero(), + ), + ); + let storage_op_1 = Operation::new( + RWCounter::from(19), + RW::READ, + StorageOp::new( + address!("0x0000000000000000000000000000000000000001"), + Word::from(0x40), + Word::from(33), /* Fails because it is READ op + * and not the same + * value as in the previous + * row. */ + Word::zero(), + 1usize, + Word::zero(), + ), + ); + let storage_op_2 = Operation::new( + RWCounter::from(20), + RW::WRITE, + StorageOp::new( + address!("0x0000000000000000000000000000000000000001"), + Word::from(0x40), + Word::from(32), + Word::zero(), /* Fails because not the same + * as value in the previous row - note: this + * is WRITE. */ + 1usize, + Word::zero(), + ), + ); + let storage_op_3 = Operation::new( + RWCounter::from(21), + RW::READ, + StorageOp::new( + address!("0x0000000000000000000000000000000000000001"), + Word::from(0x40), + Word::from(32), + Word::from(1), /* Fails because not the same + * as value_prev in the previous row - note: + * this is READ. */ + 1usize, + Word::from(1), + ), + ); + + const MEMORY_ROWS_MAX: usize = 2; + const STORAGE_ROWS_MAX: usize = 2; + test_state_circuit_error!( + 17, + 2000, + MEMORY_ROWS_MAX, + 1000, + STORAGE_ROWS_MAX, + 1023, + 1000, + vec![], + vec![], + vec![storage_op_0, storage_op_1, storage_op_2, storage_op_3] + ); + } + + #[test] + fn trace() { + let bytecode = bytecode! { + PUSH1(0x80) + PUSH1(0x40) + MSTORE + PUSH1(0x40) + MLOAD + STOP + }; + + // Create a custom tx setting Gas to + let block: GethData = TestContext::<2, 1>::new( + None, + |accs| { + accs[0] + .address(address!("0x0000000000000000000000000000000000000010")) + .balance(Word::from(1u64 << 20)) + .code(bytecode); + accs[1] + .address(address!("0x0000000000000000000000000000000000000000")) + .balance(Word::from(1u64 << 20)); + }, + |mut txs, accs| { + txs[0].to(accs[0].address).from(accs[1].address); + }, + |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 stack_ops = builder.block.container.sorted_stack(); + let memory_ops = builder.block.container.sorted_memory(); + let storage_ops = builder.block.container.sorted_storage(); + + test_state_circuit_ok!( + 17, + 2000, + 100, + 0x80, + 100, + 1023, + 1000, + memory_ops, + stack_ops, + storage_ops, + Ok(()) + ); + } +} diff --git a/zkevm-circuits/src/test_util.rs b/zkevm-circuits/src/test_util.rs index b953f7150a..b58ca0c90c 100644 --- a/zkevm-circuits/src/test_util.rs +++ b/zkevm-circuits/src/test_util.rs @@ -34,6 +34,8 @@ pub fn get_fixed_table(conf: FixedTableConfig) -> Vec { FixedTableTag::Range1024, FixedTableTag::SignByte, FixedTableTag::ResponsibleOpcode, + FixedTableTag::Bitslevel, + FixedTableTag::Pow64, ] } FixedTableConfig::Complete => FixedTableTag::iter().collect(), diff --git a/zkevm-circuits/src/util.rs b/zkevm-circuits/src/util.rs index 96eada18e4..13d4218302 100644 --- a/zkevm-circuits/src/util.rs +++ b/zkevm-circuits/src/util.rs @@ -63,3 +63,6 @@ impl Expr for i32 { ) } } + +// the magic number is `echo 'zkevm-circuits' | hexdump` +pub(crate) const DEFAULT_RAND: u128 = 0x6b7a76652d6d6963637269757374u128;