Skip to content

Commit

Permalink
Add Definition::Sequence::length_range
Browse files Browse the repository at this point in the history
Having a length_range field in the Sequence definition allows to
better specify encoding formats for bounded vectors.  This in turn
helps with performing calculations such as figuring out the maximum
or minimum length of encoding of a type.
  • Loading branch information
mina86 committed Sep 14, 2023
1 parent ec111e1 commit b78e6bd
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 31 deletions.
75 changes: 65 additions & 10 deletions borsh/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,25 @@ pub type FieldName = String;
pub enum Definition {
/// A fixed-size array with the length known at the compile time and the same-type elements.
Array { length: u32, elements: Declaration },
/// A sequence of elements of length known at the run time and the same-type elements.
Sequence { elements: Declaration },

/// A dynamically-sized sequence of homogeneous elements.
///
/// The sequence is encoded with a 4-byte length followed by the elements of
/// the sequence. Prototypical example of type which uses this definition
/// is `Vec<T>`.
Sequence {
/// Bounds on the possible lengths of the sequence.
///
/// For unbounded sequences (such as `Vec<T>`) it’s `0..=u32::MAX`
/// however custom types may have limits which can be specified as
/// minimum and maximum length. For example, a `BridgePlayers` type may
/// be defined as a sequence of players with length range `3..=4`.
length_range: core::ops::RangeInclusive<u32>,

/// Type of each element of the sequence.
elements: Declaration,
},

/// A fixed-size tuple with the length known at the compile time and the elements of different
/// types.
Tuple { elements: Vec<Declaration> },
Expand Down Expand Up @@ -83,6 +100,15 @@ pub enum Definition {
Struct { fields: Fields },
}

impl Definition {
/// Convenience constant representing the length range of a standard borsh
/// sequence.
///
/// It equals `0..=u32::MAX`. Can be used for `Definition::Array::length`
/// and `Definition::Sequence::length`.
pub const DEFAULT_LENGTH_RANGE: core::ops::RangeInclusive<u32> = 0..=u32::MAX;
}

/// The collection representing the fields of a struct.
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize, BorshSchemaMacro)]
pub enum Fields {
Expand Down Expand Up @@ -400,6 +426,7 @@ where
{
fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
let definition = Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: T::declaration(),
};
add_definition(Self::declaration(), definition, definitions);
Expand All @@ -417,6 +444,7 @@ where
{
fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
let definition = Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: T::declaration(),
};
add_definition(Self::declaration(), definition, definitions);
Expand Down Expand Up @@ -450,6 +478,7 @@ pub mod hashes {
{
fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
let definition = Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: <(K, V)>::declaration(),
};
add_definition(Self::declaration(), definition, definitions);
Expand All @@ -466,6 +495,7 @@ pub mod hashes {
{
fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
let definition = Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: <T>::declaration(),
};
add_definition(Self::declaration(), definition, definitions);
Expand All @@ -485,6 +515,7 @@ where
{
fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
let definition = Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: <(K, V)>::declaration(),
};
add_definition(Self::declaration(), definition, definitions);
Expand All @@ -502,6 +533,7 @@ where
{
fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
let definition = Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: <T>::declaration(),
};
add_definition(Self::declaration(), definition, definitions);
Expand Down Expand Up @@ -647,7 +679,10 @@ mod tests {
assert_eq!("Vec<u64>", actual_name);
assert_eq!(
map! {
"Vec<u64>" => Definition::Sequence { elements: "u64".to_string() }
"Vec<u64>" => Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: "u64".to_string(),
}
},
actual_defs
);
Expand All @@ -661,8 +696,14 @@ mod tests {
assert_eq!("Vec<Vec<u64>>", actual_name);
assert_eq!(
map! {
"Vec<u64>" => Definition::Sequence { elements: "u64".to_string() },
"Vec<Vec<u64>>" => Definition::Sequence { elements: "Vec<u64>".to_string() }
"Vec<u64>" => Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: "u64".to_string(),
},
"Vec<Vec<u64>>" => Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: "Vec<u64>".to_string(),
}
},
actual_defs
);
Expand Down Expand Up @@ -716,8 +757,13 @@ mod tests {
assert_eq!("HashMap<u64, string>", actual_name);
assert_eq!(
map! {
"HashMap<u64, string>" => Definition::Sequence { elements: "Tuple<u64, string>".to_string()} ,
"Tuple<u64, string>" => Definition::Tuple { elements: vec![ "u64".to_string(), "string".to_string()]}
"HashMap<u64, string>" => Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: "Tuple<u64, string>".to_string(),
} ,
"Tuple<u64, string>" => Definition::Tuple {
elements: vec![ "u64".to_string(), "string".to_string()],
}
},
actual_defs
);
Expand All @@ -732,7 +778,10 @@ mod tests {
assert_eq!("HashSet<string>", actual_name);
assert_eq!(
map! {
"HashSet<string>" => Definition::Sequence { elements: "string".to_string()}
"HashSet<string>" => Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: "string".to_string(),
}
},
actual_defs
);
Expand All @@ -746,7 +795,10 @@ mod tests {
assert_eq!("BTreeMap<u64, string>", actual_name);
assert_eq!(
map! {
"BTreeMap<u64, string>" => Definition::Sequence { elements: "Tuple<u64, string>".to_string()} ,
"BTreeMap<u64, string>" => Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: "Tuple<u64, string>".to_string(),
} ,
"Tuple<u64, string>" => Definition::Tuple { elements: vec![ "u64".to_string(), "string".to_string()]}
},
actual_defs
Expand All @@ -761,7 +813,10 @@ mod tests {
assert_eq!("BTreeSet<string>", actual_name);
assert_eq!(
map! {
"BTreeSet<string>" => Definition::Sequence { elements: "string".to_string()}
"BTreeSet<string>" => Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: "string".to_string(),
}
},
actual_defs
);
Expand Down
52 changes: 39 additions & 13 deletions borsh/src/schema_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,17 +143,17 @@ fn max_serialized_size_impl<'a>(
schema: &'a BorshSchemaContainer,
stack: &mut Vec<&'a str>,
) -> core::result::Result<usize, MaxSizeError> {
use core::convert::TryFrom;
use core::result::Result;

/// Maximum number of elements in a vector or length of a string which can
/// be serialised.
const MAX_LEN: usize = u32::MAX as usize;
fn usize_from<T: core::convert::TryInto<usize>>(value: T) -> Result<usize, MaxSizeError> {
value.try_into().map_err(|_| MaxSizeError::Overflow)
}

fn add(x: usize, y: usize) -> core::result::Result<usize, MaxSizeError> {
fn add(x: usize, y: usize) -> Result<usize, MaxSizeError> {
x.checked_add(y).ok_or(MaxSizeError::Overflow)
}

fn mul(x: usize, y: usize) -> core::result::Result<usize, MaxSizeError> {
fn mul(x: usize, y: usize) -> Result<usize, MaxSizeError> {
x.checked_mul(y).ok_or(MaxSizeError::Overflow)
}

Expand All @@ -163,7 +163,7 @@ fn max_serialized_size_impl<'a>(
elements: impl core::iter::IntoIterator<Item = &'a Declaration>,
schema: &'a BorshSchemaContainer,
stack: &mut Vec<&'a str>,
) -> ::core::result::Result<usize, MaxSizeError> {
) -> Result<usize, MaxSizeError> {
let mut sum: usize = 0;
for el in elements {
sum = add(sum, max_serialized_size_impl(1, el, schema, stack)?)?;
Expand All @@ -180,7 +180,7 @@ fn max_serialized_size_impl<'a>(
Ok(Definition::Array { length, elements }) => {
// Aggregate `count` and `length` to a single number. If this
// overflows, check if array’s element is zero-sized.
let count = usize::try_from(*length)
let count = usize_from(*length)
.ok()
.and_then(|len| len.checked_mul(count));
match count {
Expand All @@ -190,13 +190,16 @@ fn max_serialized_size_impl<'a>(
None => Err(MaxSizeError::Overflow),
}
}
Ok(Definition::Sequence { elements }) => {
Ok(Definition::Sequence {
length_range,
elements,
}) => {
if is_zero_size(elements, schema) {
return Err(MaxSizeError::ZSTSequenceNotArray);
}
// Assume that sequence has MAX_LEN elements since that’s the most
// it can have.
let sz = max_serialized_size_impl(MAX_LEN, elements, schema, stack)?;
// Assume sequence has the maximum number of elements.
let max = usize_from(*length_range.end())?;
let sz = max_serialized_size_impl(max, elements, schema, stack)?;
mul(count, add(sz, 4)?)
}

Expand Down Expand Up @@ -232,7 +235,7 @@ fn max_serialized_size_impl<'a>(
Err("i128" | "u128" | "nonzero_i128" | "nonzero_u128") => mul(count, 16),

// string is just Vec<u8>
Err("string") => mul(count, add(MAX_LEN, 4)?),
Err("string") => mul(count, add(4, usize_from(u32::MAX)?)?),

Err(_) => Err(MaxSizeError::MissingDefinition),
}?;
Expand Down Expand Up @@ -379,4 +382,27 @@ mod tests {
test_ok::<Maybe<4, u16>>(6);
test_ok::<Maybe<4, u64>>(12);
}

#[test]
fn max_serialized_size_custom_sequence() {
#[allow(dead_code)]
struct BoundVec<const N: usize>;

impl<const N: usize> BorshSchema for BoundVec<N> {
fn declaration() -> Declaration {
format!("BoundVec<{}>", N)
}
fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
let definition = Definition::Sequence {
tag_width: 0..=(HI as u64),
elements: "u8",
};
crate::schema::add_definition(Self::declaration(), definition, definitions);
}
}

test_ok::<BoundVec<0, 0>>(4);
test_ok::<BoundVec<0, { u32::MAX as usize }>>(4 + u32::MAX as usize);
test_ok::<BoundVec<0, 20>>(24);
}
}
5 changes: 4 additions & 1 deletion borsh/tests/test_schema_enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,10 @@ fn common_map() -> BTreeMap<String, Definition> {
("z".to_string(), "i8".to_string())
])},
"EnumParametrizedC<string>" => Definition::Struct{ fields: Fields::UnnamedFields(vec!["string".to_string(), "u16".to_string()])},
"BTreeMap<u32, u16>" => Definition::Sequence { elements: "Tuple<u32, u16>".to_string()},
"BTreeMap<u32, u16>" => Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: "Tuple<u32, u16>".to_string(),
},
"Tuple<u32, u16>" => Definition::Tuple { elements: vec!["u32".to_string(), "u16".to_string()]}
}
}
Expand Down
5 changes: 4 additions & 1 deletion borsh/tests/test_schema_nested.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@ pub fn duplicated_instantiations() {
"ASausage<string>" => Definition::Struct{ fields: Fields::NamedFields(vec![("wrapper".to_string(), "string".to_string()), ("filling".to_string(), "Filling".to_string())])},
"Cucumber" => Definition::Struct {fields: Fields::Empty},
"Filling" => Definition::Struct {fields: Fields::Empty},
"HashMap<u64, string>" => Definition::Sequence { elements: "Tuple<u64, string>".to_string()},
"HashMap<u64, string>" => Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: "Tuple<u64, string>".to_string(),
},
"Oil<u64, string>" => Definition::Struct { fields: Fields::NamedFields(vec![("seeds".to_string(), "HashMap<u64, string>".to_string()), ("liquid".to_string(), "Option<u64>".to_string())])},
"Option<string>" => Definition::Enum {
tag_width: 1,
Expand Down
4 changes: 2 additions & 2 deletions borsh/tests/test_schema_recursive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ pub fn recursive_struct_schema() {

},
"BTreeMap<string, CRecC>" => Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: "Tuple<string, CRecC>".to_string(),

},
"Tuple<string, CRecC>" => Definition::Tuple {elements: vec!["string".to_string(), "CRecC".to_string()]}
},
Expand Down Expand Up @@ -97,8 +97,8 @@ pub fn recursive_enum_schema() {
])
},
"Vec<ERecD>" => Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: "ERecD".to_string(),

}
},
defs
Expand Down
10 changes: 8 additions & 2 deletions borsh/tests/test_schema_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ pub fn boxed() {
A::add_definitions_recursively(&mut defs);
assert_eq!(
map! {
"Vec<u8>" => Definition::Sequence { elements: "u8".to_string() },
"Vec<u8>" => Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: "u8".to_string(),
},
"A" => Definition::Struct{ fields: Fields::NamedFields(vec![
("_f1".to_string(), "u64".to_string()),
("_f2".to_string(), "string".to_string()),
Expand Down Expand Up @@ -168,7 +171,10 @@ pub fn simple_generics() {
("_f2".to_string(), "string".to_string())
])
},
"HashMap<u64, string>" => Definition::Sequence {elements: "Tuple<u64, string>".to_string()},
"HashMap<u64, string>" => Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: "Tuple<u64, string>".to_string(),
},
"Tuple<u64, string>" => Definition::Tuple{elements: vec!["u64".to_string(), "string".to_string()]}
},
defs
Expand Down
10 changes: 8 additions & 2 deletions borsh/tests/test_schema_with.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ pub fn struct_overriden() {
"ThirdParty<u64, string>" => Definition::Struct { fields: Fields::UnnamedFields(vec![
"BTreeMap<u64, string>".to_string(),
]) },
"BTreeMap<u64, string>"=> Definition::Sequence { elements: "Tuple<u64, string>".to_string() },
"BTreeMap<u64, string>"=> Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: "Tuple<u64, string>".to_string(),
},
"Tuple<u64, string>" => Definition::Tuple { elements: vec!["u64".to_string(), "string".to_string()]}
},
defs
Expand Down Expand Up @@ -144,7 +147,10 @@ pub fn enum_overriden() {
"ThirdParty<u64, string>" => Definition::Struct { fields: Fields::UnnamedFields(vec![
"BTreeMap<u64, string>".to_string(),
]) },
"BTreeMap<u64, string>"=> Definition::Sequence { elements: "Tuple<u64, string>".to_string() },
"BTreeMap<u64, string>"=> Definition::Sequence {
length_range: Definition::DEFAULT_LENGTH_RANGE,
elements: "Tuple<u64, string>".to_string(),
},
"Tuple<u64, string>" => Definition::Tuple { elements: vec!["u64".to_string(), "string".to_string()]}
},
defs
Expand Down

0 comments on commit b78e6bd

Please sign in to comment.