Skip to main content

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