Skip to main content

xrpl_common_stdlib/types/
currency.rs

1use crate::fields::decoder::{FieldDecoder, FromLedger, decode_exact};
2use crate::types::decode_error::DecodeError;
3
4pub const CURRENCY_SIZE: usize = 20;
5pub const STANDARD_CURRENCY_SIZE: usize = 3; // For standard currencies like USD, EUR, etc.
6
7/// Represents a currency code in the XRPL, which is a 20-byte identifier.
8///
9/// Currency codes in XRPL can be either:
10/// - **Standard currencies**: 3-character ASCII codes (e.g., "USD", "EUR") stored in bytes 12-14
11/// - **Non-standard currencies**: Full 20-byte hex values for custom tokens
12///
13/// ## Derived Traits
14///
15/// - `Copy`: Efficient for this 20-byte struct, enabling implicit copying
16/// - `PartialEq, Eq`: Enable comparisons and use in hash-based collections
17/// - `Debug, Clone`: Standard traits for development and consistency
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[repr(C)]
20pub struct Currency(pub [u8; CURRENCY_SIZE]);
21
22impl Currency {
23    /// Creates a new Currency from a 20-byte array.
24    pub fn new(code: [u8; CURRENCY_SIZE]) -> Self {
25        Currency(code)
26    }
27
28    /// Gets the raw bytes of the Currency.
29    pub fn as_bytes(&self) -> &[u8; CURRENCY_SIZE] {
30        &self.0
31    }
32}
33
34impl From<[u8; CURRENCY_SIZE]> for Currency {
35    fn from(value: [u8; CURRENCY_SIZE]) -> Self {
36        Currency(value)
37    }
38}
39
40// Implement From<[u8; 3]> to create Currency from the standard currency array type
41impl From<[u8; STANDARD_CURRENCY_SIZE]> for Currency {
42    fn from(bytes: [u8; STANDARD_CURRENCY_SIZE]) -> Self {
43        let mut arr = [0u8; CURRENCY_SIZE];
44        arr[12..15].copy_from_slice(&bytes);
45        Self(arr)
46    }
47}
48
49/// `FieldDecoder` for XRPL currency codes: decodes a 20-byte buffer into a `Currency`, failing if
50/// the host wrote a different number of bytes.
51impl FieldDecoder for Currency {
52    type Buffer = [u8; CURRENCY_SIZE];
53
54    #[inline]
55    fn empty_buffer() -> Self::Buffer {
56        [0u8; CURRENCY_SIZE]
57    }
58
59    #[inline]
60    fn decode(buf: Self::Buffer, bytes_written: usize) -> core::result::Result<Self, DecodeError> {
61        decode_exact(buf, bytes_written)
62    }
63}
64
65impl FromLedger for Currency {}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_currency_from_standard_bytes() {
73        // Test From<[u8; 3]> - places 3-byte code at bytes 12-14
74        let standard_bytes = *b"USD";
75        let currency = Currency::from(standard_bytes);
76
77        let mut expected = [0u8; CURRENCY_SIZE];
78        expected[12..15].copy_from_slice(&standard_bytes);
79
80        assert_eq!(currency.as_bytes(), &expected);
81    }
82
83    #[test]
84    fn test_standard_currency_byte_layout() {
85        // Standard currencies are placed at bytes 12-14 with zeros elsewhere
86        let eur = Currency::from(*b"EUR");
87        let bytes = eur.as_bytes();
88
89        // Bytes 0-11 should be zero
90        assert_eq!(&bytes[0..12], &[0u8; 12]);
91        // Bytes 12-14 should be "EUR"
92        assert_eq!(&bytes[12..15], b"EUR");
93        // Bytes 15-19 should be zero
94        assert_eq!(&bytes[15..20], &[0u8; 5]);
95    }
96
97    #[test]
98    fn test_currency_new_and_from_20_bytes() {
99        // Non-standard 20-byte currency code
100        let original: [u8; CURRENCY_SIZE] = [
101            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
102            0x0F, 0x10, 0x11, 0x12, 0x13, 0x14,
103        ];
104
105        // Exercise Currency::new
106        let currency_new = Currency::new(original);
107        assert_eq!(currency_new.as_bytes(), &original);
108
109        // Exercise From<[u8; 20]> for Currency
110        let currency_from = Currency::from(original);
111        assert_eq!(currency_from.as_bytes(), &original);
112
113        // Both constructors should produce the same result
114        assert_eq!(currency_new, currency_from);
115    }
116
117    #[test]
118    fn test_currency_equality() {
119        // Identical byte arrays compare equal
120        let code1 = Currency::new([3u8; CURRENCY_SIZE]);
121        let code2 = Currency::new([3u8; CURRENCY_SIZE]);
122        assert_eq!(code1, code2);
123
124        // Differing byte arrays compare unequal
125        let code3 = Currency::new([4u8; CURRENCY_SIZE]);
126        assert_ne!(code1, code3);
127
128        // Distinct standard currency codes compare unequal
129        assert_ne!(Currency::from(*b"USD"), Currency::from(*b"EUR"));
130    }
131}