casper_types/
transfer.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};
10
11#[cfg(feature = "datasize")]
12use datasize::DataSize;
13use rand::{
14    distributions::{Distribution, Standard},
15    Rng,
16};
17#[cfg(feature = "json-schema")]
18use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
19use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize, Serializer};
20
21use crate::{
22    account::AccountHash,
23    bytesrepr::{self, FromBytes, ToBytes},
24    checksummed_hex, CLType, CLTyped, URef, U512,
25};
26
27/// The length of a deploy hash.
28pub const DEPLOY_HASH_LENGTH: usize = 32;
29/// The length of a transfer address.
30pub const TRANSFER_ADDR_LENGTH: usize = 32;
31pub(super) const TRANSFER_ADDR_FORMATTED_STRING_PREFIX: &str = "transfer-";
32
33/// A newtype wrapping a <code>[u8; [DEPLOY_HASH_LENGTH]]</code> which is the raw bytes of the
34/// deploy hash.
35#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
36#[cfg_attr(feature = "datasize", derive(DataSize))]
37pub struct DeployHash([u8; DEPLOY_HASH_LENGTH]);
38
39impl DeployHash {
40    /// Constructs a new `DeployHash` instance from the raw bytes of a deploy hash.
41    pub const fn new(value: [u8; DEPLOY_HASH_LENGTH]) -> DeployHash {
42        DeployHash(value)
43    }
44
45    /// Returns the raw bytes of the deploy hash as an array.
46    pub fn value(&self) -> [u8; DEPLOY_HASH_LENGTH] {
47        self.0
48    }
49
50    /// Returns the raw bytes of the deploy hash as a `slice`.
51    pub fn as_bytes(&self) -> &[u8] {
52        &self.0
53    }
54}
55
56#[cfg(feature = "json-schema")]
57impl JsonSchema for DeployHash {
58    fn schema_name() -> String {
59        String::from("DeployHash")
60    }
61
62    fn json_schema(gen: &mut SchemaGenerator) -> Schema {
63        let schema = gen.subschema_for::<String>();
64        let mut schema_object = schema.into_object();
65        schema_object.metadata().description = Some("Hex-encoded deploy hash.".to_string());
66        schema_object.into()
67    }
68}
69
70impl ToBytes for DeployHash {
71    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
72        self.0.to_bytes()
73    }
74
75    fn serialized_length(&self) -> usize {
76        self.0.serialized_length()
77    }
78
79    fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
80        self.0.write_bytes(writer)?;
81        Ok(())
82    }
83}
84
85impl FromBytes for DeployHash {
86    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
87        <[u8; DEPLOY_HASH_LENGTH]>::from_bytes(bytes)
88            .map(|(inner, remainder)| (DeployHash(inner), remainder))
89    }
90}
91
92impl Serialize for DeployHash {
93    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
94        if serializer.is_human_readable() {
95            base16::encode_lower(&self.0).serialize(serializer)
96        } else {
97            self.0.serialize(serializer)
98        }
99    }
100}
101
102impl<'de> Deserialize<'de> for DeployHash {
103    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
104        let bytes = if deserializer.is_human_readable() {
105            let hex_string = String::deserialize(deserializer)?;
106            let vec_bytes =
107                checksummed_hex::decode(hex_string.as_bytes()).map_err(SerdeError::custom)?;
108            <[u8; DEPLOY_HASH_LENGTH]>::try_from(vec_bytes.as_ref()).map_err(SerdeError::custom)?
109        } else {
110            <[u8; DEPLOY_HASH_LENGTH]>::deserialize(deserializer)?
111        };
112        Ok(DeployHash(bytes))
113    }
114}
115
116impl Debug for DeployHash {
117    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
118        write!(formatter, "DeployHash({})", base16::encode_lower(&self.0))
119    }
120}
121
122impl Distribution<DeployHash> for Standard {
123    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> DeployHash {
124        DeployHash::new(rng.gen())
125    }
126}
127
128/// Represents a transfer from one purse to another
129#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize, Default)]
130#[cfg_attr(feature = "datasize", derive(DataSize))]
131#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
132#[serde(deny_unknown_fields)]
133pub struct Transfer {
134    /// Deploy that created the transfer
135    pub deploy_hash: DeployHash,
136    /// Account from which transfer was executed
137    pub from: AccountHash,
138    /// Account to which funds are transferred
139    pub to: Option<AccountHash>,
140    /// Source purse
141    pub source: URef,
142    /// Target purse
143    pub target: URef,
144    /// Transfer amount
145    pub amount: U512,
146    /// Gas
147    pub gas: U512,
148    /// User-defined id
149    pub id: Option<u64>,
150}
151
152impl Transfer {
153    /// Creates a [`Transfer`].
154    #[allow(clippy::too_many_arguments)]
155    pub fn new(
156        deploy_hash: DeployHash,
157        from: AccountHash,
158        to: Option<AccountHash>,
159        source: URef,
160        target: URef,
161        amount: U512,
162        gas: U512,
163        id: Option<u64>,
164    ) -> Self {
165        Transfer {
166            deploy_hash,
167            from,
168            to,
169            source,
170            target,
171            amount,
172            gas,
173            id,
174        }
175    }
176}
177
178impl FromBytes for Transfer {
179    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
180        let (deploy_hash, rem) = FromBytes::from_bytes(bytes)?;
181        let (from, rem) = AccountHash::from_bytes(rem)?;
182        let (to, rem) = <Option<AccountHash>>::from_bytes(rem)?;
183        let (source, rem) = URef::from_bytes(rem)?;
184        let (target, rem) = URef::from_bytes(rem)?;
185        let (amount, rem) = U512::from_bytes(rem)?;
186        let (gas, rem) = U512::from_bytes(rem)?;
187        let (id, rem) = <Option<u64>>::from_bytes(rem)?;
188        Ok((
189            Transfer {
190                deploy_hash,
191                from,
192                to,
193                source,
194                target,
195                amount,
196                gas,
197                id,
198            },
199            rem,
200        ))
201    }
202}
203
204impl ToBytes for Transfer {
205    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
206        let mut result = bytesrepr::allocate_buffer(self)?;
207        self.deploy_hash.write_bytes(&mut result)?;
208        self.from.write_bytes(&mut result)?;
209        self.to.write_bytes(&mut result)?;
210        self.source.write_bytes(&mut result)?;
211        self.target.write_bytes(&mut result)?;
212        self.amount.write_bytes(&mut result)?;
213        self.gas.write_bytes(&mut result)?;
214        self.id.write_bytes(&mut result)?;
215        Ok(result)
216    }
217
218    fn serialized_length(&self) -> usize {
219        self.deploy_hash.serialized_length()
220            + self.from.serialized_length()
221            + self.to.serialized_length()
222            + self.source.serialized_length()
223            + self.target.serialized_length()
224            + self.amount.serialized_length()
225            + self.gas.serialized_length()
226            + self.id.serialized_length()
227    }
228
229    fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
230        self.deploy_hash.write_bytes(writer)?;
231        self.from.write_bytes(writer)?;
232        self.to.write_bytes(writer)?;
233        self.source.write_bytes(writer)?;
234        self.target.write_bytes(writer)?;
235        self.amount.write_bytes(writer)?;
236        self.gas.write_bytes(writer)?;
237        self.id.write_bytes(writer)?;
238        Ok(())
239    }
240}
241
242/// Error returned when decoding a `TransferAddr` from a formatted string.
243#[derive(Debug)]
244#[non_exhaustive]
245pub enum FromStrError {
246    /// The prefix is invalid.
247    InvalidPrefix,
248    /// The address is not valid hex.
249    Hex(base16::DecodeError),
250    /// The slice is the wrong length.
251    Length(TryFromSliceError),
252}
253
254impl From<base16::DecodeError> for FromStrError {
255    fn from(error: base16::DecodeError) -> Self {
256        FromStrError::Hex(error)
257    }
258}
259
260impl From<TryFromSliceError> for FromStrError {
261    fn from(error: TryFromSliceError) -> Self {
262        FromStrError::Length(error)
263    }
264}
265
266impl Display for FromStrError {
267    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
268        match self {
269            FromStrError::InvalidPrefix => write!(f, "prefix is not 'transfer-'"),
270            FromStrError::Hex(error) => {
271                write!(f, "failed to decode address portion from hex: {}", error)
272            }
273            FromStrError::Length(error) => write!(f, "address portion is wrong length: {}", error),
274        }
275    }
276}
277
278/// A newtype wrapping a <code>[u8; [TRANSFER_ADDR_LENGTH]]</code> which is the raw bytes of the
279/// transfer address.
280#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
281#[cfg_attr(feature = "datasize", derive(DataSize))]
282pub struct TransferAddr([u8; TRANSFER_ADDR_LENGTH]);
283
284impl TransferAddr {
285    /// Constructs a new `TransferAddr` instance from the raw bytes.
286    pub const fn new(value: [u8; TRANSFER_ADDR_LENGTH]) -> TransferAddr {
287        TransferAddr(value)
288    }
289
290    /// Returns the raw bytes of the transfer address as an array.
291    pub fn value(&self) -> [u8; TRANSFER_ADDR_LENGTH] {
292        self.0
293    }
294
295    /// Returns the raw bytes of the transfer address as a `slice`.
296    pub fn as_bytes(&self) -> &[u8] {
297        &self.0
298    }
299
300    /// Formats the `TransferAddr` as a prefixed, hex-encoded string.
301    pub fn to_formatted_string(self) -> String {
302        format!(
303            "{}{}",
304            TRANSFER_ADDR_FORMATTED_STRING_PREFIX,
305            base16::encode_lower(&self.0),
306        )
307    }
308
309    /// Parses a string formatted as per `Self::to_formatted_string()` into a `TransferAddr`.
310    pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
311        let remainder = input
312            .strip_prefix(TRANSFER_ADDR_FORMATTED_STRING_PREFIX)
313            .ok_or(FromStrError::InvalidPrefix)?;
314        let bytes =
315            <[u8; TRANSFER_ADDR_LENGTH]>::try_from(checksummed_hex::decode(remainder)?.as_ref())?;
316        Ok(TransferAddr(bytes))
317    }
318}
319
320#[cfg(feature = "json-schema")]
321impl JsonSchema for TransferAddr {
322    fn schema_name() -> String {
323        String::from("TransferAddr")
324    }
325
326    fn json_schema(gen: &mut SchemaGenerator) -> Schema {
327        let schema = gen.subschema_for::<String>();
328        let mut schema_object = schema.into_object();
329        schema_object.metadata().description = Some("Hex-encoded transfer address.".to_string());
330        schema_object.into()
331    }
332}
333
334impl Serialize for TransferAddr {
335    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
336        if serializer.is_human_readable() {
337            self.to_formatted_string().serialize(serializer)
338        } else {
339            self.0.serialize(serializer)
340        }
341    }
342}
343
344impl<'de> Deserialize<'de> for TransferAddr {
345    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
346        if deserializer.is_human_readable() {
347            let formatted_string = String::deserialize(deserializer)?;
348            TransferAddr::from_formatted_str(&formatted_string).map_err(SerdeError::custom)
349        } else {
350            let bytes = <[u8; TRANSFER_ADDR_LENGTH]>::deserialize(deserializer)?;
351            Ok(TransferAddr(bytes))
352        }
353    }
354}
355
356impl Display for TransferAddr {
357    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
358        write!(f, "{}", base16::encode_lower(&self.0))
359    }
360}
361
362impl Debug for TransferAddr {
363    fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
364        write!(f, "TransferAddr({})", base16::encode_lower(&self.0))
365    }
366}
367
368impl CLTyped for TransferAddr {
369    fn cl_type() -> CLType {
370        CLType::ByteArray(TRANSFER_ADDR_LENGTH as u32)
371    }
372}
373
374impl ToBytes for TransferAddr {
375    #[inline(always)]
376    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
377        self.0.to_bytes()
378    }
379
380    #[inline(always)]
381    fn serialized_length(&self) -> usize {
382        self.0.serialized_length()
383    }
384
385    #[inline(always)]
386    fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
387        self.0.write_bytes(writer)?;
388        Ok(())
389    }
390}
391
392impl FromBytes for TransferAddr {
393    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
394        let (bytes, remainder) = FromBytes::from_bytes(bytes)?;
395        Ok((TransferAddr::new(bytes), remainder))
396    }
397}
398
399impl AsRef<[u8]> for TransferAddr {
400    fn as_ref(&self) -> &[u8] {
401        self.0.as_ref()
402    }
403}
404
405impl Distribution<TransferAddr> for Standard {
406    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> TransferAddr {
407        TransferAddr::new(rng.gen())
408    }
409}
410
411/// Generators for [`Transfer`]
412#[cfg(any(feature = "testing", feature = "gens", test))]
413pub mod gens {
414    use proptest::prelude::{prop::option, Arbitrary, Strategy};
415
416    use crate::{
417        deploy_info::gens::{account_hash_arb, deploy_hash_arb},
418        gens::{u512_arb, uref_arb},
419        Transfer,
420    };
421
422    /// Creates an arbitrary [`Transfer`]
423    pub fn transfer_arb() -> impl Strategy<Value = Transfer> {
424        (
425            deploy_hash_arb(),
426            account_hash_arb(),
427            option::of(account_hash_arb()),
428            uref_arb(),
429            uref_arb(),
430            u512_arb(),
431            u512_arb(),
432            option::of(<u64>::arbitrary()),
433        )
434            .prop_map(|(deploy_hash, from, to, source, target, amount, gas, id)| {
435                Transfer {
436                    deploy_hash,
437                    from,
438                    to,
439                    source,
440                    target,
441                    amount,
442                    gas,
443                    id,
444                }
445            })
446    }
447}
448
449#[cfg(test)]
450mod tests {
451    use proptest::prelude::*;
452
453    use crate::bytesrepr;
454
455    use super::*;
456
457    proptest! {
458        #[test]
459        fn test_serialization_roundtrip(transfer in gens::transfer_arb()) {
460            bytesrepr::test_serialization_roundtrip(&transfer)
461        }
462    }
463
464    #[test]
465    fn transfer_addr_from_str() {
466        let transfer_address = TransferAddr([4; 32]);
467        let encoded = transfer_address.to_formatted_string();
468        let decoded = TransferAddr::from_formatted_str(&encoded).unwrap();
469        assert_eq!(transfer_address, decoded);
470
471        let invalid_prefix =
472            "transfe-0000000000000000000000000000000000000000000000000000000000000000";
473        assert!(TransferAddr::from_formatted_str(invalid_prefix).is_err());
474
475        let invalid_prefix =
476            "transfer0000000000000000000000000000000000000000000000000000000000000000";
477        assert!(TransferAddr::from_formatted_str(invalid_prefix).is_err());
478
479        let short_addr = "transfer-00000000000000000000000000000000000000000000000000000000000000";
480        assert!(TransferAddr::from_formatted_str(short_addr).is_err());
481
482        let long_addr =
483            "transfer-000000000000000000000000000000000000000000000000000000000000000000";
484        assert!(TransferAddr::from_formatted_str(long_addr).is_err());
485
486        let invalid_hex =
487            "transfer-000000000000000000000000000000000000000000000000000000000000000g";
488        assert!(TransferAddr::from_formatted_str(invalid_hex).is_err());
489    }
490
491    #[test]
492    fn transfer_addr_serde_roundtrip() {
493        let transfer_address = TransferAddr([255; 32]);
494        let serialized = bincode::serialize(&transfer_address).unwrap();
495        let decoded = bincode::deserialize(&serialized).unwrap();
496        assert_eq!(transfer_address, decoded);
497    }
498
499    #[test]
500    fn transfer_addr_json_roundtrip() {
501        let transfer_address = TransferAddr([255; 32]);
502        let json_string = serde_json::to_string_pretty(&transfer_address).unwrap();
503        let decoded = serde_json::from_str(&json_string).unwrap();
504        assert_eq!(transfer_address, decoded);
505    }
506}