titan_types/
rune_id.rs

1use std::{fmt::Display, str::FromStr};
2
3use borsh::{BorshDeserialize, BorshSerialize};
4use bytemuck::{Pod, Zeroable};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash, Ord, PartialOrd, Pod, Zeroable)]
8#[repr(C)]
9pub struct RuneId {
10    pub block: u64,
11    pub tx: u32,
12    _padding: [u8; 4],
13}
14
15impl RuneId {
16    pub const BTC: Self = RuneId {
17        block: 0,
18        tx: 0,
19        _padding: [0; 4],
20    };
21
22    pub fn new(block: u64, tx: u32) -> Self {
23        RuneId {
24            block,
25            tx,
26            _padding: [0; 4],
27        }
28    }
29
30    /// Returns token bytes as a fixed-size array without heap allocation
31    pub fn to_bytes(&self) -> [u8; 12] {
32        let mut result = [0u8; 12];
33        result[0..8].copy_from_slice(&self.block.to_le_bytes());
34        result[8..12].copy_from_slice(&self.tx.to_le_bytes());
35        result
36    }
37
38    /// Deterministically sort the two `RuneId`s and return their little-endian
39    /// byte representations.
40    ///
41    /// This is the canonical way we ensure that the *same* pair of rune always
42    /// maps to the *same* PDA seeds, regardless of call-site ordering.
43    /// The function is `const`-friendly and completely stack-allocated so it can be
44    /// evaluated at compile-time in tests.
45    pub fn get_sorted_rune_ids(rune0: &RuneId, rune1: &RuneId) -> ([u8; 12], [u8; 12]) {
46        let rune0_bytes = rune0.to_bytes();
47        let rune1_bytes = rune1.to_bytes();
48        if rune0_bytes <= rune1_bytes {
49            (rune0_bytes, rune1_bytes)
50        } else {
51            (rune1_bytes, rune0_bytes)
52        }
53    }
54
55    /// Calculate the delta between two RuneIds (same as ordinals implementation)
56    pub fn delta(self, next: RuneId) -> Option<(u128, u128)> {
57        let block = next.block.checked_sub(self.block)?;
58
59        let tx = if block == 0 {
60            next.tx.checked_sub(self.tx)?
61        } else {
62            next.tx
63        };
64
65        Some((block.into(), tx.into()))
66    }
67
68    /// Calculate the next RuneId given deltas (same as ordinals implementation)
69    pub fn next(self, block: u128, tx: u128) -> Option<RuneId> {
70        Some(RuneId::new(
71            self.block.checked_add(block.try_into().ok()?)?,
72            if block == 0 {
73                self.tx.checked_add(tx.try_into().ok()?)?
74            } else {
75                tx.try_into().ok()?
76            },
77        ))
78    }
79}
80
81impl Display for RuneId {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        write!(f, "{}:{}", self.block, self.tx)
84    }
85}
86
87impl BorshSerialize for RuneId {
88    fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
89        borsh::BorshSerialize::serialize(&self.block, writer)?;
90        borsh::BorshSerialize::serialize(&self.tx, writer)?;
91        Ok(())
92    }
93}
94
95impl BorshDeserialize for RuneId {
96    fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
97        let block = <u64 as borsh::BorshDeserialize>::deserialize(buf)?;
98        let tx = <u32 as borsh::BorshDeserialize>::deserialize(buf)?;
99        Ok(RuneId::new(block, tx))
100    }
101
102    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
103        let block = u64::deserialize_reader(reader)?;
104        let tx = u32::deserialize_reader(reader)?;
105        Ok(RuneId::new(block, tx))
106    }
107}
108
109impl FromStr for RuneId {
110    type Err = String;
111
112    fn from_str(s: &str) -> Result<Self, Self::Err> {
113        let (height, index) = s
114            .split_once(':')
115            .ok_or_else(|| "Invalid format: expected 'block:tx'".to_string())?;
116
117        let block = height
118            .parse::<u64>()
119            .map_err(|_| "Invalid block number".to_string())?;
120        let tx = index
121            .parse::<u32>()
122            .map_err(|_| "Invalid transaction number".to_string())?;
123
124        Ok(RuneId::new(block, tx))
125    }
126}
127
128impl Serialize for RuneId {
129    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
130    where
131        S: serde::Serializer,
132    {
133        serializer.serialize_str(&self.to_string())
134    }
135}
136
137impl<'de> Deserialize<'de> for RuneId {
138    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
139    where
140        D: serde::Deserializer<'de>,
141    {
142        let s = <String as serde::Deserialize>::deserialize(deserializer)?;
143        let rune_id = RuneId::from_str(&s).map_err(serde::de::Error::custom)?;
144        Ok(rune_id)
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn delta() {
154        let mut expected = [
155            RuneId::new(3, 1),
156            RuneId::new(4, 2),
157            RuneId::new(1, 2),
158            RuneId::new(1, 1),
159            RuneId::new(3, 1),
160            RuneId::new(2, 0),
161        ];
162
163        expected.sort();
164
165        assert_eq!(
166            expected,
167            [
168                RuneId::new(1, 1),
169                RuneId::new(1, 2),
170                RuneId::new(2, 0),
171                RuneId::new(3, 1),
172                RuneId::new(3, 1),
173                RuneId::new(4, 2),
174            ]
175        );
176
177        let mut previous = RuneId::default();
178        let mut deltas = Vec::new();
179        for id in expected {
180            deltas.push(previous.delta(id).unwrap());
181            previous = id;
182        }
183
184        assert_eq!(deltas, [(1, 1), (0, 1), (1, 0), (1, 1), (0, 0), (1, 2)]);
185
186        let mut previous = RuneId::default();
187        let mut actual = Vec::new();
188        for (block, tx) in deltas {
189            let next = previous.next(block, tx).unwrap();
190            actual.push(next);
191            previous = next;
192        }
193
194        assert_eq!(actual, expected);
195    }
196
197    #[test]
198    fn display() {
199        assert_eq!(RuneId::new(1, 2).to_string(), "1:2");
200    }
201
202    #[test]
203    fn from_str() {
204        assert!("123".parse::<RuneId>().is_err());
205        assert!(":".parse::<RuneId>().is_err());
206        assert!("1:".parse::<RuneId>().is_err());
207        assert!(":2".parse::<RuneId>().is_err());
208        assert!("a:2".parse::<RuneId>().is_err());
209        assert!("1:a".parse::<RuneId>().is_err());
210        assert_eq!("1:2".parse::<RuneId>().unwrap(), RuneId::new(1, 2));
211        // block == 0 && tx > 0 is now valid
212        assert_eq!("0:1".parse::<RuneId>().unwrap(), RuneId::new(0, 1));
213    }
214
215    #[test]
216    fn serde() {
217        let rune_id = RuneId::new(1, 2);
218        let json = "\"1:2\"";
219        assert_eq!(serde_json::to_string(&rune_id).unwrap(), json);
220        assert_eq!(serde_json::from_str::<RuneId>(json).unwrap(), rune_id);
221    }
222
223    #[test]
224    fn to_bytes() {
225        let rune_id = RuneId::new(0x1234567890ABCDEF, 0x12345678);
226        let bytes = rune_id.to_bytes();
227
228        // Check little-endian encoding
229        assert_eq!(bytes[0..8], 0x1234567890ABCDEFu64.to_le_bytes());
230        assert_eq!(bytes[8..12], 0x12345678u32.to_le_bytes());
231    }
232
233    #[test]
234    fn get_sorted_rune_ids() {
235        let rune1 = RuneId::new(1, 1);
236        let rune2 = RuneId::new(2, 2);
237
238        let (a, b) = RuneId::get_sorted_rune_ids(&rune1, &rune2);
239        assert!(a <= b);
240
241        // Should be same regardless of order
242        let (c, d) = RuneId::get_sorted_rune_ids(&rune2, &rune1);
243        assert_eq!((a, b), (c, d));
244    }
245
246    #[test]
247    fn borsh_roundtrip() {
248        let rune_id = RuneId::new(840000, 1);
249        let serialized = borsh::to_vec(&rune_id).unwrap();
250        let deserialized: RuneId = borsh::from_slice(&serialized).unwrap();
251        assert_eq!(rune_id, deserialized);
252    }
253}