ree_types/
coin_id.rs

1use alloc::str::FromStr;
2use candid::{
3    CandidType,
4    types::{Serializer, Type, TypeInner},
5};
6use ic_stable_structures::{Storable, storable::Bound};
7
8/// The identifier for a RUNE in the Bitcoin network. Specially, BTC is represented by `CoinId::btc()`.
9#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
10pub struct CoinId {
11    pub block: u64,
12    pub tx: u32,
13}
14
15impl Ord for CoinId {
16    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
17        self.block.cmp(&other.block).then(self.tx.cmp(&other.tx))
18    }
19}
20
21impl PartialOrd for CoinId {
22    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
23        Some(self.cmp(other))
24    }
25}
26
27impl Storable for CoinId {
28    const BOUND: Bound = Bound::Bounded {
29        max_size: 12,
30        is_fixed_size: true,
31    };
32
33    fn to_bytes(&self) -> alloc::borrow::Cow<'_, [u8]> {
34        let mut bytes = vec![];
35        bytes.extend_from_slice(self.block.to_be_bytes().as_ref());
36        bytes.extend_from_slice(self.tx.to_be_bytes().as_ref());
37        alloc::borrow::Cow::Owned(bytes)
38    }
39
40    fn into_bytes(self) -> Vec<u8> {
41        let mut bytes = vec![];
42        bytes.extend_from_slice(self.block.to_be_bytes().as_ref());
43        bytes.extend_from_slice(self.tx.to_be_bytes().as_ref());
44        bytes
45    }
46
47    fn from_bytes(bytes: alloc::borrow::Cow<'_, [u8]>) -> Self {
48        let block: [u8; 8] = bytes.as_ref()[0..8]
49            .try_into()
50            .expect("failed to decode CoinId");
51        let tx: [u8; 4] = bytes.as_ref()[8..12]
52            .try_into()
53            .expect("failed to decode CoinId");
54        Self {
55            block: u64::from_be_bytes(block),
56            tx: u32::from_be_bytes(tx),
57        }
58    }
59}
60
61impl CoinId {
62    pub fn rune(block: u64, tx: u32) -> Self {
63        Self { block, tx }
64    }
65
66    #[inline]
67    pub const fn btc() -> Self {
68        Self { block: 0, tx: 0 }
69    }
70
71    pub fn to_bytes(&self) -> Vec<u8> {
72        let mut bytes = vec![];
73        bytes.extend_from_slice(self.block.to_be_bytes().as_ref());
74        bytes.extend_from_slice(self.tx.to_be_bytes().as_ref());
75        bytes
76    }
77
78    pub fn from_bytes(bytes: &[u8]) -> Self {
79        let block: [u8; 8] = bytes[0..8].try_into().expect("failed to decode CoinId");
80        let tx: [u8; 4] = bytes[8..12].try_into().expect("failed to decode CoinId");
81        Self {
82            block: u64::from_be_bytes(block),
83            tx: u32::from_be_bytes(tx),
84        }
85    }
86}
87
88impl FromStr for CoinId {
89    type Err = String;
90
91    fn from_str(s: &str) -> Result<Self, Self::Err> {
92        let mut parts = s.split(':');
93        let block = parts
94            .next()
95            .map(|s| s.parse().ok())
96            .flatten()
97            .ok_or("Invalid CoinId".to_string())?;
98        let tx = parts
99            .next()
100            .map(|s| s.parse().ok())
101            .flatten()
102            .ok_or("Invalid CoinId".to_string())?;
103        Ok(Self { block, tx })
104    }
105}
106
107impl serde::Serialize for CoinId {
108    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
109    where
110        S: serde::ser::Serializer,
111    {
112        serializer.serialize_str(&self.to_string())
113    }
114}
115
116impl CandidType for CoinId {
117    fn _ty() -> Type {
118        TypeInner::Text.into()
119    }
120
121    fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
122    where
123        S: Serializer,
124    {
125        serializer.serialize_text(&self.to_string())
126    }
127}
128
129impl core::fmt::Display for CoinId {
130    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
131        write!(f, "{}:{}", self.block, self.tx)
132    }
133}
134
135struct CoinIdVisitor;
136
137impl<'de> serde::de::Visitor<'de> for CoinIdVisitor {
138    type Value = CoinId;
139
140    fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
141        write!(formatter, "Id of a coin in btc")
142    }
143
144    fn visit_str<E>(self, value: &str) -> Result<CoinId, E>
145    where
146        E: serde::de::Error,
147    {
148        CoinId::from_str(value)
149            .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(value), &self))
150    }
151}
152
153impl<'de> serde::Deserialize<'de> for CoinId {
154    fn deserialize<D>(deserializer: D) -> Result<CoinId, D::Error>
155    where
156        D: serde::de::Deserializer<'de>,
157    {
158        deserializer.deserialize_str(CoinIdVisitor)
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_bincode_serde() {
168        let coin_id = CoinId::rune(840000, 846);
169        let encoded = hex::encode(bincode::serialize(&coin_id).unwrap());
170        let decoded = bincode::deserialize::<CoinId>(&hex::decode(encoded).unwrap()).unwrap();
171        assert_eq!(coin_id, decoded);
172    }
173
174    #[test]
175    fn test_bytes_serde() {
176        let coin_id = CoinId::rune(840000, 846);
177        let bytes = coin_id.to_bytes();
178        let decoded = CoinId::from_bytes(&bytes);
179        assert_eq!(coin_id, decoded);
180    }
181}