Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
techraed committed Dec 20, 2023
1 parent 18834ca commit f66699f
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 36 deletions.
13 changes: 11 additions & 2 deletions utils/wasm-gen/src/config/syscalls/param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,11 @@ pub enum PtrParamAllowedValues {
}

impl PtrParamAllowedValues {
const VALUE_WORDS: usize = mem::size_of::<u128>() / mem::size_of::<i32>();
const HASH_WORDS: usize = mem::size_of::<Hash>() / mem::size_of::<i32>();
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<u128>) -> Vec<Self> {
HashType::all()
.into_iter()
Expand All @@ -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())?);
Expand All @@ -298,6 +303,10 @@ impl PtrParamAllowedValues {
}
}

pub(crate) fn default_hash_with_value() -> Vec<i32> {
vec![0i32; Self::HASH_WITH_VALUE_WORDS]
}

fn get_value(unstructured: &mut Unstructured, range: RangeInclusive<u128>) -> Result<Vec<i32>> {
let value = unstructured.int_in_range(range)?;
Ok(Self::get_for_instructions(value.to_le_bytes()))
Expand Down
19 changes: 10 additions & 9 deletions utils/wasm-gen/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
59 changes: 37 additions & 22 deletions utils/wasm-gen/src/generator/syscalls/invocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -326,6 +325,17 @@ impl<'a, 'b> SyscallsInvocator<'a, 'b> {
call_indexes_handle: CallIndexesHandle,
destination_arg_idx: usize,
) -> Result<Vec<Instruction>> {
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)?;
Expand All @@ -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::<Hash>();
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() {
Expand Down
66 changes: 63 additions & 3 deletions utils/wasm-gen/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -235,7 +237,7 @@ fn test_msg_value_ptr() {
&mut unstructured,
syscalls_config,
None,
1,
1024,
false,
INITIAL_BALANCE,
);
Expand All @@ -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();
Expand Down

0 comments on commit f66699f

Please sign in to comment.