diff --git a/eravm-stable-interface/src/state_interface.rs b/eravm-stable-interface/src/state_interface.rs index cfccce75..9c8192a2 100644 --- a/eravm-stable-interface/src/state_interface.rs +++ b/eravm-stable-interface/src/state_interface.rs @@ -4,6 +4,7 @@ pub trait StateInterface { fn read_register(&self, register: u8) -> (U256, bool); fn set_register(&mut self, register: u8, value: U256, is_pointer: bool); + fn current_frame(&mut self) -> impl CallframeInterface + '_; fn number_of_callframes(&self) -> usize; /// zero is the current frame, one is the frame before that etc. fn callframe(&mut self, n: usize) -> impl CallframeInterface + '_; @@ -132,6 +133,10 @@ impl StateInterface for DummyState { unimplemented!() } + fn current_frame(&mut self) -> impl CallframeInterface + '_ { + DummyState + } + fn number_of_callframes(&self) -> usize { unimplemented!() } diff --git a/eravm-stable-interface/src/tracer_interface.rs b/eravm-stable-interface/src/tracer_interface.rs index 53c3a122..d497d0f5 100644 --- a/eravm-stable-interface/src/tracer_interface.rs +++ b/eravm-stable-interface/src/tracer_interface.rs @@ -208,6 +208,19 @@ impl OpcodeType for opcodes::Ret { pub trait Tracer { fn before_instruction(&mut self, _state: &mut S) {} fn after_instruction(&mut self, _state: &mut S) {} + + fn on_extra_prover_cycles(&mut self, _stats: CycleStats) {} +} + +#[derive(Debug, Clone, Copy)] +pub enum CycleStats { + Keccak256(u32), + Sha256(u32), + EcRecover(u32), + Secp256k1Verify(u32), + Decommit(u32), + StorageRead, + StorageWrite, } impl Tracer for () {} @@ -223,6 +236,11 @@ impl Tracer for (A, B) { self.0.after_instruction::(state); self.1.after_instruction::(state); } + + fn on_extra_prover_cycles(&mut self, stats: CycleStats) { + self.0.on_extra_prover_cycles(stats); + self.1.on_extra_prover_cycles(stats); + } } #[cfg(test)] diff --git a/src/decommit.rs b/src/decommit.rs index 3075f7d1..34b6553e 100644 --- a/src/decommit.rs +++ b/src/decommit.rs @@ -1,13 +1,15 @@ use crate::{program::Program, world_diff::WorldDiff, World}; +use eravm_stable_interface::{CycleStats, Tracer}; use u256::{H160, U256}; use zkevm_opcode_defs::{ ethereum_types::Address, system_params::DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW, }; impl WorldDiff { - pub(crate) fn decommit( + pub(crate) fn decommit( &mut self, world: &mut impl World, + tracer: &mut T, address: U256, default_aa_code_hash: [u8; 32], evm_interpreter_code_hash: [u8; 32], @@ -19,8 +21,12 @@ impl WorldDiff { let mut is_evm = false; let mut code_info = { - let code_info = - self.read_storage_without_refund(world, deployer_system_contract_address, address); + let code_info = self.read_storage_without_refund( + world, + tracer, + deployer_system_contract_address, + address, + ); let mut code_info_bytes = [0; 32]; code_info.to_big_endian(&mut code_info_bytes); @@ -81,21 +87,25 @@ impl WorldDiff { /// Returns the decommitted contract code and a flag set to `true` if this is a fresh decommit (i.e., /// the code wasn't decommitted previously in the same VM run). #[doc(hidden)] // should be used for testing purposes only; can break VM operation otherwise - pub fn decommit_opcode( + pub fn decommit_opcode( &mut self, world: &mut impl World, + tracer: &mut T, code_hash: U256, ) -> (Vec, bool) { - let was_decommitted = self.decommitted_hashes.as_ref().get(&code_hash) == Some(&true); - if !was_decommitted { - self.decommitted_hashes.insert(code_hash, true); + let is_new = self.decommitted_hashes.insert(code_hash, true) != Some(true); + let code = world.decommit_code(code_hash); + if is_new { + // Decommitter can process two words per cycle + tracer.on_extra_prover_cycles(CycleStats::Decommit((code.len() as u32 + 63) / 64)); } - (world.decommit_code(code_hash), !was_decommitted) + (code, is_new) } - pub(crate) fn pay_for_decommit>( + pub(crate) fn pay_for_decommit>( &mut self, world: &mut W, + tracer: &mut T, decommit: UnpaidDecommit, gas: &mut u32, ) -> Option> { @@ -107,9 +117,17 @@ impl WorldDiff { return None; } - self.decommitted_hashes.insert(decommit.code_key, true); + let is_new = self.decommitted_hashes.insert(decommit.code_key, true) != Some(true); *gas -= decommit.cost; - Some(world.decommit(decommit.code_key)) + + let decommit = world.decommit(decommit.code_key); + if is_new { + tracer.on_extra_prover_cycles(CycleStats::Decommit( + (decommit.code_page().len() as u32 + 1) / 2, + )); + } + + Some(decommit) } } diff --git a/src/instruction_handlers/binop.rs b/src/instruction_handlers/binop.rs index b7b8429c..54253a27 100644 --- a/src/instruction_handlers/binop.rs +++ b/src/instruction_handlers/binop.rs @@ -1,4 +1,4 @@ -use super::common::instruction_boilerplate; +use super::common::boilerplate; use crate::{ addressing_modes::{ AbsoluteStack, Addressable, AdvanceStackPointer, AnyDestination, AnySource, Arguments, @@ -28,7 +28,7 @@ fn binop< world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + boilerplate::(vm, world, tracer, |vm, args| { let a = In1::get(args, &mut vm.state); let b = Register2::get(args, &mut vm.state); let (a, b) = if SWAP { (b, a) } else { (a, b) }; diff --git a/src/instruction_handlers/common.rs b/src/instruction_handlers/common.rs index 20d89fc6..d274e740 100644 --- a/src/instruction_handlers/common.rs +++ b/src/instruction_handlers/common.rs @@ -3,28 +3,41 @@ use crate::{addressing_modes::Arguments, instruction::ExecutionStatus, VirtualMa use eravm_stable_interface::{opcodes, OpcodeType, Tracer}; #[inline(always)] -pub(crate) fn instruction_boilerplate( +pub(crate) fn boilerplate( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, - business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut W), + business_logic: impl FnOnce(&mut VirtualMachine, &Arguments), ) -> ExecutionStatus { - instruction_boilerplate_ext::(vm, world, tracer, |vm, args, _, world| { - business_logic(vm, args, world); + full_boilerplate::(vm, world, tracer, |vm, args, _, _| { + business_logic(vm, args); ExecutionStatus::Running }) } #[inline(always)] -pub(crate) fn instruction_boilerplate_ext( +pub(crate) fn boilerplate_ext( + vm: &mut VirtualMachine, + world: &mut W, + tracer: &mut T, + business_logic: impl FnOnce(&mut VirtualMachine, &Arguments, &mut W, &mut T), +) -> ExecutionStatus { + full_boilerplate::(vm, world, tracer, |vm, args, world, tracer| { + business_logic(vm, args, world, tracer); + ExecutionStatus::Running + }) +} + +#[inline(always)] +pub(crate) fn full_boilerplate( vm: &mut VirtualMachine, world: &mut W, tracer: &mut T, business_logic: impl FnOnce( &mut VirtualMachine, &Arguments, - &mut T, &mut W, + &mut T, ) -> ExecutionStatus, ) -> ExecutionStatus { let args = unsafe { &(*vm.state.current_frame.pc).arguments }; @@ -41,7 +54,7 @@ pub(crate) fn instruction_boilerplate_ext( if args.predicate().satisfied(&vm.state.flags) { tracer.before_instruction::(vm); vm.state.current_frame.pc = unsafe { vm.state.current_frame.pc.add(1) }; - let result = business_logic(vm, args, tracer, world); + let result = business_logic(vm, args, world, tracer); tracer.after_instruction::(vm); result } else { diff --git a/src/instruction_handlers/context.rs b/src/instruction_handlers/context.rs index 66a14956..74c02650 100644 --- a/src/instruction_handlers/context.rs +++ b/src/instruction_handlers/context.rs @@ -1,4 +1,4 @@ -use super::common::instruction_boilerplate; +use super::common::boilerplate; use crate::{ addressing_modes::{Arguments, Destination, Register1, Source}, decommit::address_into_u256, @@ -18,7 +18,7 @@ fn context( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + boilerplate::(vm, world, tracer, |vm, args| { let result = Op::get(&vm.state); Register1::set(args, &mut vm.state, result) }) @@ -69,7 +69,7 @@ fn context_meta( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + boilerplate::(vm, world, tracer, |vm, args| { let result = VmMetaParameters { heap_size: vm.state.current_frame.heap_size, aux_heap_size: vm.state.current_frame.aux_heap_size, @@ -94,7 +94,7 @@ fn set_context_u128( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + boilerplate::(vm, world, tracer, |vm, args| { let value = Register1::get(args, &mut vm.state).low_u128(); vm.state.set_context_u128(value); }) @@ -105,7 +105,7 @@ fn increment_tx_number( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, _, _| { + boilerplate::(vm, world, tracer, |vm, _| { vm.start_new_tx(); }) } @@ -115,7 +115,7 @@ fn aux_mutating( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |_, _, _| { + boilerplate::(vm, world, tracer, |_, _| { // This instruction just crashes or nops }) } diff --git a/src/instruction_handlers/decommit.rs b/src/instruction_handlers/decommit.rs index 770cb180..dc2e0d9c 100644 --- a/src/instruction_handlers/decommit.rs +++ b/src/instruction_handlers/decommit.rs @@ -1,4 +1,4 @@ -use super::common::instruction_boilerplate; +use super::common::boilerplate_ext; use crate::{ addressing_modes::{Arguments, Destination, Register1, Register2, Source}, fat_pointer::FatPointer, @@ -14,7 +14,7 @@ fn decommit>( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, world| { + boilerplate_ext::(vm, world, tracer, |vm, args, world, tracer| { let code_hash = Register1::get(args, &mut vm.state); let extra_cost = Register2::get(args, &mut vm.state).low_u32(); @@ -32,7 +32,7 @@ fn decommit>( return; } - let (program, is_fresh) = vm.world_diff.decommit_opcode(world, code_hash); + let (program, is_fresh) = vm.world_diff.decommit_opcode(world, tracer, code_hash); if !is_fresh { vm.state.current_frame.gas += extra_cost; } diff --git a/src/instruction_handlers/event.rs b/src/instruction_handlers/event.rs index d6174caa..62895734 100644 --- a/src/instruction_handlers/event.rs +++ b/src/instruction_handlers/event.rs @@ -1,4 +1,4 @@ -use super::common::instruction_boilerplate; +use super::common::boilerplate_ext; use crate::{ addressing_modes::{Arguments, Immediate1, Register1, Register2, Source}, instruction::ExecutionStatus, @@ -14,7 +14,7 @@ fn event( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + boilerplate_ext::(vm, world, tracer, |vm, args, _, _| { if vm.state.current_frame.address == H160::from_low_u64_be(ADDRESS_EVENT_WRITER as u64) { let key = Register1::get(args, &mut vm.state); let value = Register2::get(args, &mut vm.state); @@ -36,7 +36,7 @@ fn l2_to_l1( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + boilerplate_ext::(vm, world, tracer, |vm, args, _, _| { let key = Register1::get(args, &mut vm.state); let value = Register2::get(args, &mut vm.state); let is_service = Immediate1::get(args, &mut vm.state).low_u32() == 1; diff --git a/src/instruction_handlers/far_call.rs b/src/instruction_handlers/far_call.rs index a473fe65..9437ee57 100644 --- a/src/instruction_handlers/far_call.rs +++ b/src/instruction_handlers/far_call.rs @@ -1,5 +1,5 @@ use super::{ - common::instruction_boilerplate_ext, + common::boilerplate_ext, heap_access::grow_heap, ret::{panic_from_failed_far_call, RETURN_COST}, AuxHeap, Heap, @@ -43,7 +43,7 @@ fn far_call< world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate_ext::, _, _>(vm, world, tracer, |vm, args, tracer, world| { + boilerplate_ext::, _, _>(vm, world, tracer, |vm, args, world, tracer| { let (raw_abi, raw_abi_is_pointer) = Register1::get_with_pointer_flag(args, &mut vm.state); let address_mask: U256 = U256::MAX >> (256 - 160); @@ -65,6 +65,7 @@ fn far_call< let failing_part = (|| { let decommit_result = vm.world_diff.decommit( world, + tracer, destination_address, vm.settings.default_aa_code_hash, vm.settings.evm_interpreter_code_hash, @@ -92,6 +93,7 @@ fn far_call< let (unpaid_decommit, is_evm) = decommit_result?; let program = vm.world_diff.pay_for_decommit( world, + tracer, unpaid_decommit, &mut vm.state.current_frame.gas, )?; @@ -107,7 +109,7 @@ fn far_call< let Some((calldata, program, is_evm_interpreter)) = failing_part else { vm.state.current_frame.gas += new_frame_gas.saturating_sub(RETURN_COST); panic_from_failed_far_call(vm, tracer, exception_handler); - return ExecutionStatus::Running; + return; }; let stipend = if is_evm_interpreter { @@ -152,8 +154,6 @@ fn far_call< | u8::from(abi.is_constructor_call); vm.state.registers[2] = call_type.into(); - - ExecutionStatus::Running }) } diff --git a/src/instruction_handlers/heap_access.rs b/src/instruction_handlers/heap_access.rs index defc7838..74ff1ce5 100644 --- a/src/instruction_handlers/heap_access.rs +++ b/src/instruction_handlers/heap_access.rs @@ -1,4 +1,4 @@ -use super::common::{instruction_boilerplate, instruction_boilerplate_ext}; +use super::common::{boilerplate, full_boilerplate}; use crate::{ addressing_modes::{ Arguments, Destination, DestinationWriter, Immediate1, Register1, Register2, @@ -64,7 +64,7 @@ fn load( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + boilerplate::(vm, world, tracer, |vm, args| { // Pointers need not be masked here even though we do not care about them being pointers. // They will panic, though because they are larger than 2^32. let (pointer, _) = In::get_with_pointer_flag(args, &mut vm.state); @@ -107,7 +107,7 @@ fn store< world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate_ext::(vm, world, tracer, |vm, args, _, _| { + full_boilerplate::(vm, world, tracer, |vm, args, _, _| { // Pointers need not be masked here even though we do not care about them being pointers. // They will panic, though because they are larger than 2^32. let (pointer, _) = In::get_with_pointer_flag(args, &mut vm.state); @@ -165,7 +165,7 @@ fn load_pointer( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + boilerplate::(vm, world, tracer, |vm, args| { let (input, input_is_pointer) = Register1::get_with_pointer_flag(args, &mut vm.state); if !input_is_pointer { vm.state.current_frame.pc = &*vm.panic; diff --git a/src/instruction_handlers/jump.rs b/src/instruction_handlers/jump.rs index bcd801bf..28ccc4db 100644 --- a/src/instruction_handlers/jump.rs +++ b/src/instruction_handlers/jump.rs @@ -1,4 +1,4 @@ -use super::common::instruction_boilerplate; +use super::common::boilerplate; use crate::{ addressing_modes::{ AbsoluteStack, AdvanceStackPointer, AnySource, Arguments, CodePage, Destination, @@ -14,7 +14,7 @@ fn jump( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + boilerplate::(vm, world, tracer, |vm, args| { let target = In::get(args, &mut vm.state).low_u32() as u16; let next_instruction = vm.state.current_frame.get_pc_as_u16(); diff --git a/src/instruction_handlers/near_call.rs b/src/instruction_handlers/near_call.rs index c73a6759..3f3b74b1 100644 --- a/src/instruction_handlers/near_call.rs +++ b/src/instruction_handlers/near_call.rs @@ -1,4 +1,4 @@ -use super::common::instruction_boilerplate; +use super::common::boilerplate; use crate::{ addressing_modes::{Arguments, Immediate1, Immediate2, Register1, Source}, instruction::ExecutionStatus, @@ -12,7 +12,7 @@ fn near_call( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + boilerplate::(vm, world, tracer, |vm, args| { let gas_to_pass = Register1::get(args, &mut vm.state).0[0] as u32; let destination = Immediate1::get(args, &mut vm.state); let error_handler = Immediate2::get(args, &mut vm.state); diff --git a/src/instruction_handlers/nop.rs b/src/instruction_handlers/nop.rs index 79aafa27..b9a00168 100644 --- a/src/instruction_handlers/nop.rs +++ b/src/instruction_handlers/nop.rs @@ -1,4 +1,4 @@ -use super::common::instruction_boilerplate; +use super::common::boilerplate; use crate::{ addressing_modes::{destination_stack_address, AdvanceStackPointer, Arguments, Source}, instruction::ExecutionStatus, @@ -11,7 +11,7 @@ fn nop( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + boilerplate::(vm, world, tracer, |vm, args| { // nop's addressing modes can move the stack pointer! AdvanceStackPointer::get(args, &mut vm.state); vm.state.current_frame.sp = vm diff --git a/src/instruction_handlers/pointer.rs b/src/instruction_handlers/pointer.rs index f6e4ec4b..f450ad74 100644 --- a/src/instruction_handlers/pointer.rs +++ b/src/instruction_handlers/pointer.rs @@ -1,4 +1,4 @@ -use super::common::instruction_boilerplate; +use super::common::boilerplate; use crate::{ addressing_modes::{ AbsoluteStack, AdvanceStackPointer, AnyDestination, AnySource, Arguments, CodePage, @@ -19,7 +19,7 @@ fn ptr world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + boilerplate::(vm, world, tracer, |vm, args| { let ((a, a_is_pointer), (b, b_is_pointer)) = if SWAP { ( Register2::get_with_pointer_flag(args, &mut vm.state), diff --git a/src/instruction_handlers/precompiles.rs b/src/instruction_handlers/precompiles.rs index 8357c603..a337a11c 100644 --- a/src/instruction_handlers/precompiles.rs +++ b/src/instruction_handlers/precompiles.rs @@ -1,11 +1,11 @@ -use super::{common::instruction_boilerplate, HeapInterface}; +use super::{common::boilerplate_ext, HeapInterface}; use crate::{ addressing_modes::{Arguments, Destination, Register1, Register2, Source}, heap::Heaps, instruction::ExecutionStatus, Instruction, VirtualMachine, }; -use eravm_stable_interface::{opcodes, HeapId, Tracer}; +use eravm_stable_interface::{opcodes, CycleStats, HeapId, Tracer}; use zk_evm_abstractions::{ aux::Timestamp, precompiles::{ @@ -28,7 +28,7 @@ fn precompile_call( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, _| { + boilerplate_ext::(vm, world, tracer, |vm, args, _, tracer| { // The user gets to decide how much gas to burn // This is safe because system contracts are trusted let aux_data = PrecompileAuxData::from_u256(Register2::get(args, &mut vm.state)); @@ -66,16 +66,24 @@ fn precompile_call( let heaps = &mut vm.state.heaps; match address_low { KECCAK256_ROUND_FUNCTION_PRECOMPILE_ADDRESS => { - keccak256_rounds_function::<_, false>(0, query, heaps); + tracer.on_extra_prover_cycles(CycleStats::Keccak256( + keccak256_rounds_function::<_, false>(0, query, heaps).0 as u32, + )); } SHA256_ROUND_FUNCTION_PRECOMPILE_ADDRESS => { - sha256_rounds_function::<_, false>(0, query, heaps); + tracer.on_extra_prover_cycles(CycleStats::Sha256( + sha256_rounds_function::<_, false>(0, query, heaps).0 as u32, + )); } ECRECOVER_INNER_FUNCTION_PRECOMPILE_ADDRESS => { - ecrecover_function::<_, false>(0, query, heaps); + tracer.on_extra_prover_cycles(CycleStats::EcRecover( + ecrecover_function::<_, false>(0, query, heaps).0 as u32, + )); } SECP256R1_VERIFY_PRECOMPILE_ADDRESS => { - secp256r1_verify_function::<_, false>(0, query, heaps); + tracer.on_extra_prover_cycles(CycleStats::Secp256k1Verify( + secp256r1_verify_function::<_, false>(0, query, heaps).0 as u32, + )); } _ => { // A precompile call may be used just to burn gas diff --git a/src/instruction_handlers/ret.rs b/src/instruction_handlers/ret.rs index a4f4e292..30ad2394 100644 --- a/src/instruction_handlers/ret.rs +++ b/src/instruction_handlers/ret.rs @@ -1,4 +1,4 @@ -use super::{common::instruction_boilerplate_ext, far_call::get_far_call_calldata, HeapInterface}; +use super::{common::full_boilerplate, far_call::get_far_call_calldata, HeapInterface}; use crate::{ addressing_modes::{Arguments, Immediate1, Register1, Source, INVALID_INSTRUCTION_COST}, callframe::FrameRemnant, @@ -119,7 +119,7 @@ fn ret( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate_ext::, _, _>(vm, world, tracer, |vm, args, _, _| { + full_boilerplate::, _, _>(vm, world, tracer, |vm, args, _, _| { naked_ret::(vm, args) }) } diff --git a/src/instruction_handlers/storage.rs b/src/instruction_handlers/storage.rs index 9a27e485..7f6b857c 100644 --- a/src/instruction_handlers/storage.rs +++ b/src/instruction_handlers/storage.rs @@ -1,4 +1,4 @@ -use super::common::instruction_boilerplate; +use super::common::{boilerplate, boilerplate_ext}; use crate::{ addressing_modes::{ Arguments, Destination, Register1, Register2, Source, SLOAD_COST, SSTORE_COST, @@ -13,13 +13,13 @@ fn sstore>( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, world| { + boilerplate_ext::(vm, world, tracer, |vm, args, world, tracer| { let key = Register1::get(args, &mut vm.state); let value = Register2::get(args, &mut vm.state); - let refund = vm - .world_diff - .write_storage(world, vm.state.current_frame.address, key, value); + let refund = + vm.world_diff + .write_storage(world, tracer, vm.state.current_frame.address, key, value); assert!(refund <= SSTORE_COST); vm.state.current_frame.gas += refund; @@ -31,18 +31,13 @@ fn sstore_transient( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::( - vm, - world, - tracer, - |vm, args, _| { - let key = Register1::get(args, &mut vm.state); - let value = Register2::get(args, &mut vm.state); + boilerplate::(vm, world, tracer, |vm, args| { + let key = Register1::get(args, &mut vm.state); + let value = Register2::get(args, &mut vm.state); - vm.world_diff - .write_transient_storage(vm.state.current_frame.address, key, value); - }, - ) + vm.world_diff + .write_transient_storage(vm.state.current_frame.address, key, value); + }) } fn sload>( @@ -50,11 +45,12 @@ fn sload>( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::(vm, world, tracer, |vm, args, world| { + boilerplate_ext::(vm, world, tracer, |vm, args, world, tracer| { let key = Register1::get(args, &mut vm.state); + let (value, refund) = vm.world_diff - .read_storage(world, vm.state.current_frame.address, key); + .read_storage(world, tracer, vm.state.current_frame.address, key); assert!(refund <= SLOAD_COST); vm.state.current_frame.gas += refund; @@ -68,19 +64,14 @@ fn sload_transient( world: &mut W, tracer: &mut T, ) -> ExecutionStatus { - instruction_boilerplate::( - vm, - world, - tracer, - |vm, args, _| { - let key = Register1::get(args, &mut vm.state); - let value = vm - .world_diff - .read_transient_storage(vm.state.current_frame.address, key); + boilerplate::(vm, world, tracer, |vm, args| { + let key = Register1::get(args, &mut vm.state); + let value = vm + .world_diff + .read_transient_storage(vm.state.current_frame.address, key); - Register1::set(args, &mut vm.state, value); - }, - ) + Register1::set(args, &mut vm.state, value); + }) } impl> Instruction { diff --git a/src/lib.rs b/src/lib.rs index c50f148c..d52150b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ use u256::{H160, U256}; pub use decommit::address_into_u256; pub use decommit::initial_decommit; pub use eravm_stable_interface::{ - CallframeInterface, HeapId, Opcode, OpcodeType, StateInterface, Tracer, + CallframeInterface, CycleStats, HeapId, Opcode, OpcodeType, ReturnType, StateInterface, Tracer, }; pub use heap::FIRST_HEAP; pub use instruction::{jump_to_beginning, ExecutionEnd, Instruction}; diff --git a/src/rollback.rs b/src/rollback.rs index 72c68b82..2b1c9f6d 100644 --- a/src/rollback.rs +++ b/src/rollback.rs @@ -17,7 +17,7 @@ pub struct RollbackableMap { impl RollbackableMap { pub fn insert(&mut self, key: K, value: V) -> Option { let old_value = self.map.insert(key.clone(), value); - self.old_entries.push((key.clone(), old_value.clone())); + self.old_entries.push((key, old_value.clone())); old_value } @@ -64,15 +64,44 @@ impl AsRef> for RollbackableMap { } } -pub type RollbackableSet = RollbackableMap; +#[derive(Default)] +pub struct RollbackableSet { + map: BTreeMap, + old_entries: Vec, +} impl RollbackableSet { - pub fn add(&mut self, key: T) { - self.insert(key, ()); + /// Adds `key` to the set and returns if it was added (not present earlier). + pub fn add(&mut self, key: T) -> bool { + let is_new = self.map.insert(key.clone(), ()).is_none(); + if is_new { + self.old_entries.push(key); + } + is_new + } +} + +impl Rollback for RollbackableSet { + type Snapshot = usize; + + fn snapshot(&self) -> Self::Snapshot { + self.old_entries.len() } - pub fn contains(&self, key: &T) -> bool { - self.as_ref().contains_key(key) + fn rollback(&mut self, snapshot: Self::Snapshot) { + for k in self.old_entries.drain(snapshot..) { + self.map.remove(&k); + } + } + + fn delete_history(&mut self) { + self.old_entries.clear(); + } +} + +impl AsRef> for RollbackableSet { + fn as_ref(&self) -> &BTreeMap { + &self.map } } diff --git a/src/state.rs b/src/state.rs index f5dac677..ec7fcd0d 100644 --- a/src/state.rs +++ b/src/state.rs @@ -163,6 +163,8 @@ impl Clone for State { impl PartialEq for State { fn eq(&self, other: &Self) -> bool { + // does not compare cycle counts to work with tests that + // expect no change after a rollback self.registers == other.registers && self.register_pointer_flags == other.register_pointer_flags && self.flags == other.flags diff --git a/src/tracing.rs b/src/tracing.rs index 14afee25..5e266614 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -32,6 +32,13 @@ impl StateInterface for VirtualMachine { + 1 } + fn current_frame(&mut self) -> impl CallframeInterface + '_ { + CallframeWrapper { + frame: &mut self.state.current_frame, + near_call: None, + } + } + fn callframe(&mut self, mut n: usize) -> impl CallframeInterface + '_ { for far_frame in std::iter::once(&mut self.state.current_frame) .chain(self.state.previous_frames.iter_mut().rev()) diff --git a/src/world_diff.rs b/src/world_diff.rs index 2053097d..4fca03bb 100644 --- a/src/world_diff.rs +++ b/src/world_diff.rs @@ -4,6 +4,7 @@ use crate::{ rollback::{Rollback, RollbackableLog, RollbackableMap, RollbackablePod, RollbackableSet}, StorageInterface, }; +use eravm_stable_interface::{CycleStats, Tracer}; use u256::{H160, U256}; use zkevm_opcode_defs::system_params::{ STORAGE_ACCESS_COLD_READ_COST, STORAGE_ACCESS_COLD_WRITE_COST, STORAGE_ACCESS_WARM_READ_COST, @@ -72,10 +73,11 @@ impl WorldDiff { pub(crate) fn read_storage( &mut self, world: &mut impl StorageInterface, + tracer: &mut impl Tracer, contract: H160, key: U256, ) -> (U256, u32) { - let (value, refund) = self.read_storage_inner(world, contract, key); + let (value, refund) = self.read_storage_inner(world, tracer, contract, key); self.storage_refunds.push(refund); (value, refund) } @@ -86,15 +88,17 @@ impl WorldDiff { pub(crate) fn read_storage_without_refund( &mut self, world: &mut impl StorageInterface, + tracer: &mut impl Tracer, contract: H160, key: U256, ) -> U256 { - self.read_storage_inner(world, contract, key).0 + self.read_storage_inner(world, tracer, contract, key).0 } fn read_storage_inner( &mut self, world: &mut impl StorageInterface, + tracer: &mut impl Tracer, contract: H160, key: U256, ) -> (U256, u32) { @@ -105,12 +109,14 @@ impl WorldDiff { .copied() .unwrap_or_else(|| world.read_storage(contract, key).unwrap_or_default()); - let refund = if world.is_free_storage_slot(&contract, &key) - || self.read_storage_slots.contains(&(contract, key)) - { + let newly_added = self.read_storage_slots.add((contract, key)); + if newly_added { + tracer.on_extra_prover_cycles(CycleStats::StorageRead); + } + + let refund = if !newly_added || world.is_free_storage_slot(&contract, &key) { WARM_READ_REFUND } else { - self.read_storage_slots.add((contract, key)); 0 }; self.pubdata_costs.push(0); @@ -121,6 +127,7 @@ impl WorldDiff { pub(crate) fn write_storage( &mut self, world: &mut impl StorageInterface, + tracer: &mut impl Tracer, contract: H160, key: U256, value: U256, @@ -133,6 +140,11 @@ impl WorldDiff { .or_insert_with(|| world.read_storage(contract, key)); if world.is_free_storage_slot(&contract, &key) { + if self.written_storage_slots.add((contract, key)) { + tracer.on_extra_prover_cycles(CycleStats::StorageWrite); + } + self.read_storage_slots.add((contract, key)); + self.storage_refunds.push(WARM_WRITE_REFUND); self.pubdata_costs.push(0); return WARM_WRITE_REFUND; @@ -144,19 +156,14 @@ impl WorldDiff { .insert((contract, key), update_cost) .unwrap_or(0); - let refund = if self - .written_storage_slots - .as_ref() - .contains_key(&(contract, key)) - { + let refund = if !self.written_storage_slots.add((contract, key)) { WARM_WRITE_REFUND } else { - self.written_storage_slots.add((contract, key)); + tracer.on_extra_prover_cycles(CycleStats::StorageWrite); - if self.read_storage_slots.contains(&(contract, key)) { + if !self.read_storage_slots.add((contract, key)) { COLD_WRITE_AFTER_WARM_READ_REFUND } else { - self.read_storage_slots.add((contract, key)); 0 } }; @@ -389,7 +396,7 @@ mod tests { let checkpoint1 = world_diff.snapshot(); for (key, value) in &first_changes { - world_diff.write_storage(&mut NoWorld, key.0, key.1, *value); + world_diff.write_storage(&mut NoWorld, &mut (), key.0, key.1, *value); } assert_eq!( world_diff @@ -410,7 +417,7 @@ mod tests { let checkpoint2 = world_diff.snapshot(); for (key, value) in &second_changes { - world_diff.write_storage(&mut NoWorld, key.0, key.1, *value); + world_diff.write_storage(&mut NoWorld, &mut (), key.0, key.1, *value); } assert_eq!( world_diff