1use alloc::str::FromStr;
2use candid::{
3 CandidType,
4 types::{Serializer, Type, TypeInner},
5};
6use ic_stable_structures::{Storable, storable::Bound};
7
8#[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}