diff --git a/src/decode.rs b/src/decode.rs index afc14635..b952904f 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -283,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 a3806c6b..6d0a2a5b 100644 --- a/src/decommit.rs +++ b/src/decommit.rs @@ -77,6 +77,13 @@ impl WorldDiff { 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, 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_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/heap_access.rs b/src/instruction_handlers/heap_access.rs index 91b7a9e0..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 { 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/lib.rs b/src/lib.rs index 8103f726..cd99d71b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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/single_instruction_test/heap.rs b/src/single_instruction_test/heap.rs index c33548bf..89904189 100644 --- a/src/single_instruction_test/heap.rs +++ b/src/single_instruction_test/heap.rs @@ -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(3) + 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/vm.rs b/src/single_instruction_test/vm.rs index 503d8981..ecf6203e 100644 --- a/src/single_instruction_test/vm.rs +++ b/src/single_instruction_test/vm.rs @@ -1,4 +1,4 @@ -use super::stack::StackPool; +use super::{heap::Heaps, stack::StackPool}; use crate::{ callframe::Callframe, fat_pointer::FatPointer, instruction::InstructionResult, instruction_handlers::free_panic, HeapId, Instruction, Settings, State, VirtualMachine, World, @@ -40,11 +40,6 @@ impl VirtualMachine { } pub fn instruction_is_not_precompile_call(&self) -> bool { - // TODO PLA-934 implement Decommit - if self.current_opcode() == 1093 { - return false; - } - // TODO PLA-972 implement StaticMemoryRead/Write if (1096..=1103).contains(&self.current_opcode()) { return false; @@ -81,6 +76,8 @@ impl<'a> Arbitrary<'a> for VirtualMachine { register_pointer_flags |= (is_pointer as u16) << i; } + let heaps = Heaps::from_id(current_frame.heap, u)?; + Ok(Self { state: State { registers, @@ -90,7 +87,7 @@ impl<'a> Arbitrary<'a> for VirtualMachine { // 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: u.arbitrary()?, + heaps, transaction_number: u.arbitrary()?, context_u128: u.arbitrary()?, }, 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/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/world_diff.rs b/src/world_diff.rs index a0f7377d..4565f153 100644 --- a/src/world_diff.rs +++ b/src/world_diff.rs @@ -376,7 +376,7 @@ 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, @@ -432,5 +432,9 @@ mod tests { fn is_free_storage_slot(&self, _: &H160, _: &U256) -> bool { false } + + fn decommit_code(&mut self, _: U256) -> Vec { + unimplemented!() + } } }