casper_types/
uref.rs

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