cosmwasm_std/
binary.rs

1use core::fmt;
2use core::ops::Deref;
3
4use base64::engine::{Engine, GeneralPurpose};
5use schemars::JsonSchema;
6use serde::{de, ser, Deserialize, Deserializer, Serialize};
7
8use crate::errors::{StdError, StdResult};
9
10/// Binary is a wrapper around Vec<u8> to add base64 de/serialization
11/// with serde. It also adds some helper methods to help encode inline.
12///
13/// This is only needed as serde-json-{core,wasm} has a horrible encoding for Vec<u8>.
14/// See also <https://github.com/CosmWasm/cosmwasm/blob/main/docs/MESSAGE_TYPES.md>.
15#[derive(Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, JsonSchema)]
16pub struct Binary(#[schemars(with = "String")] pub Vec<u8>);
17
18impl Binary {
19    /// Base64 encoding engine used in conversion to/from base64.
20    ///
21    /// The engine adds padding when encoding and accepts strings with or
22    /// without padding when decoding.
23    const B64_ENGINE: GeneralPurpose = GeneralPurpose::new(
24        &base64::alphabet::STANDARD,
25        base64::engine::GeneralPurposeConfig::new()
26            .with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent),
27    );
28
29    /// take an (untrusted) string and decode it into bytes.
30    /// fails if it is not valid base64
31    pub fn from_base64(encoded: &str) -> StdResult<Self> {
32        Self::B64_ENGINE
33            .decode(encoded.as_bytes())
34            .map(Binary::from)
35            .map_err(StdError::invalid_base64)
36    }
37
38    /// encode to base64 string (guaranteed to be success as we control the data inside).
39    /// this returns normalized form (with trailing = if needed)
40    pub fn to_base64(&self) -> String {
41        Self::B64_ENGINE.encode(self.0.as_slice())
42    }
43
44    pub fn as_slice(&self) -> &[u8] {
45        self.0.as_slice()
46    }
47
48    /// Copies content into fixed-sized array.
49    ///
50    /// # Examples
51    ///
52    /// Copy to array of explicit length
53    ///
54    /// ```
55    /// # use cosmwasm_std::Binary;
56    /// let binary = Binary::from(&[0xfb, 0x1f, 0x37]);
57    /// let array: [u8; 3] = binary.to_array().unwrap();
58    /// assert_eq!(array, [0xfb, 0x1f, 0x37]);
59    /// ```
60    ///
61    /// Copy to integer
62    ///
63    /// ```
64    /// # use cosmwasm_std::Binary;
65    /// let binary = Binary::from(&[0x8b, 0x67, 0x64, 0x84, 0xb5, 0xfb, 0x1f, 0x37]);
66    /// let num = u64::from_be_bytes(binary.to_array().unwrap());
67    /// assert_eq!(num, 10045108015024774967);
68    /// ```
69    pub fn to_array<const LENGTH: usize>(&self) -> StdResult<[u8; LENGTH]> {
70        if self.len() != LENGTH {
71            return Err(StdError::invalid_data_size(LENGTH, self.len()));
72        }
73
74        let mut out: [u8; LENGTH] = [0; LENGTH];
75        out.copy_from_slice(&self.0);
76        Ok(out)
77    }
78}
79
80impl fmt::Display for Binary {
81    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82        write!(f, "{}", self.to_base64())
83    }
84}
85
86impl fmt::Debug for Binary {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        // Use an output inspired by tuples (https://doc.rust-lang.org/std/fmt/struct.Formatter.html#method.debug_tuple)
89        // but with a custom implementation to avoid the need for an intemediate hex string.
90        write!(f, "Binary(")?;
91        for byte in self.0.iter() {
92            write!(f, "{byte:02x}")?;
93        }
94        write!(f, ")")?;
95        Ok(())
96    }
97}
98
99/// Just like Vec<u8>, Binary is a smart pointer to [u8].
100/// This implements `*binary` for us and allows us to
101/// do `&*binary`, returning a `&[u8]` from a `&Binary`.
102/// With [deref coercions](https://doc.rust-lang.org/1.22.1/book/first-edition/deref-coercions.html#deref-coercions),
103/// this allows us to use `&binary` whenever a `&[u8]` is required.
104impl Deref for Binary {
105    type Target = [u8];
106
107    fn deref(&self) -> &Self::Target {
108        self.as_slice()
109    }
110}
111
112impl AsRef<[u8]> for Binary {
113    fn as_ref(&self) -> &[u8] {
114        self.as_slice()
115    }
116}
117
118// Slice
119impl From<&[u8]> for Binary {
120    fn from(binary: &[u8]) -> Self {
121        Self(binary.to_vec())
122    }
123}
124
125// Array reference
126impl<const LENGTH: usize> From<&[u8; LENGTH]> for Binary {
127    fn from(source: &[u8; LENGTH]) -> Self {
128        Self(source.to_vec())
129    }
130}
131
132// Owned array
133impl<const LENGTH: usize> From<[u8; LENGTH]> for Binary {
134    fn from(source: [u8; LENGTH]) -> Self {
135        Self(source.into())
136    }
137}
138
139impl From<Vec<u8>> for Binary {
140    fn from(vec: Vec<u8>) -> Self {
141        Self(vec)
142    }
143}
144
145impl From<Binary> for Vec<u8> {
146    fn from(original: Binary) -> Vec<u8> {
147        original.0
148    }
149}
150
151/// Implement `encoding::Binary == alloc::vec::Vec<u8>`
152impl PartialEq<Vec<u8>> for Binary {
153    fn eq(&self, rhs: &Vec<u8>) -> bool {
154        // Use Vec<u8> == Vec<u8>
155        self.0 == *rhs
156    }
157}
158
159/// Implement `alloc::vec::Vec<u8> == encoding::Binary`
160impl PartialEq<Binary> for Vec<u8> {
161    fn eq(&self, rhs: &Binary) -> bool {
162        // Use Vec<u8> == Vec<u8>
163        *self == rhs.0
164    }
165}
166
167/// Implement `Binary == &[u8]`
168impl PartialEq<&[u8]> for Binary {
169    fn eq(&self, rhs: &&[u8]) -> bool {
170        // Use &[u8] == &[u8]
171        self.as_slice() == *rhs
172    }
173}
174
175/// Implement `&[u8] == Binary`
176impl PartialEq<Binary> for &[u8] {
177    fn eq(&self, rhs: &Binary) -> bool {
178        // Use &[u8] == &[u8]
179        *self == rhs.as_slice()
180    }
181}
182
183/// Implement `Binary == &[u8; LENGTH]`
184impl<const LENGTH: usize> PartialEq<&[u8; LENGTH]> for Binary {
185    fn eq(&self, rhs: &&[u8; LENGTH]) -> bool {
186        self.as_slice() == rhs.as_slice()
187    }
188}
189
190/// Implement `&[u8; LENGTH] == Binary`
191impl<const LENGTH: usize> PartialEq<Binary> for &[u8; LENGTH] {
192    fn eq(&self, rhs: &Binary) -> bool {
193        self.as_slice() == rhs.as_slice()
194    }
195}
196
197/// Implement `Binary == [u8; LENGTH]`
198impl<const LENGTH: usize> PartialEq<[u8; LENGTH]> for Binary {
199    fn eq(&self, rhs: &[u8; LENGTH]) -> bool {
200        self.as_slice() == rhs.as_slice()
201    }
202}
203
204/// Implement `[u8; LENGTH] == Binary`
205impl<const LENGTH: usize> PartialEq<Binary> for [u8; LENGTH] {
206    fn eq(&self, rhs: &Binary) -> bool {
207        self.as_slice() == rhs.as_slice()
208    }
209}
210
211/// Serializes as a base64 string
212impl Serialize for Binary {
213    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
214    where
215        S: ser::Serializer,
216    {
217        if serializer.is_human_readable() {
218            serializer.serialize_str(&self.to_base64())
219        } else {
220            panic!("Binary is only intended to be used with JSON serialization for now. If you are hitting this panic please open an issue at https://github.com/CosmWasm/cosmwasm describing your use case.")
221        }
222    }
223}
224
225/// Deserializes as a base64 string
226impl<'de> Deserialize<'de> for Binary {
227    fn deserialize<D>(deserializer: D) -> Result<Binary, D::Error>
228    where
229        D: Deserializer<'de>,
230    {
231        if deserializer.is_human_readable() {
232            deserializer.deserialize_str(Base64Visitor)
233        } else {
234            panic!("Binary is only intended to be used with JSON serialization for now. If you are hitting this panic please open an issue at https://github.com/CosmWasm/cosmwasm describing your use case.")
235        }
236    }
237}
238
239struct Base64Visitor;
240
241impl<'de> de::Visitor<'de> for Base64Visitor {
242    type Value = Binary;
243
244    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
245        formatter.write_str("valid base64 encoded string")
246    }
247
248    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
249    where
250        E: de::Error,
251    {
252        match Binary::from_base64(v) {
253            Ok(binary) => Ok(binary),
254            Err(_) => Err(E::custom(format!("invalid base64: {v}"))),
255        }
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262    use crate::assert_hash_works;
263    use crate::errors::StdError;
264    use crate::serde::{from_json, to_json_vec};
265
266    #[test]
267    fn to_array_works() {
268        // simple
269        let binary = Binary::from(&[1, 2, 3]);
270        let array: [u8; 3] = binary.to_array().unwrap();
271        assert_eq!(array, [1, 2, 3]);
272
273        // empty
274        let binary = Binary::from(&[]);
275        let array: [u8; 0] = binary.to_array().unwrap();
276        assert_eq!(array, [] as [u8; 0]);
277
278        // invalid size
279        let binary = Binary::from(&[1, 2, 3]);
280        let error = binary.to_array::<8>().unwrap_err();
281        match error {
282            StdError::InvalidDataSize {
283                expected, actual, ..
284            } => {
285                assert_eq!(expected, 8);
286                assert_eq!(actual, 3);
287            }
288            err => panic!("Unexpected error: {err:?}"),
289        }
290
291        // long array (32 bytes)
292        let binary = Binary::from_base64("t119JOQox4WUQEmO/nyqOZfO+wjJm91YG2sfn4ZglvA=").unwrap();
293        let array: [u8; 32] = binary.to_array().unwrap();
294        assert_eq!(
295            array,
296            [
297                0xb7, 0x5d, 0x7d, 0x24, 0xe4, 0x28, 0xc7, 0x85, 0x94, 0x40, 0x49, 0x8e, 0xfe, 0x7c,
298                0xaa, 0x39, 0x97, 0xce, 0xfb, 0x08, 0xc9, 0x9b, 0xdd, 0x58, 0x1b, 0x6b, 0x1f, 0x9f,
299                0x86, 0x60, 0x96, 0xf0,
300            ]
301        );
302
303        // very long array > 32 bytes (requires Rust 1.47+)
304        let binary =
305            Binary::from_base64("t119JOQox4WUQEmO/nyqOZfO+wjJm91YG2sfn4ZglvBzyMOwMWq+").unwrap();
306        let array: [u8; 39] = binary.to_array().unwrap();
307        assert_eq!(
308            array,
309            [
310                0xb7, 0x5d, 0x7d, 0x24, 0xe4, 0x28, 0xc7, 0x85, 0x94, 0x40, 0x49, 0x8e, 0xfe, 0x7c,
311                0xaa, 0x39, 0x97, 0xce, 0xfb, 0x08, 0xc9, 0x9b, 0xdd, 0x58, 0x1b, 0x6b, 0x1f, 0x9f,
312                0x86, 0x60, 0x96, 0xf0, 0x73, 0xc8, 0xc3, 0xb0, 0x31, 0x6a, 0xbe,
313            ]
314        );
315    }
316
317    #[test]
318    fn test_base64_encoding_success() {
319        for (value, encoded, encoded_no_pad) in [
320            (&b""[..], "", ""),
321            (&b"hello"[..], "aGVsbG8=", "aGVsbG8"),
322            (&b"\x0C\xBB\x00\x11\xFA\x01"[..], "DLsAEfoB", "DLsAEfoB"),
323            (&b"rand"[..], "cmFuZA==", "cmFuZA"),
324            (&b"rand"[..], "cmFuZA==", "cmFuZA="),
325            (&b"randomiZ"[..], "cmFuZG9taVo=", "cmFuZG9taVo"),
326        ] {
327            let value = Binary::from(value);
328            assert_eq!(encoded, value.to_base64());
329            assert_eq!(Ok(value.clone()), Binary::from_base64(encoded));
330            assert_eq!(Ok(value.clone()), Binary::from_base64(encoded_no_pad));
331        }
332    }
333
334    #[test]
335    fn test_base64_encoding_error() {
336        for (invalid_base64, want) in [
337            ("cm%uZG9taVo", "Invalid byte 37, offset 2."),
338            ("cmFuZ", "Encoded text cannot have a 6-bit remainder."),
339        ] {
340            match Binary::from_base64(invalid_base64) {
341                Err(StdError::InvalidBase64 { msg }) => assert_eq!(want, msg),
342                result => panic!("Unexpected result: {result:?}"),
343            }
344        }
345    }
346
347    #[test]
348    fn from_slice_works() {
349        let original: &[u8] = &[0u8, 187, 61, 11, 250, 0];
350        let binary: Binary = original.into();
351        assert_eq!(binary.as_slice(), [0u8, 187, 61, 11, 250, 0]);
352    }
353
354    #[test]
355    fn from_fixed_length_array_works() {
356        let original = &[];
357        let binary: Binary = original.into();
358        assert_eq!(binary.len(), 0);
359
360        let original = &[0u8];
361        let binary: Binary = original.into();
362        assert_eq!(binary.as_slice(), [0u8]);
363
364        let original = &[0u8, 187, 61, 11, 250, 0];
365        let binary: Binary = original.into();
366        assert_eq!(binary.as_slice(), [0u8, 187, 61, 11, 250, 0]);
367
368        let original = &[
369            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
370            1, 1, 1,
371        ];
372        let binary: Binary = original.into();
373        assert_eq!(
374            binary.as_slice(),
375            [
376                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
377                1, 1, 1, 1,
378            ]
379        );
380    }
381
382    #[test]
383    fn from_owned_fixed_length_array_works() {
384        let original = [];
385        let binary: Binary = original.into();
386        assert_eq!(binary.len(), 0);
387
388        let original = [0u8];
389        let binary: Binary = original.into();
390        assert_eq!(binary.as_slice(), [0u8]);
391
392        let original = [0u8, 187, 61, 11, 250, 0];
393        let binary: Binary = original.into();
394        assert_eq!(binary.as_slice(), [0u8, 187, 61, 11, 250, 0]);
395
396        let original = [
397            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
398            1, 1, 1,
399        ];
400        let binary: Binary = original.into();
401        assert_eq!(
402            binary.as_slice(),
403            [
404                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
405                1, 1, 1, 1,
406            ]
407        );
408    }
409
410    #[test]
411    fn from_literal_works() {
412        let a: Binary = b"".into();
413        assert_eq!(a.len(), 0);
414
415        let a: Binary = b".".into();
416        assert_eq!(a.len(), 1);
417
418        let a: Binary = b"...".into();
419        assert_eq!(a.len(), 3);
420
421        let a: Binary = b"...............................".into();
422        assert_eq!(a.len(), 31);
423
424        let a: Binary = b"................................".into();
425        assert_eq!(a.len(), 32);
426
427        let a: Binary = b".................................".into();
428        assert_eq!(a.len(), 33);
429    }
430
431    #[test]
432    fn from_vec_works() {
433        let original = vec![0u8, 187, 61, 11, 250, 0];
434        let original_ptr = original.as_ptr();
435        let binary: Binary = original.into();
436        assert_eq!(binary.as_slice(), [0u8, 187, 61, 11, 250, 0]);
437        assert_eq!(binary.0.as_ptr(), original_ptr, "vector must not be copied");
438    }
439
440    #[test]
441    fn into_vec_works() {
442        // Into<Vec<u8>> for Binary
443        let original = Binary(vec![0u8, 187, 61, 11, 250, 0]);
444        let original_ptr = original.0.as_ptr();
445        let vec: Vec<u8> = original.into();
446        assert_eq!(vec.as_slice(), [0u8, 187, 61, 11, 250, 0]);
447        assert_eq!(vec.as_ptr(), original_ptr, "vector must not be copied");
448
449        // From<Binary> for Vec<u8>
450        let original = Binary(vec![7u8, 35, 49, 101, 0, 255]);
451        let original_ptr = original.0.as_ptr();
452        let vec = Vec::<u8>::from(original);
453        assert_eq!(vec.as_slice(), [7u8, 35, 49, 101, 0, 255]);
454        assert_eq!(vec.as_ptr(), original_ptr, "vector must not be copied");
455    }
456
457    #[test]
458    fn serialization_works() {
459        let binary = Binary(vec![0u8, 187, 61, 11, 250, 0]);
460
461        let json = to_json_vec(&binary).unwrap();
462        let deserialized: Binary = from_json(json).unwrap();
463
464        assert_eq!(binary, deserialized);
465    }
466
467    #[test]
468    fn deserialize_from_valid_string() {
469        let b64_str = "ALs9C/oA";
470        // this is the binary behind above string
471        let expected = vec![0u8, 187, 61, 11, 250, 0];
472
473        let serialized = to_json_vec(&b64_str).unwrap();
474        let deserialized: Binary = from_json(serialized).unwrap();
475        assert_eq!(expected, deserialized.as_slice());
476    }
477
478    #[test]
479    fn deserialize_from_invalid_string() {
480        let invalid_str = "**BAD!**";
481        let serialized = to_json_vec(&invalid_str).unwrap();
482        let res = from_json::<Binary>(&serialized);
483        assert!(res.is_err());
484    }
485
486    #[test]
487    fn binary_implements_debug() {
488        // Some data
489        let binary = Binary(vec![0x07, 0x35, 0xAA, 0xcb, 0x00, 0xff]);
490        assert_eq!(format!("{binary:?}"), "Binary(0735aacb00ff)",);
491
492        // Empty
493        let binary = Binary(vec![]);
494        assert_eq!(format!("{binary:?}"), "Binary()",);
495    }
496
497    #[test]
498    fn binary_implements_deref() {
499        // Dereference to [u8]
500        let binary = Binary(vec![7u8, 35, 49, 101, 0, 255]);
501        assert_eq!(*binary, [7u8, 35, 49, 101, 0, 255]);
502
503        // This checks deref coercions from &Binary to &[u8] works
504        let binary = Binary(vec![7u8, 35, 49, 101, 0, 255]);
505        assert_eq!(binary.len(), 6);
506        let binary_slice: &[u8] = &binary;
507        assert_eq!(binary_slice, &[7u8, 35, 49, 101, 0, 255]);
508    }
509
510    #[test]
511    fn binary_implements_as_ref() {
512        let want = &[7u8, 35, 49, 101, 0, 255];
513        let data = Binary(want.to_vec());
514        assert_eq!(want, AsRef::<[u8]>::as_ref(&data));
515        assert_eq!(want, AsRef::<[u8]>::as_ref(&&data));
516    }
517
518    /// Tests that `Binary` implements `EQ` and `Hash` correctly and thus can be
519    /// used with hash maps and sets.
520    #[test]
521    fn binary_implements_hash_eq() {
522        let a = Binary::from([0, 187, 61, 11, 250, 0]);
523        let b = Binary::from([16, 21, 33, 0, 255, 9]);
524        assert_hash_works!(a, b);
525    }
526
527    #[test]
528    fn binary_implements_partial_eq_with_vector() {
529        let a = Binary(vec![5u8; 3]);
530        let b = vec![5u8; 3];
531        let c = vec![9u8; 3];
532        assert_eq!(a, b);
533        assert_eq!(b, a);
534        assert_ne!(a, c);
535        assert_ne!(c, a);
536    }
537
538    #[test]
539    fn binary_implements_partial_eq_with_slice_and_array() {
540        let a = Binary(vec![0xAA, 0xBB]);
541
542        // Slice: &[u8]
543        assert_eq!(a, b"\xAA\xBB" as &[u8]);
544        assert_eq!(b"\xAA\xBB" as &[u8], a);
545        assert_ne!(a, b"\x11\x22" as &[u8]);
546        assert_ne!(b"\x11\x22" as &[u8], a);
547
548        // Array reference: &[u8; 2]
549        assert_eq!(a, b"\xAA\xBB");
550        assert_eq!(b"\xAA\xBB", a);
551        assert_ne!(a, b"\x11\x22");
552        assert_ne!(b"\x11\x22", a);
553
554        // Array: [u8; 2]
555        assert_eq!(a, [0xAA, 0xBB]);
556        assert_eq!([0xAA, 0xBB], a);
557        assert_ne!(a, [0x11, 0x22]);
558        assert_ne!([0x11, 0x22], a);
559    }
560}