diff --git a/utils/wasm-gen/src/config/syscalls/param.rs b/utils/wasm-gen/src/config/syscalls/param.rs index 52f09ae7f47..60cdec3b1bf 100644 --- a/utils/wasm-gen/src/config/syscalls/param.rs +++ b/utils/wasm-gen/src/config/syscalls/param.rs @@ -266,6 +266,11 @@ pub enum PtrParamAllowedValues { } impl PtrParamAllowedValues { + const VALUE_WORDS: usize = mem::size_of::() / mem::size_of::(); + const HASH_WORDS: usize = mem::size_of::() / mem::size_of::(); + const HASH_WITH_VALUE_WORDS: usize = Self::HASH_WORDS + Self::VALUE_WORDS; + const TWO_HASHES_WITH_VALUE_WORDS: usize = 2 * Self::HASH_WORDS + Self::VALUE_WORDS; + pub fn all_hash_with_range(range: RangeInclusive) -> Vec { HashType::all() .into_iter() @@ -281,14 +286,14 @@ impl PtrParamAllowedValues { match self { PtrParamAllowedValues::Value(range) => Self::get_value(unstructured, range.clone()), PtrParamAllowedValues::HashWithValue { range, .. } => { - let mut ret = Vec::with_capacity(8 + 4); + let mut ret = Vec::with_capacity(Self::HASH_WITH_VALUE_WORDS); ret.extend(Self::get_default_hash()); ret.extend(Self::get_value(unstructured, range.clone())?); Ok(ret) } PtrParamAllowedValues::TwoHashesWithValue { range, .. } => { - let mut ret = Vec::with_capacity(8 + 8 + 4); + let mut ret = Vec::with_capacity(Self::TWO_HASHES_WITH_VALUE_WORDS); ret.extend(Self::get_default_hash()); ret.extend(Self::get_default_hash()); ret.extend(Self::get_value(unstructured, range.clone())?); @@ -298,6 +303,10 @@ impl PtrParamAllowedValues { } } + pub(crate) fn default_hash_with_value() -> Vec { + vec![0i32; Self::HASH_WITH_VALUE_WORDS] + } + fn get_value(unstructured: &mut Unstructured, range: RangeInclusive) -> Result> { let value = unstructured.int_in_range(range)?; Ok(Self::get_for_instructions(value.to_le_bytes())) diff --git a/utils/wasm-gen/src/generator.rs b/utils/wasm-gen/src/generator.rs index dda2eb3b285..104416e32ae 100644 --- a/utils/wasm-gen/src/generator.rs +++ b/utils/wasm-gen/src/generator.rs @@ -139,15 +139,16 @@ impl<'a, 'b> GearWasmGenerator<'a, 'b> { .into_wasm_module() .into_inner(); - let module = if let Some(critical_gas_limit) = config.critical_gas_limit { - log::trace!("Injecting critical gas limit"); - utils::inject_critical_gas_limit(module, critical_gas_limit) - } else { - log::trace!("Critical gas limit is not set"); - module - }; - - let module = utils::inject_stack_limiter(module); + // TODO: Uncomment after tests + // let module = if let Some(critical_gas_limit) = config.critical_gas_limit { + // log::trace!("Injecting critical gas limit"); + // utils::inject_critical_gas_limit(module, critical_gas_limit) + // } else { + // log::trace!("Critical gas limit is not set"); + // module + // }; + + // let module = utils::inject_stack_limiter(module); Ok(if config.remove_recursions { log::trace!("Removing recursions"); diff --git a/utils/wasm-gen/src/generator/syscalls/invocator.rs b/utils/wasm-gen/src/generator/syscalls/invocator.rs index 0e4624429a6..d9d9032900a 100644 --- a/utils/wasm-gen/src/generator/syscalls/invocator.rs +++ b/utils/wasm-gen/src/generator/syscalls/invocator.rs @@ -32,10 +32,9 @@ use gear_wasm_instrument::{ parity_wasm::elements::{BlockType, Instruction, Internal, ValueType}, syscalls::{ FallibleSyscallSignature, ParamType, Ptr, RegularParamType, SyscallName, SyscallSignature, - SystemSyscallSignature, + SystemSyscallSignature, HashType, }, }; -use gsys::Hash; use std::{ collections::{btree_map::Entry, BTreeMap, BinaryHeap, HashSet}, fmt::{self, Debug, Display}, @@ -326,6 +325,17 @@ impl<'a, 'b> SyscallsInvocator<'a, 'b> { call_indexes_handle: CallIndexesHandle, destination_arg_idx: usize, ) -> Result> { + use ParamType::*; + use RegularParamType::*; + + // Check syscall destination param type. + let Regular(Pointer(ptr @ Ptr::HashWithValue(HashType::ActorId))) = invocable.into_signature().params()[destination_arg_idx] else { + panic!( + "{syscall_str} syscall's param under index {destination_arg_idx} is not `HashWithValue`", + syscall_str = invocable.to_str(), + ) + }; + // The value for the destination param is chosen from config. // It's either the result of `gr_source`, some existing address (set in the data section) or a completely random value. let mut original_instructions = self.build_call(invocable, call_indexes_handle)?; @@ -349,31 +359,36 @@ impl<'a, 'b> SyscallsInvocator<'a, 'b> { .memory_size(); // Subtract a bit more so entities from `gsys` fit. let upper_limit = mem_size.saturating_sub(100); - let offset = self.unstructured.int_in_range(0..=upper_limit)?; + let offset = self.unstructured.int_in_range(0..=upper_limit)? as i32; + + let mut ret = if invocable.has_destination_param_with_value() { + let data = if let Some(allowed_values) = self.config.params_config().get_ptr_rule(ptr) { + allowed_values.get(self.unstructured)? + } else { + PtrParamAllowedValues::default_hash_with_value() + }; + + // The last instruction is `I32Const(offset)`. + ParamsTranslator::new_i32_with_data(offset, data) + .translate() + } else { + vec![Instruction::I32Const(offset)] + }; + + assert!( + matches!(ret.last(), Some(Instruction::I32Const(actual_offset)) if *actual_offset == offset), + "Invalid instructions prepared to set destination for the syscall." + ); - // 3 instructions for invoking `gsys::gr_source` and possibly 3 more - // for defining value param so HashWithValue will be constructed. - let mut ret = Vec::with_capacity(6); ret.extend_from_slice(&[ - // call `gsys::gr_source` storing actor id and some `offset` pointer. - Instruction::I32Const(offset as i32), + // Call `gsys::gr_source` storing actor id at `offset`. + // If syscall has destination with value, then actor id + // bytes were already set. So it's only required to re-set + // actor id by calling `gr_source` on the same offset. Instruction::Call(gr_source_call_indexes_handle), - Instruction::I32Const(offset as i32), + Instruction::I32Const(offset), ]); - if invocable.has_destination_param_with_value() { - // We have to skip actor id bytes to define the following value param. - let skip_bytes = mem::size_of::(); - ret.extend_from_slice(&[ - // Define 0 value for HashWithValue - Instruction::I32Const(0), - // Store value on the offset + skip_bytes. That will form HashWithValue. - Instruction::I32Store(2, skip_bytes as u32), - // Pass the offset as the first argument to the syscall with destination. - Instruction::I32Const(offset as i32), - ]); - } - ret } else { let address_offset = match self.offsets.as_mut() { diff --git a/utils/wasm-gen/src/tests.rs b/utils/wasm-gen/src/tests.rs index d42370e6a7d..c9b0700f908 100644 --- a/utils/wasm-gen/src/tests.rs +++ b/utils/wasm-gen/src/tests.rs @@ -209,10 +209,12 @@ fn injecting_addresses_works() { assert_eq!(*ptr, size + (stack_end_page * WASM_PAGE_SIZE as u32) as i32); } -// test for syscalls with destination -// test for syscalls with destination and existing addresses +// TODO test for syscalls with destination and existing addresses // Syscalls of a `gr_*reply*` kind are the only of those, which has `Value` input param. +// Message value param for these syscalls is set during the common syscalls params +// processing flow. +// todo check for precis reservation_reply #[test] fn test_msg_value_ptr() { const INITIAL_BALANCE: u128 = 10_000; @@ -235,7 +237,7 @@ fn test_msg_value_ptr() { &mut unstructured, syscalls_config, None, - 1, + 1024, false, INITIAL_BALANCE, ); @@ -250,6 +252,64 @@ fn test_msg_value_ptr() { ); } +// Syscalls which have destination with value param. +// Params for these syscalls aren't processed the usual way: destination argument is +// set from existing config or set by calling `gr_source`. Should be mentioned that +// destination is not only 32 bytes hash value, but a struct of hash and message value. +// So here it tests that message value in this struct is properly set. +#[test] +fn test_msg_value_ptr_dest() { + gear_utils::init_default_logger(); + const INITIAL_BALANCE: u128 = 10_000; + const REPLY_VALUE: u128 = 1_000; + + let mut params_config = SyscallsParamsConfig::default_regular(); + params_config.set_rule(RegularParamType::Gas, (0..=0).into()); + params_config.set_ptr_rule(PtrParamAllowedValues::HashWithValue { + ty: HashType::ActorId, + range: REPLY_VALUE..=REPLY_VALUE, + }); + + let tested_syscalls = [ + // InvocableSyscall::Loose(SyscallName::Send), + // InvocableSyscall::Loose(SyscallName::SendInput), + // InvocableSyscall::Precise(SyscallName::ReservationSend), + InvocableSyscall::Precise(SyscallName::SendCommit), + // InvocableSyscall::Precise(SyscallName::ReplyDeposit), + ]; + + for syscall in tested_syscalls { + let mut buf = vec![2; UNSTRUCTURED_SIZE]; + let mut unstructured = Unstructured::new(&buf); + + let mut injection_types = SyscallsInjectionTypes::all_never(); + injection_types.set(syscall, 1, 1); + let syscalls_config = SyscallsConfigBuilder::new(injection_types) + .with_params_config(params_config.clone()) + .with_error_processing_config(ErrorProcessingConfig::All) + .build(); + + let backend_report = execute_wasm_with_custom_configs( + &mut unstructured, + syscalls_config, + None, + 1024, + false, + INITIAL_BALANCE, + ); + + assert_eq!( + backend_report.ext.context.value_counter.left(), + INITIAL_BALANCE - REPLY_VALUE + ); + assert_eq!( + backend_report.termination_reason, + TerminationReason::Actor(ActorTerminationReason::Success) + ); + } +} + + #[test] fn error_processing_works_for_fallible_syscalls() { gear_utils::init_default_logger();