diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index e4c628ed16..d7ab9d9447 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -129,6 +129,7 @@ mod sload; mod sstore; mod stop; mod swap; +mod tload; use self::{ begin_chunk::BeginChunkGadget, block_ctx::BlockCtxGadget, end_chunk::EndChunkGadget, @@ -214,6 +215,7 @@ use sload::SloadGadget; use sstore::SstoreGadget; use stop::StopGadget; use swap::SwapGadget; +use tload::TloadGadget; pub(crate) trait ExecutionGadget { const NAME: &'static str; @@ -318,6 +320,7 @@ pub struct ExecutionConfig { signextend_gadget: Box>, sload_gadget: Box>, sstore_gadget: Box>, + tload_gadget: Box>, stop_gadget: Box>, swap_gadget: Box>, blockhash_gadget: Box>, @@ -647,6 +650,7 @@ impl ExecutionConfig { signextend_gadget: configure_gadget!(), sload_gadget: configure_gadget!(), sstore_gadget: configure_gadget!(), + tload_gadget: configure_gadget!(), stop_gadget: configure_gadget!(), swap_gadget: configure_gadget!(), block_ctx_gadget: configure_gadget!(), diff --git a/zkevm-circuits/src/evm_circuit/execution/tload.rs b/zkevm-circuits/src/evm_circuit/execution/tload.rs new file mode 100644 index 0000000000..cc57361409 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/tload.rs @@ -0,0 +1,188 @@ +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + step::ExecutionState, + util::{ + common_gadget::SameContextGadget, + constraint_builder::{ + EVMConstraintBuilder, ReversionInfo, StepStateTransition, Transition::Delta, + }, + CachedRegion, Cell, StepRws, + }, + witness::{Block, Call, Chunk, ExecStep, Transaction}, + }, + table::CallContextFieldTag, + util::{ + word::{WordExpr, WordLoHiCell}, + Expr, + }, +}; +use bus_mapping::evm::OpcodeId; +use eth_types::Field; +use halo2_proofs::{circuit::Value, plonk::Error}; + +#[derive(Clone, Debug)] +pub(crate) struct TloadGadget { + same_context: SameContextGadget, + tx_id: Cell, + reversion_info: ReversionInfo, + callee_address: WordLoHiCell, + key: WordLoHiCell, + value: WordLoHiCell, + committed_value: WordLoHiCell, +} + +impl ExecutionGadget for TloadGadget { + const NAME: &'static str = "TLOAD"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::TLOAD; + + fn configure(cb: &mut EVMConstraintBuilder) -> Self { + let opcode = cb.query_cell(); + + let tx_id = cb.call_context(None, CallContextFieldTag::TxId); + let reversion_info = cb.reversion_info_read(None); + let callee_address = cb.call_context_read_as_word(None, CallContextFieldTag::CalleeAddress); + + let key = cb.query_word_unchecked(); + // Pop the key from the stack + cb.stack_pop(key.to_word()); + + let value = cb.query_word_unchecked(); + let committed_value = cb.query_word_unchecked(); + cb.account_storage_read( + callee_address.to_word(), + key.to_word(), + value.to_word(), + tx_id.expr(), + committed_value.to_word(), + ); + + cb.stack_push(value.to_word()); + + let step_state_transition = StepStateTransition { + rw_counter: Delta(9.expr()), + program_counter: Delta(1.expr()), + reversible_write_counter: Delta(1.expr()), + gas_left: Delta(-OpcodeId::TLOAD.constant_gas_cost().expr()), + ..Default::default() + }; + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); + + Self { + same_context, + tx_id, + reversion_info, + callee_address, + key, + value, + committed_value, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + _chunk: &Chunk, + tx: &Transaction, + call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + self.same_context.assign_exec_step(region, offset, step)?; + + self.tx_id + .assign(region, offset, Value::known(F::from(tx.id)))?; + self.reversion_info.assign( + region, + offset, + call.rw_counter_end_of_reversion, + call.is_persistent, + )?; + self.callee_address + .assign_h160(region, offset, call.address)?; + + let mut rws = StepRws::new(block, step); + + rws.offset_add(4); + + let key = rws.next().stack_value(); + let (_, committed_value) = rws.next().aux_pair(); + let value = rws.next().stack_value(); + + self.key.assign_u256(region, offset, key)?; + self.value.assign_u256(region, offset, value)?; + + self.committed_value + .assign_u256(region, offset, committed_value)?; + + rws.next(); + + Ok(()) + } +} + +#[cfg(test)] +mod test { + + use crate::{evm_circuit::test::rand_word, test_util::CircuitTestBuilder}; + use eth_types::{bytecode, Word}; + use mock::{test_ctx::helpers::tx_from_1_to_0, TestContext, MOCK_ACCOUNTS}; + + fn test_ok(key: Word, value: Word) { + // Here we use two bytecodes to test both is_persistent(STOP) or not(REVERT) + // Besides, in bytecode we use two TLOADs, + // the first TLOAD is used to test cold, and the second is used to test warm + let bytecode_success = bytecode! { + PUSH32(key) + TLOAD + PUSH32(key) + TLOAD + STOP + }; + let bytecode_failure = bytecode! { + PUSH32(key) + TLOAD + PUSH32(key) + TLOAD + PUSH32(0) + PUSH32(0) + REVERT + }; + for bytecode in [bytecode_success, bytecode_failure] { + let ctx = TestContext::<2, 1>::new( + None, + |accs| { + accs[0] + .address(MOCK_ACCOUNTS[0]) + .balance(Word::from(10u64.pow(19))) + .code(bytecode) + .storage(vec![(key, value)].into_iter()); + accs[1] + .address(MOCK_ACCOUNTS[1]) + .balance(Word::from(10u64.pow(19))); + }, + tx_from_1_to_0, + |block, _txs| block, + ) + .unwrap(); + + CircuitTestBuilder::new_from_test_ctx(ctx).run(); + } + } + + #[test] + fn tload_gadget_simple() { + let key = 0x030201.into(); + let value = 0x060504.into(); + test_ok(key, value); + } + + #[test] + fn tload_gadget_rand() { + let key = rand_word(); + let value = rand_word(); + test_ok(key, value); + } +} diff --git a/zkevm-circuits/src/evm_circuit/step.rs b/zkevm-circuits/src/evm_circuit/step.rs index f26f9290f6..beaf773c20 100644 --- a/zkevm-circuits/src/evm_circuit/step.rs +++ b/zkevm-circuits/src/evm_circuit/step.rs @@ -119,6 +119,8 @@ pub enum ExecutionState { MSIZE, GAS, JUMPDEST, + TLOAD, + TSTORE, /// PUSH0, PUSH1, PUSH2, ..., PUSH32 PUSH, /// DUP1, DUP2, ..., DUP16 @@ -309,6 +311,7 @@ impl From<&ExecStep> for ExecutionState { OpcodeId::SHL | OpcodeId::SHR => ExecutionState::SHL_SHR, OpcodeId::SLOAD => ExecutionState::SLOAD, OpcodeId::SSTORE => ExecutionState::SSTORE, + OpcodeId::TLOAD => ExecutionState::TLOAD, OpcodeId::CALLDATASIZE => ExecutionState::CALLDATASIZE, OpcodeId::CALLDATACOPY => ExecutionState::CALLDATACOPY, OpcodeId::CHAINID => ExecutionState::CHAINID,