diff --git a/eth-types/src/evm_types.rs b/eth-types/src/evm_types.rs index 29ffa3272d..f1e7cef7b2 100644 --- a/eth-types/src/evm_types.rs +++ b/eth-types/src/evm_types.rs @@ -103,6 +103,16 @@ impl GasCost { pub const COLD_ACCOUNT_ACCESS_COST: Self = Self(2600); /// Constant cost for a warm storage read pub const WARM_STORAGE_READ_COST: Self = Self(100); + + /// Constant cost for a warm storage read XXXXXX + pub const SLOAD_GAS: Self = Self(100); + /// Constant cost for a warm storage read XXXXXX + pub const SSTORE_SET_GAS: Self = Self(20000); + /// Constant cost for a warm storage read XXXXXX + pub const SSTORE_RESET_GAS: Self = Self(2900); + /// Constant cost for a warm storage read XXXXXX + pub const SSTORE_CLEARS_SCHEDULE: Self = Self(15000); + /// Constant cost for a non-creation transaction pub const TX: Self = Self(21000); /// Constant cost for creation transaction diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 0cddf45e9a..23988bfc25 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -44,6 +44,7 @@ mod selfbalance; mod signed_comparator; mod signextend; mod sload; +mod sstore; mod stop; mod swap; mod timestamp; @@ -75,6 +76,7 @@ use selfbalance::SelfbalanceGadget; use signed_comparator::SignedComparatorGadget; use signextend::SignextendGadget; use sload::SloadGadget; +use sstore::SstoreGadget; use stop::StopGadget; use swap::SwapGadget; use timestamp::TimestampGadget; @@ -133,6 +135,7 @@ pub(crate) struct ExecutionConfig { timestamp_gadget: TimestampGadget, selfbalance_gadget: SelfbalanceGadget, sload_gadget: SloadGadget, + sstore_gadget: SstoreGadget, } impl ExecutionConfig { @@ -268,6 +271,7 @@ impl ExecutionConfig { coinbase_gadget: configure_gadget!(), timestamp_gadget: configure_gadget!(), sload_gadget: configure_gadget!(), + sstore_gadget: configure_gadget!(), step: step_curr, presets_map, }; @@ -524,6 +528,7 @@ impl ExecutionConfig { } ExecutionState::SELFBALANCE => assign_exec_step!(self.selfbalance_gadget), ExecutionState::SLOAD => assign_exec_step!(self.sload_gadget), + ExecutionState::SSTORE => assign_exec_step!(self.sstore_gadget), ExecutionState::CALLDATACOPY => { assign_exec_step!(self.calldatacopy_gadget) } diff --git a/zkevm-circuits/src/evm_circuit/execution/sstore.rs b/zkevm-circuits/src/evm_circuit/execution/sstore.rs new file mode 100644 index 0000000000..7515146c23 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/sstore.rs @@ -0,0 +1,885 @@ +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + step::ExecutionState, + table::CallContextFieldTag, + util::{ + common_gadget::SameContextGadget, + constraint_builder::{ + ConstraintBuilder, StepStateTransition, + Transition::{Delta, To}, + }, + math_gadget::{IsEqualGadget, IsZeroGadget}, + select, Cell, Word, + }, + witness::{Block, Call, ExecStep, Transaction}, + }, + util::Expr, +}; +use eth_types::{evm_types::GasCost, Field, ToLittleEndian, ToScalar}; +use halo2_proofs::{ + circuit::Region, + plonk::{Error, Expression}, +}; + +#[derive(Clone, Debug)] +pub(crate) struct SstoreGadget { + same_context: SameContextGadget, + call_id: Cell, + tx_id: Cell, + rw_counter_end_of_reversion: Cell, + is_persistent: Cell, + callee_address: Cell, + key: Cell, + value: Cell, + value_prev: Cell, + committed_value: Cell, + is_warm: Cell, + tx_refund_prev: Cell, + gas_cost: SstoreGasGadget, +} + +impl ExecutionGadget for SstoreGadget { + const NAME: &'static str = "SSTORE"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::SSTORE; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let opcode = cb.query_cell(); + + let call_id = cb.query_cell(); + let [tx_id, rw_counter_end_of_reversion, is_persistent, callee_address] = [ + CallContextFieldTag::TxId, + CallContextFieldTag::RwCounterEndOfReversion, + CallContextFieldTag::IsPersistent, + CallContextFieldTag::CalleeAddress, + ] + .map(|field_tag| cb.call_context(Some(call_id.expr()), field_tag)); + + let key = cb.query_cell(); + // Pop the key from the stack + cb.stack_pop(key.expr()); + + let value = cb.query_cell(); + // Pop the value from the stack + cb.stack_pop(value.expr()); + + let value_prev = cb.query_cell(); + let committed_value = cb.query_cell(); + cb.account_storage_write_with_reversion( + callee_address.expr(), + key.expr(), + value.expr(), + value_prev.expr(), + tx_id.expr(), + committed_value.expr(), + is_persistent.expr(), + rw_counter_end_of_reversion.expr(), + ); + + let is_warm = cb.query_bool(); + cb.account_storage_access_list_write_with_reversion( + tx_id.expr(), + callee_address.expr(), + key.expr(), + true.expr(), + is_warm.expr(), + is_persistent.expr(), + rw_counter_end_of_reversion.expr(), + ); + + let gas_cost = SstoreGasGadget::construct( + cb, + value.clone(), + value_prev.clone(), + committed_value.clone(), + is_warm.clone(), + ); + + // TODO: TxRefund + let tx_refund_prev = cb.query_cell(); + let tx_refund = cb.query_cell(); + cb.tx_refund_write_with_reversion( + tx_id.expr(), + tx_refund.expr(), + tx_refund_prev.expr(), + is_persistent.expr(), + rw_counter_end_of_reversion.expr(), + ); + + let step_state_transition = StepStateTransition { + rw_counter: Delta(9.expr()), + program_counter: Delta(1.expr()), + stack_pointer: Delta(-(2.expr())), + state_write_counter: To(3.expr()), + ..Default::default() + }; + let same_context = + SameContextGadget::construct(cb, opcode, step_state_transition, Some(gas_cost.expr())); + + Self { + same_context, + call_id, + tx_id, + rw_counter_end_of_reversion, + is_persistent, + callee_address, + key, + value, + value_prev, + committed_value, + is_warm, + tx_refund_prev, + gas_cost, + } + } + + fn assign_exec_step( + &self, + region: &mut Region<'_, F>, + offset: usize, + block: &Block, + tx: &Transaction, + call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + self.same_context.assign_exec_step(region, offset, step)?; + + self.call_id + .assign(region, offset, Some(F::from(call.id as u64)))?; + + self.tx_id + .assign(region, offset, Some(F::from(tx.id as u64)))?; + self.rw_counter_end_of_reversion.assign( + region, + offset, + Some(F::from(call.rw_counter_end_of_reversion as u64)), + )?; + self.is_persistent + .assign(region, offset, Some(F::from(call.is_persistent as u64)))?; + self.callee_address + .assign(region, offset, call.callee_address.to_scalar())?; + + let [key, value] = + [step.rw_indices[4], step.rw_indices[5]].map(|idx| block.rws[idx].stack_value()); + self.key.assign( + region, + offset, + Some(Word::random_linear_combine( + key.to_le_bytes(), + block.randomness, + )), + )?; + self.value.assign( + region, + offset, + Some(Word::random_linear_combine( + value.to_le_bytes(), + block.randomness, + )), + )?; + + let (_, value_prev, _, committed_value) = block.rws[step.rw_indices[6]].storage_value_aux(); + self.value_prev.assign( + region, + offset, + Some(Word::random_linear_combine( + value_prev.to_le_bytes(), + block.randomness, + )), + )?; + self.committed_value.assign( + region, + offset, + Some(Word::random_linear_combine( + committed_value.to_le_bytes(), + block.randomness, + )), + )?; + + let (_, is_warm) = block.rws[step.rw_indices[7]].tx_access_list_value_pair(); + self.is_warm + .assign(region, offset, Some(F::from(is_warm as u64)))?; + + let (_, tx_refund_prev) = block.rws[step.rw_indices[8]].tx_refund_value_pair(); + self.tx_refund_prev + .assign(region, offset, Some(F::from(tx_refund_prev.as_u64())))?; + + self.gas_cost.assign( + region, + offset, + value, + value_prev, + committed_value, + is_warm, + block.randomness, + )?; + + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct SstoreGasGadget { + value: Cell, + value_prev: Cell, + committed_value: Cell, + is_warm: Cell, + gas_cost: Expression, + value_eq_prev: IsEqualGadget, + original_eq_prev: IsEqualGadget, + original_is_zero: IsZeroGadget, +} + +impl SstoreGasGadget { + pub(crate) fn construct( + cb: &mut ConstraintBuilder, + value: Cell, + value_prev: Cell, + committed_value: Cell, + is_warm: Cell, + ) -> Self { + let value_eq_prev = IsEqualGadget::construct(cb, value.expr(), value_prev.expr()); + let original_eq_prev = + IsEqualGadget::construct(cb, committed_value.expr(), value_prev.expr()); + let original_is_zero = IsZeroGadget::construct(cb, committed_value.expr()); + let warm_case_gas = select::expr( + value_eq_prev.expr(), + GasCost::SLOAD_GAS.expr(), + select::expr( + original_eq_prev.expr(), + select::expr( + original_is_zero.expr(), + GasCost::SSTORE_SET_GAS.expr(), + GasCost::SSTORE_RESET_GAS.expr(), + ), + GasCost::SLOAD_GAS.expr(), + ), + ); + let gas_cost = select::expr( + is_warm.expr(), + warm_case_gas.expr(), + warm_case_gas + GasCost::COLD_SLOAD_COST.expr(), + ); + + Self { + value, + value_prev, + committed_value, + is_warm, + gas_cost, + value_eq_prev, + original_eq_prev, + original_is_zero, + } + } + + pub(crate) fn expr(&self) -> Expression { + // Return the gas cost + self.gas_cost.clone() + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn assign( + &self, + region: &mut Region<'_, F>, + offset: usize, + value: eth_types::Word, + value_prev: eth_types::Word, + committed_value: eth_types::Word, + is_warm: bool, + randomness: F, + ) -> Result<(), Error> { + self.value.assign( + region, + offset, + Some(Word::random_linear_combine(value.to_le_bytes(), randomness)), + )?; + self.value_prev.assign( + region, + offset, + Some(Word::random_linear_combine( + value_prev.to_le_bytes(), + randomness, + )), + )?; + self.committed_value.assign( + region, + offset, + Some(Word::random_linear_combine( + committed_value.to_le_bytes(), + randomness, + )), + )?; + self.is_warm + .assign(region, offset, Some(F::from(is_warm as u64)))?; + self.value_eq_prev.assign( + region, + offset, + Word::random_linear_combine(value.to_le_bytes(), randomness), + Word::random_linear_combine(value_prev.to_le_bytes(), randomness), + )?; + self.original_eq_prev.assign( + region, + offset, + Word::random_linear_combine(committed_value.to_le_bytes(), randomness), + Word::random_linear_combine(value_prev.to_le_bytes(), randomness), + )?; + self.original_is_zero.assign( + region, + offset, + Word::random_linear_combine(committed_value.to_le_bytes(), randomness), + )?; + Ok(()) + } +} + +// TODO: +#[derive(Clone, Debug)] +pub(crate) struct SstoreTxRefundGadget { + value: Expression, + value_prev: Expression, + committed_value: Expression, + is_warm: Expression, + tx_refund_old: Expression, + tx_refund_new: Expression, +} + +// TODO: +impl SstoreTxRefundGadget { + pub(crate) fn construct( + _cb: &mut ConstraintBuilder, + value: Expression, + value_prev: Expression, + committed_value: Expression, + tx_refund_old: Expression, + is_warm: Expression, + ) -> Self { + let tx_refund_new = select::expr( + is_warm.expr(), + GasCost::WARM_STORAGE_READ_COST.expr(), + GasCost::COLD_SLOAD_COST.expr(), + ); + + Self { + value, + value_prev, + committed_value, + is_warm, + tx_refund_old, + tx_refund_new, + } + } + + pub(crate) fn expr(&self) -> Expression { + // Return the new tx_refund + self.tx_refund_new.clone() + } +} + +#[cfg(test)] +mod test { + use crate::evm_circuit::{ + param::STACK_CAPACITY, + step::ExecutionState, + table::{CallContextFieldTag, RwTableTag}, + test::{rand_fp, run_test_circuit_incomplete_fixed_table}, + witness::{Block, Bytecode, Call, CodeSource, ExecStep, Rw, RwMap, Transaction}, + }; + + use bus_mapping::evm::OpcodeId; + use eth_types::{address, bytecode, evm_types::GasCost, ToWord, Word}; + use std::convert::TryInto; + + fn calc_expected_gas_cost( + value: Word, + value_prev: Word, + committed_value: Word, + is_warm: bool, + ) -> u64 { + let warm_case_gas = if value_prev == value { + GasCost::SLOAD_GAS + } else if committed_value == value_prev { + if committed_value == Word::from(0) { + GasCost::SSTORE_SET_GAS + } else { + GasCost::SSTORE_RESET_GAS + } + } else { + GasCost::SLOAD_GAS + }; + if is_warm { + warm_case_gas.as_u64() + } else { + warm_case_gas.as_u64() + GasCost::COLD_SLOAD_COST.as_u64() + } + } + + fn test_ok( + tx: eth_types::Transaction, + key: Word, + value: Word, + value_prev: Word, + committed_value: Word, + is_warm: bool, + result: bool, + ) { + let gas = calc_expected_gas_cost(value, value_prev, committed_value, is_warm); + let rw_counter_end_of_reversion = if result { 0 } else { 14 }; + + let call_data_gas_cost = tx + .input + .0 + .iter() + .fold(0, |acc, byte| acc + if *byte == 0 { 4 } else { 16 }); + + let randomness = rand_fp(); + let bytecode = Bytecode::from(&bytecode! { + PUSH32(value) + PUSH32(key) + #[start] + SSTORE + STOP + }); + let block = Block { + randomness, + txs: vec![Transaction { + id: 1, + nonce: tx.nonce.try_into().unwrap(), + gas: tx.gas.try_into().unwrap(), + gas_price: tx.gas_price.unwrap_or_else(Word::zero), + caller_address: tx.from, + callee_address: tx.to.unwrap(), + is_create: tx.to.is_none(), + value: tx.value, + call_data: tx.input.to_vec(), + call_data_length: tx.input.0.len(), + call_data_gas_cost, + calls: vec![Call { + id: 1, + is_root: true, + is_create: false, + code_source: CodeSource::Account(bytecode.hash), + rw_counter_end_of_reversion, + is_persistent: result, + is_success: result, + callee_address: tx.to.unwrap(), + ..Default::default() + }], + steps: vec![ + ExecStep { + rw_indices: [ + vec![ + (RwTableTag::CallContext, 0), + (RwTableTag::CallContext, 1), + (RwTableTag::CallContext, 2), + (RwTableTag::CallContext, 3), + (RwTableTag::Stack, 0), + (RwTableTag::Stack, 1), + (RwTableTag::AccountStorage, 0), + (RwTableTag::TxAccessListAccountStorage, 0), + (RwTableTag::TxRefund, 0), + ], + if result { + vec![] + } else { + vec![ + (RwTableTag::TxRefund, 1), + (RwTableTag::TxAccessListAccountStorage, 1), + (RwTableTag::AccountStorage, 1), + ] + }, + ] + .concat(), + execution_state: ExecutionState::SSTORE, + rw_counter: 1, + program_counter: 66, + stack_pointer: STACK_CAPACITY, + gas_left: gas, + gas_cost: gas, + opcode: Some(OpcodeId::SSTORE), + ..Default::default() + }, + ExecStep { + execution_state: ExecutionState::STOP, + rw_counter: 10, + program_counter: 67, + stack_pointer: STACK_CAPACITY - 2, + gas_left: 0, + opcode: Some(OpcodeId::STOP), + state_write_counter: 3, + ..Default::default() + }, + ], + }], + rws: RwMap( + [ + ( + RwTableTag::Stack, + vec![ + Rw::Stack { + rw_counter: 5, + is_write: false, + call_id: 1, + stack_pointer: STACK_CAPACITY, + value: key, + }, + Rw::Stack { + rw_counter: 6, + is_write: false, + call_id: 1, + stack_pointer: STACK_CAPACITY + 1, + value, + }, + ], + ), + ( + RwTableTag::AccountStorage, + [ + vec![Rw::AccountStorage { + rw_counter: 7, + is_write: true, + account_address: tx.to.unwrap(), + storage_key: key, + value, + value_prev, + tx_id: 1usize, + committed_value, + }], + if result { + vec![] + } else { + vec![Rw::AccountStorage { + rw_counter: rw_counter_end_of_reversion, + is_write: true, + account_address: tx.to.unwrap(), + storage_key: key, + value: value_prev, + value_prev: value, + tx_id: 1usize, + committed_value, + }] + }, + ] + .concat(), + ), + ( + RwTableTag::TxAccessListAccountStorage, + [ + vec![Rw::TxAccessListAccountStorage { + rw_counter: 8, + is_write: true, + tx_id: 1usize, + account_address: tx.to.unwrap(), + storage_key: key, + value: true, + value_prev: is_warm, + }], + if result { + vec![] + } else { + vec![Rw::TxAccessListAccountStorage { + rw_counter: rw_counter_end_of_reversion - 1, + is_write: true, + tx_id: 1usize, + account_address: tx.to.unwrap(), + storage_key: key, + value: is_warm, + value_prev: true, + }] + }, + ] + .concat(), + ), + ( + RwTableTag::TxRefund, + [ + vec![Rw::TxRefund { + rw_counter: 9, + is_write: true, + tx_id: 1usize, + value: Word::from(0), // TODO: + value_prev: Word::from(998), + }], + if result { + vec![] + } else { + vec![Rw::TxRefund { + rw_counter: rw_counter_end_of_reversion - 2, + is_write: true, + tx_id: 1usize, + value: Word::from(998), + value_prev: Word::from(0), // TODO: + }] + }, + ] + .concat(), + ), + ( + RwTableTag::CallContext, + vec![ + Rw::CallContext { + rw_counter: 1, + is_write: false, + call_id: 1, + field_tag: CallContextFieldTag::TxId, + value: Word::one(), + }, + Rw::CallContext { + rw_counter: 2, + is_write: false, + call_id: 1, + field_tag: CallContextFieldTag::RwCounterEndOfReversion, + value: Word::from(rw_counter_end_of_reversion), + }, + Rw::CallContext { + rw_counter: 3, + is_write: false, + call_id: 1, + field_tag: CallContextFieldTag::IsPersistent, + value: Word::from(result as u64), + }, + Rw::CallContext { + rw_counter: 4, + is_write: false, + call_id: 1, + field_tag: CallContextFieldTag::CalleeAddress, + value: tx.to.unwrap().to_word(), + }, + ], + ), + ] + .into(), + ), + bytecodes: vec![bytecode], + ..Default::default() + }; + + assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); + } + + fn mock_tx() -> eth_types::Transaction { + let from = address!("0x00000000000000000000000000000000000000fe"); + let to = address!("0x00000000000000000000000000000000000000ff"); + eth_types::Transaction { + from, + to: Some(to), + ..Default::default() + } + } + + #[test] + fn sstore_gadget_warm_persist() { + // value_prev == value + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060504.into(), + 0x060504.into(), + true, + true, + ); + // value_prev != value, original_value == value_prev, original_value != 0 + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060505.into(), + 0x060505.into(), + true, + true, + ); + // value_prev != value, original_value == value_prev, original_value == 0 + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0.into(), + 0.into(), + true, + true, + ); + // value_prev != value, original_value != value_prev, value != original_value + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060505.into(), + 0x060506.into(), + true, + true, + ); + // value_prev != value, original_value != value_prev, value == original_value + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060505.into(), + 0x060504.into(), + true, + true, + ); + } + + fn sstore_gadget_warm_revert() { + // value_prev == value + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060504.into(), + 0x060504.into(), + true, + false, + ); + // value_prev != value, original_value == value_prev, original_value != 0 + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060505.into(), + 0x060505.into(), + true, + false, + ); + // value_prev != value, original_value == value_prev, original_value == 0 + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0.into(), + 0.into(), + true, + false, + ); + // value_prev != value, original_value != value_prev, value != original_value + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060505.into(), + 0x060506.into(), + true, + false, + ); + // value_prev != value, original_value != value_prev, value == original_value + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060505.into(), + 0x060504.into(), + true, + false, + ); + } + + #[test] + fn sstore_gadget_cold_persist() { + // value_prev == value + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060504.into(), + 0x060504.into(), + false, + true, + ); + // value_prev != value, original_value == value_prev, original_value != 0 + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060505.into(), + 0x060505.into(), + false, + true, + ); + // value_prev != value, original_value == value_prev, original_value == 0 + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0.into(), + 0.into(), + false, + true, + ); + // value_prev != value, original_value != value_prev, value != original_value + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060505.into(), + 0x060506.into(), + false, + true, + ); + // value_prev != value, original_value != value_prev, value == original_value + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060505.into(), + 0x060504.into(), + false, + true, + ); + } + + #[test] + fn sstore_gadget_cold_revert() { + // value_prev == value + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060504.into(), + 0x060504.into(), + false, + false, + ); + // value_prev != value, original_value == value_prev, original_value != 0 + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060505.into(), + 0x060505.into(), + false, + false, + ); + // value_prev != value, original_value == value_prev, original_value == 0 + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0.into(), + 0.into(), + false, + false, + ); + // value_prev != value, original_value != value_prev, value != original_value + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060505.into(), + 0x060506.into(), + false, + false, + ); + // value_prev != value, original_value != value_prev, value == original_value + test_ok( + mock_tx(), + 0x030201.into(), + 0x060504.into(), + 0x060505.into(), + 0x060504.into(), + false, + false, + ); + } +} diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index 74acbea7d9..d22a60d068 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -704,6 +704,33 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { ); } + // TxRefund + + pub(crate) fn tx_refund_write_with_reversion( + &mut self, + tx_id: Expression, + value: Expression, + value_prev: Expression, + is_persistent: Expression, + rw_counter_end_of_reversion: Expression, + ) { + self.state_write_with_reversion( + "tx_refund_write_with_reversion", + RwTableTag::TxRefund, + [ + tx_id, + 0.expr(), + 0.expr(), + value, + value_prev, + 0.expr(), + 0.expr(), + ], + is_persistent, + rw_counter_end_of_reversion, + ); + } + // Account pub(crate) fn account_read( @@ -803,6 +830,35 @@ impl<'a, F: FieldExt> ConstraintBuilder<'a, F> { ); } + #[allow(clippy::too_many_arguments)] + pub(crate) fn account_storage_write_with_reversion( + &mut self, + account_address: Expression, + key: Expression, + value: Expression, + value_prev: Expression, + tx_id: Expression, + committed_value: Expression, + is_persistent: Expression, + rw_counter_end_of_reversion: Expression, + ) { + self.state_write_with_reversion( + "account_storage_write_with_reversion", + RwTableTag::AccountStorage, + [ + account_address, + key, + 0.expr(), + value, + value_prev, + tx_id, + committed_value, + ], + is_persistent, + rw_counter_end_of_reversion, + ); + } + // Call context pub(crate) fn call_context( diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index 194158317d..40bd25f4ce 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -560,6 +560,15 @@ impl Rw { } } + pub fn tx_refund_value_pair(&self) -> (Word, Word) { + match self { + Self::TxRefund { + value, value_prev, .. + } => (*value, *value_prev), + _ => unreachable!(), + } + } + pub fn account_value_pair(&self) -> (Word, Word) { match self { Self::Account { @@ -580,6 +589,20 @@ impl Rw { } } + // TODO: merge with aux_pair + pub fn storage_value_aux(&self) -> (Word, Word, usize, Word) { + match self { + Self::AccountStorage { + value, + value_prev, + tx_id, + committed_value, + .. + } => (*value, *value_prev, *tx_id, *committed_value), + _ => unreachable!(), + } + } + pub fn call_context_value(&self) -> Word { match self { Self::CallContext { value, .. } => *value, @@ -647,6 +670,25 @@ impl Rw { F::zero(), ] .into(), + Self::TxRefund { + rw_counter, + is_write, + tx_id, + value, + value_prev, + } => [ + F::from(*rw_counter as u64), + F::from(*is_write as u64), + F::from(RwTableTag::TxRefund as u64), + F::from(*tx_id as u64), + F::zero(), + F::zero(), + F::from(value.as_u64()), + F::from(value_prev.as_u64()), + F::zero(), + F::zero(), + ] + .into(), Self::Account { rw_counter, is_write, @@ -1053,6 +1095,7 @@ impl From<&bus_mapping::circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::GAS => ExecutionState::GAS, OpcodeId::SELFBALANCE => ExecutionState::SELFBALANCE, OpcodeId::SLOAD => ExecutionState::SLOAD, + OpcodeId::SSTORE => ExecutionState::SSTORE, _ => unimplemented!("unimplemented opcode {:?}", step.op), } }