From f801369ffe0c56ad9c0a9e4d47bf8aa05ad17b05 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 24 Jan 2025 11:19:31 +0200 Subject: [PATCH 1/4] Remove stipend for calls to EVM contracts --- crates/vm2/src/callframe.rs | 5 - .../vm2/src/instruction_handlers/far_call.rs | 15 +- crates/vm2/src/instruction_handlers/ret.rs | 6 +- .../src/single_instruction_test/callframe.rs | 2 - .../state_to_zk_evm.rs | 2 +- crates/vm2/src/state.rs | 1 - crates/vm2/src/tests/mod.rs | 1 - crates/vm2/src/tests/stipend.rs | 150 ------------------ crates/vm2/src/tracing.rs | 3 +- crates/vm2/src/vm.rs | 2 - 10 files changed, 4 insertions(+), 183 deletions(-) delete mode 100644 crates/vm2/src/tests/stipend.rs diff --git a/crates/vm2/src/callframe.rs b/crates/vm2/src/callframe.rs index 4a98928..9142cc8 100644 --- a/crates/vm2/src/callframe.rs +++ b/crates/vm2/src/callframe.rs @@ -25,7 +25,6 @@ pub(crate) struct Callframe { pub(crate) stack: Box, pub(crate) sp: u16, pub(crate) gas: u32, - pub(crate) stipend: u32, pub(crate) near_calls: Vec, pub(crate) pc: *const Instruction, pub(crate) program: Program, @@ -68,7 +67,6 @@ impl Callframe { aux_heap: HeapId, calldata_heap: HeapId, gas: u32, - stipend: u32, exception_handler: u16, context_u128: u128, is_static: bool, @@ -99,7 +97,6 @@ impl Callframe { heaps_i_am_keeping_alive: vec![], sp: 0, gas, - stipend, exception_handler, near_calls: vec![], world_before_this_frame, @@ -253,7 +250,6 @@ impl Clone for Callframe { stack: self.stack.clone(), sp: self.sp, gas: self.gas, - stipend: self.stipend, near_calls: self.near_calls.clone(), pc: self.pc, program: self.program.clone(), @@ -279,7 +275,6 @@ impl PartialEq for Callframe { && self.stack == other.stack && self.sp == other.sp && self.gas == other.gas - && self.stipend == other.stipend && self.near_calls == other.near_calls && self.pc == other.pc && self.program == other.program diff --git a/crates/vm2/src/instruction_handlers/far_call.rs b/crates/vm2/src/instruction_handlers/far_call.rs index bf4e700..dc46a70 100644 --- a/crates/vm2/src/instruction_handlers/far_call.rs +++ b/crates/vm2/src/instruction_handlers/far_call.rs @@ -1,8 +1,5 @@ use primitive_types::U256; -use zkevm_opcode_defs::{ - system_params::{EVM_SIMULATOR_STIPEND, MSG_VALUE_SIMULATOR_ADDITIVE_COST}, - ADDRESS_MSG_VALUE, -}; +use zkevm_opcode_defs::{system_params::MSG_VALUE_SIMULATOR_ADDITIVE_COST, ADDRESS_MSG_VALUE}; use zksync_vm2_interface::{ opcodes::{FarCall, TypeLevelCallingMode}, Tracer, @@ -110,21 +107,11 @@ where let (calldata, program, is_evm_interpreter) = failing_part.unwrap_or_else(|| (U256::zero().into(), Program::new_panicking(), false)); - let stipend = if is_evm_interpreter { - EVM_SIMULATOR_STIPEND - } else { - 0 - }; - let new_frame_gas = new_frame_gas - .checked_add(stipend) - .expect("stipend must not cause overflow"); - let new_frame_is_static = IS_STATIC || vm.state.current_frame.is_static; vm.push_frame::( u256_into_address(destination_address), program, new_frame_gas, - stipend, exception_handler, new_frame_is_static && !is_evm_interpreter, calldata.memory_page, diff --git a/crates/vm2/src/instruction_handlers/ret.rs b/crates/vm2/src/instruction_handlers/ret.rs index 0ed303a..3a3bcf7 100644 --- a/crates/vm2/src/instruction_handlers/ret.rs +++ b/crates/vm2/src/instruction_handlers/ret.rs @@ -55,11 +55,7 @@ fn naked_ret, RT: TypeLevelReturnType, const TO_LABEL: bo result }; - let leftover_gas = vm - .state - .current_frame - .gas - .saturating_sub(vm.state.current_frame.stipend); + let leftover_gas = vm.state.current_frame.gas; let Some(FrameRemnant { exception_handler, diff --git a/crates/vm2/src/single_instruction_test/callframe.rs b/crates/vm2/src/single_instruction_test/callframe.rs index 4c478be..a0bf23c 100644 --- a/crates/vm2/src/single_instruction_test/callframe.rs +++ b/crates/vm2/src/single_instruction_test/callframe.rs @@ -42,7 +42,6 @@ impl<'a, T: Tracer, W: World> Arbitrary<'a> for Callframe { sp: u.arbitrary()?, // It is assumed that it is always possible to add the stipend gas: u.int_in_range(0..=u32::MAX - EVM_SIMULATOR_STIPEND)?, - stipend: u.arbitrary()?, near_calls: vec![], pc: program.instruction(0).unwrap(), program, @@ -78,7 +77,6 @@ impl> Callframe { stack: StackPool {}.get(), sp: 0, gas: 0, - stipend: 0, near_calls: vec![], pc: std::ptr::null(), program: Program::for_decommit(), diff --git a/crates/vm2/src/single_instruction_test/state_to_zk_evm.rs b/crates/vm2/src/single_instruction_test/state_to_zk_evm.rs index 1ff8f5f..597a64c 100644 --- a/crates/vm2/src/single_instruction_test/state_to_zk_evm.rs +++ b/crates/vm2/src/single_instruction_test/state_to_zk_evm.rs @@ -86,7 +86,7 @@ fn vm2_frame_to_zk_evm_frames( heap_bound: frame.heap_size, aux_heap_bound: frame.aux_heap_size, total_pubdata_spent: PubdataCost(0), - stipend: frame.stipend, + stipend: 0, }; let mut result = vec![far_frame]; diff --git a/crates/vm2/src/state.rs b/crates/vm2/src/state.rs index 4ba5550..8494c49 100644 --- a/crates/vm2/src/state.rs +++ b/crates/vm2/src/state.rs @@ -63,7 +63,6 @@ impl State { gas, 0, 0, - 0, false, world_before_this_frame, ), diff --git a/crates/vm2/src/tests/mod.rs b/crates/vm2/src/tests/mod.rs index 19ae030..39dd04d 100644 --- a/crates/vm2/src/tests/mod.rs +++ b/crates/vm2/src/tests/mod.rs @@ -3,5 +3,4 @@ mod bytecode_behaviour; mod far_call_decommitment; mod panic; -mod stipend; mod trace_failing_far_call; diff --git a/crates/vm2/src/tests/stipend.rs b/crates/vm2/src/tests/stipend.rs deleted file mode 100644 index d76b1fb..0000000 --- a/crates/vm2/src/tests/stipend.rs +++ /dev/null @@ -1,150 +0,0 @@ -use primitive_types::U256; -use zkevm_opcode_defs::ethereum_types::Address; -use zksync_vm2_interface::{opcodes, CallframeInterface, StateInterface}; - -use crate::{ - addressing_modes::{ - Arguments, CodePage, Immediate1, Register, Register1, Register2, RegisterAndImmediate, - }, - instruction_handlers::address_into_u256, - testonly::{initial_decommit, TestWorld}, - ExecutionEnd, Instruction, ModeRequirements, Predicate, Program, Settings, VirtualMachine, -}; - -const INITIAL_GAS: u32 = 1000; - -fn test_scenario(gas_to_pass: u32) -> (ExecutionEnd, u32) { - let r0 = Register::new(0); - let r1 = Register::new(1); - let r2 = Register::new(2); - - let ethereum_address = 0x_00ee_eeee; - let mut abi = U256::zero(); - abi.0[3] = gas_to_pass.into(); - - let main_program = Program::from_raw( - vec![ - Instruction::from_add( - CodePage(RegisterAndImmediate { - immediate: 0, - register: r0, - }) - .into(), - Register2(r0), - Register1(r1).into(), - Arguments::new(Predicate::Always, 6, ModeRequirements::none()), - false, - false, - ), - Instruction::from_add( - CodePage(RegisterAndImmediate { - immediate: 1, - register: r0, - }) - .into(), - Register2(r0), - Register1(r2).into(), - Arguments::new(Predicate::Always, 6, ModeRequirements::none()), - false, - false, - ), - Instruction::from_far_call::( - Register1(r1), - Register2(r2), - // crash on error - Immediate1(0xFFFF), - false, - false, - Arguments::new(Predicate::Always, 200, ModeRequirements::none()), - ), - Instruction::from_ret( - Register1(Register::new(0)), - None, - Arguments::new(Predicate::Always, 5, ModeRequirements::none()), - ), - ], - vec![abi, ethereum_address.into()], - ); - - let interpreter = Program::from_raw( - vec![ - Instruction::from_add( - Register1(r0).into(), - Register2(r0), - Register1(r0).into(), - Arguments::new(Predicate::Always, 6, ModeRequirements::none()), - false, - false, - ), - Instruction::from_ret( - Register1(Register::new(0)), - None, - Arguments::new(Predicate::Always, 5, ModeRequirements::none()), - ), - ], - vec![], - ); - - let main_address = Address::from_low_u64_be(0x_0fed_dead_beef); - let interpreter_address = Address::from_low_u64_be(0x_1234_5678_90ab_cdef); - let mut world = TestWorld::new(&[ - (interpreter_address, interpreter), - (main_address, main_program), - ]); - let interpreter_hash = world.address_to_hash[&address_into_u256(interpreter_address)].into(); - - let mut ethereum_hash = [0; 32]; - ethereum_hash[0] = 2; - world - .address_to_hash - .insert(ethereum_address.into(), ethereum_hash.into()); - - let program = initial_decommit(&mut world, main_address); - - let mut vm = VirtualMachine::new( - main_address, - program, - Address::zero(), - &[], - INITIAL_GAS, - Settings { - default_aa_code_hash: [0; 32], - evm_interpreter_code_hash: interpreter_hash, - hook_address: 0, - }, - ); - - let result = vm.run(&mut world, &mut ()); - let remaining_gas = vm.current_frame().gas(); - (result, remaining_gas) -} - -#[test] -fn test() { - // without gas, relying on stipend - let (result, gas_without_paying) = test_scenario(0); - assert_eq!(result, ExecutionEnd::ProgramFinished(vec![])); - assert!(gas_without_paying < INITIAL_GAS); - - // with gas - let passed_gas = 500; - let (result, gas_when_paying) = test_scenario(passed_gas); - assert_eq!(result, ExecutionEnd::ProgramFinished(vec![])); - assert!(gas_when_paying < INITIAL_GAS); - - assert!( - gas_without_paying > gas_when_paying, - "stipend should be used only when extra gas is needed" - ); - - // with insufficient gas - let (result, gas_when_paying_one) = test_scenario(1); - assert_eq!(result, ExecutionEnd::ProgramFinished(vec![])); - assert!(gas_when_paying_one < INITIAL_GAS); - - assert_eq!( - gas_without_paying, - gas_when_paying_one + 1, - "stipend should cover missing gas" - ); -} diff --git a/crates/vm2/src/tracing.rs b/crates/vm2/src/tracing.rs index 021893e..790efb4 100644 --- a/crates/vm2/src/tracing.rs +++ b/crates/vm2/src/tracing.rs @@ -195,7 +195,7 @@ impl> CallframeInterface for CallframeWrapper<'_, T, W> { } fn stipend(&self) -> u32 { - self.frame.stipend + 0 // stipend is no longer used } fn context_u128(&self) -> u128 { @@ -472,7 +472,6 @@ mod test { H160::from_low_u64_be(1), program.clone(), (*counter).into(), - 0, *counter, false, HeapId::from_u32_unchecked(5), diff --git a/crates/vm2/src/vm.rs b/crates/vm2/src/vm.rs index 6c8d436..9dcc486 100644 --- a/crates/vm2/src/vm.rs +++ b/crates/vm2/src/vm.rs @@ -197,7 +197,6 @@ impl VirtualMachine { code_address: H160, program: Program, gas: u32, - stipend: u32, exception_handler: u16, is_static: bool, calldata_heap: HeapId, @@ -221,7 +220,6 @@ impl VirtualMachine { self.state.heaps.allocate(), calldata_heap, gas, - stipend, exception_handler, if M::VALUE == CallingMode::Delegate { self.state.current_frame.context_u128 From 34ec535b64c42ec12bb0a8ae54e7759d1cbbab6f Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 24 Jan 2025 11:59:07 +0200 Subject: [PATCH 2/4] Add memory stipend for EVM interpreter calls --- crates/vm2/src/callframe.rs | 6 ++++++ crates/vm2/src/instruction_handlers/far_call.rs | 5 +++-- crates/vm2/src/state.rs | 1 + crates/vm2/src/tracing.rs | 1 + crates/vm2/src/vm.rs | 2 ++ 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/vm2/src/callframe.rs b/crates/vm2/src/callframe.rs index 9142cc8..f9f5175 100644 --- a/crates/vm2/src/callframe.rs +++ b/crates/vm2/src/callframe.rs @@ -13,6 +13,9 @@ use crate::{ Instruction, World, }; +// FIXME: use `zkevm_opcode_defs::system_params` once it's released +const NEW_EVM_FRAME_MEMORY_STIPEND: u32 = 56 << 10; + #[derive(Debug)] pub(crate) struct Callframe { pub(crate) address: H160, @@ -70,11 +73,14 @@ impl Callframe { exception_handler: u16, context_u128: u128, is_static: bool, + is_evm_interpreter: bool, world_before_this_frame: Snapshot, ) -> Self { let is_kernel = is_kernel(address); let heap_size = if is_kernel { NEW_KERNEL_FRAME_MEMORY_STIPEND + } else if is_evm_interpreter { + NEW_EVM_FRAME_MEMORY_STIPEND } else { NEW_FRAME_MEMORY_STIPEND }; diff --git a/crates/vm2/src/instruction_handlers/far_call.rs b/crates/vm2/src/instruction_handlers/far_call.rs index dc46a70..9796469 100644 --- a/crates/vm2/src/instruction_handlers/far_call.rs +++ b/crates/vm2/src/instruction_handlers/far_call.rs @@ -59,7 +59,7 @@ where 0 }; - let failing_part = (|| { + let fallible_part = (|| { let decommit_result = vm.world_diff.decommit( world, tracer, @@ -105,7 +105,7 @@ where // A far call pushes a new frame and returns from it in the next instruction if it panics. let (calldata, program, is_evm_interpreter) = - failing_part.unwrap_or_else(|| (U256::zero().into(), Program::new_panicking(), false)); + fallible_part.unwrap_or_else(|| (U256::zero().into(), Program::new_panicking(), false)); let new_frame_is_static = IS_STATIC || vm.state.current_frame.is_static; vm.push_frame::( @@ -114,6 +114,7 @@ where new_frame_gas, exception_handler, new_frame_is_static && !is_evm_interpreter, + is_evm_interpreter, calldata.memory_page, vm.world_diff.snapshot(), ); diff --git a/crates/vm2/src/state.rs b/crates/vm2/src/state.rs index 8494c49..cd6faab 100644 --- a/crates/vm2/src/state.rs +++ b/crates/vm2/src/state.rs @@ -64,6 +64,7 @@ impl State { 0, 0, false, + false, world_before_this_frame, ), previous_frames: vec![], diff --git a/crates/vm2/src/tracing.rs b/crates/vm2/src/tracing.rs index 790efb4..03bdbda 100644 --- a/crates/vm2/src/tracing.rs +++ b/crates/vm2/src/tracing.rs @@ -474,6 +474,7 @@ mod test { (*counter).into(), *counter, false, + false, HeapId::from_u32_unchecked(5), vm.world_diff.snapshot(), ); diff --git a/crates/vm2/src/vm.rs b/crates/vm2/src/vm.rs index 9dcc486..ef1f7bc 100644 --- a/crates/vm2/src/vm.rs +++ b/crates/vm2/src/vm.rs @@ -199,6 +199,7 @@ impl VirtualMachine { gas: u32, exception_handler: u16, is_static: bool, + is_evm_interpreter: bool, calldata_heap: HeapId, world_before_this_frame: Snapshot, ) { @@ -227,6 +228,7 @@ impl VirtualMachine { self.state.context_u128 }, is_static, + is_evm_interpreter, world_before_this_frame, ); self.state.context_u128 = 0; From 5a0bd5fc793c067226dfa8fa7944bd7068e713a8 Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Fri, 24 Jan 2025 12:02:18 +0200 Subject: [PATCH 3/4] Breaking: remove `CallframeInterface::stipend()` --- crates/vm2-interface/src/state_interface.rs | 6 ------ crates/vm2/src/tracing.rs | 4 ---- 2 files changed, 10 deletions(-) diff --git a/crates/vm2-interface/src/state_interface.rs b/crates/vm2-interface/src/state_interface.rs index 046630d..78b4c80 100644 --- a/crates/vm2-interface/src/state_interface.rs +++ b/crates/vm2-interface/src/state_interface.rs @@ -115,8 +115,6 @@ pub trait CallframeInterface { fn gas(&self) -> u32; /// Sets the remaining amount of gas. fn set_gas(&mut self, new_gas: u32); - /// Additional gas provided for the duration of this callframe. - fn stipend(&self) -> u32; /// Returns the context value for this call. This context is accessible via [`ContextU128`](crate::opcodes::ContextU128) opcode. fn context_u128(&self) -> u128; @@ -379,10 +377,6 @@ pub(crate) mod testonly { unimplemented!() } - fn stipend(&self) -> u32 { - unimplemented!() - } - fn context_u128(&self) -> u128 { unimplemented!() } diff --git a/crates/vm2/src/tracing.rs b/crates/vm2/src/tracing.rs index 03bdbda..c8eb82b 100644 --- a/crates/vm2/src/tracing.rs +++ b/crates/vm2/src/tracing.rs @@ -194,10 +194,6 @@ impl> CallframeInterface for CallframeWrapper<'_, T, W> { self.frame.is_kernel } - fn stipend(&self) -> u32 { - 0 // stipend is no longer used - } - fn context_u128(&self) -> u128 { self.frame.context_u128 } From 194763be5bc538e5089baa452c0e6f197a019f3c Mon Sep 17 00:00:00 2001 From: Alex Ostrovski Date: Mon, 27 Jan 2025 12:54:40 +0200 Subject: [PATCH 4/4] Fix gas generation for fuzzing --- crates/vm2/src/single_instruction_test/callframe.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/vm2/src/single_instruction_test/callframe.rs b/crates/vm2/src/single_instruction_test/callframe.rs index a0bf23c..e4d8b73 100644 --- a/crates/vm2/src/single_instruction_test/callframe.rs +++ b/crates/vm2/src/single_instruction_test/callframe.rs @@ -1,6 +1,5 @@ use arbitrary::Arbitrary; use primitive_types::H160; -use zkevm_opcode_defs::EVM_SIMULATOR_STIPEND; use zksync_vm2_interface::{HeapId, Tracer}; use super::stack::{Stack, StackPool}; @@ -40,8 +39,7 @@ impl<'a, T: Tracer, W: World> Arbitrary<'a> for Callframe { is_kernel: is_kernel(address), stack: Box::new(Stack::new_arbitrary(u, calldata_heap, base_page)?), sp: u.arbitrary()?, - // It is assumed that it is always possible to add the stipend - gas: u.int_in_range(0..=u32::MAX - EVM_SIMULATOR_STIPEND)?, + gas: u.arbitrary()?, near_calls: vec![], pc: program.instruction(0).unwrap(), program,