1use alloc::{format, string::String, vec::Vec};
2use core::{
3 array::TryFromSliceError,
4 convert::TryFrom,
5 fmt::{self, Debug, Display, Formatter},
6};
7
8#[cfg(feature = "datasize")]
9use datasize::DataSize;
10#[cfg(feature = "json-schema")]
11use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
12use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize, Serializer};
13
14use crate::{
15 account,
16 account::TryFromSliceForAccountHashError,
17 bytesrepr::{Bytes, Error, FromBytes, ToBytes},
18 checksummed_hex, uref, CLType, CLTyped, HashAddr,
19};
20
21const CONTRACT_WASM_MAX_DISPLAY_LEN: usize = 16;
22const KEY_HASH_LENGTH: usize = 32;
23const WASM_STRING_PREFIX: &str = "contract-wasm-";
24
25#[derive(Debug)]
27pub struct TryFromSliceForContractHashError(());
28
29#[derive(Debug)]
30#[non_exhaustive]
31pub enum FromStrError {
32 InvalidPrefix,
33 Hex(base16::DecodeError),
34 Account(TryFromSliceForAccountHashError),
35 Hash(TryFromSliceError),
36 AccountHash(account::FromStrError),
37 URef(uref::FromStrError),
38}
39
40impl From<base16::DecodeError> for FromStrError {
41 fn from(error: base16::DecodeError) -> Self {
42 FromStrError::Hex(error)
43 }
44}
45
46impl From<TryFromSliceForAccountHashError> for FromStrError {
47 fn from(error: TryFromSliceForAccountHashError) -> Self {
48 FromStrError::Account(error)
49 }
50}
51
52impl From<TryFromSliceError> for FromStrError {
53 fn from(error: TryFromSliceError) -> Self {
54 FromStrError::Hash(error)
55 }
56}
57
58impl From<account::FromStrError> for FromStrError {
59 fn from(error: account::FromStrError) -> Self {
60 FromStrError::AccountHash(error)
61 }
62}
63
64impl From<uref::FromStrError> for FromStrError {
65 fn from(error: uref::FromStrError) -> Self {
66 FromStrError::URef(error)
67 }
68}
69
70impl Display for FromStrError {
71 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
72 match self {
73 FromStrError::InvalidPrefix => write!(f, "invalid prefix"),
74 FromStrError::Hex(error) => write!(f, "decode from hex: {}", error),
75 FromStrError::Account(error) => write!(f, "account from string error: {:?}", error),
76 FromStrError::Hash(error) => write!(f, "hash from string error: {}", error),
77 FromStrError::AccountHash(error) => {
78 write!(f, "account hash from string error: {:?}", error)
79 }
80 FromStrError::URef(error) => write!(f, "uref from string error: {:?}", error),
81 }
82 }
83}
84
85#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
88#[cfg_attr(feature = "datasize", derive(DataSize))]
89pub struct ContractWasmHash(HashAddr);
90
91impl ContractWasmHash {
92 pub const fn new(value: HashAddr) -> ContractWasmHash {
94 ContractWasmHash(value)
95 }
96
97 pub fn value(&self) -> HashAddr {
99 self.0
100 }
101
102 pub fn as_bytes(&self) -> &[u8] {
104 &self.0
105 }
106
107 pub fn to_formatted_string(self) -> String {
109 format!("{}{}", WASM_STRING_PREFIX, base16::encode_lower(&self.0),)
110 }
111
112 pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
115 let remainder = input
116 .strip_prefix(WASM_STRING_PREFIX)
117 .ok_or(FromStrError::InvalidPrefix)?;
118 let bytes = HashAddr::try_from(checksummed_hex::decode(remainder)?.as_ref())?;
119 Ok(ContractWasmHash(bytes))
120 }
121}
122
123impl Display for ContractWasmHash {
124 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
125 write!(f, "{}", base16::encode_lower(&self.0))
126 }
127}
128
129impl Debug for ContractWasmHash {
130 fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
131 write!(f, "ContractWasmHash({})", base16::encode_lower(&self.0))
132 }
133}
134
135impl CLTyped for ContractWasmHash {
136 fn cl_type() -> CLType {
137 CLType::ByteArray(KEY_HASH_LENGTH as u32)
138 }
139}
140
141impl ToBytes for ContractWasmHash {
142 #[inline(always)]
143 fn to_bytes(&self) -> Result<Vec<u8>, Error> {
144 self.0.to_bytes()
145 }
146
147 #[inline(always)]
148 fn serialized_length(&self) -> usize {
149 self.0.serialized_length()
150 }
151
152 #[inline(always)]
153 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), Error> {
154 self.0.write_bytes(writer)?;
155 Ok(())
156 }
157}
158
159impl FromBytes for ContractWasmHash {
160 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
161 let (bytes, rem) = FromBytes::from_bytes(bytes)?;
162 Ok((ContractWasmHash::new(bytes), rem))
163 }
164}
165
166impl From<[u8; 32]> for ContractWasmHash {
167 fn from(bytes: [u8; 32]) -> Self {
168 ContractWasmHash(bytes)
169 }
170}
171
172impl Serialize for ContractWasmHash {
173 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
174 if serializer.is_human_readable() {
175 self.to_formatted_string().serialize(serializer)
176 } else {
177 self.0.serialize(serializer)
178 }
179 }
180}
181
182impl<'de> Deserialize<'de> for ContractWasmHash {
183 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
184 if deserializer.is_human_readable() {
185 let formatted_string = String::deserialize(deserializer)?;
186 ContractWasmHash::from_formatted_str(&formatted_string).map_err(SerdeError::custom)
187 } else {
188 let bytes = HashAddr::deserialize(deserializer)?;
189 Ok(ContractWasmHash(bytes))
190 }
191 }
192}
193
194impl AsRef<[u8]> for ContractWasmHash {
195 fn as_ref(&self) -> &[u8] {
196 self.0.as_ref()
197 }
198}
199
200impl TryFrom<&[u8]> for ContractWasmHash {
201 type Error = TryFromSliceForContractHashError;
202
203 fn try_from(bytes: &[u8]) -> Result<Self, TryFromSliceForContractHashError> {
204 HashAddr::try_from(bytes)
205 .map(ContractWasmHash::new)
206 .map_err(|_| TryFromSliceForContractHashError(()))
207 }
208}
209
210impl TryFrom<&Vec<u8>> for ContractWasmHash {
211 type Error = TryFromSliceForContractHashError;
212
213 fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> {
214 HashAddr::try_from(bytes as &[u8])
215 .map(ContractWasmHash::new)
216 .map_err(|_| TryFromSliceForContractHashError(()))
217 }
218}
219
220#[cfg(feature = "json-schema")]
221impl JsonSchema for ContractWasmHash {
222 fn schema_name() -> String {
223 String::from("ContractWasmHash")
224 }
225
226 fn json_schema(gen: &mut SchemaGenerator) -> Schema {
227 let schema = gen.subschema_for::<String>();
228 let mut schema_object = schema.into_object();
229 schema_object.metadata().description =
230 Some("The hash address of the contract wasm".to_string());
231 schema_object.into()
232 }
233}
234
235#[derive(PartialEq, Eq, Clone, Serialize)]
237#[cfg_attr(feature = "datasize", derive(DataSize))]
238pub struct ContractWasm {
239 bytes: Bytes,
240}
241
242impl Debug for ContractWasm {
243 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
244 if self.bytes.len() > CONTRACT_WASM_MAX_DISPLAY_LEN {
245 write!(
246 f,
247 "ContractWasm(0x{}...)",
248 base16::encode_lower(&self.bytes[..CONTRACT_WASM_MAX_DISPLAY_LEN])
249 )
250 } else {
251 write!(f, "ContractWasm(0x{})", base16::encode_lower(&self.bytes))
252 }
253 }
254}
255
256impl ContractWasm {
257 pub fn new(bytes: Vec<u8>) -> Self {
259 ContractWasm {
260 bytes: bytes.into(),
261 }
262 }
263
264 pub fn take_bytes(self) -> Vec<u8> {
266 self.bytes.into()
267 }
268
269 pub fn bytes(&self) -> &[u8] {
271 self.bytes.as_ref()
272 }
273}
274
275impl ToBytes for ContractWasm {
276 fn to_bytes(&self) -> Result<Vec<u8>, Error> {
277 self.bytes.to_bytes()
278 }
279
280 fn serialized_length(&self) -> usize {
281 self.bytes.serialized_length()
282 }
283
284 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), Error> {
285 self.bytes.write_bytes(writer)?;
286 Ok(())
287 }
288}
289
290impl FromBytes for ContractWasm {
291 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
292 let (bytes, rem1) = FromBytes::from_bytes(bytes)?;
293 Ok((ContractWasm { bytes }, rem1))
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 #[test]
301 fn test_debug_repr_of_short_wasm() {
302 const SIZE: usize = 8;
303 let wasm_bytes = vec![0; SIZE];
304 let contract_wasm = ContractWasm::new(wasm_bytes);
305 assert_eq!(
307 format!("{:?}", contract_wasm),
308 "ContractWasm(0x0000000000000000)"
309 );
310 }
311
312 #[test]
313 fn test_debug_repr_of_long_wasm() {
314 const SIZE: usize = 65;
315 let wasm_bytes = vec![0; SIZE];
316 let contract_wasm = ContractWasm::new(wasm_bytes);
317 assert_eq!(
319 format!("{:?}", contract_wasm),
320 "ContractWasm(0x00000000000000000000000000000000...)"
321 );
322 }
323
324 #[test]
325 fn contract_wasm_hash_from_slice() {
326 let bytes: Vec<u8> = (0..32).collect();
327 let contract_hash =
328 HashAddr::try_from(&bytes[..]).expect("should create contract wasm hash");
329 let contract_hash = ContractWasmHash::new(contract_hash);
330 assert_eq!(&bytes, &contract_hash.as_bytes());
331 }
332
333 #[test]
334 fn contract_wasm_hash_from_str() {
335 let contract_hash = ContractWasmHash([3; 32]);
336 let encoded = contract_hash.to_formatted_string();
337 let decoded = ContractWasmHash::from_formatted_str(&encoded).unwrap();
338 assert_eq!(contract_hash, decoded);
339
340 let invalid_prefix =
341 "contractwasm-0000000000000000000000000000000000000000000000000000000000000000";
342 assert!(ContractWasmHash::from_formatted_str(invalid_prefix).is_err());
343
344 let short_addr =
345 "contract-wasm-00000000000000000000000000000000000000000000000000000000000000";
346 assert!(ContractWasmHash::from_formatted_str(short_addr).is_err());
347
348 let long_addr =
349 "contract-wasm-000000000000000000000000000000000000000000000000000000000000000000";
350 assert!(ContractWasmHash::from_formatted_str(long_addr).is_err());
351
352 let invalid_hex =
353 "contract-wasm-000000000000000000000000000000000000000000000000000000000000000g";
354 assert!(ContractWasmHash::from_formatted_str(invalid_hex).is_err());
355 }
356
357 #[test]
358 fn contract_wasm_hash_serde_roundtrip() {
359 let contract_hash = ContractWasmHash([255; 32]);
360 let serialized = bincode::serialize(&contract_hash).unwrap();
361 let deserialized = bincode::deserialize(&serialized).unwrap();
362 assert_eq!(contract_hash, deserialized)
363 }
364
365 #[test]
366 fn contract_wasm_hash_json_roundtrip() {
367 let contract_hash = ContractWasmHash([255; 32]);
368 let json_string = serde_json::to_string_pretty(&contract_hash).unwrap();
369 let decoded = serde_json::from_str(&json_string).unwrap();
370 assert_eq!(contract_hash, decoded)
371 }
372}