piecrust_uplink/
types.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) DUSK NETWORK. All rights reserved.
6
7use alloc::string::String;
8use alloc::vec::Vec;
9
10use bytecheck::CheckBytes;
11use rkyv::{
12    ser::serializers::{BufferScratch, BufferSerializer, CompositeSerializer},
13    Archive, Deserialize, Serialize,
14};
15
16use crate::SCRATCH_BUF_BYTES;
17
18/// And event emitted by a contract.
19#[derive(
20    Debug,
21    Clone,
22    PartialEq,
23    Eq,
24    PartialOrd,
25    Ord,
26    Hash,
27    Archive,
28    Serialize,
29    Deserialize,
30)]
31#[archive_attr(derive(CheckBytes))]
32pub struct Event {
33    pub source: ContractId,
34    pub topic: String,
35    pub data: Vec<u8>,
36}
37
38/// Type with `rkyv` serialization capabilities for specific types.
39pub type StandardBufSerializer<'a> = CompositeSerializer<
40    BufferSerializer<&'a mut [u8]>,
41    BufferScratch<&'a mut [u8; SCRATCH_BUF_BYTES]>,
42>;
43
44/// The length of [`ContractId`] in bytes
45pub const CONTRACT_ID_BYTES: usize = 32;
46
47/// ID to identify the wasm contracts after they have been deployed
48#[derive(
49    PartialEq,
50    Eq,
51    Archive,
52    Serialize,
53    CheckBytes,
54    Deserialize,
55    PartialOrd,
56    Ord,
57    Hash,
58    Clone,
59    Copy,
60)]
61#[archive(as = "Self")]
62#[repr(C)]
63pub struct ContractId([u8; CONTRACT_ID_BYTES]);
64
65impl ContractId {
66    /// Creates a new [`ContractId`] from an array of bytes
67    pub const fn from_bytes(bytes: [u8; CONTRACT_ID_BYTES]) -> Self {
68        Self(bytes)
69    }
70
71    /// Returns the array of bytes that make up the [`ContractId`]
72    pub const fn to_bytes(self) -> [u8; CONTRACT_ID_BYTES] {
73        self.0
74    }
75
76    /// Returns a reference to the array of bytes that make up the
77    /// [`ContractId`]
78    pub fn as_bytes(&self) -> &[u8] {
79        &self.0
80    }
81
82    /// Returns a mutable reference to the array of bytes that make up the
83    /// [`ContractId`]
84    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
85        &mut self.0
86    }
87}
88
89impl From<[u8; CONTRACT_ID_BYTES]> for ContractId {
90    fn from(bytes: [u8; CONTRACT_ID_BYTES]) -> Self {
91        Self::from_bytes(bytes)
92    }
93}
94
95impl AsRef<[u8]> for ContractId {
96    fn as_ref(&self) -> &[u8] {
97        self.as_bytes()
98    }
99}
100
101impl AsMut<[u8]> for ContractId {
102    fn as_mut(&mut self) -> &mut [u8] {
103        self.as_bytes_mut()
104    }
105}
106
107impl PartialEq<[u8; CONTRACT_ID_BYTES]> for ContractId {
108    fn eq(&self, other: &[u8; CONTRACT_ID_BYTES]) -> bool {
109        self.0.eq(other)
110    }
111}
112
113/// Debug implementation for [`ContractId`]
114///
115/// This implementation uses the normal display implementation.
116impl core::fmt::Debug for ContractId {
117    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
118        core::fmt::Display::fmt(self, f)
119    }
120}
121
122/// Display implementation for [`ContractId`]
123///
124/// This implementation will display the hexadecimal representation of the bytes
125/// of the [`ContractId`]. If the alternate flag is set, it will also display
126/// the `0x` prefix.
127impl core::fmt::Display for ContractId {
128    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
129        if f.alternate() {
130            write!(f, "0x")?
131        }
132        for byte in self.0 {
133            write!(f, "{:02x}", &byte)?
134        }
135        Ok(())
136    }
137}
138
139impl TryFrom<String> for ContractId {
140    type Error = core::fmt::Error;
141
142    /// Tries to convert a hexadecimal string into a [`ContractId`]
143    ///
144    /// The string can be prefixed with `0x` or not.
145    fn try_from(value: String) -> core::result::Result<Self, Self::Error> {
146        let value = value.trim_start_matches("0x");
147        let decoded = hex::decode(value).map_err(|_| core::fmt::Error)?;
148        let bytes: [u8; CONTRACT_ID_BYTES] =
149            decoded.try_into().map_err(|_| core::fmt::Error)?;
150
151        Ok(ContractId::from_bytes(bytes))
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use alloc::{format, string::ToString};
158
159    use rand::rngs::StdRng;
160    use rand::{Rng, SeedableRng};
161
162    use super::*;
163
164    const CONTRACT_ID_STR: &str =
165        "0000000000000000000000000000000000000000000000000000000000000000";
166    const CONTRACT_ID_STR_PRETTY: &str =
167        "0x0000000000000000000000000000000000000000000000000000000000000000";
168
169    #[test]
170    fn contract_id_display() {
171        let contract_id = ContractId::from_bytes([0u8; CONTRACT_ID_BYTES]);
172        assert_eq!(format!("{}", contract_id), CONTRACT_ID_STR);
173
174        let contract_id = ContractId::from_bytes([0u8; CONTRACT_ID_BYTES]);
175        assert_eq!(format!("{:#?}", contract_id), CONTRACT_ID_STR_PRETTY);
176    }
177
178    #[test]
179    fn contract_id_debug() {
180        let contract_id = ContractId::from_bytes([0u8; CONTRACT_ID_BYTES]);
181        assert_eq!(format!("{}", contract_id), CONTRACT_ID_STR);
182    }
183
184    #[test]
185    fn contract_id_to_from_string() {
186        let mut rng = StdRng::seed_from_u64(1618);
187        let contract_id = ContractId::from_bytes(rng.gen());
188
189        let string = contract_id.to_string();
190
191        assert_eq!(string.starts_with("0x"), false);
192        assert_eq!(string.len(), CONTRACT_ID_BYTES * 2);
193
194        let contract_id_from_string = ContractId::try_from(string).unwrap();
195
196        assert_eq!(contract_id, contract_id_from_string);
197    }
198
199    #[test]
200    fn contract_id_try_from_invalid_string() {
201        let too_short = ContractId::try_from("0x".to_string()).is_err();
202
203        let too_long =
204            ContractId::try_from(format!("{}0", CONTRACT_ID_STR)).is_err();
205
206        assert!(too_short);
207        assert!(too_long);
208    }
209}