casper_types/
uref.rs

1// TODO - remove once schemars stops causing warning.
2#![allow(clippy::field_reassign_with_default)]
3
4use alloc::{format, string::String, vec::Vec};
5use core::{
6    array::TryFromSliceError,
7    convert::TryFrom,
8    fmt::{self, Debug, Display, Formatter},
9    num::ParseIntError,
10};
11
12#[cfg(feature = "datasize")]
13use datasize::DataSize;
14use rand::{
15    distributions::{Distribution, Standard},
16    Rng,
17};
18#[cfg(feature = "json-schema")]
19use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
20use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize, Serializer};
21
22use crate::{
23    bytesrepr,
24    bytesrepr::{Error, FromBytes},
25    checksummed_hex, AccessRights, ApiError, Key, ACCESS_RIGHTS_SERIALIZED_LENGTH,
26};
27
28/// The number of bytes in a [`URef`] address.
29pub const UREF_ADDR_LENGTH: usize = 32;
30
31/// The number of bytes in a serialized [`URef`] where the [`AccessRights`] are not `None`.
32pub const UREF_SERIALIZED_LENGTH: usize = UREF_ADDR_LENGTH + ACCESS_RIGHTS_SERIALIZED_LENGTH;
33
34pub(super) const UREF_FORMATTED_STRING_PREFIX: &str = "uref-";
35
36/// The address of a `URef` (unforgeable reference) on the network.
37pub type URefAddr = [u8; UREF_ADDR_LENGTH];
38
39/// Error while parsing a URef from a formatted string.
40#[derive(Debug)]
41#[non_exhaustive]
42pub enum FromStrError {
43    /// Prefix is not "uref-".
44    InvalidPrefix,
45    /// No access rights as suffix.
46    MissingSuffix,
47    /// Access rights are invalid.
48    InvalidAccessRights,
49    /// Failed to decode address portion of URef.
50    Hex(base16::DecodeError),
51    /// Failed to parse an int.
52    Int(ParseIntError),
53    /// The address portion is the wrong length.
54    Address(TryFromSliceError),
55}
56
57impl From<base16::DecodeError> for FromStrError {
58    fn from(error: base16::DecodeError) -> Self {
59        FromStrError::Hex(error)
60    }
61}
62
63impl From<ParseIntError> for FromStrError {
64    fn from(error: ParseIntError) -> Self {
65        FromStrError::Int(error)
66    }
67}
68
69impl From<TryFromSliceError> for FromStrError {
70    fn from(error: TryFromSliceError) -> Self {
71        FromStrError::Address(error)
72    }
73}
74
75impl Display for FromStrError {
76    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
77        match self {
78            FromStrError::InvalidPrefix => write!(f, "prefix is not 'uref-'"),
79            FromStrError::MissingSuffix => write!(f, "no access rights as suffix"),
80            FromStrError::InvalidAccessRights => write!(f, "invalid access rights"),
81            FromStrError::Hex(error) => {
82                write!(f, "failed to decode address portion from hex: {}", error)
83            }
84            FromStrError::Int(error) => write!(f, "failed to parse an int: {}", error),
85            FromStrError::Address(error) => {
86                write!(f, "address portion is the wrong length: {}", error)
87            }
88        }
89    }
90}
91
92/// Represents an unforgeable reference, containing an address in the network's global storage and
93/// the [`AccessRights`] of the reference.
94///
95/// A `URef` can be used to index entities such as [`CLValue`](crate::CLValue)s, or smart contracts.
96#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
97#[cfg_attr(feature = "datasize", derive(DataSize))]
98pub struct URef(URefAddr, AccessRights);
99
100impl URef {
101    /// Constructs a [`URef`] from an address and access rights.
102    pub const fn new(address: URefAddr, access_rights: AccessRights) -> Self {
103        URef(address, access_rights)
104    }
105
106    /// Returns the address of this [`URef`].
107    pub fn addr(&self) -> URefAddr {
108        self.0
109    }
110
111    /// Returns the access rights of this [`URef`].
112    pub fn access_rights(&self) -> AccessRights {
113        self.1
114    }
115
116    /// Returns a new [`URef`] with the same address and updated access rights.
117    #[must_use]
118    pub fn with_access_rights(self, access_rights: AccessRights) -> Self {
119        URef(self.0, access_rights)
120    }
121
122    /// Removes the access rights from this [`URef`].
123    #[must_use]
124    pub fn remove_access_rights(self) -> Self {
125        URef(self.0, AccessRights::NONE)
126    }
127
128    /// Returns `true` if the access rights are `Some` and
129    /// [`is_readable`](AccessRights::is_readable) is `true` for them.
130    #[must_use]
131    pub fn is_readable(self) -> bool {
132        self.1.is_readable()
133    }
134
135    /// Returns a new [`URef`] with the same address and [`AccessRights::READ`] permission.
136    #[must_use]
137    pub fn into_read(self) -> URef {
138        URef(self.0, AccessRights::READ)
139    }
140
141    /// Returns a new [`URef`] with the same address and [`AccessRights::WRITE`] permission.
142    #[must_use]
143    pub fn into_write(self) -> URef {
144        URef(self.0, AccessRights::WRITE)
145    }
146
147    /// Returns a new [`URef`] with the same address and [`AccessRights::ADD`] permission.
148    #[must_use]
149    pub fn into_add(self) -> URef {
150        URef(self.0, AccessRights::ADD)
151    }
152
153    /// Returns a new [`URef`] with the same address and [`AccessRights::READ_ADD_WRITE`]
154    /// permission.
155    #[must_use]
156    pub fn into_read_add_write(self) -> URef {
157        URef(self.0, AccessRights::READ_ADD_WRITE)
158    }
159
160    /// Returns a new [`URef`] with the same address and [`AccessRights::READ_WRITE`]
161    /// permission.
162    #[must_use]
163    pub fn into_read_write(self) -> URef {
164        URef(self.0, AccessRights::READ_WRITE)
165    }
166
167    /// Returns `true` if the access rights are `Some` and
168    /// [`is_writeable`](AccessRights::is_writeable) is `true` for them.
169    pub fn is_writeable(self) -> bool {
170        self.1.is_writeable()
171    }
172
173    /// Returns `true` if the access rights are `Some` and [`is_addable`](AccessRights::is_addable)
174    /// is `true` for them.
175    pub fn is_addable(self) -> bool {
176        self.1.is_addable()
177    }
178
179    /// Formats the address and access rights of the [`URef`] in a unique way that could be used as
180    /// a name when storing the given `URef` in a global state.
181    pub fn to_formatted_string(self) -> String {
182        // Extract bits as numerical value, with no flags marked as 0.
183        let access_rights_bits = self.access_rights().bits();
184        // Access rights is represented as octal, which means that max value of u8 can
185        // be represented as maximum of 3 octal digits.
186        format!(
187            "{}{}-{:03o}",
188            UREF_FORMATTED_STRING_PREFIX,
189            base16::encode_lower(&self.addr()),
190            access_rights_bits
191        )
192    }
193
194    /// Parses a string formatted as per `Self::to_formatted_string()` into a `URef`.
195    pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
196        let remainder = input
197            .strip_prefix(UREF_FORMATTED_STRING_PREFIX)
198            .ok_or(FromStrError::InvalidPrefix)?;
199        let parts = remainder.splitn(2, '-').collect::<Vec<_>>();
200        if parts.len() != 2 {
201            return Err(FromStrError::MissingSuffix);
202        }
203        let addr = URefAddr::try_from(checksummed_hex::decode(parts[0])?.as_ref())?;
204        let access_rights_value = u8::from_str_radix(parts[1], 8)?;
205        let access_rights = AccessRights::from_bits(access_rights_value)
206            .ok_or(FromStrError::InvalidAccessRights)?;
207        Ok(URef(addr, access_rights))
208    }
209
210    /// Removes specific access rights from this URef if present.
211    pub fn disable_access_rights(&mut self, access_rights: AccessRights) {
212        self.1.remove(access_rights)
213    }
214}
215
216#[cfg(feature = "json-schema")]
217impl JsonSchema for URef {
218    fn schema_name() -> String {
219        String::from("URef")
220    }
221
222    fn json_schema(gen: &mut SchemaGenerator) -> Schema {
223        let schema = gen.subschema_for::<String>();
224        let mut schema_object = schema.into_object();
225        schema_object.metadata().description = Some(String::from("Hex-encoded, formatted URef."));
226        schema_object.into()
227    }
228}
229
230impl Display for URef {
231    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
232        let addr = self.addr();
233        let access_rights = self.access_rights();
234        write!(
235            f,
236            "URef({}, {})",
237            base16::encode_lower(&addr),
238            access_rights
239        )
240    }
241}
242
243impl Debug for URef {
244    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
245        write!(f, "{}", self)
246    }
247}
248
249impl bytesrepr::ToBytes for URef {
250    fn to_bytes(&self) -> Result<Vec<u8>, Error> {
251        let mut result = bytesrepr::unchecked_allocate_buffer(self);
252        result.append(&mut self.0.to_bytes()?);
253        result.append(&mut self.1.to_bytes()?);
254        Ok(result)
255    }
256
257    fn serialized_length(&self) -> usize {
258        UREF_SERIALIZED_LENGTH
259    }
260
261    fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), self::Error> {
262        writer.extend_from_slice(&self.0);
263        self.1.write_bytes(writer)?;
264        Ok(())
265    }
266}
267
268impl FromBytes for URef {
269    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
270        let (id, rem) = FromBytes::from_bytes(bytes)?;
271        let (access_rights, rem) = FromBytes::from_bytes(rem)?;
272        Ok((URef(id, access_rights), rem))
273    }
274}
275
276impl Serialize for URef {
277    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
278        if serializer.is_human_readable() {
279            self.to_formatted_string().serialize(serializer)
280        } else {
281            (self.0, self.1).serialize(serializer)
282        }
283    }
284}
285
286impl<'de> Deserialize<'de> for URef {
287    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
288        if deserializer.is_human_readable() {
289            let formatted_string = String::deserialize(deserializer)?;
290            URef::from_formatted_str(&formatted_string).map_err(D::Error::custom)
291        } else {
292            let (address, access_rights) = <(URefAddr, AccessRights)>::deserialize(deserializer)?;
293            Ok(URef(address, access_rights))
294        }
295    }
296}
297
298impl TryFrom<Key> for URef {
299    type Error = ApiError;
300
301    fn try_from(key: Key) -> Result<Self, Self::Error> {
302        if let Key::URef(uref) = key {
303            Ok(uref)
304        } else {
305            Err(ApiError::UnexpectedKeyVariant)
306        }
307    }
308}
309
310impl Distribution<URef> for Standard {
311    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> URef {
312        URef::new(rng.gen(), rng.gen())
313    }
314}
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319
320    #[test]
321    fn uref_as_string() {
322        // Since we are putting URefs to named_keys map keyed by the label that
323        // `as_string()` returns, any changes to the string representation of
324        // that type cannot break the format.
325        let addr_array = [0u8; 32];
326        let uref_a = URef::new(addr_array, AccessRights::READ);
327        assert_eq!(
328            uref_a.to_formatted_string(),
329            "uref-0000000000000000000000000000000000000000000000000000000000000000-001"
330        );
331        let uref_b = URef::new(addr_array, AccessRights::WRITE);
332        assert_eq!(
333            uref_b.to_formatted_string(),
334            "uref-0000000000000000000000000000000000000000000000000000000000000000-002"
335        );
336
337        let uref_c = uref_b.remove_access_rights();
338        assert_eq!(
339            uref_c.to_formatted_string(),
340            "uref-0000000000000000000000000000000000000000000000000000000000000000-000"
341        );
342    }
343
344    fn round_trip(uref: URef) {
345        let string = uref.to_formatted_string();
346        let parsed_uref = URef::from_formatted_str(&string).unwrap();
347        assert_eq!(uref, parsed_uref);
348    }
349
350    #[test]
351    fn uref_from_str() {
352        round_trip(URef::new([0; 32], AccessRights::NONE));
353        round_trip(URef::new([255; 32], AccessRights::READ_ADD_WRITE));
354
355        let invalid_prefix =
356            "ref-0000000000000000000000000000000000000000000000000000000000000000-000";
357        assert!(URef::from_formatted_str(invalid_prefix).is_err());
358
359        let invalid_prefix =
360            "uref0000000000000000000000000000000000000000000000000000000000000000-000";
361        assert!(URef::from_formatted_str(invalid_prefix).is_err());
362
363        let short_addr = "uref-00000000000000000000000000000000000000000000000000000000000000-000";
364        assert!(URef::from_formatted_str(short_addr).is_err());
365
366        let long_addr =
367            "uref-000000000000000000000000000000000000000000000000000000000000000000-000";
368        assert!(URef::from_formatted_str(long_addr).is_err());
369
370        let invalid_hex =
371            "uref-000000000000000000000000000000000000000000000000000000000000000g-000";
372        assert!(URef::from_formatted_str(invalid_hex).is_err());
373
374        let invalid_suffix_separator =
375            "uref-0000000000000000000000000000000000000000000000000000000000000000:000";
376        assert!(URef::from_formatted_str(invalid_suffix_separator).is_err());
377
378        let invalid_suffix =
379            "uref-0000000000000000000000000000000000000000000000000000000000000000-abc";
380        assert!(URef::from_formatted_str(invalid_suffix).is_err());
381
382        let invalid_access_rights =
383            "uref-0000000000000000000000000000000000000000000000000000000000000000-200";
384        assert!(URef::from_formatted_str(invalid_access_rights).is_err());
385    }
386
387    #[test]
388    fn serde_roundtrip() {
389        let uref = URef::new([255; 32], AccessRights::READ_ADD_WRITE);
390        let serialized = bincode::serialize(&uref).unwrap();
391        let decoded = bincode::deserialize(&serialized).unwrap();
392        assert_eq!(uref, decoded);
393    }
394
395    #[test]
396    fn json_roundtrip() {
397        let uref = URef::new([255; 32], AccessRights::READ_ADD_WRITE);
398        let json_string = serde_json::to_string_pretty(&uref).unwrap();
399        let decoded = serde_json::from_str(&json_string).unwrap();
400        assert_eq!(uref, decoded);
401    }
402
403    #[test]
404    fn should_disable_access_rights() {
405        let mut uref = URef::new([255; 32], AccessRights::READ_ADD_WRITE);
406        assert!(uref.is_writeable());
407        uref.disable_access_rights(AccessRights::WRITE);
408        assert_eq!(uref.access_rights(), AccessRights::READ_ADD);
409
410        uref.disable_access_rights(AccessRights::WRITE);
411        assert!(
412            !uref.is_writeable(),
413            "Disabling access bit twice should be a noop"
414        );
415
416        assert_eq!(uref.access_rights(), AccessRights::READ_ADD);
417
418        uref.disable_access_rights(AccessRights::READ_ADD);
419        assert_eq!(uref.access_rights(), AccessRights::NONE);
420
421        uref.disable_access_rights(AccessRights::READ_ADD);
422        assert_eq!(uref.access_rights(), AccessRights::NONE);
423
424        uref.disable_access_rights(AccessRights::NONE);
425        assert_eq!(uref.access_rights(), AccessRights::NONE);
426    }
427}