Skip to main content

xrpl_common_stdlib/types/
mpt_id.rs

1use crate::types::account_id::AccountID;
2
3pub const MPT_ID_SIZE: usize = 24;
4pub const MPT_SEQUENCE_NUM_SIZE: usize = 4;
5
6/// A 24-byte Multi-Purpose Token (MPT) identifier on the XRP Ledger.
7///
8/// An MPT ID uniquely identifies a multi-purpose token and consists of:
9/// - **Bytes 0-3**: Sequence number (32 bits, big-endian)
10/// - **Bytes 4-23**: Issuer account ID (160 bits)
11///
12/// ## Derived Traits
13///
14/// - `Copy`: Efficient for this 24-byte struct, enabling implicit copying
15/// - `PartialEq, Eq`: Enable comparisons and use in hash-based collections
16/// - `Debug, Clone`: Standard traits for development and consistency
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18#[repr(C)]
19pub struct MptId([u8; MPT_ID_SIZE]);
20
21impl MptId {
22    /// Creates a new MptId from a sequence number and an issuer account ID.
23    pub fn new(sequence_num: u32, issuer: AccountID) -> Self {
24        let mut bytes = [0u8; MPT_ID_SIZE];
25
26        // Set the sequence number (first 4 bytes)
27        bytes[0..4].copy_from_slice(&sequence_num.to_be_bytes());
28
29        // Set the issuer account ID (last 20 bytes)
30        bytes[4..MPT_ID_SIZE].copy_from_slice(&issuer.0);
31
32        MptId(bytes)
33    }
34
35    /// Gets the sequence number part of the MptId.
36    pub fn get_sequence_num(&self) -> u32 {
37        // Transform the first 4 bytes of self.0 into a u32.
38        u32::from_be_bytes([self.0[0], self.0[1], self.0[2], self.0[3]])
39    }
40
41    /// Gets the issuer account ID part of the MptId.
42    pub fn get_issuer(&self) -> AccountID {
43        // Transform the last 20 bytes of self.0 into an AccountID.
44        let mut account_bytes = [0u8; 20]; // AccountID is 20 bytes
45        account_bytes.copy_from_slice(&self.0[4..24]); // Extract bytes 4-23 (20 bytes total)
46        AccountID::from(account_bytes)
47    }
48
49    /// Gets the raw bytes of the MptId.
50    pub fn as_bytes(&self) -> &[u8; 24] {
51        &self.0
52    }
53
54    pub fn as_ptr(&self) -> *const u8 {
55        self.0.as_ptr()
56    }
57
58    pub fn len(&self) -> usize {
59        self.0.len()
60    }
61
62    pub fn is_empty(&self) -> bool {
63        self.0.iter().all(|&byte| byte == 0)
64    }
65}
66
67impl From<[u8; 24]> for MptId {
68    fn from(value: [u8; 24]) -> Self {
69        MptId(value)
70    }
71}
72
73impl From<(u32, AccountID)> for MptId {
74    fn from(value: (u32, AccountID)) -> Self {
75        MptId::new(value.0, value.1)
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_mpt_id_creation() {
85        // Create a test account ID
86        let account_bytes = [1u8; 20];
87        let account_id = AccountID::from(account_bytes);
88
89        // Create an MptId using the constructor
90        let sequence_num = 12345u32;
91        let mpt_id = MptId::new(sequence_num, account_id);
92
93        // Verify the sequence number and issuer
94        assert_eq!(mpt_id.get_sequence_num(), sequence_num);
95        assert_eq!(mpt_id.get_issuer(), account_id);
96    }
97
98    #[test]
99    fn test_mpt_id_byte_layout() {
100        // Test that sequence number is stored as big-endian in first 4 bytes
101        let sequence_num = 0x12345678u32;
102        let account_id = AccountID::from([0xAA; 20]);
103        let mpt_id = MptId::new(sequence_num, account_id);
104
105        let bytes = mpt_id.as_bytes();
106        // Sequence number should be big-endian
107        assert_eq!(&bytes[0..4], &[0x12, 0x34, 0x56, 0x78]);
108        // Issuer should follow
109        assert_eq!(&bytes[4..24], &[0xAA; 20]);
110    }
111
112    #[test]
113    fn test_mpt_id_from_bytes() {
114        // Create a test byte array
115        let mut bytes = [0u8; 24];
116        // Set sequence number bytes (first 4 bytes, big-endian)
117        bytes[0..4].copy_from_slice(&67890u32.to_be_bytes());
118        // Set account ID bytes (last 20 bytes)
119        for byte in bytes.iter_mut().skip(4).take(20) {
120            *byte = 2;
121        }
122
123        // Create an MptId from bytes
124        let mpt_id = MptId::from(bytes);
125
126        // Verify the sequence number and issuer
127        assert_eq!(mpt_id.get_sequence_num(), 67890u32);
128        assert_eq!(mpt_id.get_issuer(), AccountID::from([2u8; 20]));
129    }
130
131    #[test]
132    fn test_mpt_id_as_bytes_and_len() {
133        let issuer = AccountID::from([0xAA; 20]);
134        let mpt_id = MptId::new(42, issuer);
135
136        assert_eq!(mpt_id.len(), 24);
137        assert_eq!(&mpt_id.as_bytes()[0..4], &42u32.to_be_bytes());
138        assert_eq!(&mpt_id.as_bytes()[4..], &[0xAA; 20]);
139
140        let ptr_slice = unsafe { core::slice::from_raw_parts(mpt_id.as_ptr(), mpt_id.len()) };
141        assert_eq!(ptr_slice, mpt_id.as_bytes());
142    }
143
144    #[test]
145    fn test_mpt_id_is_empty() {
146        let empty = MptId::from([0u8; 24]);
147        assert!(empty.is_empty());
148
149        let non_empty = MptId::new(1, AccountID::from([0u8; 20]));
150        assert!(!non_empty.is_empty());
151    }
152
153    #[test]
154    fn test_mpt_id_from_tuple() {
155        // Create a test account ID
156        let account_bytes = [3u8; 20];
157        let account_id = AccountID::from(account_bytes);
158
159        // Create an MptId from a tuple
160        let sequence_num = 54321u32;
161        let mpt_id = MptId::from((sequence_num, account_id));
162
163        // Verify the sequence number and issuer
164        assert_eq!(mpt_id.get_sequence_num(), sequence_num);
165        assert_eq!(mpt_id.get_issuer(), account_id);
166    }
167
168    #[test]
169    fn test_mpt_id_is_empty_when_all_zeros() {
170        let mpt_id = MptId::from([0u8; 24]);
171        assert!(mpt_id.is_empty());
172    }
173
174    #[test]
175    fn test_mpt_id_is_not_empty_when_has_data() {
176        // Any non-zero byte means not empty
177        let mut bytes = [0u8; 24];
178        bytes[0] = 1; // Set sequence number to 1
179        let mpt_id = MptId::from(bytes);
180        assert!(!mpt_id.is_empty());
181
182        // Also test with non-zero issuer
183        let mut bytes2 = [0u8; 24];
184        bytes2[10] = 0xFF; // Non-zero byte in issuer portion
185        let mpt_id2 = MptId::from(bytes2);
186        assert!(!mpt_id2.is_empty());
187    }
188
189    #[test]
190    fn test_mpt_id_sequence_num_zero() {
191        // Edge case: sequence number 0
192        let account_id = AccountID::from([0xBB; 20]);
193        let mpt_id = MptId::new(0, account_id);
194
195        assert_eq!(mpt_id.get_sequence_num(), 0);
196        assert_eq!(&mpt_id.as_bytes()[0..4], &[0, 0, 0, 0]);
197    }
198
199    #[test]
200    fn test_mpt_id_sequence_num_max() {
201        // Edge case: max u32 sequence number
202        let account_id = AccountID::from([0xCC; 20]);
203        let mpt_id = MptId::new(u32::MAX, account_id);
204
205        assert_eq!(mpt_id.get_sequence_num(), u32::MAX);
206        assert_eq!(&mpt_id.as_bytes()[0..4], &[0xFF, 0xFF, 0xFF, 0xFF]);
207    }
208}