From f3946f75bf0c6d2700e5da8eb7f492d7920bf7ea Mon Sep 17 00:00:00 2001 From: Jacqueline Nabaglo Date: Tue, 14 Feb 2023 22:30:19 -0800 Subject: [PATCH] Gas constraints (#880) * Gas constraints * Bugfix * make test pass post rebase --- evm/src/cpu/columns/mod.rs | 3 + evm/src/cpu/columns/ops.rs | 4 +- evm/src/cpu/control_flow.rs | 4 +- evm/src/cpu/cpu_stark.rs | 6 +- evm/src/cpu/decode.rs | 4 +- evm/src/cpu/gas.rs | 195 ++++++++++++++++++ evm/src/cpu/jumps.rs | 29 ++- evm/src/cpu/kernel/asm/core/syscall.asm | 2 +- evm/src/cpu/kernel/asm/core/syscall_stubs.asm | 1 + evm/src/cpu/kernel/asm/util/basic_macros.asm | 21 ++ evm/src/cpu/mod.rs | 1 + evm/src/cpu/stack.rs | 2 - evm/src/cpu/syscalls.rs | 37 +++- evm/src/memory/memory_stark.rs | 12 +- evm/src/witness/gas.rs | 50 +++++ evm/src/witness/memory.rs | 4 +- evm/src/witness/mod.rs | 1 + evm/src/witness/operation.rs | 45 ++-- evm/src/witness/state.rs | 2 + evm/src/witness/transition.rs | 10 +- 20 files changed, 358 insertions(+), 75 deletions(-) create mode 100644 evm/src/cpu/gas.rs create mode 100644 evm/src/witness/gas.rs diff --git a/evm/src/cpu/columns/mod.rs b/evm/src/cpu/columns/mod.rs index 408e17dc..f073c78e 100644 --- a/evm/src/cpu/columns/mod.rs +++ b/evm/src/cpu/columns/mod.rs @@ -62,6 +62,9 @@ pub struct CpuColumnsView { /// If CPU cycle: We're in kernel (privileged) mode. pub is_kernel_mode: T, + /// If CPU cycle: Gas counter. + pub gas: T, + /// If CPU cycle: flags for EVM instructions (a few cannot be shared; see the comments in /// `OpsColumnsView`). pub op: OpsColumnsView, diff --git a/evm/src/cpu/columns/ops.rs b/evm/src/cpu/columns/ops.rs index 1000d2fa..e3dc84d3 100644 --- a/evm/src/cpu/columns/ops.rs +++ b/evm/src/cpu/columns/ops.rs @@ -40,7 +40,6 @@ pub struct OpsColumnsView { pub jump: T, // Note: This column must be 0 when is_cpu_cycle = 0. pub jumpi: T, // Note: This column must be 0 when is_cpu_cycle = 0. pub pc: T, - pub gas: T, pub jumpdest: T, pub push: T, pub dup: T, @@ -48,13 +47,12 @@ pub struct OpsColumnsView { // TODO: combine GET_CONTEXT and SET_CONTEXT into one flag pub get_context: T, pub set_context: T, - pub consume_gas: T, pub exit_kernel: T, // TODO: combine MLOAD_GENERAL and MSTORE_GENERAL into one flag pub mload_general: T, pub mstore_general: T, - pub syscall: T, + pub syscall: T, // Note: This column must be 0 when is_cpu_cycle = 0. } // `u8` is guaranteed to have a `size_of` of 1. diff --git a/evm/src/cpu/control_flow.rs b/evm/src/cpu/control_flow.rs index c0adc7bd..0611744a 100644 --- a/evm/src/cpu/control_flow.rs +++ b/evm/src/cpu/control_flow.rs @@ -8,7 +8,7 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer use crate::cpu::columns::{CpuColumnsView, COL_MAP}; use crate::cpu::kernel::aggregator::KERNEL; -const NATIVE_INSTRUCTIONS: [usize; 33] = [ +const NATIVE_INSTRUCTIONS: [usize; 31] = [ COL_MAP.op.add, COL_MAP.op.mul, COL_MAP.op.sub, @@ -35,14 +35,12 @@ const NATIVE_INSTRUCTIONS: [usize; 33] = [ // not JUMP (need to jump) // not JUMPI (possible need to jump) COL_MAP.op.pc, - COL_MAP.op.gas, COL_MAP.op.jumpdest, // not PUSH (need to increment by more than 1) COL_MAP.op.dup, COL_MAP.op.swap, COL_MAP.op.get_context, COL_MAP.op.set_context, - COL_MAP.op.consume_gas, // not EXIT_KERNEL (performs a jump) COL_MAP.op.mload_general, COL_MAP.op.mstore_general, diff --git a/evm/src/cpu/cpu_stark.rs b/evm/src/cpu/cpu_stark.rs index b0e61dd2..d922c473 100644 --- a/evm/src/cpu/cpu_stark.rs +++ b/evm/src/cpu/cpu_stark.rs @@ -12,8 +12,8 @@ use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS}; use crate::cpu::membus::NUM_GP_CHANNELS; use crate::cpu::{ - bootstrap_kernel, contextops, control_flow, decode, dup_swap, jumps, membus, memio, modfp254, - pc, shift, simple_logic, stack, stack_bounds, syscalls, + bootstrap_kernel, contextops, control_flow, decode, dup_swap, gas, jumps, membus, memio, + modfp254, pc, shift, simple_logic, stack, stack_bounds, syscalls, }; use crate::cross_table_lookup::Column; use crate::memory::segments::Segment; @@ -148,6 +148,7 @@ impl, const D: usize> Stark for CpuStark, const D: usize> Stark for CpuStark = Some(0); +const G_JUMPDEST: Option = Some(1); +const G_BASE: Option = Some(2); +const G_VERYLOW: Option = Some(3); +const G_LOW: Option = Some(5); +const G_MID: Option = Some(8); +const G_HIGH: Option = Some(10); + +const SIMPLE_OPCODES: OpsColumnsView> = OpsColumnsView { + add: G_VERYLOW, + mul: G_LOW, + sub: G_VERYLOW, + div: G_LOW, + mod_: G_LOW, + addmod: G_MID, + mulmod: G_MID, + addfp254: KERNEL_ONLY_INSTR, + mulfp254: KERNEL_ONLY_INSTR, + subfp254: KERNEL_ONLY_INSTR, + submod: KERNEL_ONLY_INSTR, + lt: G_VERYLOW, + gt: G_VERYLOW, + eq: G_VERYLOW, + iszero: G_VERYLOW, + and: G_VERYLOW, + or: G_VERYLOW, + xor: G_VERYLOW, + not: G_VERYLOW, + byte: G_VERYLOW, + shl: G_VERYLOW, + shr: G_VERYLOW, + keccak_general: KERNEL_ONLY_INSTR, + prover_input: KERNEL_ONLY_INSTR, + pop: G_BASE, + jump: G_MID, + jumpi: G_HIGH, + pc: G_BASE, + jumpdest: G_JUMPDEST, + push: G_VERYLOW, + dup: G_VERYLOW, + swap: G_VERYLOW, + get_context: KERNEL_ONLY_INSTR, + set_context: KERNEL_ONLY_INSTR, + exit_kernel: None, + mload_general: KERNEL_ONLY_INSTR, + mstore_general: KERNEL_ONLY_INSTR, + syscall: None, +}; + +fn eval_packed_accumulate( + lv: &CpuColumnsView

, + nv: &CpuColumnsView

, + yield_constr: &mut ConstraintConsumer

, +) { + // Is it an instruction that we constrain here? + // I.e., does it always cost a constant amount of gas? + let is_simple_instr: P = SIMPLE_OPCODES + .into_iter() + .enumerate() + .filter_map(|(i, maybe_cost)| { + // Add flag `lv.op[i]` to the sum if `SIMPLE_OPCODES[i]` is `Some`. + maybe_cost.map(|_| lv.op[i]) + }) + .sum(); + let filter = lv.is_cpu_cycle * is_simple_instr; + + // How much gas did we use? + let gas_used: P = SIMPLE_OPCODES + .into_iter() + .enumerate() + .filter_map(|(i, maybe_cost)| { + maybe_cost.map(|cost| P::Scalar::from_canonical_u32(cost) * lv.op[i]) + }) + .sum(); + + let constr = nv.gas - (lv.gas + gas_used); + yield_constr.constraint_transition(filter * constr); + + for (maybe_cost, op_flag) in izip!(SIMPLE_OPCODES.into_iter(), lv.op.into_iter()) { + if let Some(cost) = maybe_cost { + let cost = P::Scalar::from_canonical_u32(cost); + yield_constr + .constraint_transition(lv.is_cpu_cycle * op_flag * (nv.gas - lv.gas - cost)); + } + } +} + +fn eval_packed_init( + lv: &CpuColumnsView

, + nv: &CpuColumnsView

, + yield_constr: &mut ConstraintConsumer

, +) { + // `nv` is the first row that executes an instruction. + let filter = (lv.is_cpu_cycle - P::ONES) * nv.is_cpu_cycle; + // Set initial gas to zero. + yield_constr.constraint_transition(filter * nv.gas); +} + +pub fn eval_packed( + lv: &CpuColumnsView

, + nv: &CpuColumnsView

, + yield_constr: &mut ConstraintConsumer

, +) { + eval_packed_accumulate(lv, nv, yield_constr); + eval_packed_init(lv, nv, yield_constr); +} + +fn eval_ext_circuit_accumulate, const D: usize>( + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + lv: &CpuColumnsView>, + nv: &CpuColumnsView>, + yield_constr: &mut RecursiveConstraintConsumer, +) { + // Is it an instruction that we constrain here? + // I.e., does it always cost a constant amount of gas? + let is_simple_instr = SIMPLE_OPCODES.into_iter().enumerate().fold( + builder.zero_extension(), + |cumul, (i, maybe_cost)| { + // Add flag `lv.op[i]` to the sum if `SIMPLE_OPCODES[i]` is `Some`. + match maybe_cost { + None => cumul, + Some(_) => builder.add_extension(lv.op[i], cumul), + } + }, + ); + let filter = builder.mul_extension(lv.is_cpu_cycle, is_simple_instr); + + // How much gas did we use? + let gas_used = SIMPLE_OPCODES.into_iter().enumerate().fold( + builder.zero_extension(), + |cumul, (i, maybe_cost)| match maybe_cost { + None => cumul, + Some(cost) => { + let cost_ext = builder.constant_extension(F::from_canonical_u32(cost).into()); + builder.mul_add_extension(lv.op[i], cost_ext, cumul) + } + }, + ); + + let constr = { + let t = builder.add_extension(lv.gas, gas_used); + builder.sub_extension(nv.gas, t) + }; + let filtered_constr = builder.mul_extension(filter, constr); + yield_constr.constraint_transition(builder, filtered_constr); + + for (maybe_cost, op_flag) in izip!(SIMPLE_OPCODES.into_iter(), lv.op.into_iter()) { + if let Some(cost) = maybe_cost { + let filter = builder.mul_extension(lv.is_cpu_cycle, op_flag); + let nv_lv_diff = builder.sub_extension(nv.gas, lv.gas); + let constr = builder.arithmetic_extension( + F::ONE, + -F::from_canonical_u32(cost), + filter, + nv_lv_diff, + filter, + ); + yield_constr.constraint_transition(builder, constr); + } + } +} + +fn eval_ext_circuit_init, const D: usize>( + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + lv: &CpuColumnsView>, + nv: &CpuColumnsView>, + yield_constr: &mut RecursiveConstraintConsumer, +) { + // `nv` is the first row that executes an instruction. + let filter = builder.mul_sub_extension(lv.is_cpu_cycle, nv.is_cpu_cycle, nv.is_cpu_cycle); + // Set initial gas to zero. + let constr = builder.mul_extension(filter, nv.gas); + yield_constr.constraint_transition(builder, constr); +} + +pub fn eval_ext_circuit, const D: usize>( + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + lv: &CpuColumnsView>, + nv: &CpuColumnsView>, + yield_constr: &mut RecursiveConstraintConsumer, +) { + eval_ext_circuit_accumulate(builder, lv, nv, yield_constr); + eval_ext_circuit_init(builder, lv, nv, yield_constr); +} diff --git a/evm/src/cpu/jumps.rs b/evm/src/cpu/jumps.rs index d6fabc4d..2f2f30a9 100644 --- a/evm/src/cpu/jumps.rs +++ b/evm/src/cpu/jumps.rs @@ -15,16 +15,16 @@ pub fn eval_packed_exit_kernel( yield_constr: &mut ConstraintConsumer

, ) { let input = lv.mem_channels[0].value; + let filter = lv.is_cpu_cycle * lv.op.exit_kernel; - // If we are executing `EXIT_KERNEL` then we simply restore the program counter and kernel mode - // flag. The top 6 (32-bit) limbs are ignored (this is not part of the spec, but we trust the - // kernel to set them to zero). - yield_constr.constraint_transition( - lv.is_cpu_cycle * lv.op.exit_kernel * (input[0] - nv.program_counter), - ); - yield_constr.constraint_transition( - lv.is_cpu_cycle * lv.op.exit_kernel * (input[1] - nv.is_kernel_mode), - ); + // If we are executing `EXIT_KERNEL` then we simply restore the program counter, kernel mode + // flag, and gas counter. The middle 4 (32-bit) limbs are ignored (this is not part of the spec, + // but we trust the kernel to set them to zero). + yield_constr.constraint_transition(filter * (input[0] - nv.program_counter)); + yield_constr.constraint_transition(filter * (input[1] - nv.is_kernel_mode)); + yield_constr.constraint_transition(filter * (input[6] - nv.gas)); + // High limb of gas must be 0 for convenient detection of overflow. + yield_constr.constraint(filter * input[7]); } pub fn eval_ext_circuit_exit_kernel, const D: usize>( @@ -47,6 +47,17 @@ pub fn eval_ext_circuit_exit_kernel, const D: usize let kernel_constr = builder.sub_extension(input[1], nv.is_kernel_mode); let kernel_constr = builder.mul_extension(filter, kernel_constr); yield_constr.constraint_transition(builder, kernel_constr); + + { + let diff = builder.sub_extension(input[6], nv.gas); + let constr = builder.mul_extension(filter, diff); + yield_constr.constraint_transition(builder, constr); + } + { + // High limb of gas must be 0 for convenient detection of overflow. + let constr = builder.mul_extension(filter, input[7]); + yield_constr.constraint(builder, constr); + } } pub fn eval_packed_jump_jumpi( diff --git a/evm/src/cpu/kernel/asm/core/syscall.asm b/evm/src/cpu/kernel/asm/core/syscall.asm index 2de50993..d3e7683e 100644 --- a/evm/src/cpu/kernel/asm/core/syscall.asm +++ b/evm/src/cpu/kernel/asm/core/syscall.asm @@ -84,7 +84,7 @@ global syscall_jumptable: JUMPTABLE panic // jumpi is implemented natively JUMPTABLE panic // pc is implemented natively JUMPTABLE sys_msize - JUMPTABLE panic // gas is implemented natively + JUMPTABLE sys_gas JUMPTABLE panic // jumpdest is implemented natively JUMPTABLE panic // 0x5c is an invalid opcode JUMPTABLE panic // 0x5d is an invalid opcode diff --git a/evm/src/cpu/kernel/asm/core/syscall_stubs.asm b/evm/src/cpu/kernel/asm/core/syscall_stubs.asm index 0f029143..dfaa8bac 100644 --- a/evm/src/cpu/kernel/asm/core/syscall_stubs.asm +++ b/evm/src/cpu/kernel/asm/core/syscall_stubs.asm @@ -35,6 +35,7 @@ global sys_basefee: global sys_sload: global sys_sstore: global sys_msize: +global sys_gas: global sys_log0: global sys_log1: global sys_log2: diff --git a/evm/src/cpu/kernel/asm/util/basic_macros.asm b/evm/src/cpu/kernel/asm/util/basic_macros.asm index 2fbd2bd1..6640892f 100644 --- a/evm/src/cpu/kernel/asm/util/basic_macros.asm +++ b/evm/src/cpu/kernel/asm/util/basic_macros.asm @@ -345,3 +345,24 @@ %endrep // stack: a || b || c || d %endmacro + +// Charge gas. +// Arguments: +// stack[0]: gas to be charged +// stack[1]: syscall info +// Returns: +// new syscall info +%macro charge_gas + %shl_const(192) + ADD +%endmacro + +// Charge gas and exit kernel code. +// Arguments: +// stack[0]: gas to be charged +// stack[1]: syscall info +// Returns: nothing +%macro charge_gas_and_exit + %charge_gas + EXIT_KERNEL +%endmacro diff --git a/evm/src/cpu/mod.rs b/evm/src/cpu/mod.rs index 466cf112..22878e80 100644 --- a/evm/src/cpu/mod.rs +++ b/evm/src/cpu/mod.rs @@ -5,6 +5,7 @@ pub(crate) mod control_flow; pub mod cpu_stark; pub(crate) mod decode; mod dup_swap; +mod gas; mod jumps; pub mod kernel; pub(crate) mod membus; diff --git a/evm/src/cpu/stack.rs b/evm/src/cpu/stack.rs index ee96a682..e9258172 100644 --- a/evm/src/cpu/stack.rs +++ b/evm/src/cpu/stack.rs @@ -80,7 +80,6 @@ const STACK_BEHAVIORS: OpsColumnsView> = OpsColumnsView { pushes: true, disable_other_channels: true, }), - gas: None, // TODO jumpdest: Some(StackBehavior { num_pops: 0, pushes: false, @@ -95,7 +94,6 @@ const STACK_BEHAVIORS: OpsColumnsView> = OpsColumnsView { disable_other_channels: true, }), set_context: None, // SET_CONTEXT is special since it involves the old and the new stack. - consume_gas: None, // TODO exit_kernel: Some(StackBehavior { num_pops: 1, pushes: false, diff --git a/evm/src/cpu/syscalls.rs b/evm/src/cpu/syscalls.rs index d8020a7a..0686bf48 100644 --- a/evm/src/cpu/syscalls.rs +++ b/evm/src/cpu/syscalls.rs @@ -24,7 +24,7 @@ pub fn eval_packed( nv: &CpuColumnsView

, yield_constr: &mut ConstraintConsumer

, ) { - let filter = lv.is_cpu_cycle * lv.op.syscall; + let filter = lv.op.syscall; // Look up the handler in memory let code_segment = P::Scalar::from_canonical_usize(Segment::Code as usize); @@ -69,15 +69,20 @@ pub fn eval_packed( yield_constr.constraint_transition(filter * (nv.is_kernel_mode - P::ONES)); // Maintain current context yield_constr.constraint_transition(filter * (nv.context - lv.context)); + // Reset gas counter to zero. + yield_constr.constraint_transition(filter * nv.gas); // This memory channel is constrained in `stack.rs`. let output = lv.mem_channels[NUM_GP_CHANNELS - 1].value; - // Push current PC + 1 to stack + // Push to stack: current PC + 1 (limb 0), kernel flag (limb 1), gas counter (limbs 6 and 7). yield_constr.constraint(filter * (output[0] - (lv.program_counter + P::ONES))); - // Push current kernel flag to stack (share register with PC) yield_constr.constraint(filter * (output[1] - lv.is_kernel_mode)); + yield_constr.constraint(filter * (output[6] - lv.gas)); + // TODO: Range check `output[6]`. + yield_constr.constraint(filter * output[7]); // High limb of gas is zero. + // Zero the rest of that register - for &limb in &output[2..] { + for &limb in &output[2..6] { yield_constr.constraint(filter * limb); } } @@ -88,7 +93,7 @@ pub fn eval_ext_circuit, const D: usize>( nv: &CpuColumnsView>, yield_constr: &mut RecursiveConstraintConsumer, ) { - let filter = builder.mul_extension(lv.is_cpu_cycle, lv.op.syscall); + let filter = lv.op.syscall; // Look up the handler in memory let code_segment = F::from_canonical_usize(Segment::Code as usize); @@ -177,24 +182,40 @@ pub fn eval_ext_circuit, const D: usize>( let constr = builder.mul_extension(filter, diff); yield_constr.constraint_transition(builder, constr); } + // Reset gas counter to zero. + { + let constr = builder.mul_extension(filter, nv.gas); + yield_constr.constraint_transition(builder, constr); + } // This memory channel is constrained in `stack.rs`. let output = lv.mem_channels[NUM_GP_CHANNELS - 1].value; - // Push current PC + 1 to stack + // Push to stack: current PC + 1 (limb 0), kernel flag (limb 1), gas counter (limbs 6 and 7). { let pc_plus_1 = builder.add_const_extension(lv.program_counter, F::ONE); let diff = builder.sub_extension(output[0], pc_plus_1); let constr = builder.mul_extension(filter, diff); yield_constr.constraint(builder, constr); } - // Push current kernel flag to stack (share register with PC) { let diff = builder.sub_extension(output[1], lv.is_kernel_mode); let constr = builder.mul_extension(filter, diff); yield_constr.constraint(builder, constr); } + { + let diff = builder.sub_extension(output[6], lv.gas); + let constr = builder.mul_extension(filter, diff); + yield_constr.constraint(builder, constr); + } + // TODO: Range check `output[6]`. + { + // High limb of gas is zero. + let constr = builder.mul_extension(filter, output[7]); + yield_constr.constraint(builder, constr); + } + // Zero the rest of that register - for &limb in &output[2..] { + for &limb in &output[2..6] { let constr = builder.mul_extension(filter, limb); yield_constr.constraint(builder, constr); } diff --git a/evm/src/memory/memory_stark.rs b/evm/src/memory/memory_stark.rs index 88dcbf40..a0481029 100644 --- a/evm/src/memory/memory_stark.rs +++ b/evm/src/memory/memory_stark.rs @@ -1,5 +1,6 @@ use std::marker::PhantomData; +use ethereum_types::U256; use itertools::Itertools; use plonky2::field::extension::{Extendable, FieldExtension}; use plonky2::field::packed::PackedField; @@ -175,14 +176,14 @@ impl, const D: usize> MemoryStark { while next.address.virt - curr.address.virt - 1 > max_rc { let mut dummy_address = curr.address; dummy_address.virt += max_rc + 1; - let dummy_read = MemoryOp::new_dummy_read(dummy_address, 0); + let dummy_read = MemoryOp::new_dummy_read(dummy_address, 0, U256::zero()); memory_ops.push(dummy_read); curr = dummy_read; } } else { while next.timestamp - curr.timestamp > max_rc { let dummy_read = - MemoryOp::new_dummy_read(curr.address, curr.timestamp + max_rc); + MemoryOp::new_dummy_read(curr.address, curr.timestamp + max_rc, curr.value); memory_ops.push(dummy_read); curr = dummy_read; } @@ -311,8 +312,9 @@ impl, const D: usize> Stark for MemoryStark, const D: usize> Stark for MemoryStark u64 { + use crate::arithmetic::BinaryOperator::*; + use crate::witness::operation::Operation::*; + match op { + Iszero => G_VERYLOW, + Not => G_VERYLOW, + Byte => G_VERYLOW, + Syscall(_) => KERNEL_ONLY_INSTR, + Eq => G_VERYLOW, + BinaryLogic(_) => G_VERYLOW, + BinaryArithmetic(Add) => G_VERYLOW, + BinaryArithmetic(Mul) => G_LOW, + BinaryArithmetic(Sub) => G_VERYLOW, + BinaryArithmetic(Div) => G_LOW, + BinaryArithmetic(Mod) => G_LOW, + BinaryArithmetic(Lt) => G_VERYLOW, + BinaryArithmetic(Gt) => G_VERYLOW, + Shl => G_VERYLOW, + Shr => G_VERYLOW, + BinaryArithmetic(AddFp254) => KERNEL_ONLY_INSTR, + BinaryArithmetic(MulFp254) => KERNEL_ONLY_INSTR, + BinaryArithmetic(SubFp254) => KERNEL_ONLY_INSTR, + TernaryArithmetic(_) => G_MID, + KeccakGeneral => KERNEL_ONLY_INSTR, + ProverInput => KERNEL_ONLY_INSTR, + Pop => G_BASE, + Jump => G_MID, + Jumpi => G_HIGH, + Pc => G_BASE, + Jumpdest => G_JUMPDEST, + Push(_) => G_VERYLOW, + Dup(_) => G_VERYLOW, + Swap(_) => G_VERYLOW, + GetContext => KERNEL_ONLY_INSTR, + SetContext => KERNEL_ONLY_INSTR, + ExitKernel => KERNEL_ONLY_INSTR, + MloadGeneral => KERNEL_ONLY_INSTR, + MstoreGeneral => KERNEL_ONLY_INSTR, + } +} diff --git a/evm/src/witness/memory.rs b/evm/src/witness/memory.rs index e0eba65e..f42dcaac 100644 --- a/evm/src/witness/memory.rs +++ b/evm/src/witness/memory.rs @@ -94,13 +94,13 @@ impl MemoryOp { } } - pub(crate) fn new_dummy_read(address: MemoryAddress, timestamp: usize) -> Self { + pub(crate) fn new_dummy_read(address: MemoryAddress, timestamp: usize, value: U256) -> Self { Self { filter: false, timestamp, address, kind: MemoryOpKind::Read, - value: U256::zero(), + value, } } diff --git a/evm/src/witness/mod.rs b/evm/src/witness/mod.rs index b9da345e..7d491e4e 100644 --- a/evm/src/witness/mod.rs +++ b/evm/src/witness/mod.rs @@ -1,4 +1,5 @@ mod errors; +mod gas; pub(crate) mod memory; mod operation; pub(crate) mod state; diff --git a/evm/src/witness/operation.rs b/evm/src/witness/operation.rs index 54d8ce3f..3d1dddde 100644 --- a/evm/src/witness/operation.rs +++ b/evm/src/witness/operation.rs @@ -14,8 +14,8 @@ use crate::memory::segments::Segment; use crate::witness::errors::ProgramError; use crate::witness::memory::{MemoryAddress, MemoryOp}; use crate::witness::util::{ - keccak_sponge_log, mem_read_code_with_log_and_fill, mem_read_gp_with_log_and_fill, - mem_write_gp_log_and_fill, stack_pop_with_log_and_fill, stack_push_log_and_fill, + keccak_sponge_log, mem_read_gp_with_log_and_fill, mem_write_gp_log_and_fill, + stack_pop_with_log_and_fill, stack_push_log_and_fill, }; use crate::{arithmetic, logic}; @@ -37,14 +37,12 @@ pub(crate) enum Operation { Jump, Jumpi, Pc, - Gas, Jumpdest, Push(u8), Dup(u8), Swap(u8), GetContext, SetContext, - ConsumeGas, ExitKernel, MloadGeneral, MstoreGeneral, @@ -323,8 +321,6 @@ pub(crate) fn generate_push( let code_context = state.registers.code_context(); let num_bytes = n as usize + 1; let initial_offset = state.registers.program_counter + 1; - let offsets = initial_offset..initial_offset + num_bytes; - let mut addrs = offsets.map(|offset| MemoryAddress::new(code_context, Segment::Code, offset)); // First read val without going through `mem_read_with_log` type methods, so we can pass it // to stack_push_log_and_fill. @@ -344,33 +340,9 @@ pub(crate) fn generate_push( let val = U256::from_big_endian(&bytes); let write = stack_push_log_and_fill(state, &mut row, val)?; - // In the first cycle, we read up to NUM_GP_CHANNELS - 1 bytes, leaving the last GP channel - // to push the result. - for (i, addr) in (&mut addrs).take(NUM_GP_CHANNELS - 1).enumerate() { - let (_, read) = mem_read_gp_with_log_and_fill(i, addr, state, &mut row); - state.traces.push_memory(read); - } state.traces.push_memory(write); state.traces.push_cpu(row); - // In any subsequent cycles, we read up to 1 + NUM_GP_CHANNELS bytes. - for mut addrs_chunk in &addrs.chunks(1 + NUM_GP_CHANNELS) { - let mut row = CpuColumnsView::default(); - row.is_cpu_cycle = F::ONE; - row.op.push = F::ONE; - - let first_addr = addrs_chunk.next().unwrap(); - let (_, first_read) = mem_read_code_with_log_and_fill(first_addr, state, &mut row); - state.traces.push_memory(first_read); - - for (i, addr) in addrs_chunk.enumerate() { - let (_, read) = mem_read_gp_with_log_and_fill(i, addr, state, &mut row); - state.traces.push_memory(read); - } - - state.traces.push_cpu(row); - } - Ok(()) } @@ -530,6 +502,10 @@ pub(crate) fn generate_syscall( state: &mut GenerationState, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { + if TryInto::::try_into(state.registers.gas_used).is_err() { + panic!(); + } + let handler_jumptable_addr = KERNEL.global_labels["syscall_jumptable"]; let handler_addr_addr = handler_jumptable_addr + (opcode as usize) * (BYTES_PER_OFFSET as usize); @@ -557,12 +533,14 @@ pub(crate) fn generate_syscall( let new_program_counter = handler_addr.as_usize(); let syscall_info = U256::from(state.registers.program_counter + 1) - + (U256::from(u64::from(state.registers.is_kernel)) << 32); + + (U256::from(u64::from(state.registers.is_kernel)) << 32) + + (U256::from(state.registers.gas_used) << 192); let log_out = stack_push_log_and_fill(state, &mut row, syscall_info)?; state.registers.program_counter = new_program_counter; log::debug!("Syscall to {}", KERNEL.offset_name(new_program_counter)); state.registers.is_kernel = true; + state.registers.gas_used = 0; state.traces.push_memory(log_in0); state.traces.push_memory(log_in1); @@ -601,9 +579,14 @@ pub(crate) fn generate_exit_kernel( let is_kernel_mode_val = (kexit_info_u64 >> 32) as u32; assert!(is_kernel_mode_val == 0 || is_kernel_mode_val == 1); let is_kernel_mode = is_kernel_mode_val != 0; + let gas_used_val = kexit_info.0[3]; + if TryInto::::try_into(gas_used_val).is_err() { + panic!(); + } state.registers.program_counter = program_counter; state.registers.is_kernel = is_kernel_mode; + state.registers.gas_used = gas_used_val; log::debug!( "Exiting to {}, is_kernel={}", KERNEL.offset_name(program_counter), diff --git a/evm/src/witness/state.rs b/evm/src/witness/state.rs index ecb1bfb2..3b37b01e 100644 --- a/evm/src/witness/state.rs +++ b/evm/src/witness/state.rs @@ -8,6 +8,7 @@ pub struct RegistersState { pub is_kernel: bool, pub stack_len: usize, pub context: usize, + pub gas_used: u64, } impl RegistersState { @@ -27,6 +28,7 @@ impl Default for RegistersState { is_kernel: true, stack_len: 0, context: 0, + gas_used: 0, } } } diff --git a/evm/src/witness/transition.rs b/evm/src/witness/transition.rs index a60141c5..689dbdd1 100644 --- a/evm/src/witness/transition.rs +++ b/evm/src/witness/transition.rs @@ -6,6 +6,7 @@ use crate::cpu::kernel::aggregator::KERNEL; use crate::generation::state::GenerationState; use crate::memory::segments::Segment; use crate::witness::errors::ProgramError; +use crate::witness::gas::gas_to_charge; use crate::witness::memory::MemoryAddress; use crate::witness::operation::*; use crate::witness::state::RegistersState; @@ -106,7 +107,6 @@ fn decode(registers: RegistersState, opcode: u8) -> Result Ok(Operation::Jumpi), (0x58, _) => Ok(Operation::Pc), (0x59, _) => Ok(Operation::Syscall(opcode)), - (0x5a, _) => Ok(Operation::Gas), (0x5b, _) => Ok(Operation::Jumpdest), (0x60..=0x7f, _) => Ok(Operation::Push(opcode & 0x1f)), (0x80..=0x8f, _) => Ok(Operation::Dup(opcode & 0xf)), @@ -128,7 +128,6 @@ fn decode(registers: RegistersState, opcode: u8) -> Result Ok(Operation::Syscall(opcode)), (0xf6, true) => Ok(Operation::GetContext), (0xf7, true) => Ok(Operation::SetContext), - (0xf8, true) => Ok(Operation::ConsumeGas), (0xf9, true) => Ok(Operation::ExitKernel), (0xfa, _) => Ok(Operation::Syscall(opcode)), (0xfb, true) => Ok(Operation::MloadGeneral), @@ -177,11 +176,9 @@ fn fill_op_flag(op: Operation, row: &mut CpuColumnsView) { Operation::Jump => &mut flags.jump, Operation::Jumpi => &mut flags.jumpi, Operation::Pc => &mut flags.pc, - Operation::Gas => &mut flags.gas, Operation::Jumpdest => &mut flags.jumpdest, Operation::GetContext => &mut flags.get_context, Operation::SetContext => &mut flags.set_context, - Operation::ConsumeGas => &mut flags.consume_gas, Operation::ExitKernel => &mut flags.exit_kernel, Operation::MloadGeneral => &mut flags.mload_general, Operation::MstoreGeneral => &mut flags.mstore_general, @@ -215,11 +212,9 @@ fn perform_op( Operation::Jump => generate_jump(state, row)?, Operation::Jumpi => generate_jumpi(state, row)?, Operation::Pc => generate_pc(state, row)?, - Operation::Gas => todo!(), Operation::Jumpdest => generate_jumpdest(state, row)?, Operation::GetContext => generate_get_context(state, row)?, Operation::SetContext => generate_set_context(state, row)?, - Operation::ConsumeGas => todo!(), Operation::ExitKernel => generate_exit_kernel(state, row)?, Operation::MloadGeneral => generate_mload_general(state, row)?, Operation::MstoreGeneral => generate_mstore_general(state, row)?, @@ -232,6 +227,8 @@ fn perform_op( _ => 1, }; + state.registers.gas_used += gas_to_charge(op); + Ok(()) } @@ -242,6 +239,7 @@ fn try_perform_instruction(state: &mut GenerationState) -> Result<( row.context = F::from_canonical_usize(state.registers.context); row.program_counter = F::from_canonical_usize(state.registers.program_counter); row.is_kernel_mode = F::from_bool(state.registers.is_kernel); + row.gas = F::from_canonical_u64(state.registers.gas_used); row.stack_len = F::from_canonical_usize(state.registers.stack_len); let opcode = read_code_memory(state, &mut row);