casper_types/
contract_wasm.rs

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    addressable_entity::TryFromSliceForAccountHashError,
17    bytesrepr::{Bytes, Error, FromBytes, ToBytes},
18    checksummed_hex, uref, ByteCode, ByteCodeKind, 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/// Associated error type of `TryFrom<&[u8]>` for `ContractWasmHash`.
26#[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/// A newtype wrapping a `HashAddr` which is the raw bytes of
86/// the ContractWasmHash
87#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
88#[cfg_attr(feature = "datasize", derive(DataSize))]
89pub struct ContractWasmHash(HashAddr);
90
91impl ContractWasmHash {
92    /// Constructs a new `ContractWasmHash` from the raw bytes of the contract wasm hash.
93    pub const fn new(value: HashAddr) -> ContractWasmHash {
94        ContractWasmHash(value)
95    }
96
97    /// Returns the raw bytes of the contract hash as an array.
98    pub fn value(&self) -> HashAddr {
99        self.0
100    }
101
102    /// Returns the raw bytes of the contract hash as a `slice`.
103    pub fn as_bytes(&self) -> &[u8] {
104        &self.0
105    }
106
107    /// Formats the `ContractWasmHash` for users getting and putting.
108    pub fn to_formatted_string(self) -> String {
109        format!("{}{}", WASM_STRING_PREFIX, base16::encode_lower(&self.0),)
110    }
111
112    /// Parses a string formatted as per `Self::to_formatted_string()` into a
113    /// `ContractWasmHash`.
114    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 Default for ContractWasmHash {
124    fn default() -> Self {
125        ContractWasmHash::new([0; 32])
126    }
127}
128
129impl Display for ContractWasmHash {
130    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
131        write!(f, "{}", base16::encode_lower(&self.0))
132    }
133}
134
135impl Debug for ContractWasmHash {
136    fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
137        write!(f, "ContractWasmHash({})", base16::encode_lower(&self.0))
138    }
139}
140
141impl CLTyped for ContractWasmHash {
142    fn cl_type() -> CLType {
143        CLType::ByteArray(KEY_HASH_LENGTH as u32)
144    }
145}
146
147impl ToBytes for ContractWasmHash {
148    #[inline(always)]
149    fn to_bytes(&self) -> Result<Vec<u8>, Error> {
150        self.0.to_bytes()
151    }
152
153    #[inline(always)]
154    fn serialized_length(&self) -> usize {
155        self.0.serialized_length()
156    }
157
158    #[inline(always)]
159    fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), Error> {
160        self.0.write_bytes(writer)?;
161        Ok(())
162    }
163}
164
165impl FromBytes for ContractWasmHash {
166    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
167        let (bytes, rem) = FromBytes::from_bytes(bytes)?;
168        Ok((ContractWasmHash::new(bytes), rem))
169    }
170}
171
172impl From<[u8; 32]> for ContractWasmHash {
173    fn from(bytes: [u8; 32]) -> Self {
174        ContractWasmHash(bytes)
175    }
176}
177
178impl Serialize for ContractWasmHash {
179    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
180        if serializer.is_human_readable() {
181            self.to_formatted_string().serialize(serializer)
182        } else {
183            self.0.serialize(serializer)
184        }
185    }
186}
187
188impl<'de> Deserialize<'de> for ContractWasmHash {
189    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
190        if deserializer.is_human_readable() {
191            let formatted_string = String::deserialize(deserializer)?;
192            ContractWasmHash::from_formatted_str(&formatted_string).map_err(SerdeError::custom)
193        } else {
194            let bytes = HashAddr::deserialize(deserializer)?;
195            Ok(ContractWasmHash(bytes))
196        }
197    }
198}
199
200impl AsRef<[u8]> for ContractWasmHash {
201    fn as_ref(&self) -> &[u8] {
202        self.0.as_ref()
203    }
204}
205
206impl TryFrom<&[u8]> for ContractWasmHash {
207    type Error = TryFromSliceForContractHashError;
208
209    fn try_from(bytes: &[u8]) -> Result<Self, TryFromSliceForContractHashError> {
210        HashAddr::try_from(bytes)
211            .map(ContractWasmHash::new)
212            .map_err(|_| TryFromSliceForContractHashError(()))
213    }
214}
215
216impl TryFrom<&Vec<u8>> for ContractWasmHash {
217    type Error = TryFromSliceForContractHashError;
218
219    fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> {
220        HashAddr::try_from(bytes as &[u8])
221            .map(ContractWasmHash::new)
222            .map_err(|_| TryFromSliceForContractHashError(()))
223    }
224}
225
226#[cfg(feature = "json-schema")]
227impl JsonSchema for ContractWasmHash {
228    fn schema_name() -> String {
229        String::from("ContractWasmHash")
230    }
231
232    fn json_schema(gen: &mut SchemaGenerator) -> Schema {
233        let schema = gen.subschema_for::<String>();
234        let mut schema_object = schema.into_object();
235        schema_object.metadata().description =
236            Some("The hash address of the contract wasm".to_string());
237        schema_object.into()
238    }
239}
240
241/// A container for contract's WASM bytes.
242#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
243#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
244#[cfg_attr(feature = "datasize", derive(DataSize))]
245pub struct ContractWasm {
246    bytes: Bytes,
247}
248
249impl ContractWasm {
250    /// Creates a new `ContractWasm`.
251    pub fn new(bytes: Vec<u8>) -> Self {
252        Self {
253            bytes: bytes.into(),
254        }
255    }
256
257    pub fn take_bytes(self) -> Vec<u8> {
258        self.bytes.into()
259    }
260}
261
262impl Debug for ContractWasm {
263    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
264        if self.bytes.len() > CONTRACT_WASM_MAX_DISPLAY_LEN {
265            write!(
266                f,
267                "ContractWasm(0x{}...)",
268                base16::encode_lower(&self.bytes[..CONTRACT_WASM_MAX_DISPLAY_LEN])
269            )
270        } else {
271            write!(f, "ContractWasm(0x{})", base16::encode_lower(&self.bytes))
272        }
273    }
274}
275
276impl ToBytes for ContractWasm {
277    fn to_bytes(&self) -> Result<Vec<u8>, Error> {
278        self.bytes.to_bytes()
279    }
280
281    fn serialized_length(&self) -> usize {
282        self.bytes.serialized_length()
283    }
284
285    fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), Error> {
286        self.bytes.write_bytes(writer)?;
287        Ok(())
288    }
289}
290
291impl FromBytes for ContractWasm {
292    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
293        let (bytes, rem1) = FromBytes::from_bytes(bytes)?;
294        Ok((ContractWasm { bytes }, rem1))
295    }
296}
297
298impl From<ContractWasm> for ByteCode {
299    fn from(value: ContractWasm) -> Self {
300        ByteCode::new(ByteCodeKind::V1CasperWasm, value.take_bytes())
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307    #[test]
308    fn test_debug_repr_of_short_wasm() {
309        const SIZE: usize = 8;
310        let wasm_bytes = vec![0; SIZE];
311        let contract_wasm = ContractWasm::new(wasm_bytes);
312        // String output is less than the bytes itself
313        assert_eq!(
314            format!("{:?}", contract_wasm),
315            "ContractWasm(0x0000000000000000)"
316        );
317    }
318
319    #[test]
320    fn test_debug_repr_of_long_wasm() {
321        const SIZE: usize = 65;
322        let wasm_bytes = vec![0; SIZE];
323        let contract_wasm = ContractWasm::new(wasm_bytes);
324        // String output is less than the bytes itself
325        assert_eq!(
326            format!("{:?}", contract_wasm),
327            "ContractWasm(0x00000000000000000000000000000000...)"
328        );
329    }
330
331    #[test]
332    fn contract_wasm_hash_from_slice() {
333        let bytes: Vec<u8> = (0..32).collect();
334        let contract_hash =
335            HashAddr::try_from(&bytes[..]).expect("should create contract wasm hash");
336        let contract_hash = ContractWasmHash::new(contract_hash);
337        assert_eq!(&bytes, &contract_hash.as_bytes());
338    }
339
340    #[test]
341    fn contract_wasm_hash_from_str() {
342        let contract_hash = ContractWasmHash([3; 32]);
343        let encoded = contract_hash.to_formatted_string();
344        let decoded = ContractWasmHash::from_formatted_str(&encoded).unwrap();
345        assert_eq!(contract_hash, decoded);
346
347        let invalid_prefix =
348            "contractwasm-0000000000000000000000000000000000000000000000000000000000000000";
349        assert!(ContractWasmHash::from_formatted_str(invalid_prefix).is_err());
350
351        let short_addr =
352            "contract-wasm-00000000000000000000000000000000000000000000000000000000000000";
353        assert!(ContractWasmHash::from_formatted_str(short_addr).is_err());
354
355        let long_addr =
356            "contract-wasm-000000000000000000000000000000000000000000000000000000000000000000";
357        assert!(ContractWasmHash::from_formatted_str(long_addr).is_err());
358
359        let invalid_hex =
360            "contract-wasm-000000000000000000000000000000000000000000000000000000000000000g";
361        assert!(ContractWasmHash::from_formatted_str(invalid_hex).is_err());
362    }
363
364    #[test]
365    fn contract_wasm_hash_serde_roundtrip() {
366        let contract_hash = ContractWasmHash([255; 32]);
367        let serialized = bincode::serialize(&contract_hash).unwrap();
368        let deserialized = bincode::deserialize(&serialized).unwrap();
369        assert_eq!(contract_hash, deserialized)
370    }
371
372    #[test]
373    fn contract_wasm_hash_json_roundtrip() {
374        let contract_hash = ContractWasmHash([255; 32]);
375        let json_string = serde_json::to_string_pretty(&contract_hash).unwrap();
376        let decoded = serde_json::from_str(&json_string).unwrap();
377        assert_eq!(contract_hash, decoded)
378    }
379}