From 9ca29121176030f4aa1326f89337f9fa03b46428 Mon Sep 17 00:00:00 2001 From: BowTiedWoo <168228753+BowTiedWoo@users.noreply.github.com> Date: Mon, 14 Apr 2025 15:59:32 +0300 Subject: [PATCH 1/3] fix: allocate right amount of space for `contract-call?` args --- clarity/src/vm/clarity_wasm.rs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/clarity/src/vm/clarity_wasm.rs b/clarity/src/vm/clarity_wasm.rs index 2fd1ed9af8..e490fdceaa 100644 --- a/clarity/src/vm/clarity_wasm.rs +++ b/clarity/src/vm/clarity_wasm.rs @@ -542,10 +542,12 @@ pub fn call_function<'a>( .get_memory(&mut store, "memory") .ok_or(Error::Wasm(WasmError::MemoryNotFound))?; - // Determine how much space is needed for arguments + // The only argument that needs space to be written at `offset` is the list + // type according to pass_argument_to_wasm, the function that writes to + // memory. Because of this we only allocate space for lists. let mut arg_size = 0; - for arg in func_types.get_arg_types() { - arg_size += get_type_in_memory_size(arg, false); + for arg in args { + arg_size += get_list_size(arg); } let mut in_mem_offset = offset + arg_size; @@ -613,6 +615,25 @@ pub const CONTRACT_NAME_MAX_LENGTH: usize = 128; // Standard principal, but at most 128 character function name pub const PRINCIPAL_BYTES_MAX: usize = STANDARD_PRINCIPAL_BYTES + CONTRACT_NAME_MAX_LENGTH; +// Return the number of bytes required to represent a list in memory. +fn get_list_size(val: &Value) -> i32 { + match val { + Value::Sequence(SequenceData::List(list)) => + // 8 bytes for the offset and length + { + 8 + list + .data + .iter() + .map(|item| match item { + Value::Sequence(SequenceData::List(_)) => get_list_size(item), + _ => get_type_size(list.type_signature.get_list_item_type()), + }) + .sum::() + } + _ => 0, + } +} + /// Return the number of bytes required to representation of a value of the /// type `ty`. For in-memory types, this is just the size of the offset and /// length. For non-in-memory types, this is the size of the value itself. From 412a49d4e4e7f2b0c1eb7396d4e361b81d7dccf6 Mon Sep 17 00:00:00 2001 From: BowTiedWoo <168228753+BowTiedWoo@users.noreply.github.com> Date: Wed, 16 Apr 2025 16:03:12 +0300 Subject: [PATCH 2/3] fix: add response, optional and tuple types + fix comments + fix function name --- clarity/src/vm/clarity_wasm.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/clarity/src/vm/clarity_wasm.rs b/clarity/src/vm/clarity_wasm.rs index e490fdceaa..c95546d156 100644 --- a/clarity/src/vm/clarity_wasm.rs +++ b/clarity/src/vm/clarity_wasm.rs @@ -542,12 +542,14 @@ pub fn call_function<'a>( .get_memory(&mut store, "memory") .ok_or(Error::Wasm(WasmError::MemoryNotFound))?; - // The only argument that needs space to be written at `offset` is the list - // type according to pass_argument_to_wasm, the function that writes to - // memory. Because of this we only allocate space for lists. + // Values that need space to be written at `offset` are lists according to + // pass_argument_to_wasm, the function that writes to memory. Tuples, + // optionals and response types can also include lists, thus the function + // recursively checks the nested types to calculate the total space needed + // for representations in memory. let mut arg_size = 0; for arg in args { - arg_size += get_list_size(arg); + arg_size += get_arg_repr_size(arg); } let mut in_mem_offset = offset + arg_size; @@ -615,9 +617,17 @@ pub const CONTRACT_NAME_MAX_LENGTH: usize = 128; // Standard principal, but at most 128 character function name pub const PRINCIPAL_BYTES_MAX: usize = STANDARD_PRINCIPAL_BYTES + CONTRACT_NAME_MAX_LENGTH; -// Return the number of bytes required to represent a list in memory. -fn get_list_size(val: &Value) -> i32 { +// Return the number of bytes required to represent arguments in memory. +fn get_arg_repr_size(val: &Value) -> i32 { match val { + Value::Optional(OptionalData { data }) => match data { + Some(val) => get_arg_repr_size(val), + None => 0, + }, + Value::Response(ResponseData { data, .. }) => get_arg_repr_size(&data), + Value::Tuple(TupleData { data_map, .. }) => { + data_map.iter().map(|(_, val)| get_arg_repr_size(val)).sum() + } Value::Sequence(SequenceData::List(list)) => // 8 bytes for the offset and length { @@ -625,7 +635,7 @@ fn get_list_size(val: &Value) -> i32 { .data .iter() .map(|item| match item { - Value::Sequence(SequenceData::List(_)) => get_list_size(item), + Value::Sequence(SequenceData::List(_)) => get_arg_repr_size(item), _ => get_type_size(list.type_signature.get_list_item_type()), }) .sum::() From 349794e7d19acadbe4db19001b0a0c143d55b14f Mon Sep 17 00:00:00 2001 From: BowTiedWoo <168228753+BowTiedWoo@users.noreply.github.com> Date: Mon, 28 Apr 2025 13:50:57 +0300 Subject: [PATCH 3/3] fix: calculate right amount of bytes required for repr --- clarity/src/vm/clarity_wasm.rs | 190 +++++++++++++++++++++++++++++---- 1 file changed, 168 insertions(+), 22 deletions(-) diff --git a/clarity/src/vm/clarity_wasm.rs b/clarity/src/vm/clarity_wasm.rs index c95546d156..c1fc77eca8 100644 --- a/clarity/src/vm/clarity_wasm.rs +++ b/clarity/src/vm/clarity_wasm.rs @@ -548,8 +548,8 @@ pub fn call_function<'a>( // recursively checks the nested types to calculate the total space needed // for representations in memory. let mut arg_size = 0; - for arg in args { - arg_size += get_arg_repr_size(arg); + for (arg, ty) in args.iter().zip(func_types.get_arg_types()) { + arg_size += get_arg_repr_size(arg, ty, false); } let mut in_mem_offset = offset + arg_size; @@ -617,30 +617,176 @@ pub const CONTRACT_NAME_MAX_LENGTH: usize = 128; // Standard principal, but at most 128 character function name pub const PRINCIPAL_BYTES_MAX: usize = STANDARD_PRINCIPAL_BYTES + CONTRACT_NAME_MAX_LENGTH; -// Return the number of bytes required to represent arguments in memory. -fn get_arg_repr_size(val: &Value) -> i32 { - match val { - Value::Optional(OptionalData { data }) => match data { - Some(val) => get_arg_repr_size(val), - None => 0, - }, - Value::Response(ResponseData { data, .. }) => get_arg_repr_size(&data), - Value::Tuple(TupleData { data_map, .. }) => { - data_map.iter().map(|(_, val)| get_arg_repr_size(val)).sum() +/// Return the number of bytes required to represent arguments in memory. +/// The function calculates the space needed for the representations to be +/// written to memory without writing. +/// - When a value is in a list, calculate its full representation size; +/// - When a value contains a list (e.g. response of list), only calculate the +/// list's size +fn get_arg_repr_size(value: &Value, ty: &TypeSignature, in_list: bool) -> i32 { + match ty { + TypeSignature::NoType => { + if in_list { + 4 + } else { + 0 + } } - Value::Sequence(SequenceData::List(list)) => - // 8 bytes for the offset and length - { - 8 + list + TypeSignature::IntType | TypeSignature::UIntType => { + if in_list { + 16 + } else { + 0 + } + } + TypeSignature::BoolType => { + if in_list { + 4 + } else { + 0 + } + } + TypeSignature::SequenceType(SequenceSubtype::ListType(list)) => { + let list_data = value_as_list(value).expect("Failed to get Value of List"); + let elem_ty = list.get_list_item_type(); + let size = list_data .data .iter() - .map(|item| match item { - Value::Sequence(SequenceData::List(_)) => get_arg_repr_size(item), - _ => get_type_size(list.type_signature.get_list_item_type()), - }) - .sum::() + .map(|elem| get_arg_repr_size(elem, elem_ty, true)) + .sum::(); + + if in_list { + size + 8 // offset + length + } else { + size + } + } + // for string and buffer types + TypeSignature::SequenceType(_) => { + if in_list { + 8 + } else { + 0 + } // offset + length + } + TypeSignature::PrincipalType + | TypeSignature::CallableType(_) + | TypeSignature::TraitReferenceType(_) => { + if in_list { + 8 + } else { + 0 + } + } + TypeSignature::TupleType(type_sig) => { + let tuple_data = value_as_tuple(value).expect("Failed to get Tuple of value"); + + if in_list { + type_sig + .get_type_map() + .iter() + .map(|(key, val_type)| { + let val = tuple_data + .data_map + .get(key) + .ok_or(Error::Wasm(WasmError::ValueTypeMismatch)) + .unwrap(); + get_arg_repr_size(val, val_type, in_list) + }) + .sum() + } else { + // Check for nested lists in tuple + type_sig + .get_type_map() + .iter() + .filter_map(|(key, val_type)| match val_type { + TypeSignature::SequenceType(SequenceSubtype::ListType(_)) + | TypeSignature::ResponseType(_) + | TypeSignature::OptionalType(_) + | TypeSignature::TupleType(_) => { + let val = tuple_data + .data_map + .get(key) + .ok_or(Error::Wasm(WasmError::ValueTypeMismatch)) + .unwrap(); + Some(get_arg_repr_size(val, val_type, in_list)) + } + _ => None, + }) + .sum() + } + } + TypeSignature::OptionalType(inner_ty) => { + let opt_data = value_as_optional(value).expect("Failed to get OptionalData from value"); + + if in_list { + let mut size = 4; // Size for indicator + if let Some(inner) = opt_data.data.as_ref() { + size += get_arg_repr_size(inner, inner_ty, in_list); + } else { + size += get_type_size(inner_ty); + } + size + } else { + // Check for nested list in optional + if let Some(inner) = opt_data.data.as_ref() { + match **inner_ty { + TypeSignature::SequenceType(SequenceSubtype::ListType(_)) + | TypeSignature::ResponseType(_) + | TypeSignature::OptionalType(_) + | TypeSignature::TupleType(_) => { + return get_arg_repr_size(inner, inner_ty, in_list); + } + _ => 0, + } + } else { + 0 + } + } + } + TypeSignature::ResponseType(inner_types) => { + let res = value_as_response(value).expect("failed to get ResponseData from value"); + + if in_list { + let mut size = 4; // Size for indicator + if res.committed { + size += get_arg_repr_size(&res.data, &inner_types.0, in_list); + // Skip space for the err value + size += get_type_size(&inner_types.1); + } else { + // Skip space for the ok value + size += get_type_size(&inner_types.0); + size += get_arg_repr_size(&res.data, &inner_types.1, in_list); + } + size + } else { + // Check for nested list in response + if res.committed { + match inner_types.0 { + TypeSignature::SequenceType(SequenceSubtype::ListType(_)) + | TypeSignature::ResponseType(_) + | TypeSignature::OptionalType(_) + | TypeSignature::TupleType(_) => { + return get_arg_repr_size(&res.data, &inner_types.0, in_list); + } + _ => 0, + } + } else { + match inner_types.1 { + TypeSignature::SequenceType(SequenceSubtype::ListType(_)) + | TypeSignature::ResponseType(_) + | TypeSignature::OptionalType(_) + | TypeSignature::TupleType(_) => { + return get_arg_repr_size(&res.data, &inner_types.1, in_list); + } + _ => 0, + } + } + } + } + TypeSignature::ListUnionType(_) => { + unreachable!("not a value type") } - _ => 0, } }