sidechain_domain/
byte_string.rs

1//! Module providing various byte sequence wrapper types for use by the Partner Chains Toolkit crates
2
3use alloc::string::ToString;
4use alloc::vec::Vec;
5use byte_string_derive::byte_string;
6use core::fmt::{Debug, Display};
7use core::ops::Deref;
8use derive_where::derive_where;
9use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
10use scale_info::TypeInfo;
11use serde::de::Error as DeError;
12use serde::ser::Error as SerError;
13use serde::{Deserialize, Serialize};
14use sp_core::Get;
15use sp_core::bounded::BoundedVec;
16
17/// Wrapper for bytes that is serialized as hex string
18/// To be used for binary data that we want to display nicely but don't have a specific type for
19#[derive(Eq, Clone, PartialEq, TypeInfo, Default, Encode, Decode, PartialOrd, Ord)]
20#[byte_string(debug)]
21#[cfg_attr(feature = "std", byte_string(to_hex_string, decode_hex))]
22#[cfg_attr(feature = "serde", byte_string(hex_serialize, hex_deserialize))]
23pub struct ByteString(pub Vec<u8>);
24
25impl From<&[u8]> for ByteString {
26	fn from(bytes: &[u8]) -> Self {
27		Self(bytes.to_vec())
28	}
29}
30
31impl From<Vec<u8>> for ByteString {
32	fn from(vec: Vec<u8>) -> Self {
33		Self(vec)
34	}
35}
36
37impl Deref for ByteString {
38	type Target = [u8];
39
40	fn deref(&self) -> &Self::Target {
41		&self.0
42	}
43}
44
45/// Constant size variant of `ByteString` that's usable in a FRAME runtime
46#[derive(
47	Eq,
48	Clone,
49	PartialEq,
50	TypeInfo,
51	MaxEncodedLen,
52	Encode,
53	Decode,
54	DecodeWithMemTracking,
55	PartialOrd,
56	Ord,
57)]
58#[byte_string(debug)]
59#[byte_string(to_hex_string)]
60#[cfg_attr(feature = "std", byte_string(decode_hex))]
61#[cfg_attr(feature = "serde", byte_string(hex_serialize, hex_deserialize))]
62pub struct SizedByteString<const N: usize>(pub [u8; N]);
63
64impl<const N: usize> From<[u8; N]> for SizedByteString<N> {
65	fn from(value: [u8; N]) -> Self {
66		Self(value)
67	}
68}
69
70impl<const N: usize> TryFrom<Vec<u8>> for SizedByteString<N> {
71	type Error = <[u8; N] as TryFrom<Vec<u8>>>::Error;
72
73	fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
74		Ok(SizedByteString(value.try_into()?))
75	}
76}
77
78impl<const N: usize> Default for SizedByteString<N> {
79	fn default() -> Self {
80		Self([0; N])
81	}
82}
83
84/// Bounded-length bytes representing a UTF-8 text string
85#[derive(TypeInfo, Encode, DecodeWithMemTracking, MaxEncodedLen)]
86#[scale_info(skip_type_params(T))]
87#[derive_where(Clone, PartialEq, Eq, Default, PartialOrd, Ord)]
88pub struct BoundedString<T: Get<u32>>(BoundedVec<u8, T>);
89
90/// Macro that creates a [BoundedString], supporting string interpolation like [alloc::format].
91#[macro_export]
92macro_rules! bounded_str {
93    ($($arg:expr)+) => {
94		sidechain_domain::byte_string::BoundedString::try_from(format!($($arg)+).as_str()).unwrap()
95    };
96}
97
98impl<T: Get<u32>> Decode for BoundedString<T> {
99	fn decode<I: parity_scale_codec::Input>(
100		input: &mut I,
101	) -> Result<Self, parity_scale_codec::Error> {
102		let bounded_vec: BoundedVec<u8, T> = Decode::decode(input)?;
103
104		// Check if bytes are valid UTF-8 characters
105		core::str::from_utf8(bounded_vec.as_slice())
106			.map_err(|_err| parity_scale_codec::Error::from("UTF-8 decode failed"))?;
107
108		Ok(Self(bounded_vec))
109	}
110}
111
112impl<'a, T: Get<u32>> Deserialize<'a> for BoundedString<T> {
113	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
114	where
115		D: serde::Deserializer<'a>,
116	{
117		Ok(Self(
118			BoundedVec::try_from(
119				alloc::string::String::deserialize(deserializer)?.as_bytes().to_vec(),
120			)
121			.map_err(|_| D::Error::custom("Size limit exceeded"))?,
122		))
123	}
124}
125
126impl<T: Get<u32>> Serialize for BoundedString<T> {
127	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
128	where
129		S: serde::Serializer,
130	{
131		let str = alloc::string::String::from_utf8(self.0.to_vec())
132			.map_err(|_| S::Error::custom("String is not valid UTF-8"))?;
133		serializer.serialize_str(&str)
134	}
135}
136
137impl<T: Get<u32>> Display for BoundedString<T> {
138	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
139		f.write_str(
140			&alloc::string::String::from_utf8(self.0.to_vec()).map_err(|_| core::fmt::Error)?,
141		)
142	}
143}
144
145impl<T: Get<u32>> Debug for BoundedString<T> {
146	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
147		f.write_str(&alloc::format!("BoundedString<{}>({})", T::get(), self))
148	}
149}
150
151impl<T: Get<u32>> TryFrom<&str> for BoundedString<T> {
152	type Error = alloc::string::String;
153
154	fn try_from(value: &str) -> Result<Self, Self::Error> {
155		Ok(Self(BoundedVec::try_from(value.as_bytes().to_vec()).map_err(|_| value.to_string())?))
156	}
157}
158
159#[cfg(test)]
160mod tests {
161	use super::*;
162
163	#[test]
164	fn decode_valid_utf8_string() {
165		let original = "hello world".encode();
166
167		let decoded = BoundedString::<sp_core::ConstU32<32>>::decode(&mut &*original).unwrap();
168		assert_eq!(original, decoded.encode());
169	}
170
171	#[test]
172	fn fail_to_decode_invalid_utf8_string() {
173		let original = vec![0xC0, 0x80, 0xE0, 0xFF].encode(); // some invalid utf8 characters
174		assert_eq!(
175			BoundedString::<sp_core::ConstU32<32>>::decode(&mut &*original),
176			Err(parity_scale_codec::Error::from("UTF-8 decode failed"))
177		);
178
179		use parity_scale_codec::DecodeWithMemLimit;
180
181		// Safety check, due we leave derived DecodeWithMemTracking, so we want to ensure that it is also using Decode trait impl
182		assert_eq!(
183			BoundedString::<sp_core::ConstU32<32>>::decode_with_mem_limit(&mut &*original, 100),
184			Err(parity_scale_codec::Error::from("UTF-8 decode failed"))
185		);
186	}
187}