From 2407d39608e07e33b570f62d953bca04afb09e82 Mon Sep 17 00:00:00 2001 From: Joonatan Saarhelo Date: Mon, 22 Jul 2024 14:23:30 +0100 Subject: [PATCH] feat: implement kernel mode (#42) - privileged instructions may only be used by kernel addresses - outside the kernel, fat pointers and pubdata are masked Because I wanted to use the fuzzer to test this feature and it is tricky to test, I ended up fixing all other discrepancies wrt. zk_evm as well. --------- Co-authored-by: Fedor Sakharov --- Cargo.toml | 3 +- afl-fuzz/.gitignore | 1 - ..._to_msg_value_far_call_forward_fat_pointer | Bin 0 -> 834 bytes afl-fuzz/in/return_calldata | Bin 0 -> 799 bytes afl-fuzz/src/main.rs | 21 +- afl-fuzz/src/show_testcase.rs | 19 +- benches/nested_near_call.rs | 13 +- fuzz/.gitignore | 4 - fuzz/Cargo.toml | 30 --- fuzz/fuzz.sh | 1 - fuzz/fuzz_targets/fuzz_target_1.rs | 38 ---- src/addressing_modes.rs | 50 ++++- src/arbitrary_instruction.rs | 184 ------------------ src/callframe.rs | 21 +- src/decode.rs | 35 +++- src/decommit.rs | 85 +++++--- src/heap.rs | 3 + src/instruction.rs | 7 +- src/instruction_handlers/context.rs | 47 +++-- src/instruction_handlers/decommit.rs | 67 +++++++ src/instruction_handlers/event.rs | 78 +++----- src/instruction_handlers/far_call.rs | 146 +++++++++----- src/instruction_handlers/heap_access.rs | 49 ++--- src/instruction_handlers/jump.rs | 27 +-- src/instruction_handlers/mod.rs | 1 + src/instruction_handlers/pointer.rs | 16 +- src/instruction_handlers/precompiles.rs | 2 - src/instruction_handlers/ret.rs | 19 +- src/instruction_handlers/storage.rs | 46 ++--- src/lib.rs | 6 +- src/mode_requirements.rs | 17 ++ src/single_instruction_test/callframe.rs | 37 +++- src/single_instruction_test/heap.rs | 22 ++- src/single_instruction_test/into_zk_evm.rs | 143 ++++++++++---- src/single_instruction_test/mod.rs | 2 +- .../print_mock_info.rs | 16 +- src/single_instruction_test/stack.rs | 18 +- .../state_to_zk_evm.rs | 96 ++++++--- .../universal_state.rs | 41 +++- src/single_instruction_test/validation.rs | 12 +- src/single_instruction_test/vm.rs | 97 +++++++-- src/single_instruction_test/world.rs | 4 + src/state.rs | 4 + src/testworld.rs | 12 ++ src/vm.rs | 34 ++-- src/world_diff.rs | 37 ++-- tests/panic.rs | 8 +- tests/stipend.rs | 15 +- 48 files changed, 940 insertions(+), 694 deletions(-) create mode 100644 afl-fuzz/in/kernel_to_msg_value_far_call_forward_fat_pointer create mode 100644 afl-fuzz/in/return_calldata delete mode 100644 fuzz/.gitignore delete mode 100644 fuzz/Cargo.toml delete mode 100644 fuzz/fuzz.sh delete mode 100644 fuzz/fuzz_targets/fuzz_target_1.rs delete mode 100644 src/arbitrary_instruction.rs create mode 100644 src/instruction_handlers/decommit.rs create mode 100644 src/mode_requirements.rs diff --git a/Cargo.toml b/Cargo.toml index 0d5feab5..ea9b70b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ zk_evm_abstractions = {git = "https://github.com/matter-labs/era-zk_evm_abstract u256 = { package = "primitive-types", version = "0.12.1" } enum_dispatch = "0.3" arbitrary = { version = "1", features = ["derive"], optional = true } -zk_evm = { git = "https://github.com/matter-labs/era-zk_evm.git", branch = "v1.5.0", optional = true } +zk_evm = { git = "https://github.com/matter-labs/era-zk_evm.git", branch = "jms-remove-assert", optional = true } anyhow = { version = "1", optional = true } [dev-dependencies] @@ -26,3 +26,4 @@ harness = false [features] single_instruction_test = ["arbitrary", "u256/arbitrary", "zk_evm", "anyhow"] +trace = [] diff --git a/afl-fuzz/.gitignore b/afl-fuzz/.gitignore index 932d7700..1fcb1529 100644 --- a/afl-fuzz/.gitignore +++ b/afl-fuzz/.gitignore @@ -1,2 +1 @@ -in out diff --git a/afl-fuzz/in/kernel_to_msg_value_far_call_forward_fat_pointer b/afl-fuzz/in/kernel_to_msg_value_far_call_forward_fat_pointer new file mode 100644 index 0000000000000000000000000000000000000000..b9842250bcf515c86b0dc6c4f7185e8b5ce0abad GIT binary patch literal 834 zcmZQzKnL3{{`c;eHu|T;U#nf3v!iErB8$`U97cZzMn=Xx9h^YrjJ385rK@eTzBfOw zjL-=-+U#ck-v2km!;n|-jz?X zG>t3%9W(L|JM`({0tLOPN_F!lIc{9KWc{2@>p50@)4aC4Rc*Z$wrq3R^Gz3aTgEQ= zU(diWJJ;<%41dnTt&6V}{#sxnb!iufS<-snh;{AJTbI3zb<>f(jQ(69b$DTU%o2h(t=|*;!y-#nQ@V2u5pLqmSn%f5+nPWd9UJkdJI^kvbG)}}TXlRT*ZmC!d~qB0 z?YL*ja3tbE6_?gOaS11ZrIO~;BQy3Yl-}F3;W88)#O>YeV)j`Kj0~TZmv?dKmoqfZap5)cE zv%vY#tfgo9PgZ5F>^i$v(`lcA(fRKOYqSo;^XKL1XN2&F`lc`*LDmXkBPU*DOBEUa za)B_q<)t~7P!jmw?+UI!ni$)Z%_6H@Z)POCNx1WZ{o>R7w`I#F Kq^1NvfHDC0rCp~0 literal 0 HcmV?d00001 diff --git a/afl-fuzz/in/return_calldata b/afl-fuzz/in/return_calldata new file mode 100644 index 0000000000000000000000000000000000000000..501238b834ce82a391aeebf4e267357bc77edbb6 GIT binary patch literal 799 zcmZQzKnL3{{`c;eHu|T;U#nf3v!iErB8$`U97cZzMn=ZH-xYu)LyZ+<{uPI!jz2SM zVr)}3i>z|JnUU}&;m!;8i%)|elqkk=eiw;;m=vPb@8>rUkgm6F6{y_OIq(6v93LO>#~=z&RGfHTH#FIbB&d*4%$0c zb~bU&{k^!2(d^zEj_x3@HuOM2gB6{RO){!cDvx3*MZ1TN7xbV4fA=1a$ziz~82R$KC% zKd^eCNaBaQWDy zrPD2r!fUrqezJqx0Vn*5C(P2SR;Q7>~R#6*gb#w{elG(t$YstRnp~fBuRx o{ro`wc>bJ%+<+^l!sme@@34ba=QwJ-0(p&dTzHMHU4w-y03f7NV*mgE literal 0 HcmV?d00001 diff --git a/afl-fuzz/src/main.rs b/afl-fuzz/src/main.rs index a9ae550e..b65a8274 100644 --- a/afl-fuzz/src/main.rs +++ b/afl-fuzz/src/main.rs @@ -1,5 +1,5 @@ use differential_fuzzing::VmAndWorld; -use vm2::single_instruction_test::{vm2_to_zk_evm, NoTracer, UniversalVmState}; +use vm2::single_instruction_test::{add_heap_to_zk_evm, vm2_to_zk_evm, NoTracer, UniversalVmState}; fn main() { afl::fuzz!(|data: &[u8]| { @@ -9,17 +9,28 @@ fn main() { // Tests that running one instruction and converting to zk_evm produces the same result as // first converting to zk_evm and then running one instruction. - let mut zk_evm = vm2_to_zk_evm(&vm, world.clone()); + let mut zk_evm = vm2_to_zk_evm( + &vm, + world.clone(), + vm.state.current_frame.pc_from_u16(0).unwrap(), + ); - let _ = vm.run_single_instruction(&mut world); + let pc = vm.run_single_instruction(&mut world).unwrap(); assert!(vm.is_in_valid_state()); + add_heap_to_zk_evm(&mut zk_evm, &vm); let _ = zk_evm.cycle(&mut NoTracer); + + // vm2 does not build a frame for a failed far call, so we need to run the panic + // to get a meaningful comparison. + if vm.instruction_is_far_call() && zk_evm.local_state.pending_exception { + let _ = zk_evm.cycle(&mut NoTracer); + } + assert_eq!( UniversalVmState::from(zk_evm), - vm2_to_zk_evm(&vm, world.clone()).into() + vm2_to_zk_evm(&vm, world.clone(), pc).into() ); - // TODO compare emitted events, storage changes and pubdata } } }); diff --git a/afl-fuzz/src/show_testcase.rs b/afl-fuzz/src/show_testcase.rs index 60485726..2f119cec 100644 --- a/afl-fuzz/src/show_testcase.rs +++ b/afl-fuzz/src/show_testcase.rs @@ -2,6 +2,7 @@ use differential_fuzzing::VmAndWorld; use pretty_assertions::assert_eq; use std::env; use std::fs; +use vm2::single_instruction_test::add_heap_to_zk_evm; use vm2::single_instruction_test::vm2_to_zk_evm; use vm2::single_instruction_test::NoTracer; use vm2::single_instruction_test::UniversalVmState; @@ -20,22 +21,34 @@ fn main() { println!("{:?}", vm.state); assert!(vm.is_in_valid_state()); - let mut zk_evm = vm2_to_zk_evm(&vm, world.clone()); + let mut zk_evm = vm2_to_zk_evm( + &vm, + world.clone(), + vm.state.current_frame.pc_from_u16(0).unwrap(), + ); let (parsed, _) = EncodingModeProduction::parse_preliminary_variant_and_absolute_number( vm.state.current_frame.raw_first_instruction(), ); println!("{}", parsed); - let _ = vm.run_single_instruction(&mut world); + let pc = vm.run_single_instruction(&mut world).unwrap(); println!("Mocks that have been touched:"); vm.print_mock_info(); assert!(vm.is_in_valid_state()); + add_heap_to_zk_evm(&mut zk_evm, &vm); let _ = zk_evm.cycle(&mut NoTracer); + + // vm2 does not build a frame for a failed far call, so we need to run the panic + // to get a meaningful comparison. + if vm.instruction_is_far_call() && zk_evm.local_state.pending_exception { + let _ = zk_evm.cycle(&mut NoTracer); + } + assert_eq!( UniversalVmState::from(zk_evm), - vm2_to_zk_evm(&vm, world.clone()).into() + vm2_to_zk_evm(&vm, world.clone(), pc).into() ); } diff --git a/benches/nested_near_call.rs b/benches/nested_near_call.rs index 1ab3e189..c919b77a 100644 --- a/benches/nested_near_call.rs +++ b/benches/nested_near_call.rs @@ -3,7 +3,7 @@ use vm2::{ addressing_modes::{Arguments, Immediate1, Immediate2, Register, Register1, Register2}, initial_decommit, testworld::TestWorld, - Instruction, + Instruction, ModeRequirements, Predicate::Always, Program, }; @@ -17,7 +17,7 @@ fn nested_near_call(bencher: Bencher) { Register1(Register::new(0)), Immediate1(0), Immediate2(0), - Arguments::new(Always, 10), + Arguments::new(Always, 10, ModeRequirements::none()), )], vec![], ); @@ -48,19 +48,22 @@ fn nested_near_call(bencher: Bencher) { fn nested_near_call_with_storage_write(bencher: Bencher) { let program = Program::new( vec![ - Instruction::from_ergs_left(Register1(Register::new(1)), Arguments::new(Always, 5)), + Instruction::from_ergs_left( + Register1(Register::new(1)), + Arguments::new(Always, 5, ModeRequirements::none()), + ), Instruction::from_sstore( // always use same storage slot to get a warm write discount Register1(Register::new(0)), Register2(Register::new(1)), - Arguments::new(Always, 5511), // need to use actual cost to not create free gas from refunds + Arguments::new(Always, 5511, ModeRequirements::none()), // need to use actual cost to not create free gas from refunds ), Instruction::from_near_call( // zero means pass all gas Register1(Register::new(0)), Immediate1(0), Immediate2(0), - Arguments::new(Always, 25), + Arguments::new(Always, 25, ModeRequirements::none()), ), ], vec![], diff --git a/fuzz/.gitignore b/fuzz/.gitignore deleted file mode 100644 index 1a45eee7..00000000 --- a/fuzz/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -target -corpus -artifacts -coverage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml deleted file mode 100644 index ab54139e..00000000 --- a/fuzz/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "vm2-fuzz" -version = "0.0.0" -publish = false -edition = "2021" - -[package.metadata] -cargo-fuzz = true - -[dependencies] -libfuzzer-sys = "0.4" -arbitrary = { version = "1", features = ["derive"] } -u256 = { package = "primitive-types", version = "0.12.1" } - -[dependencies.vm2] -path = ".." -features = ["arbitrary"] - -# Prevent this from interfering with workspaces -[workspace] -members = ["."] - -[profile.release] -debug = 1 - -[[bin]] -name = "fuzz_target_1" -path = "fuzz_targets/fuzz_target_1.rs" -test = false -doc = false diff --git a/fuzz/fuzz.sh b/fuzz/fuzz.sh deleted file mode 100644 index 0fa6ea3b..00000000 --- a/fuzz/fuzz.sh +++ /dev/null @@ -1 +0,0 @@ -cargo +nightly fuzz run -O fuzz_target_1 --sanitizer none -j 8 \ No newline at end of file diff --git a/fuzz/fuzz_targets/fuzz_target_1.rs b/fuzz/fuzz_targets/fuzz_target_1.rs deleted file mode 100644 index 5685ae72..00000000 --- a/fuzz/fuzz_targets/fuzz_target_1.rs +++ /dev/null @@ -1,38 +0,0 @@ -#![no_main] - -use arbitrary::{Arbitrary, Unstructured}; -use libfuzzer_sys::fuzz_target; -use u256::H160; -use vm2::{ - jump_to_beginning, testworld::TestWorld, Instruction, Program, Settings, VirtualMachine, -}; - -fuzz_target!(|data: &[u8]| { - let mut u = Unstructured::new(data); - let mut program: Vec = Arbitrary::arbitrary(&mut u).unwrap(); - - if program.len() >= 1 << 16 { - program.truncate(1 << 16); - program.push(jump_to_beginning()); - } else { - program.push(Instruction::from_invalid()); - } - - let address = H160::from_low_u64_be(0x1234567890abcdef); - let mut world = TestWorld::new(&[(address, Program::new(program, vec![]))]); - let program = vm2::initial_decommit(&mut world, address); - - let mut state = VirtualMachine::new( - address, - program, - H160::zero(), - vec![], - u32::MAX, - Settings { - default_aa_code_hash: [0; 32], - evm_interpreter_code_hash: [0; 32], - hook_address: 0, - }, - ); - state.run(&mut world); -}); diff --git a/src/addressing_modes.rs b/src/addressing_modes.rs index d7aa699a..94d5c2a5 100644 --- a/src/addressing_modes.rs +++ b/src/addressing_modes.rs @@ -1,16 +1,35 @@ -use crate::predication::Predicate; +use crate::{mode_requirements::ModeRequirements, predication::Predicate}; #[cfg(feature = "arbitrary")] use arbitrary::{Arbitrary, Unstructured}; use enum_dispatch::enum_dispatch; use u256::U256; +use zkevm_opcode_defs::erase_fat_pointer_metadata; pub(crate) trait Source { + /// Get a word's value for non-pointer operations. (Pointers are erased.) fn get(args: &Arguments, state: &mut impl Addressable) -> U256 { - Self::get_with_pointer_flag(args, state).0 + Self::get_with_pointer_flag_and_erasing(args, state).0 } + + /// Get a word's value and pointer flag. fn get_with_pointer_flag(args: &Arguments, state: &mut impl Addressable) -> (U256, bool) { (Self::get(args, state), false) } + + /// Get a word's value, erasing pointers but also returning the pointer flag. + /// The flag will always be false unless in kernel mode. + /// Necessary for pointer operations, which for some reason erase their second argument + /// but also panic when it was a pointer. + fn get_with_pointer_flag_and_erasing( + args: &Arguments, + state: &mut impl Addressable, + ) -> (U256, bool) { + let (mut value, is_pointer) = Self::get_with_pointer_flag(args, state); + if is_pointer && !state.in_kernel_mode() { + erase_fat_pointer_metadata(&mut value) + } + (value, is_pointer && state.in_kernel_mode()) + } } pub(crate) trait Destination { @@ -35,6 +54,8 @@ pub trait Addressable { fn clear_stack_pointer_flag(&mut self, slot: u16); fn code_page(&self) -> &[U256]; + + fn in_kernel_mode(&self) -> bool; } #[enum_dispatch] @@ -63,13 +84,14 @@ impl DestinationWriter for Option { } } -#[derive(Hash, Debug)] +// It is important for performance that this fits into 8 bytes. +#[derive(Debug)] pub struct Arguments { source_registers: PackedRegisters, destination_registers: PackedRegisters, immediate1: u16, immediate2: u16, - pub predicate: Predicate, + predicate_and_mode_requirements: u8, static_gas_cost: u8, } @@ -79,13 +101,21 @@ pub(crate) const SLOAD_COST: u32 = 2008; pub(crate) const INVALID_INSTRUCTION_COST: u32 = 4294967295; impl Arguments { - pub const fn new(predicate: Predicate, gas_cost: u32) -> Self { + pub const fn new( + predicate: Predicate, + gas_cost: u32, + mode_requirements: ModeRequirements, + ) -> Self { + // Make sure that these two can be packed into 8 bits without overlapping + assert!(predicate as u8 & (0b11 << 6) == 0); + assert!(mode_requirements.0 & !(0b11) == 0); + Self { source_registers: PackedRegisters(0), destination_registers: PackedRegisters(0), immediate1: 0, immediate2: 0, - predicate, + predicate_and_mode_requirements: (predicate as u8) << 2 | mode_requirements.0, static_gas_cost: Self::encode_static_gas_cost(gas_cost), } } @@ -117,6 +147,14 @@ impl Arguments { } } + pub(crate) fn predicate(&self) -> Predicate { + unsafe { std::mem::transmute(self.predicate_and_mode_requirements >> 2) } + } + + pub(crate) fn mode_requirements(&self) -> ModeRequirements { + ModeRequirements(self.predicate_and_mode_requirements & 0b11) + } + pub(crate) fn write_source(mut self, sw: &impl SourceWriter) -> Self { sw.write_source(&mut self); self diff --git a/src/arbitrary_instruction.rs b/src/arbitrary_instruction.rs deleted file mode 100644 index cff283bc..00000000 --- a/src/arbitrary_instruction.rs +++ /dev/null @@ -1,184 +0,0 @@ -use crate::addressing_modes::Arguments; -use crate::instruction_handlers::{ - Add, And, CallingMode, Div, Heap, Mul, Or, PtrAdd, PtrPack, PtrShrink, PtrSub, RotateLeft, - RotateRight, ShiftLeft, ShiftRight, Sub, Xor, -}; -use crate::{instruction::Instruction, Predicate}; -use arbitrary::Arbitrary; - -impl<'a> Arbitrary<'a> for Instruction { - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - let predicate = if u.arbitrary()? { - Predicate::Always - } else { - u.arbitrary()? - }; - - // 1 to 4 are reserved gas costs and also skip 0 - let gas_cost = u.arbitrary::()?.saturating_add(5); - let arguments = Arguments::new(predicate, gas_cost as u32); - - Ok(match u.choose_index(23)? { - 0 => Self::from_binop::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - (), - arguments, - u.arbitrary()?, - u.arbitrary()?, - ), - 1 => Self::from_binop::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - (), - arguments, - u.arbitrary()?, - u.arbitrary()?, - ), - 2 => Self::from_binop::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - (), - arguments, - u.arbitrary()?, - u.arbitrary()?, - ), - 3 => Self::from_binop::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - (), - arguments, - u.arbitrary()?, - u.arbitrary()?, - ), - 4 => Self::from_binop::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - (), - arguments, - u.arbitrary()?, - u.arbitrary()?, - ), - 5 => Self::from_binop::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - (), - arguments, - u.arbitrary()?, - u.arbitrary()?, - ), - 6 => Self::from_binop::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - (), - arguments, - u.arbitrary()?, - u.arbitrary()?, - ), - 7 => Self::from_binop::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - (), - arguments, - u.arbitrary()?, - u.arbitrary()?, - ), - 8 => Self::from_binop::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - (), - arguments, - u.arbitrary()?, - u.arbitrary()?, - ), - 9 => Self::from_binop::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - (), - arguments, - u.arbitrary()?, - u.arbitrary()?, - ), - 10 => Self::from_binop::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - arguments, - u.arbitrary()?, - u.arbitrary()?, - ), - 11 => Self::from_binop::
( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - arguments, - u.arbitrary()?, - u.arbitrary()?, - ), - 12 => Self::from_jump(u.arbitrary()?, arguments), - 13 => Self::from_ptr::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - arguments, - u.arbitrary()?, - ), - 14 => Self::from_ptr::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - arguments, - u.arbitrary()?, - ), - 15 => Self::from_ptr::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - arguments, - u.arbitrary()?, - ), - 16 => Self::from_ptr::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - arguments, - u.arbitrary()?, - ), - 17 => { - Self::from_load::(u.arbitrary()?, u.arbitrary()?, u.arbitrary()?, arguments) - } - 18 => Self::from_store::( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - arguments, - false, - ), - 19 => { - Self::from_load_pointer(u.arbitrary()?, u.arbitrary()?, u.arbitrary()?, arguments) - } - 20 => Self::from_sstore(u.arbitrary()?, u.arbitrary()?, arguments), - 21 => Self::from_sload(u.arbitrary()?, u.arbitrary()?, arguments), - 22 => Self::from_far_call::<{ CallingMode::Normal as u8 }>( - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - u.arbitrary()?, - arguments, - ), - _ => unreachable!(), - }) - } -} diff --git a/src/callframe.rs b/src/callframe.rs index 69c937e7..f8b14357 100644 --- a/src/callframe.rs +++ b/src/callframe.rs @@ -1,6 +1,6 @@ use crate::{ - address_into_u256, decommit::is_kernel, heap::HeapId, program::Program, stack::Stack, - world_diff::Snapshot, Instruction, + decommit::is_kernel, heap::HeapId, program::Program, stack::Stack, world_diff::Snapshot, + Instruction, }; use u256::H160; use zkevm_opcode_defs::system_params::{NEW_FRAME_MEMORY_STIPEND, NEW_KERNEL_FRAME_MEMORY_STIPEND}; @@ -14,6 +14,7 @@ pub struct Callframe { pub exception_handler: u16, pub context_u128: u128, pub is_static: bool, + pub is_kernel: bool, pub stack: Box, pub sp: u16, @@ -49,10 +50,10 @@ pub struct Callframe { #[derive(Clone, PartialEq, Debug)] pub(crate) struct NearCallFrame { - call_instruction: u16, - exception_handler: u16, - previous_frame_sp: u16, - previous_frame_gas: u32, + pub(crate) call_instruction: u16, + pub(crate) exception_handler: u16, + pub(crate) previous_frame_sp: u16, + pub(crate) previous_frame_gas: u32, world_before_this_frame: Snapshot, } @@ -74,7 +75,8 @@ impl Callframe { is_static: bool, world_before_this_frame: Snapshot, ) -> Self { - let heap_size = if is_kernel(address_into_u256(address)) { + let is_kernel = is_kernel(address); + let heap_size = if is_kernel { NEW_KERNEL_FRAME_MEMORY_STIPEND } else { NEW_FRAME_MEMORY_STIPEND @@ -87,6 +89,7 @@ impl Callframe { program, context_u128, is_static, + is_kernel, stack, heap, aux_heap, @@ -94,7 +97,7 @@ impl Callframe { aux_heap_size: heap_size, calldata_heap, heaps_i_am_keeping_alive: vec![], - sp: 1024, + sp: 0, gas, stipend, exception_handler, @@ -137,7 +140,7 @@ impl Callframe { unsafe { pc.offset_from(self.program.instruction(0).unwrap()) as u16 } } - pub(crate) fn pc_from_u16(&self, index: u16) -> Option<*const Instruction> { + pub fn pc_from_u16(&self, index: u16) -> Option<*const Instruction> { self.program .instruction(index) .map(|p| p as *const Instruction) diff --git a/src/decode.rs b/src/decode.rs index 70038870..b952904f 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -9,14 +9,16 @@ use crate::{ Add, And, AuxHeap, CallingMode, Div, Heap, Mul, Or, PtrAdd, PtrPack, PtrShrink, PtrSub, RotateLeft, RotateRight, ShiftLeft, ShiftRight, Sub, Xor, }, - jump_to_beginning, Instruction, Predicate, VirtualMachine, World, + jump_to_beginning, + mode_requirements::ModeRequirements, + Instruction, Predicate, VirtualMachine, World, }; use zkevm_opcode_defs::{ decoding::{EncodingModeProduction, VmEncodingMode}, ImmMemHandlerFlags, Opcode, Operand::*, - RegOrImmFlags, FAR_CALL_STATIC_FLAG_IDX, FIRST_MESSAGE_FLAG_IDX, RET_TO_LABEL_BIT_IDX, - SET_FLAGS_FLAG_IDX, SWAP_OPERANDS_FLAG_IDX_FOR_ARITH_OPCODES, + RegOrImmFlags, FAR_CALL_SHARD_FLAG_IDX, FAR_CALL_STATIC_FLAG_IDX, FIRST_MESSAGE_FLAG_IDX, + RET_TO_LABEL_BIT_IDX, SET_FLAGS_FLAG_IDX, SWAP_OPERANDS_FLAG_IDX_FOR_ARITH_OPCODES, SWAP_OPERANDS_FLAG_IDX_FOR_PTR_OPCODE, UMA_INCREMENT_FLAG_IDX, }; @@ -33,7 +35,7 @@ pub fn decode_program(raw: &[u64], is_bootloader: bool) -> Vec { } fn unimplemented_instruction(variant: Opcode) -> Instruction { - let mut arguments = Arguments::new(Predicate::Always, 0); + let mut arguments = Arguments::new(Predicate::Always, 0, ModeRequirements::none()); let variant_as_number: u16 = unsafe { std::mem::transmute(variant) }; Immediate1(variant_as_number).write_source(&mut arguments); Instruction { @@ -68,7 +70,14 @@ pub(crate) fn decode(raw: u64, is_bootloader: bool) -> Instruction { zkevm_opcode_defs::Condition::Ne => crate::Predicate::IfNotEQ, zkevm_opcode_defs::Condition::GtOrLt => crate::Predicate::IfGtOrLT, }; - let arguments = Arguments::new(predicate, parsed.variant.ergs_price()); + let arguments = Arguments::new( + predicate, + parsed.variant.ergs_price(), + ModeRequirements::new( + parsed.variant.requires_kernel_mode(), + !parsed.variant.can_be_used_in_static_context(), + ), + ); let stack_in = RegisterAndImmediate { immediate: parsed.imm_0, @@ -149,7 +158,9 @@ pub(crate) fn decode(raw: u64, is_bootloader: bool) -> Instruction { zkevm_opcode_defs::ShiftOpcode::Rol => binop!(RotateLeft, ()), zkevm_opcode_defs::ShiftOpcode::Ror => binop!(RotateRight, ()), }, - zkevm_opcode_defs::Opcode::Jump(_) => Instruction::from_jump(src1, arguments), + zkevm_opcode_defs::Opcode::Jump(_) => { + Instruction::from_jump(src1, out.try_into().unwrap(), arguments) + } zkevm_opcode_defs::Opcode::Context(x) => match x { zkevm_opcode_defs::ContextOpcode::This => { Instruction::from_this(out.try_into().unwrap(), arguments) @@ -178,7 +189,9 @@ pub(crate) fn decode(raw: u64, is_bootloader: bool) -> Instruction { zkevm_opcode_defs::ContextOpcode::IncrementTxNumber => { Instruction::from_increment_tx_number(arguments) } - x => unimplemented_instruction(zkevm_opcode_defs::Opcode::Context(x)), + zkevm_opcode_defs::ContextOpcode::AuxMutating0 => { + Instruction::from_aux_mutating(arguments) + } }, zkevm_opcode_defs::Opcode::Ptr(x) => match x { zkevm_opcode_defs::PtrOpcode::Add => ptr!(PtrAdd), @@ -209,6 +222,7 @@ pub(crate) fn decode(raw: u64, is_bootloader: bool) -> Instruction { src2, Immediate1(parsed.imm_0), parsed.variant.flags[FAR_CALL_STATIC_FLAG_IDX], + parsed.variant.flags[FAR_CALL_SHARD_FLAG_IDX], arguments, ) } @@ -269,7 +283,12 @@ pub(crate) fn decode(raw: u64, is_bootloader: bool) -> Instruction { out.try_into().unwrap(), arguments, ), - x => unimplemented_instruction(zkevm_opcode_defs::Opcode::Log(x)), + zkevm_opcode_defs::LogOpcode::Decommit => Instruction::from_decommit( + src1.try_into().unwrap(), + src2, + out.try_into().unwrap(), + arguments, + ), }, zkevm_opcode_defs::Opcode::UMA(x) => { let increment = parsed.variant.flags[UMA_INCREMENT_FLAG_IDX]; diff --git a/src/decommit.rs b/src/decommit.rs index d177cb06..6d0a2a5b 100644 --- a/src/decommit.rs +++ b/src/decommit.rs @@ -11,9 +11,8 @@ impl WorldDiff { address: U256, default_aa_code_hash: [u8; 32], evm_interpreter_code_hash: [u8; 32], - gas: &mut u32, is_constructor_call: bool, - ) -> Option<(Program, bool)> { + ) -> Option<(UnpaidDecommit, bool)> { let deployer_system_contract_address = Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW as u64); @@ -33,21 +32,34 @@ impl WorldDiff { return None; } }; - if is_constructed == is_constructor_call { - return None; - } + let try_default_aa = if is_kernel(u256_into_address(address)) { + None + } else { + Some(default_aa_code_hash) + }; + + // The address aliasing contract implements Ethereum-like behavior of calls to EOAs + // returning successfully (and address aliasing when called from the bootloader). + // It makes sense that unconstructed code is treated as an EOA but for some reason + // a constructor call to constructed code is also treated as EOA. match code_info_bytes[0] { - 1 => code_info_bytes, + 1 => { + if is_constructed == is_constructor_call { + try_default_aa? + } else { + code_info_bytes + } + } 2 => { - is_evm = true; - evm_interpreter_code_hash + if is_constructed == is_constructor_call { + try_default_aa? + } else { + is_evm = true; + evm_interpreter_code_hash + } } - - // The address aliasing contract implements Ethereum-like behavior of calls to EOAs - // returning successfully (and address aliasing when called from the bootloader). - _ if code_info == U256::zero() && !is_kernel(address) => default_aa_code_hash, - + _ if code_info == U256::zero() => try_default_aa?, _ => return None, } }; @@ -55,21 +67,42 @@ impl WorldDiff { code_info[1] = 0; let code_key: U256 = U256::from_big_endian(&code_info); - if !self.decommitted_hashes.as_ref().contains_key(&code_key) { + let cost = if self.decommitted_hashes.as_ref().contains_key(&code_key) { + 0 + } else { let code_length_in_words = u16::from_be_bytes([code_info[2], code_info[3]]); - let cost = - code_length_in_words as u32 * zkevm_opcode_defs::ERGS_PER_CODE_WORD_DECOMMITTMENT; - if cost > *gas { - // Unlike all other gas costs, this one is not paid if low on gas. - return None; - } - *gas -= cost; - self.decommitted_hashes.insert(code_key, ()); + code_length_in_words as u32 * zkevm_opcode_defs::ERGS_PER_CODE_WORD_DECOMMITTMENT }; - let program = world.decommit(code_key); - Some((program, is_evm)) + Some((UnpaidDecommit { cost, code_key }, is_evm)) } + + pub(crate) fn decommit_opcode(&mut self, world: &mut dyn World, code_hash: U256) -> Vec { + let mut code_info_bytes = [0; 32]; + code_hash.to_big_endian(&mut code_info_bytes); + self.decommitted_hashes.insert(code_hash, ()); + world.decommit_code(code_hash) + } + + pub(crate) fn pay_for_decommit( + &mut self, + world: &mut dyn World, + decommit: UnpaidDecommit, + gas: &mut u32, + ) -> Option { + if decommit.cost > *gas { + // Unlike all other gas costs, this one is not paid if low on gas. + return None; + } + *gas -= decommit.cost; + self.decommitted_hashes.insert(decommit.code_key, ()); + Some(world.decommit(decommit.code_key)) + } +} + +pub(crate) struct UnpaidDecommit { + cost: u32, + code_key: U256, } /// May be used to load code when the VM first starts up. @@ -105,6 +138,6 @@ pub(crate) fn u256_into_address(source: U256) -> H160 { result } -pub(crate) fn is_kernel(address: U256) -> bool { - address < (1 << 16).into() +pub(crate) fn is_kernel(address: H160) -> bool { + address.0[..18].iter().all(|&byte| byte == 0) } diff --git a/src/heap.rs b/src/heap.rs index 1c55a9d4..58e3a25b 100644 --- a/src/heap.rs +++ b/src/heap.rs @@ -57,6 +57,9 @@ impl HeapInterface for Heap { } result } + fn memset(&mut self, src: &[u8]) { + self.0 = src.to_vec(); + } } #[derive(Debug, Clone)] diff --git a/src/instruction.rs b/src/instruction.rs index c51197fe..f1416da4 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -1,6 +1,9 @@ use std::fmt::{self, Debug, Formatter}; -use crate::{addressing_modes::Arguments, vm::VirtualMachine, Predicate, World}; +use crate::{ + addressing_modes::Arguments, mode_requirements::ModeRequirements, vm::VirtualMachine, + Predicate, World, +}; pub struct Instruction { pub(crate) handler: Handler, @@ -33,7 +36,7 @@ pub enum ExecutionEnd { pub fn jump_to_beginning() -> Instruction { Instruction { handler: jump_to_beginning_handler, - arguments: Arguments::new(Predicate::Always, 0), + arguments: Arguments::new(Predicate::Always, 0, ModeRequirements::none()), } } fn jump_to_beginning_handler( diff --git a/src/instruction_handlers/context.rs b/src/instruction_handlers/context.rs index ce4e0d5d..f0fc054c 100644 --- a/src/instruction_handlers/context.rs +++ b/src/instruction_handlers/context.rs @@ -1,7 +1,4 @@ -use super::{ - common::{instruction_boilerplate, instruction_boilerplate_with_panic}, - free_panic, -}; +use super::common::instruction_boilerplate; use crate::{ addressing_modes::{Arguments, Destination, Register1, Source}, decommit::address_into_u256, @@ -82,8 +79,11 @@ fn context_meta( caller_shard_id: 0, code_shard_id: 0, // This field is actually pubdata! - // TODO PLA-893: This should be zero when not in kernel mode - aux_field_0: vm.world_diff.pubdata.0 as u32, + aux_field_0: if vm.state.current_frame.is_kernel { + vm.world_diff.pubdata.0 as u32 + } else { + 0 + }, } .to_u256(); @@ -96,21 +96,10 @@ fn set_context_u128( instruction: *const Instruction, world: &mut dyn World, ) -> InstructionResult { - instruction_boilerplate_with_panic( - vm, - instruction, - world, - |vm, args, world, continue_normally| { - if vm.state.current_frame.is_static { - return free_panic(vm, world); - } - - let value = Register1::get(args, &mut vm.state).low_u128(); - vm.state.set_context_u128(value); - - continue_normally - }, - ) + instruction_boilerplate(vm, instruction, world, |vm, args, _| { + let value = Register1::get(args, &mut vm.state).low_u128(); + vm.state.set_context_u128(value); + }) } fn increment_tx_number( @@ -123,6 +112,16 @@ fn increment_tx_number( }) } +fn aux_mutating( + vm: &mut VirtualMachine, + instruction: *const Instruction, + world: &mut dyn World, +) -> InstructionResult { + instruction_boilerplate(vm, instruction, world, |_, _, _| { + // This instruction just crashes or nops + }) +} + impl Instruction { fn from_context(out: Register1, arguments: Arguments) -> Self { Self { @@ -167,4 +166,10 @@ impl Instruction { arguments, } } + pub fn from_aux_mutating(arguments: Arguments) -> Self { + Self { + handler: aux_mutating, + arguments, + } + } } diff --git a/src/instruction_handlers/decommit.rs b/src/instruction_handlers/decommit.rs new file mode 100644 index 00000000..88a26973 --- /dev/null +++ b/src/instruction_handlers/decommit.rs @@ -0,0 +1,67 @@ +use u256::U256; +use zkevm_opcode_defs::{BlobSha256Format, ContractCodeSha256Format, VersionedHashLen32}; + +use crate::{ + addressing_modes::{Arguments, Destination, Register1, Register2, Source}, + fat_pointer::FatPointer, + instruction::InstructionResult, + Instruction, VirtualMachine, World, +}; + +use super::{common::instruction_boilerplate, HeapInterface}; + +fn decommit( + vm: &mut VirtualMachine, + instruction: *const Instruction, + world: &mut dyn World, +) -> InstructionResult { + instruction_boilerplate(vm, instruction, world, |vm, args, world| { + let code_hash = Register1::get(args, &mut vm.state); + let extra_cost = Register2::get(args, &mut vm.state).low_u32(); + + let mut buffer = [0u8; 32]; + code_hash.to_big_endian(&mut buffer); + + let preimage_len_in_bytes = + zkevm_opcode_defs::system_params::NEW_KERNEL_FRAME_MEMORY_STIPEND; + + if vm.state.use_gas(extra_cost).is_err() + || (!ContractCodeSha256Format::is_valid(&buffer) + && !BlobSha256Format::is_valid(&buffer)) + { + Register1::set(args, &mut vm.state, U256::zero()); + return; + } + + let program = vm.world_diff.decommit_opcode(world, code_hash); + + let heap = vm.state.heaps.allocate(); + vm.state.current_frame.heaps_i_am_keeping_alive.push(heap); + vm.state.heaps[heap].memset(program.as_ref()); + + let value = FatPointer { + offset: 0, + memory_page: heap, + start: 0, + length: preimage_len_in_bytes, + }; + let value = value.into_u256(); + Register1::set_fat_ptr(args, &mut vm.state, value); + }) +} +impl Instruction { + pub fn from_decommit( + abi: Register1, + burn: Register2, + out: Register1, + arguments: Arguments, + ) -> Self { + Self { + arguments: arguments + .write_source(&abi) + .write_source(&burn) + .write_destination(&out), + handler: decommit, + } + } +} diff --git a/src/instruction_handlers/event.rs b/src/instruction_handlers/event.rs index c8370e63..28edd194 100644 --- a/src/instruction_handlers/event.rs +++ b/src/instruction_handlers/event.rs @@ -1,4 +1,4 @@ -use super::{common::instruction_boilerplate_with_panic, free_panic}; +use super::common::instruction_boilerplate; use crate::{ addressing_modes::{Arguments, Immediate1, Register1, Register2, Source}, instruction::InstructionResult, @@ -13,32 +13,21 @@ fn event( instruction: *const Instruction, world: &mut dyn World, ) -> InstructionResult { - instruction_boilerplate_with_panic( - vm, - instruction, - world, - |vm, args, world, continue_normally| { - if vm.state.current_frame.is_static { - return free_panic(vm, world); - } - 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); - let is_first = Immediate1::get(args, &mut vm.state).low_u32() == 1; - - vm.world_diff.record_event(Event { - key, - value, - is_first, - shard_id: 0, // shards currently aren't supported - tx_number: vm.state.transaction_number, - }); - } + instruction_boilerplate(vm, instruction, world, |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); + let is_first = Immediate1::get(args, &mut vm.state).low_u32() == 1; - continue_normally - }, - ) + vm.world_diff.record_event(Event { + key, + value, + is_first, + shard_id: 0, // shards currently aren't supported + tx_number: vm.state.transaction_number, + }); + } + }) } fn l2_to_l1( @@ -46,30 +35,19 @@ fn l2_to_l1( instruction: *const Instruction, world: &mut dyn World, ) -> InstructionResult { - instruction_boilerplate_with_panic( - vm, - instruction, - world, - |vm, args, world, continue_normally| { - if vm.state.current_frame.is_static { - return free_panic(vm, world); - } - - 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; - vm.world_diff.record_l2_to_l1_log(L2ToL1Log { - key, - value, - is_service, - address: vm.state.current_frame.address, - shard_id: 0, - tx_number: vm.state.transaction_number, - }); - - continue_normally - }, - ) + instruction_boilerplate(vm, instruction, world, |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; + vm.world_diff.record_l2_to_l1_log(L2ToL1Log { + key, + value, + is_service, + address: vm.state.current_frame.address, + shard_id: 0, + tx_number: vm.state.transaction_number, + }); + }) } impl Instruction { diff --git a/src/instruction_handlers/far_call.rs b/src/instruction_handlers/far_call.rs index 201e8a1c..17e88b23 100644 --- a/src/instruction_handlers/far_call.rs +++ b/src/instruction_handlers/far_call.rs @@ -1,7 +1,11 @@ -use super::{heap_access::grow_heap, ret::panic_from_failed_far_call, AuxHeap, Heap}; +use super::{ + heap_access::grow_heap, + ret::{panic_from_failed_far_call, RETURN_COST}, + AuxHeap, Heap, +}; use crate::{ addressing_modes::{Arguments, Immediate1, Register1, Register2, Source}, - decommit::u256_into_address, + decommit::{is_kernel, u256_into_address}, fat_pointer::FatPointer, instruction::InstructionResult, predication::Flags, @@ -30,52 +34,73 @@ pub enum CallingMode { /// /// Even though all errors happen before the new stack frame, they cause a panic in the new frame, /// not in the caller! -fn far_call( +fn far_call( vm: &mut VirtualMachine, instruction: *const Instruction, world: &mut dyn World, ) -> InstructionResult { let args = unsafe { &(*instruction).arguments }; - let address_mask: U256 = U256::MAX >> (256 - 160); - let (raw_abi, raw_abi_is_pointer) = Register1::get_with_pointer_flag(args, &mut vm.state); + + let address_mask: U256 = U256::MAX >> (256 - 160); let destination_address = Register2::get(args, &mut vm.state) & address_mask; let exception_handler = Immediate1::get(args, &mut vm.state).low_u32() as u16; - let abi = get_far_call_arguments(raw_abi); + let mut abi = get_far_call_arguments(raw_abi); + abi.is_constructor_call = abi.is_constructor_call && vm.state.current_frame.is_kernel; + abi.is_system_call = abi.is_system_call && is_kernel(u256_into_address(destination_address)); - let calldata = get_far_call_calldata(raw_abi, raw_abi_is_pointer, vm); - - let decommit_result = vm.world_diff.decommit( - world, - destination_address, - vm.settings.default_aa_code_hash, - vm.settings.evm_interpreter_code_hash, - &mut vm.state.current_frame.gas, - abi.is_constructor_call, - ); - - let mandated_gas = if destination_address == ADDRESS_MSG_VALUE.into() { + let mut mandated_gas = if abi.is_system_call && destination_address == ADDRESS_MSG_VALUE.into() + { MSG_VALUE_SIMULATOR_ADDITIVE_COST } else { 0 }; - // mandated gas is passed even if it means transferring more than the 63/64 rule allows - if let Some(gas_left) = vm.state.current_frame.gas.checked_sub(mandated_gas) { - vm.state.current_frame.gas = gas_left; - } else { - return panic_from_failed_far_call(vm, exception_handler); - }; + let failing_part = (|| { + let decommit_result = vm.world_diff.decommit( + world, + destination_address, + vm.settings.default_aa_code_hash, + vm.settings.evm_interpreter_code_hash, + abi.is_constructor_call, + ); + + // calldata has to be constructed even if we already know we will panic because + // overflowing start + length makes the heap resize even when already panicking. + let already_failed = decommit_result.is_none() || IS_SHARD && abi.shard_id != 0; + + let maybe_calldata = get_far_call_calldata(raw_abi, raw_abi_is_pointer, vm, already_failed); + + // mandated gas is passed even if it means transferring more than the 63/64 rule allows + if let Some(gas_left) = vm.state.current_frame.gas.checked_sub(mandated_gas) { + vm.state.current_frame.gas = gas_left; + } else { + // If the gas is insufficient, the rest is burned + vm.state.current_frame.gas = 0; + mandated_gas = 0; + return None; + }; + + let calldata = maybe_calldata?; + let (unpaid_decommit, is_evm) = decommit_result?; + let program = vm.world_diff.pay_for_decommit( + world, + unpaid_decommit, + &mut vm.state.current_frame.gas, + )?; + + Some((calldata, program, is_evm)) + })(); let maximum_gas = vm.state.current_frame.gas / 64 * 63; - let new_frame_gas = abi.gas_to_pass.min(maximum_gas); - vm.state.current_frame.gas -= new_frame_gas; - - let new_frame_gas = new_frame_gas + mandated_gas; + let normally_passed_gas = abi.gas_to_pass.min(maximum_gas); + vm.state.current_frame.gas -= normally_passed_gas; + let new_frame_gas = normally_passed_gas + mandated_gas; - let (Some(calldata), Some((program, is_evm_interpreter))) = (calldata, decommit_result) else { + let Some((calldata, program, is_evm_interpreter)) = failing_part else { + vm.state.current_frame.gas += new_frame_gas.saturating_sub(RETURN_COST); return panic_from_failed_far_call(vm, exception_handler); }; @@ -88,6 +113,7 @@ fn far_call( .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::( instruction, u256_into_address(destination_address), @@ -95,7 +121,7 @@ fn far_call( new_frame_gas, stipend, exception_handler, - IS_STATIC && !is_evm_interpreter, + new_frame_is_static && !is_evm_interpreter, calldata.memory_page, vm.world_diff.snapshot(), ); @@ -115,7 +141,7 @@ fn far_call( vm.state.register_pointer_flags = 2; vm.state.registers[1] = calldata.into_u256(); - let is_static_call_to_evm_interpreter = IS_STATIC && is_evm_interpreter; + let is_static_call_to_evm_interpreter = new_frame_is_static && is_evm_interpreter; let call_type = (u8::from(is_static_call_to_evm_interpreter) << 2) | (u8::from(abi.is_system_call) << 1) | u8::from(abi.is_constructor_call); @@ -127,7 +153,7 @@ fn far_call( pub(crate) struct FarCallABI { pub gas_to_pass: u32, - pub _shard_id: u8, + pub shard_id: u8, pub is_constructor_call: bool, pub is_system_call: bool, } @@ -135,51 +161,64 @@ pub(crate) struct FarCallABI { pub(crate) fn get_far_call_arguments(abi: U256) -> FarCallABI { let gas_to_pass = abi.0[3] as u32; let settings = (abi.0[3] >> 32) as u32; - let [_, _shard_id, constructor_call_byte, system_call_byte] = settings.to_le_bytes(); + let [_, shard_id, constructor_call_byte, system_call_byte] = settings.to_le_bytes(); FarCallABI { gas_to_pass, - _shard_id, + shard_id, is_constructor_call: constructor_call_byte != 0, is_system_call: system_call_byte != 0, } } +/// Forms a new fat pointer or narrows an existing one, as dictated by the ABI. +/// +/// This function needs to be called even if we already know we will panic because +/// overflowing start + length makes the heap resize even when already panicking. pub(crate) fn get_far_call_calldata( raw_abi: U256, is_pointer: bool, vm: &mut VirtualMachine, + already_failed: bool, ) -> Option { let mut pointer = FatPointer::from(raw_abi); match FatPointerSource::from_abi((raw_abi.0[3] >> 32) as u8) { FatPointerSource::ForwardFatPointer => { - if !is_pointer || pointer.offset > pointer.length { + if !is_pointer || pointer.offset > pointer.length || already_failed { return None; } pointer.narrow(); } FatPointerSource::MakeNewPointer(target) => { - // This check has to be first so the penalty for an incorrect bound is always paid. - // It must be paid even in cases where memory growth wouldn't be paid due to other errors. - let Some(bound) = pointer.start.checked_add(pointer.length) else { - let _ = vm.state.use_gas(u32::MAX); - return None; - }; - if is_pointer || pointer.offset != 0 { - return None; - } - - match target { - ToHeap => { - grow_heap::(&mut vm.state, bound).ok()?; - pointer.memory_page = vm.state.current_frame.heap; + if let Some(bound) = pointer.start.checked_add(pointer.length) { + if is_pointer || pointer.offset != 0 || already_failed { + return None; } - ToAuxHeap => { - grow_heap::(&mut vm.state, pointer.start + pointer.length).ok()?; - pointer.memory_page = vm.state.current_frame.aux_heap; + match target { + ToHeap => { + grow_heap::(&mut vm.state, bound).ok()?; + pointer.memory_page = vm.state.current_frame.heap; + } + ToAuxHeap => { + grow_heap::(&mut vm.state, bound).ok()?; + pointer.memory_page = vm.state.current_frame.aux_heap; + } } + } else { + // The heap is grown even if the pointer goes out of the heap + // TODO PLA-974 revert to not growing the heap on failure as soon as zk_evm is fixed + let bound = u32::MAX; + match target { + ToHeap => { + grow_heap::(&mut vm.state, bound).ok()?; + } + ToAuxHeap => { + grow_heap::(&mut vm.state, bound).ok()?; + } + } + return None; } } } @@ -224,10 +263,11 @@ impl Instruction { src2: Register2, error_handler: Immediate1, is_static: bool, + is_shard: bool, arguments: Arguments, ) -> Self { Self { - handler: monomorphize!(far_call [MODE] match_boolean is_static), + handler: monomorphize!(far_call [MODE] match_boolean is_static match_boolean is_shard), arguments: arguments .write_source(&src1) .write_source(&src2) diff --git a/src/instruction_handlers/heap_access.rs b/src/instruction_handlers/heap_access.rs index 7fe37691..88a1949f 100644 --- a/src/instruction_handlers/heap_access.rs +++ b/src/instruction_handlers/heap_access.rs @@ -17,6 +17,7 @@ pub trait HeapInterface { fn read_u256_partially(&self, range: Range) -> U256; fn write_u256(&mut self, start_address: u32, value: U256); fn read_range_big_endian(&self, range: Range) -> Vec; + fn memset(&mut self, memory: &[u8]); } pub trait HeapFromState { @@ -53,24 +54,24 @@ fn load( world: &mut dyn World, ) -> InstructionResult { instruction_boilerplate_with_panic(vm, instruction, world, |vm, args, _, continue_normally| { - let (pointer, is_fat_pointer) = In::get_with_pointer_flag(args, &mut vm.state); - if is_fat_pointer { - return Ok(&PANIC); - } - if pointer > LAST_ADDRESS.into() { - let _ = vm.state.use_gas(u32::MAX); - return Ok(&PANIC); - } + // 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); let address = pointer.low_u32(); - // The size check above ensures this never overflows - let new_bound = address + 32; - + let new_bound = address.wrapping_add(32); if grow_heap::(&mut vm.state, new_bound).is_err() { return Ok(&PANIC); }; + // The heap is always grown even when the index nonsensical. + // TODO PLA-974 revert to not growing the heap on failure as soon as zk_evm is fixed + if pointer > LAST_ADDRESS.into() { + let _ = vm.state.use_gas(u32::MAX); + return Ok(&PANIC); + } + let value = H::get_heap(&mut vm.state).read_u256(address); Register1::set(args, &mut vm.state, value); @@ -88,25 +89,25 @@ fn store InstructionResult { instruction_boilerplate_with_panic(vm, instruction, world, |vm, args, _, continue_normally| { - let (pointer, is_fat_pointer) = In::get_with_pointer_flag(args, &mut vm.state); - if is_fat_pointer { - return Ok(&PANIC); - } - if pointer > LAST_ADDRESS.into() { - let _ = vm.state.use_gas(u32::MAX); - return Ok(&PANIC); - } - let address = pointer.low_u32(); + // 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); + let address = pointer.low_u32(); let value = Register2::get(args, &mut vm.state); - // The size check above ensures this never overflows - let new_bound = address + 32; - + let new_bound = address.wrapping_add(32); if grow_heap::(&mut vm.state, new_bound).is_err() { return Ok(&PANIC); } + // The heap is always grown even when the index nonsensical. + // TODO PLA-974 revert to not growing the heap on failure as soon as zk_evm is fixed + if pointer > LAST_ADDRESS.into() { + let _ = vm.state.use_gas(u32::MAX); + return Ok(&PANIC); + } + H::get_heap(&mut vm.state).write_u256(address, value); if INCREMENT { @@ -128,6 +129,8 @@ fn store(state: &mut State, new_bound: u32) -> Result<(), ()> { let already_paid = H::get_heap_size(state); if *already_paid < new_bound { diff --git a/src/instruction_handlers/jump.rs b/src/instruction_handlers/jump.rs index 250b2e21..6606d6a2 100644 --- a/src/instruction_handlers/jump.rs +++ b/src/instruction_handlers/jump.rs @@ -1,8 +1,8 @@ use super::ret::INVALID_INSTRUCTION; use crate::{ addressing_modes::{ - AbsoluteStack, AdvanceStackPointer, AnySource, Arguments, CodePage, Immediate1, Register1, - RelativeStack, Source, + AbsoluteStack, AdvanceStackPointer, AnySource, Arguments, CodePage, Destination, + Immediate1, Register1, RelativeStack, Source, }, instruction::{Instruction, InstructionResult}, VirtualMachine, World, @@ -10,28 +10,33 @@ use crate::{ fn jump( vm: &mut VirtualMachine, - mut instruction: *const Instruction, + instruction: *const Instruction, _: &mut dyn World, ) -> InstructionResult { unsafe { - let target = In::get(&(*instruction).arguments, &mut vm.state).low_u32() as u16; - if let Some(i) = vm.state.current_frame.program.instruction(target) { - instruction = i; + let args = &(*instruction).arguments; + let target = In::get(args, &mut vm.state).low_u32() as u16; + + let next_instruction = vm.state.current_frame.pc_to_u16(instruction) + 1; + Register1::set(args, &mut vm.state, next_instruction.into()); + + if let Some(instruction) = vm.state.current_frame.program.instruction(target) { + Ok(instruction) } else { - return Ok(&INVALID_INSTRUCTION); + Ok(&INVALID_INSTRUCTION) } - - Ok(instruction) } } use super::monomorphization::*; impl Instruction { - pub fn from_jump(source: AnySource, arguments: Arguments) -> Self { + pub fn from_jump(source: AnySource, destination: Register1, arguments: Arguments) -> Self { Self { handler: monomorphize!(jump match_source source), - arguments: arguments.write_source(&source), + arguments: arguments + .write_source(&source) + .write_destination(&destination), } } } diff --git a/src/instruction_handlers/mod.rs b/src/instruction_handlers/mod.rs index a8dc7e08..220566d7 100644 --- a/src/instruction_handlers/mod.rs +++ b/src/instruction_handlers/mod.rs @@ -7,6 +7,7 @@ pub(crate) use ret::{free_panic, PANIC}; mod binop; mod common; mod context; +mod decommit; mod event; mod far_call; mod heap_access; diff --git a/src/instruction_handlers/pointer.rs b/src/instruction_handlers/pointer.rs index cc18d5b6..9fcc62bc 100644 --- a/src/instruction_handlers/pointer.rs +++ b/src/instruction_handlers/pointer.rs @@ -16,11 +16,17 @@ fn ptr( world: &mut dyn World, ) -> InstructionResult { instruction_boilerplate_with_panic(vm, instruction, world, |vm, args, _, continue_normally| { - let a = In1::get_with_pointer_flag(args, &mut vm.state); - let b = Register2::get_with_pointer_flag(args, &mut vm.state); - let (a, b) = if SWAP { (b, a) } else { (a, b) }; - let (a, a_is_pointer) = a; - let (b, b_is_pointer) = b; + let ((a, a_is_pointer), (b, b_is_pointer)) = if SWAP { + ( + Register2::get_with_pointer_flag(args, &mut vm.state), + In1::get_with_pointer_flag_and_erasing(args, &mut vm.state), + ) + } else { + ( + In1::get_with_pointer_flag(args, &mut vm.state), + Register2::get_with_pointer_flag_and_erasing(args, &mut vm.state), + ) + }; if !a_is_pointer || b_is_pointer { return Ok(&PANIC); diff --git a/src/instruction_handlers/precompiles.rs b/src/instruction_handlers/precompiles.rs index 7fd4e58a..a3798873 100644 --- a/src/instruction_handlers/precompiles.rs +++ b/src/instruction_handlers/precompiles.rs @@ -28,8 +28,6 @@ fn precompile_call( world: &mut dyn World, ) -> InstructionResult { instruction_boilerplate_with_panic(vm, instruction, world, |vm, args, _, continue_normally| { - // TODO check that we're in a system call - // 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)); diff --git a/src/instruction_handlers/ret.rs b/src/instruction_handlers/ret.rs index 276941bd..fc087fd3 100644 --- a/src/instruction_handlers/ret.rs +++ b/src/instruction_handlers/ret.rs @@ -3,6 +3,7 @@ use crate::{ addressing_modes::{Arguments, Immediate1, Register1, Source, INVALID_INSTRUCTION_COST}, callframe::FrameRemnant, instruction::{ExecutionEnd, InstructionResult}, + mode_requirements::ModeRequirements, predication::Flags, Instruction, Predicate, VirtualMachine, World, }; @@ -63,8 +64,10 @@ fn ret( None } else { let (raw_abi, is_pointer) = Register1::get_with_pointer_flag(args, &mut vm.state); - let result = get_far_call_calldata(raw_abi, is_pointer, vm) - .filter(|pointer| pointer.memory_page != vm.state.current_frame.calldata_heap); + let result = get_far_call_calldata(raw_abi, is_pointer, vm, false).filter(|pointer| { + vm.state.current_frame.is_kernel + || pointer.memory_page != vm.state.current_frame.calldata_heap + }); if result.is_none() { return_type = ReturnType::Panic; @@ -167,13 +170,17 @@ pub(crate) fn panic_from_failed_far_call( /// Panics, burning all available gas. pub const INVALID_INSTRUCTION: Instruction = Instruction { handler: ret::<{ ReturnType::Panic as u8 }, false>, - arguments: Arguments::new(Predicate::Always, INVALID_INSTRUCTION_COST), + arguments: Arguments::new( + Predicate::Always, + INVALID_INSTRUCTION_COST, + ModeRequirements::none(), + ), }; -const RETURN_COST: u32 = 5; -pub const PANIC: Instruction = Instruction { +pub(crate) const RETURN_COST: u32 = 5; +pub static PANIC: Instruction = Instruction { handler: ret::<{ ReturnType::Panic as u8 }, false>, - arguments: Arguments::new(Predicate::Always, RETURN_COST), + arguments: Arguments::new(Predicate::Always, RETURN_COST, ModeRequirements::none()), }; /// Turn the current instruction into a panic at no extra cost. (Great value, I know.) diff --git a/src/instruction_handlers/storage.rs b/src/instruction_handlers/storage.rs index fb7199c1..b8027d18 100644 --- a/src/instruction_handlers/storage.rs +++ b/src/instruction_handlers/storage.rs @@ -1,7 +1,4 @@ -use super::{ - common::{instruction_boilerplate, instruction_boilerplate_with_panic}, - PANIC, -}; +use super::common::instruction_boilerplate; use crate::{ addressing_modes::{ Arguments, Destination, Register1, Register2, Source, SLOAD_COST, SSTORE_COST, @@ -15,32 +12,17 @@ fn sstore( instruction: *const Instruction, world: &mut dyn World, ) -> InstructionResult { - instruction_boilerplate_with_panic( - vm, - instruction, - world, - |vm, args, world, continue_normally| { - if vm.state.current_frame.is_static { - return Ok(&PANIC); - } - - 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, - vm.state.transaction_number, - ); + instruction_boilerplate(vm, instruction, world, |vm, args, world| { + let key = Register1::get(args, &mut vm.state); + let value = Register2::get(args, &mut vm.state); - assert!(refund <= SSTORE_COST); - vm.state.current_frame.gas += refund; + let refund = vm + .world_diff + .write_storage(world, vm.state.current_frame.address, key, value); - continue_normally - }, - ) + assert!(refund <= SSTORE_COST); + vm.state.current_frame.gas += refund; + }) } fn sstore_transient( @@ -48,18 +30,12 @@ fn sstore_transient( instruction: *const Instruction, world: &mut dyn World, ) -> InstructionResult { - instruction_boilerplate_with_panic(vm, instruction, world, |vm, args, _, continue_normally| { - if vm.state.current_frame.is_static { - return Ok(&PANIC); - } - + instruction_boilerplate(vm, instruction, world, |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); - - continue_normally }) } diff --git a/src/lib.rs b/src/lib.rs index 5acec37f..cd99d71b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,4 @@ pub mod addressing_modes; -#[cfg(feature = "arbitrary")] -mod arbitrary_instruction; #[cfg(not(feature = "single_instruction_test"))] mod bitset; mod callframe; @@ -11,6 +9,7 @@ pub mod fat_pointer; mod heap; mod instruction; pub mod instruction_handlers; +mod mode_requirements; mod predication; #[cfg(not(feature = "single_instruction_test"))] mod program; @@ -28,6 +27,7 @@ pub use decommit::address_into_u256; pub use decommit::initial_decommit; pub use heap::{HeapId, FIRST_HEAP}; pub use instruction::{jump_to_beginning, ExecutionEnd, Instruction}; +pub use mode_requirements::ModeRequirements; pub use predication::Predicate; pub use program::Program; pub use state::State; @@ -50,6 +50,8 @@ pub trait World { /// the world implementor's job. fn decommit(&mut self, hash: U256) -> Program; + fn decommit_code(&mut self, hash: U256) -> Vec; + /// There is no write_storage; [WorldDiff::get_storage_changes] gives a list of all storage changes. fn read_storage(&mut self, contract: H160, key: U256) -> Option; diff --git a/src/mode_requirements.rs b/src/mode_requirements.rs new file mode 100644 index 00000000..5ba2c8f6 --- /dev/null +++ b/src/mode_requirements.rs @@ -0,0 +1,17 @@ +#[derive(Debug)] +pub struct ModeRequirements(pub(crate) u8); + +impl ModeRequirements { + pub const fn new(kernel_only: bool, cannot_use_in_static: bool) -> Self { + Self((kernel_only as u8) | ((cannot_use_in_static as u8) << 1)) + } + + pub const fn none() -> Self { + Self::new(false, false) + } + + pub fn met(&self, is_kernel: bool, is_static: bool) -> bool { + let enabled_modes = (is_kernel as u8) | ((!is_static as u8) << 1); + enabled_modes & self.0 == self.0 + } +} diff --git a/src/single_instruction_test/callframe.rs b/src/single_instruction_test/callframe.rs index 8e3d451e..2e460495 100644 --- a/src/single_instruction_test/callframe.rs +++ b/src/single_instruction_test/callframe.rs @@ -1,7 +1,13 @@ -use super::{heap::FIRST_AUX_HEAP, stack::StackPool}; -use crate::{callframe::Callframe, predication::Flags, Program, WorldDiff}; +use super::{ + heap::FIRST_AUX_HEAP, + stack::{Stack, StackPool}, +}; +use crate::{ + callframe::Callframe, decommit::is_kernel, predication::Flags, HeapId, Program, WorldDiff, +}; use arbitrary::Arbitrary; use u256::H160; +use zkevm_opcode_defs::EVM_SIMULATOR_STIPEND; impl<'a> Arbitrary<'a> for Flags { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { @@ -11,24 +17,36 @@ impl<'a> Arbitrary<'a> for Flags { impl<'a> Arbitrary<'a> for Callframe { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let address: H160 = u.arbitrary()?; + + // zk_evm requires a base page, which heap and aux heap are offset from + let base_page = u.int_in_range(1..=u32::MAX - 10)?; + + // zk_evm considers smaller pages to be older + // vm2 doesn't care about the order + // but the calldata heap must be different from the heap and aux heap + let calldata_heap = HeapId::from_u32_unchecked(u.int_in_range(0..=base_page - 1)?); + let mut me = Self { - address: u.arbitrary()?, + address, code_address: u.arbitrary()?, caller: u.arbitrary()?, exception_handler: u.arbitrary()?, context_u128: u.arbitrary()?, is_static: u.arbitrary()?, - stack: u.arbitrary()?, + is_kernel: is_kernel(address), + stack: Box::new(Stack::new_arbitrary(u, calldata_heap, base_page)?), sp: u.arbitrary()?, - gas: 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![], program: u.arbitrary()?, - heap: u.arbitrary()?, - aux_heap: u.arbitrary()?, + heap: HeapId::from_u32_unchecked(base_page + 2), + aux_heap: HeapId::from_u32_unchecked(base_page + 3), heap_size: u.arbitrary()?, aux_heap_size: u.arbitrary()?, - calldata_heap: u.arbitrary()?, + calldata_heap, heaps_i_am_keeping_alive: vec![], world_before_this_frame: WorldDiff::default().snapshot(), }; @@ -57,6 +75,7 @@ impl Callframe { exception_handler: 0, context_u128: 0, is_static: false, + is_kernel: false, stack: StackPool {}.get(), sp: 0, gas: 0, @@ -67,7 +86,7 @@ impl Callframe { aux_heap: FIRST_AUX_HEAP, heap_size: 0, aux_heap_size: 0, - calldata_heap: FIRST_AUX_HEAP, + calldata_heap: HeapId::from_u32_unchecked(1), heaps_i_am_keeping_alive: vec![], world_before_this_frame: WorldDiff::default().snapshot(), } diff --git a/src/single_instruction_test/heap.rs b/src/single_instruction_test/heap.rs index 92397373..89904189 100644 --- a/src/single_instruction_test/heap.rs +++ b/src/single_instruction_test/heap.rs @@ -19,7 +19,7 @@ impl HeapInterface for Heap { fn read_u256_partially(&self, range: std::ops::Range) -> U256 { assert!(self.write.is_none()); let mut result = *self.read.get(range.start); - for byte in &mut result[0..range.len()] { + for byte in &mut result[0..32 - range.len()] { *byte = 0; } U256::from_little_endian(&result) @@ -34,6 +34,11 @@ impl HeapInterface for Heap { // This is wrong, but this method is only used to get the final return value. vec![] } + + fn memset(&mut self, src: &[u8]) { + let u = U256::from_big_endian(src); + self.write_u256(0, u); + } } impl<'a> Arbitrary<'a> for Heap { @@ -45,8 +50,9 @@ impl<'a> Arbitrary<'a> for Heap { } } -#[derive(Debug, Clone, Arbitrary)] +#[derive(Debug, Clone)] pub struct Heaps { + heap_id: HeapId, pub(crate) read: MockRead, } @@ -60,10 +66,20 @@ impl Heaps { } pub(crate) fn allocate(&mut self) -> HeapId { - HeapId(0) + self.heap_id } pub(crate) fn deallocate(&mut self, _: HeapId) {} + + pub(crate) fn from_id( + heap_id: HeapId, + u: &mut arbitrary::Unstructured<'_>, + ) -> arbitrary::Result { + Ok(Heaps { + heap_id, + read: u.arbitrary()?, + }) + } } impl Index for Heaps { diff --git a/src/single_instruction_test/into_zk_evm.rs b/src/single_instruction_test/into_zk_evm.rs index 8800a260..2f864380 100644 --- a/src/single_instruction_test/into_zk_evm.rs +++ b/src/single_instruction_test/into_zk_evm.rs @@ -1,10 +1,12 @@ use std::sync::Arc; use super::{stack::Stack, state_to_zk_evm::vm2_state_to_zk_evm_state, MockWorld}; -use crate::{zkevm_opcode_defs::decoding::EncodingModeProduction, VirtualMachine, World}; +use crate::{ + zkevm_opcode_defs::decoding::EncodingModeProduction, Instruction, VirtualMachine, World, +}; use u256::U256; use zk_evm::{ - abstractions::{DecommittmentProcessor, Memory, PrecompilesProcessor, Storage}, + abstractions::{DecommittmentProcessor, Memory, MemoryType, PrecompilesProcessor, Storage}, aux_structures::PubdataCost, block_properties::BlockProperties, reference_impls::event_sink::InMemoryEventSink, @@ -12,11 +14,10 @@ use zk_evm::{ vm_state::VmState, witness_trace::VmWitnessTracer, }; +use zk_evm_abstractions::vm::EventSink; +use zkevm_opcode_defs::TRANSIENT_STORAGE_AUX_BYTE; -pub fn vm2_to_zk_evm( - vm: &VirtualMachine, - world: MockWorld, -) -> VmState< +type ZkEvmState = VmState< MockWorldWrapper, MockMemory, InMemoryEventSink, @@ -25,9 +26,18 @@ pub fn vm2_to_zk_evm( NoOracle, 8, EncodingModeProduction, -> { +>; + +pub fn vm2_to_zk_evm( + vm: &VirtualMachine, + world: MockWorld, + program_counter: *const Instruction, +) -> ZkEvmState { + let mut event_sink = InMemoryEventSink::new(); + event_sink.start_frame(zk_evm::aux_structures::Timestamp(0)); + VmState { - local_state: vm2_state_to_zk_evm_state(&vm.state), + local_state: vm2_state_to_zk_evm_state(&vm.state, program_counter), block_properties: BlockProperties { default_aa_code_hash: U256::from_big_endian(&vm.settings.default_aa_code_hash), evm_simulator_code_hash: U256::from_big_endian(&vm.settings.evm_interpreter_code_hash), @@ -37,18 +47,71 @@ pub fn vm2_to_zk_evm( memory: MockMemory { code_page: vm.state.current_frame.program.code_page().clone(), stack: *vm.state.current_frame.stack.clone(), + heap_read: None, + heap_write: None, }, - event_sink: InMemoryEventSink::new(), + event_sink, precompiles_processor: NoOracle, decommittment_processor: MockDecommitter, witness_tracer: NoOracle, } } +pub fn add_heap_to_zk_evm(zk_evm: &mut ZkEvmState, vm_after_execution: &VirtualMachine) { + if let Some((heapid, heap)) = vm_after_execution.state.heaps.read.read_that_happened() { + if let Some((start_index, mut value)) = heap.read.read_that_happened() { + value.reverse(); + + zk_evm.memory.heap_read = Some(ExpectedHeapValue { + heap: heapid.to_u32(), + start_index, + value, + }); + } + if let Some((start_index, value_u256)) = heap.write { + let mut value = [0; 32]; + value_u256.to_big_endian(&mut value); + + zk_evm.memory.heap_write = Some(ExpectedHeapValue { + heap: heapid.to_u32(), + start_index, + value, + }); + } + } +} + #[derive(Debug)] pub struct MockMemory { code_page: Arc<[U256]>, stack: Stack, + heap_read: Option, + heap_write: Option, +} + +// One arbitrary heap value is not enough for zk_evm +// because it reads two U256s to read one U256. +#[derive(Debug, Copy, Clone)] +pub struct ExpectedHeapValue { + heap: u32, + start_index: u32, + value: [u8; 32], +} + +impl ExpectedHeapValue { + /// Returns a new U256 that contains data from the heap value and zero elsewhere. + fn partially_overlapping_u256(&self, start: u32) -> U256 { + let mut read = [0; 32]; + for i in 0..32 { + if start + i >= self.start_index { + let j = start + i - self.start_index; + if j < 32 { + read[i as usize] = self.value[j as usize]; + } + } + } + U256::from_big_endian(&read) + } } impl Memory for MockMemory { @@ -58,7 +121,7 @@ impl Memory for MockMemory { mut query: zk_evm::aux_structures::MemoryQuery, ) -> zk_evm::aux_structures::MemoryQuery { match query.location.memory_type { - zk_evm::abstractions::MemoryType::Stack => { + MemoryType::Stack => { let slot = query.location.index.0 as u16; if query.rw_flag { self.stack.set(slot, query.value); @@ -73,6 +136,24 @@ impl Memory for MockMemory { } query } + MemoryType::Heap | MemoryType::AuxHeap | MemoryType::FatPointer => { + if query.rw_flag { + let heap = self.heap_write.unwrap(); + assert_eq!(heap.heap, query.location.page.0); + + assert_eq!( + query.value, + heap.partially_overlapping_u256(query.location.index.0 * 32) + ); + } else if let Some(heap) = self.heap_read { + // ^ Writes read the heap too, but they are just fed zeroes + + assert_eq!(query.location.page.0, heap.heap); + + query.value = heap.partially_overlapping_u256(query.location.index.0 * 32); + } + query + } _ => todo!(), } } @@ -109,7 +190,7 @@ impl Storage for MockWorldWrapper { _: u32, _partial_query: &zk_evm::aux_structures::LogQuery, ) -> zk_evm::abstractions::StorageAccessRefund { - todo!() + zk_evm::abstractions::StorageAccessRefund::Cold } fn execute_partial_query( @@ -121,27 +202,24 @@ impl Storage for MockWorldWrapper { zk_evm::aux_structures::PubdataCost, ) { if query.rw_flag { - todo!() + (query, PubdataCost(0)) } else { - query.read_value = self - .0 - .read_storage(query.address, query.key) - .unwrap_or_default(); + query.read_value = if query.aux_byte == TRANSIENT_STORAGE_AUX_BYTE { + U256::zero() + } else { + self.0 + .read_storage(query.address, query.key) + .unwrap_or_default() + }; (query, PubdataCost(0)) } } - fn start_frame(&mut self, _: zk_evm::aux_structures::Timestamp) { - todo!() - } + fn start_frame(&mut self, _: zk_evm::aux_structures::Timestamp) {} - fn finish_frame(&mut self, _: zk_evm::aux_structures::Timestamp, _panicked: bool) { - todo!() - } + fn finish_frame(&mut self, _: zk_evm::aux_structures::Timestamp, _panicked: bool) {} - fn start_new_tx(&mut self, _: zk_evm::aux_structures::Timestamp) { - todo!() - } + fn start_new_tx(&mut self, _: zk_evm::aux_structures::Timestamp) {} } #[derive(Debug)] @@ -151,9 +229,10 @@ impl DecommittmentProcessor for MockDecommitter { fn prepare_to_decommit( &mut self, _: u32, - _partial_query: zk_evm::aux_structures::DecommittmentQuery, + mut partial_query: zk_evm::aux_structures::DecommittmentQuery, ) -> anyhow::Result { - todo!() + partial_query.is_fresh = true; + Ok(partial_query) } fn decommit_into_memory( @@ -162,7 +241,7 @@ impl DecommittmentProcessor for MockDecommitter { _partial_query: zk_evm::aux_structures::DecommittmentQuery, _memory: &mut M, ) -> anyhow::Result>> { - todo!() + Ok(None) } } @@ -221,13 +300,9 @@ impl PrecompilesProcessor for NoOracle { unimplemented!() } - fn start_frame(&mut self) { - unimplemented!() - } + fn start_frame(&mut self) {} - fn finish_frame(&mut self, _: bool) { - unimplemented!() - } + fn finish_frame(&mut self, _: bool) {} } impl VmWitnessTracer<8, EncodingModeProduction> for NoOracle { diff --git a/src/single_instruction_test/mod.rs b/src/single_instruction_test/mod.rs index bb6086ab..abd31636 100644 --- a/src/single_instruction_test/mod.rs +++ b/src/single_instruction_test/mod.rs @@ -18,6 +18,6 @@ mod validation; mod vm; mod world; -pub use into_zk_evm::{vm2_to_zk_evm, NoTracer}; +pub use into_zk_evm::{add_heap_to_zk_evm, vm2_to_zk_evm, NoTracer}; pub use universal_state::UniversalVmState; pub use world::MockWorld; diff --git a/src/single_instruction_test/print_mock_info.rs b/src/single_instruction_test/print_mock_info.rs index f1bd1851..9de60515 100644 --- a/src/single_instruction_test/print_mock_info.rs +++ b/src/single_instruction_test/print_mock_info.rs @@ -14,14 +14,14 @@ impl VirtualMachine { impl State { pub fn print_mock_info(&self) { - if let Some(heap) = self.heaps.read.read_that_happened() { - println!("Heap: {:?}", heap); - } - if let Some((address, value)) = self.heaps.read.value_read.read.read_that_happened() { - println!(" {value:?} read from {address:?}"); - } - if let Some((address, value)) = self.heaps.read.value_read.write { - println!(" {value:?} written to {address:?}"); + if let Some((heapid, heap)) = self.heaps.read.read_that_happened() { + println!("Heap: {:?}", heapid); + if let Some((address, value)) = heap.read.read_that_happened() { + println!(" {value:?} read from {address:?}"); + } + if let Some((address, value)) = heap.write { + println!(" {value:?} written to {address:?}"); + } } println!("Current frame:"); diff --git a/src/single_instruction_test/stack.rs b/src/single_instruction_test/stack.rs index 8c97bd13..988bc4b5 100644 --- a/src/single_instruction_test/stack.rs +++ b/src/single_instruction_test/stack.rs @@ -1,5 +1,7 @@ -use super::{mock_array::MockRead, validation::is_valid_tagged_value}; -use arbitrary::Arbitrary; +use super::{ + mock_array::MockRead, validation::is_valid_tagged_value, vm::arbitrary_register_value, +}; +use crate::HeapId; use u256::U256; #[derive(PartialEq, Debug, Clone)] @@ -10,18 +12,20 @@ pub struct Stack { pointer_tag_written: bool, } -impl<'a> Arbitrary<'a> for Stack { - fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { +impl Stack { + pub(crate) fn new_arbitrary( + u: &mut arbitrary::Unstructured, + calldata_heap: HeapId, + base_page: u32, + ) -> arbitrary::Result { Ok(Self { - read: u.arbitrary()?, + read: MockRead::new(arbitrary_register_value(u, calldata_heap, base_page)?), slot_written: None, value_written: 0.into(), pointer_tag_written: false, }) } -} -impl Stack { pub(crate) fn get(&self, slot: u16) -> U256 { assert!(self.slot_written.is_none()); self.read.get(slot).0 diff --git a/src/single_instruction_test/state_to_zk_evm.rs b/src/single_instruction_test/state_to_zk_evm.rs index b21dac20..dd64eb5a 100644 --- a/src/single_instruction_test/state_to_zk_evm.rs +++ b/src/single_instruction_test/state_to_zk_evm.rs @@ -1,3 +1,9 @@ +use crate::{ + callframe::{Callframe, NearCallFrame}, + instruction_handlers::PANIC, + Instruction, +}; +use std::iter; use u256::U256; use zk_evm::{ aux_structures::{MemoryPage, PubdataCost}, @@ -5,11 +11,22 @@ use zk_evm::{ }; use zkevm_opcode_defs::decoding::EncodingModeProduction; -use crate::callframe::Callframe; - pub(crate) fn vm2_state_to_zk_evm_state( state: &crate::State, + program_counter: *const Instruction, ) -> VmLocalState<8, EncodingModeProduction> { + // zk_evm requires an unused bottom frame + let mut callframes: Vec<_> = iter::once(CallStackEntry::empty_context()) + .chain( + state + .previous_frames + .iter() + .map(|x| x.1.clone()) + .chain(iter::once(state.current_frame.clone())) + .flat_map(vm2_frame_to_zk_evm_frames), + ) + .collect(); + VmLocalState { previous_code_word: U256([0, 0, 0, state.current_frame.raw_first_instruction()]), previous_code_memory_page: MemoryPage(0), @@ -32,42 +49,61 @@ pub(crate) fn vm2_state_to_zk_evm_state( memory_page_counter: 3000, absolute_execution_step: 0, tx_number_in_block: state.transaction_number, - pending_exception: false, + pending_exception: program_counter == &PANIC, previous_super_pc: 0, // Same as current pc so the instruction is read from previous_code_word context_u128_register: state.context_u128, callstack: Callstack { - current: (&state.current_frame).into(), + current: callframes.pop().unwrap(), // zk_evm requires an unused bottom frame - inner: std::iter::once(CallStackEntry::empty_context()) - .chain(state.previous_frames.iter().map(|(_, frame)| frame.into())) - .collect(), + inner: callframes, }, pubdata_revert_counter: PubdataCost(0), } } -impl From<&Callframe> for CallStackEntry { - fn from(frame: &Callframe) -> Self { - CallStackEntry { - this_address: frame.address, - msg_sender: frame.caller, - code_address: frame.code_address, - base_memory_page: MemoryPage(frame.heap.to_u32()), - code_page: MemoryPage(0), // TODO - sp: frame.sp, - pc: 0, - exception_handler_location: frame.exception_handler, - ergs_remaining: frame.gas, - this_shard_id: 0, - caller_shard_id: 0, - code_shard_id: 0, - is_static: frame.is_static, - is_local_frame: false, // TODO this is for making near calls - context_u128_value: frame.context_u128, - heap_bound: frame.heap_size, - aux_heap_bound: frame.aux_heap_size, - total_pubdata_spent: PubdataCost(0), - stipend: frame.stipend, - } +fn vm2_frame_to_zk_evm_frames(frame: Callframe) -> impl Iterator { + let far_frame = CallStackEntry { + this_address: frame.address, + msg_sender: frame.caller, + code_address: frame.code_address, + base_memory_page: MemoryPage(frame.heap.to_u32() - 2), + code_page: MemoryPage(0), // TODO + sp: frame.sp, + pc: 0, + exception_handler_location: frame.exception_handler, + ergs_remaining: frame.gas, + this_shard_id: 0, + caller_shard_id: 0, + code_shard_id: 0, + is_static: frame.is_static, + is_local_frame: false, + context_u128_value: frame.context_u128, + heap_bound: frame.heap_size, + aux_heap_bound: frame.aux_heap_size, + total_pubdata_spent: PubdataCost(0), + stipend: frame.stipend, + }; + + let mut result = vec![far_frame]; + for NearCallFrame { + call_instruction, + exception_handler, + previous_frame_sp, + previous_frame_gas, + .. + } in frame.near_calls + { + let last = result.last_mut().unwrap(); + last.pc = call_instruction; + last.sp = previous_frame_sp; + last.ergs_remaining = previous_frame_gas; + + result.push(CallStackEntry { + is_local_frame: true, + exception_handler_location: exception_handler, + ..far_frame + }); } + + result.into_iter() } diff --git a/src/single_instruction_test/universal_state.rs b/src/single_instruction_test/universal_state.rs index 20175ce2..fa108b54 100644 --- a/src/single_instruction_test/universal_state.rs +++ b/src/single_instruction_test/universal_state.rs @@ -14,6 +14,7 @@ pub struct UniversalVmState { transaction_number: u16, context_u128: u128, frames: Vec, + will_panic: bool, } #[derive(PartialEq, Debug)] @@ -63,6 +64,36 @@ impl } fn zk_evm_state_to_universal(vm: &VmLocalState<8, EncodingModeProduction>) -> UniversalVmState { + let mut current_frame = zk_evm_frame_to_universal(&vm.callstack.current); + // Most of the current frame doesn't matter if we panic, as it is just thrown away + // but only sp has proved problematic so far + if vm.pending_exception { + current_frame.sp = 0; + } + + let mut frames = vm + .callstack + .inner + .iter() + .skip(1) // zk_evm requires an unused bottom frame + .map(zk_evm_frame_to_universal) + .chain(std::iter::once(current_frame)) + .collect::>(); + + // In zk_evm, heap bounds grown in a near call are only propagated on return, so we need to work around that + let mut previous_heap_bounds = None; + for frame in frames.iter_mut().rev() { + if let Some((heap_bound, aux_heap_bound)) = previous_heap_bounds { + frame.heap_bound = heap_bound; + frame.aux_heap_bound = aux_heap_bound; + } + if frame.is_near_call { + previous_heap_bounds = Some((frame.heap_bound, frame.aux_heap_bound)); + } else { + previous_heap_bounds = None; + } + } + UniversalVmState { registers: vm .registers @@ -78,14 +109,8 @@ fn zk_evm_state_to_universal(vm: &VmLocalState<8, EncodingModeProduction>) -> Un ], transaction_number: vm.tx_number_in_block, context_u128: vm.context_u128_register, - frames: vm - .callstack - .inner - .iter() - .skip(1) // zk_evm requires an unused bottom frame - .chain(std::iter::once(&vm.callstack.current)) - .map(zk_evm_frame_to_universal) - .collect(), + frames, + will_panic: vm.pending_exception, } } diff --git a/src/single_instruction_test/validation.rs b/src/single_instruction_test/validation.rs index 815a3ef7..715241c8 100644 --- a/src/single_instruction_test/validation.rs +++ b/src/single_instruction_test/validation.rs @@ -1,4 +1,4 @@ -use crate::{fat_pointer::FatPointer, State}; +use crate::{callframe::Callframe, fat_pointer::FatPointer, State}; use u256::U256; pub(crate) fn is_valid_tagged_value((value, is_pointer): (U256, bool)) -> bool { @@ -12,11 +12,11 @@ pub(crate) fn is_valid_tagged_value((value, is_pointer): (U256, bool)) -> bool { impl State { pub(crate) fn is_valid(&self) -> bool { - self.current_frame.stack.is_valid() + self.current_frame.is_valid() && self .previous_frames .iter() - .all(|(_, frame)| frame.stack.is_valid()) + .all(|(_, frame)| frame.is_valid()) && (0..16).all(|i| { is_valid_tagged_value(( self.registers[i as usize], @@ -25,3 +25,9 @@ impl State { }) } } + +impl Callframe { + pub(crate) fn is_valid(&self) -> bool { + self.stack.is_valid() + } +} diff --git a/src/single_instruction_test/vm.rs b/src/single_instruction_test/vm.rs index 68e2aeea..ecf6203e 100644 --- a/src/single_instruction_test/vm.rs +++ b/src/single_instruction_test/vm.rs @@ -1,7 +1,7 @@ -use super::stack::StackPool; +use super::{heap::Heaps, stack::StackPool}; use crate::{ - callframe::Callframe, instruction::InstructionResult, instruction_handlers::free_panic, - Instruction, Settings, State, VirtualMachine, World, + callframe::Callframe, fat_pointer::FatPointer, instruction::InstructionResult, + instruction_handlers::free_panic, HeapId, Instruction, Settings, State, VirtualMachine, World, }; use arbitrary::Arbitrary; use std::fmt::Debug; @@ -17,11 +17,17 @@ impl VirtualMachine { unsafe { let args = &(*instruction).arguments; - let Ok(_) = self.state.use_gas(args.get_static_gas_cost()) else { + + if self.state.use_gas(args.get_static_gas_cost()).is_err() + || !args.mode_requirements().met( + self.state.current_frame.is_kernel, + self.state.current_frame.is_static, + ) + { return free_panic(self, world); - }; + } - if args.predicate.satisfied(&self.state.flags) { + if args.predicate().satisfied(&self.state.flags) { ((*instruction).handler)(self, instruction, world) } else { Ok(instruction.add(1)) @@ -34,31 +40,54 @@ impl VirtualMachine { } pub fn instruction_is_not_precompile_call(&self) -> bool { + // TODO PLA-972 implement StaticMemoryRead/Write + if (1096..=1103).contains(&self.current_opcode()) { + return false; + } + // Precompilecall is not allowed because it accesses memory multiple times // and only needs to work as used by trusted code - self.state.current_frame.program.raw_first_instruction & 0x7FF != 1056u64 + self.current_opcode() != 1056u64 + } + + pub fn instruction_is_far_call(&self) -> bool { + (1057..=1068).contains(&self.current_opcode()) + } + + fn current_opcode(&self) -> u64 { + self.state.current_frame.program.raw_first_instruction & 0x7FF } } impl<'a> Arbitrary<'a> for VirtualMachine { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - let mut registers: [U256; 16] = u.arbitrary()?; - registers[0] = U256::zero(); - let mut register_pointer_flags = u.arbitrary()?; - register_pointer_flags &= !1; + let current_frame: Callframe = u.arbitrary()?; + + let mut registers = [U256::zero(); 16]; + let mut register_pointer_flags = 0; + + for (i, register) in registers.iter_mut().enumerate().skip(1) { + let (value, is_pointer) = arbitrary_register_value( + u, + current_frame.calldata_heap, + current_frame.heap.to_u32() - 2, + )?; + *register = value; + register_pointer_flags |= (is_pointer as u16) << i; + } + + let heaps = Heaps::from_id(current_frame.heap, u)?; Ok(Self { state: State { registers, register_pointer_flags, flags: u.arbitrary()?, - current_frame: u.arbitrary()?, - previous_frames: if u.arbitrary()? { - vec![(0, Callframe::dummy())] - } else { - vec![] - }, - heaps: u.arbitrary()?, + current_frame, + // Exiting the final frame is different in vm2 on purpose, + // so always generate two frames to avoid that. + previous_frames: vec![(0, Callframe::dummy())], + heaps, transaction_number: u.arbitrary()?, context_u128: u.arbitrary()?, }, @@ -69,6 +98,38 @@ impl<'a> Arbitrary<'a> for VirtualMachine { } } +/// Generates a pointer or non-pointer value. +/// The pointers always point to the calldata heap or a heap larger than the base page. +/// This is because heap < base_page in zk_evm means the same as heap == calldata_heap in vm2. +pub(crate) fn arbitrary_register_value( + u: &mut arbitrary::Unstructured, + calldata_heap: HeapId, + base_page: u32, +) -> arbitrary::Result<(U256, bool)> { + Ok(if u.arbitrary()? { + ( + (U256::from(u.arbitrary::()?) << 128) + | FatPointer { + offset: u.arbitrary()?, + memory_page: if u.arbitrary()? { + // generate a pointer to calldata + calldata_heap + } else { + // generate a pointer to return data + HeapId::from_u32_unchecked(u.int_in_range(base_page..=u32::MAX)?) + }, + start: u.arbitrary()?, + length: u.arbitrary()?, + } + .into_u256(), + true, + ) + } else { + // generate a value + (u.arbitrary()?, false) + }) +} + impl<'a> Arbitrary<'a> for Settings { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { // Only contract hashes that begin 1, 0 are valid diff --git a/src/single_instruction_test/world.rs b/src/single_instruction_test/world.rs index eea47c1a..511a68cc 100644 --- a/src/single_instruction_test/world.rs +++ b/src/single_instruction_test/world.rs @@ -13,6 +13,10 @@ impl World for MockWorld { Program::for_decommit() } + fn decommit_code(&mut self, _hash: U256) -> Vec { + vec![0; 32] + } + fn read_storage(&mut self, contract: H160, key: U256) -> Option { *self.storage_slot.get((contract, key)) } diff --git a/src/state.rs b/src/state.rs index cec8fd5a..3d8681d0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -139,4 +139,8 @@ impl Addressable for State { fn code_page(&self) -> &[U256] { self.current_frame.program.code_page() } + + fn in_kernel_mode(&self) -> bool { + self.current_frame.is_kernel + } } diff --git a/src/testworld.rs b/src/testworld.rs index 7438f8e0..705b2e46 100644 --- a/src/testworld.rs +++ b/src/testworld.rs @@ -48,6 +48,18 @@ impl World for TestWorld { } } + fn decommit_code(&mut self, hash: U256) -> Vec { + self.decommit(hash) + .code_page() + .iter() + .flat_map(|u256| { + let mut buffer = [0u8; 32]; + u256.to_big_endian(&mut buffer); + buffer + }) + .collect() + } + fn read_storage(&mut self, contract: u256::H160, key: u256::U256) -> Option { let deployer_system_contract_address = Address::from_low_u64_be(DEPLOYER_SYSTEM_CONTRACT_ADDRESS_LOW as u64); diff --git a/src/vm.rs b/src/vm.rs index b85678ec..8243d016 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -76,18 +76,24 @@ impl VirtualMachine { unsafe { loop { let args = &(*instruction).arguments; - let Ok(_) = self.state.use_gas(args.get_static_gas_cost()) else { + + if self.state.use_gas(args.get_static_gas_cost()).is_err() + || !args.mode_requirements().met( + self.state.current_frame.is_kernel, + self.state.current_frame.is_static, + ) + { instruction = match free_panic(self, world) { Ok(i) => i, Err(e) => return e, }; continue; - }; + } - #[cfg(trace)] + #[cfg(feature = "trace")] self.print_instruction(instruction); - if args.predicate.satisfied(&self.state.flags) { + if args.predicate().satisfied(&self.state.flags) { instruction = match ((*instruction).handler)(self, instruction, world) { Ok(n) => n, Err(e) => return e, @@ -123,18 +129,24 @@ impl VirtualMachine { let end = unsafe { loop { let args = &(*instruction).arguments; - let Ok(_) = self.state.use_gas(args.get_static_gas_cost()) else { + + if self.state.use_gas(args.get_static_gas_cost()).is_err() + || !args.mode_requirements().met( + self.state.current_frame.is_kernel, + self.state.current_frame.is_static, + ) + { instruction = match free_panic(self, world) { Ok(i) => i, Err(end) => break end, }; continue; - }; + } - #[cfg(trace)] + #[cfg(feature = "trace")] self.print_instruction(instruction); - if args.predicate.satisfied(&self.state.flags) { + if args.predicate().satisfied(&self.state.flags) { instruction = match ((*instruction).handler)(self, instruction, world) { Ok(n) => n, Err(end) => break end, @@ -217,7 +229,7 @@ impl VirtualMachine { } else { self.state.context_u128 }, - is_static || self.state.current_frame.is_static, + is_static, world_before_this_frame, ); self.state.context_u128 = 0; @@ -267,10 +279,10 @@ impl VirtualMachine { }) } - #[cfg(trace)] + #[cfg(feature = "trace")] fn print_instruction(&self, instruction: *const Instruction) { print!("{:?}: ", unsafe { - instruction.offset_from(&self.state.current_frame.program.instructions()[0]) + instruction.offset_from(self.state.current_frame.program.instruction(0).unwrap()) }); self.state.registers[1..] .iter() diff --git a/src/world_diff.rs b/src/world_diff.rs index a0f7377d..160bff65 100644 --- a/src/world_diff.rs +++ b/src/world_diff.rs @@ -15,7 +15,7 @@ use zkevm_opcode_defs::system_params::{ #[derive(Default)] pub struct WorldDiff { // These are rolled back on revert or panic (and when the whole VM is rolled back). - storage_changes: RollbackableMap<(H160, U256), (u16, U256)>, + storage_changes: RollbackableMap<(H160, U256), U256>, paid_changes: RollbackableMap<(H160, U256), u32>, transient_storage_changes: RollbackableMap<(H160, U256), U256>, events: RollbackableLog, @@ -72,7 +72,6 @@ impl WorldDiff { .as_ref() .get(&(contract, key)) .cloned() - .map(|v| v.1) .unwrap_or_else(|| world.read_storage(contract, key).unwrap_or_default()); let refund = if world.is_free_storage_slot(&contract, &key) @@ -94,10 +93,8 @@ impl WorldDiff { contract: H160, key: U256, value: U256, - tx_number: u16, ) -> u32 { - self.storage_changes - .insert((contract, key), (tx_number, value)); + self.storage_changes.insert((contract, key), value); let initial_value = self .storage_initial_values @@ -136,21 +133,21 @@ impl WorldDiff { refund } - pub fn get_storage_state(&self) -> &BTreeMap<(H160, U256), (u16, U256)> { + pub fn get_storage_state(&self) -> &BTreeMap<(H160, U256), U256> { self.storage_changes.as_ref() } pub fn get_storage_changes( &self, - ) -> impl Iterator, U256))> + '_ { + ) -> impl Iterator, U256))> + '_ { self.storage_changes .as_ref() .iter() - .filter_map(|(key, &(tx_number, value))| { + .filter_map(|(key, &value)| { if self.storage_initial_values[key].unwrap_or_default() == value { None } else { - Some((*key, (tx_number, self.storage_initial_values[key], value))) + Some((*key, (self.storage_initial_values[key], value))) } }) } @@ -162,14 +159,13 @@ impl WorldDiff { self.storage_changes .changes_after(snapshot.storage_changes) .into_iter() - .map(|(key, (before, (tx_id, after)))| { + .map(|(key, (before, after))| { let initial = self.storage_initial_values[&key]; ( key, StorageChange { - before: before.map(|x| x.1).or(initial), + before: before.or(initial), after, - tx_number: tx_id, is_initial: initial.is_none(), }, ) @@ -313,7 +309,6 @@ pub struct Snapshot { pub struct StorageChange { pub before: Option, pub after: U256, - pub tx_number: u16, /// `true` if the slot is not set in the World. /// A write may be initial even if it isn't the first write to a slot! pub is_initial: bool, @@ -343,7 +338,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, 0); + world_diff.write_storage(&mut NoWorld, key.0, key.1, *value); } assert_eq!( world_diff @@ -357,7 +352,6 @@ mod tests { before: initial_values.get(key).copied(), after: *value, is_initial: initial_values.get(key).is_none(), - tx_number: 0, } )) .collect() @@ -365,7 +359,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, 1); + world_diff.write_storage(&mut NoWorld, key.0, key.1, *value); } assert_eq!( world_diff @@ -376,10 +370,9 @@ mod tests { .map(|(key, value)| ( *key, StorageChange { - before: first_changes.get(key).or(initial_values.get(&key)).copied(), + before: first_changes.get(key).or(initial_values.get(key)).copied(), after: *value, is_initial: initial_values.get(key).is_none(), - tx_number: 1, } )) .collect() @@ -389,13 +382,13 @@ mod tests { .into_iter() .filter_map(|(key, value)| { let initial = initial_values.get(&key).copied(); - (initial.unwrap_or_default() != value).then_some((key, (0, initial, value))) + (initial.unwrap_or_default() != value).then_some((key, (initial, value))) }) .collect::>(); for (key, value) in second_changes { let initial = initial_values.get(&key).copied(); if initial.unwrap_or_default() != value { - combined.insert(key, (1, initial, value)); + combined.insert(key, (initial, value)); } else { combined.remove(&key); } @@ -432,5 +425,9 @@ mod tests { fn is_free_storage_slot(&self, _: &H160, _: &U256) -> bool { false } + + fn decommit_code(&mut self, _: U256) -> Vec { + unimplemented!() + } } } diff --git a/tests/panic.rs b/tests/panic.rs index 6117c592..abd10c01 100644 --- a/tests/panic.rs +++ b/tests/panic.rs @@ -3,7 +3,7 @@ use vm2::{ addressing_modes::{Arguments, Immediate1, Immediate2, Register, Register1}, initial_decommit, testworld::TestWorld, - ExecutionEnd, Instruction, Predicate, Program, VirtualMachine, + ExecutionEnd, Instruction, ModeRequirements, Predicate, Program, VirtualMachine, }; use zkevm_opcode_defs::ethereum_types::Address; @@ -15,15 +15,15 @@ proptest! { Register1(Register::new(0)), Immediate1(1), Immediate2(0xFFFF), - Arguments::new(Predicate::Always, 25), + Arguments::new(Predicate::Always, 25, ModeRequirements::none()), ), - Instruction::from_panic(Some(Immediate1(label)), Arguments::new(Predicate::Always, 5)), + Instruction::from_panic(Some(Immediate1(label)), Arguments::new(Predicate::Always, 5, ModeRequirements::none())), ]; for _ in 0..98 { instructions.push(Instruction::from_ret( Register1(Register::new(0)), None, - Arguments::new(Predicate::Always, 5), + Arguments::new(Predicate::Always, 5, ModeRequirements::none()), )); } diff --git a/tests/stipend.rs b/tests/stipend.rs index db54a4f4..5ed58a12 100644 --- a/tests/stipend.rs +++ b/tests/stipend.rs @@ -7,7 +7,7 @@ use vm2::{ initial_decommit, instruction_handlers::{Add, CallingMode}, testworld::TestWorld, - ExecutionEnd, Instruction, Predicate, Program, VirtualMachine, + ExecutionEnd, Instruction, ModeRequirements, Predicate, Program, VirtualMachine, }; use zkevm_opcode_defs::ethereum_types::Address; @@ -33,7 +33,7 @@ fn test_scenario(gas_to_pass: u32) -> (ExecutionEnd, u32) { Register2(r0), Register1(r1).into(), (), - Arguments::new(Predicate::Always, 6), + Arguments::new(Predicate::Always, 6, ModeRequirements::none()), false, false, ), @@ -46,7 +46,7 @@ fn test_scenario(gas_to_pass: u32) -> (ExecutionEnd, u32) { Register2(r0), Register1(r2).into(), (), - Arguments::new(Predicate::Always, 6), + Arguments::new(Predicate::Always, 6, ModeRequirements::none()), false, false, ), @@ -56,12 +56,13 @@ fn test_scenario(gas_to_pass: u32) -> (ExecutionEnd, u32) { // crash on error Immediate1(0xFFFF), false, - Arguments::new(Predicate::Always, 200), + false, + Arguments::new(Predicate::Always, 200, ModeRequirements::none()), ), Instruction::from_ret( Register1(Register::new(0)), None, - Arguments::new(Predicate::Always, 5), + Arguments::new(Predicate::Always, 5, ModeRequirements::none()), ), ], vec![abi, ethereum_address.into()], @@ -74,14 +75,14 @@ fn test_scenario(gas_to_pass: u32) -> (ExecutionEnd, u32) { Register2(r0), Register1(r0).into(), (), - Arguments::new(Predicate::Always, 6), + Arguments::new(Predicate::Always, 6, ModeRequirements::none()), false, false, ), Instruction::from_ret( Register1(Register::new(0)), None, - Arguments::new(Predicate::Always, 5), + Arguments::new(Predicate::Always, 5, ModeRequirements::none()), ), ], vec![],