Skip to content

Commit d870a8e

Browse files
authored
Merge pull request #5953 from BowTiedWoo/fix/calrity-wasm-contract-call-oom
`contract-call?` fix OOM
2 parents 5f09c8e + 01bb817 commit d870a8e

File tree

1 file changed

+99
-1
lines changed

1 file changed

+99
-1
lines changed

clarity/src/vm/clarity_wasm.rs

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,17 @@ pub fn call_function<'a>(
549549
}
550550
let mut in_mem_offset = offset + arg_size;
551551

552+
// Ensure that the memory has enough space for the arguments
553+
let mut total_required_bytes = 0;
554+
for (arg, ty) in args.iter().zip(func_types.get_arg_types()) {
555+
total_required_bytes += get_required_bytes(ty, arg)?;
556+
}
557+
ensure_memory(
558+
&memory,
559+
&mut store,
560+
total_required_bytes + in_mem_offset as usize,
561+
)?;
562+
552563
// Convert the args into wasmtime values
553564
let mut wasm_args = vec![];
554565
for (arg, ty) in args.iter().zip(func_types.get_arg_types()) {
@@ -1486,6 +1497,94 @@ fn write_to_wasm(
14861497
}
14871498
}
14881499

1500+
/// Ensure the memory is large enough to write the given number of bytes.
1501+
fn ensure_memory(
1502+
memory: &Memory,
1503+
store: &mut impl AsContextMut,
1504+
required_bytes: usize,
1505+
) -> Result<(), Error> {
1506+
// Round up division.
1507+
let required_pages = ((required_bytes + 65535) / 65536) as u64;
1508+
let current_pages = memory.size(store.as_context_mut());
1509+
// If the current memory is not large enough, grow it by the required
1510+
// number of pages.
1511+
if current_pages < required_pages {
1512+
memory
1513+
.grow(store.as_context_mut(), required_pages - current_pages)
1514+
.map_err(|e| Error::Wasm(WasmError::UnableToWriteMemory(e.into())))?;
1515+
}
1516+
Ok(())
1517+
}
1518+
1519+
/// Get the number of bytes required to write the given value to memory.
1520+
/// This is used to ensure that the memory has enough space for the arguments.
1521+
fn get_required_bytes(ty: &TypeSignature, value: &Value) -> Result<usize, Error> {
1522+
match value {
1523+
Value::UInt(_) | Value::Int(_) | Value::Bool(_) => {
1524+
// These types don't require memory allocation
1525+
Ok(0)
1526+
}
1527+
Value::Optional(o) => {
1528+
let TypeSignature::OptionalType(inner_ty) = ty else {
1529+
return Err(Error::Wasm(WasmError::ValueTypeMismatch));
1530+
};
1531+
1532+
if let Some(inner_value) = o.data.as_ref() {
1533+
get_required_bytes(inner_ty, inner_value)
1534+
} else {
1535+
Ok(0)
1536+
}
1537+
}
1538+
Value::Response(r) => {
1539+
let TypeSignature::ResponseType(inner_tys) = ty else {
1540+
return Err(Error::Wasm(WasmError::ValueTypeMismatch));
1541+
};
1542+
get_required_bytes(
1543+
if r.committed {
1544+
&inner_tys.0
1545+
} else {
1546+
&inner_tys.1
1547+
},
1548+
&r.data,
1549+
)
1550+
}
1551+
Value::Sequence(SequenceData::String(CharType::ASCII(s))) => Ok(s.data.len()),
1552+
Value::Sequence(SequenceData::String(CharType::UTF8(s))) => Ok(s.data.len()),
1553+
Value::Sequence(SequenceData::Buffer(b)) => Ok(b.data.len()),
1554+
Value::Sequence(SequenceData::List(l)) => {
1555+
let TypeSignature::SequenceType(SequenceSubtype::ListType(ltd)) = ty else {
1556+
return Err(Error::Wasm(WasmError::ValueTypeMismatch));
1557+
};
1558+
let element_size = get_type_in_memory_size(ltd.get_list_item_type(), true) as usize;
1559+
let total_bytes = element_size * l.data.len();
1560+
Ok(total_bytes)
1561+
}
1562+
Value::Principal(PrincipalData::Standard(_)) => Ok(STANDARD_PRINCIPAL_BYTES),
1563+
Value::Principal(PrincipalData::Contract(p))
1564+
| Value::CallableContract(CallableData {
1565+
contract_identifier: p,
1566+
..
1567+
}) => Ok(PRINCIPAL_BYTES + 1 + p.name.len() as usize),
1568+
Value::Tuple(TupleData { data_map, .. }) => {
1569+
let TypeSignature::TupleType(tuple_ty) = ty else {
1570+
return Err(Error::Wasm(WasmError::ValueTypeMismatch));
1571+
};
1572+
1573+
let mut total_bytes = 0;
1574+
for (name, ty) in tuple_ty.get_type_map() {
1575+
match data_map.get(name) {
1576+
Some(value) => total_bytes += get_required_bytes(ty, value)?,
1577+
None => return Err(Error::Wasm(WasmError::ValueTypeMismatch)),
1578+
}
1579+
}
1580+
if data_map.len() != tuple_ty.get_type_map().len() {
1581+
return Err(Error::Wasm(WasmError::ValueTypeMismatch));
1582+
}
1583+
Ok(total_bytes)
1584+
}
1585+
}
1586+
}
1587+
14891588
/// Convert a Clarity `Value` into one or more Wasm `Val`. If this value
14901589
/// requires writing into the Wasm memory, write it to the provided `offset`.
14911590
/// Return a vector of `Val`s that can be passed to a Wasm function, and the
@@ -1631,7 +1730,6 @@ fn pass_argument_to_wasm(
16311730
let TypeSignature::SequenceType(SequenceSubtype::ListType(ltd)) = ty else {
16321731
return Err(Error::Wasm(WasmError::ValueTypeMismatch));
16331732
};
1634-
16351733
let mut buffer = vec![Val::I32(offset)];
16361734
let mut written = 0;
16371735
let mut in_mem_written = 0;

0 commit comments

Comments
 (0)