1use fuel_types::{
2 BlockHeight,
3 bytes::WORD_SIZE,
4};
5
6use fuel_types::canonical::{
7 Deserialize,
8 Serialize,
9};
10
11use core::{
12 fmt,
13 str,
14};
15
16#[cfg(feature = "random")]
17use rand::{
18 Rng,
19 distributions::{
20 Distribution,
21 Standard,
22 },
23};
24
25#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
27#[cfg_attr(feature = "typescript", wasm_bindgen::prelude::wasm_bindgen)]
28#[derive(serde::Serialize, serde::Deserialize)]
29#[cfg_attr(
30 feature = "da-compression",
31 derive(fuel_compression::Compress, fuel_compression::Decompress)
32)]
33#[derive(Deserialize, Serialize)]
34pub struct TxPointer {
35 block_height: BlockHeight,
37 #[cfg(feature = "u32-tx-pointer")]
39 tx_index: u32,
40 #[cfg(not(feature = "u32-tx-pointer"))]
41 tx_index: u16,
42}
43
44impl TxPointer {
45 pub const LEN: usize = 2 * WORD_SIZE;
46
47 pub const fn new(
48 block_height: BlockHeight,
49 #[cfg(feature = "u32-tx-pointer")] tx_index: u32,
50 #[cfg(not(feature = "u32-tx-pointer"))] tx_index: u16,
51 ) -> Self {
52 Self {
53 block_height,
54 tx_index,
55 }
56 }
57
58 pub const fn block_height(&self) -> BlockHeight {
59 self.block_height
60 }
61
62 #[cfg(feature = "u32-tx-pointer")]
63 pub const fn tx_index(&self) -> u32 {
64 self.tx_index
65 }
66
67 #[cfg(not(feature = "u32-tx-pointer"))]
68 pub const fn tx_index(&self) -> u16 {
69 self.tx_index
70 }
71}
72
73#[cfg(feature = "random")]
74impl Distribution<TxPointer> for Standard {
75 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> TxPointer {
76 TxPointer::new(rng.r#gen(), rng.r#gen())
77 }
78}
79
80impl fmt::Display for TxPointer {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 fmt::LowerHex::fmt(self, f)
83 }
84}
85
86#[cfg(feature = "u32-tx-pointer")]
87impl fmt::LowerHex for TxPointer {
88 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
89 if self.tx_index > u16::MAX.into() {
90 write!(f, "{:08x}{:08x}", self.block_height, self.tx_index)
91 } else {
92 write!(f, "{:08x}{:04x}", self.block_height, self.tx_index)
93 }
94 }
95}
96
97#[cfg(not(feature = "u32-tx-pointer"))]
98impl fmt::LowerHex for TxPointer {
99 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
100 write!(f, "{:08x}{:04x}", self.block_height, self.tx_index)
101 }
102}
103
104#[cfg(feature = "u32-tx-pointer")]
105impl fmt::UpperHex for TxPointer {
106 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
107 if self.tx_index > u16::MAX.into() {
108 write!(f, "{:08X}{:08X}", self.block_height, self.tx_index)
109 } else {
110 write!(f, "{:08X}{:04X}", self.block_height, self.tx_index)
111 }
112 }
113}
114
115#[cfg(not(feature = "u32-tx-pointer"))]
116impl fmt::UpperHex for TxPointer {
117 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
118 write!(f, "{:08X}{:04X}", self.block_height, self.tx_index)
119 }
120}
121
122impl str::FromStr for TxPointer {
123 type Err = &'static str;
124
125 #[cfg(feature = "u32-tx-pointer")]
126 fn from_str(s: &str) -> Result<Self, Self::Err> {
130 const ERR: &str = "Invalid encoded byte in TxPointer";
131
132 if (s.len() != 16 && s.len() != 12) || !s.is_char_boundary(8) {
133 return Err(ERR)
134 }
135
136 let (block_height, tx_index) = s.split_at(8);
137
138 let block_height = u32::from_str_radix(block_height, 16).map_err(|_| ERR)?;
139 let tx_index = match tx_index.len() {
140 4 => u16::from_str_radix(tx_index, 16).map_err(|_| ERR)?.into(),
141 8 => u32::from_str_radix(tx_index, 16).map_err(|_| ERR)?,
142 _ => return Err(ERR),
143 };
144
145 Ok(Self::new(block_height.into(), tx_index))
146 }
147
148 #[cfg(not(feature = "u32-tx-pointer"))]
149 fn from_str(s: &str) -> Result<Self, Self::Err> {
153 const ERR: &str = "Invalid encoded byte in TxPointer";
154
155 if s.len() != 12 || !s.is_char_boundary(8) {
156 return Err(ERR)
157 }
158
159 let (block_height, tx_index) = s.split_at(8);
160
161 let block_height = u32::from_str_radix(block_height, 16).map_err(|_| ERR)?;
162 let tx_index = u16::from_str_radix(tx_index, 16).map_err(|_| ERR)?;
163
164 Ok(Self::new(block_height.into(), tx_index))
165 }
166}
167
168#[cfg(feature = "typescript")]
169pub mod typescript {
170 use super::*;
171
172 use wasm_bindgen::prelude::*;
173
174 use alloc::{
175 format,
176 string::String,
177 vec::Vec,
178 };
179
180 #[wasm_bindgen]
181 impl TxPointer {
182 #[wasm_bindgen(constructor)]
183 pub fn typescript_new(value: &str) -> Result<TxPointer, js_sys::Error> {
184 use core::str::FromStr;
185 TxPointer::from_str(value).map_err(js_sys::Error::new)
186 }
187
188 #[wasm_bindgen(js_name = toString)]
189 pub fn typescript_to_string(&self) -> String {
190 format!("{:#x}", self)
191 }
192
193 #[wasm_bindgen(js_name = to_bytes)]
194 pub fn typescript_to_bytes(&self) -> Vec<u8> {
195 use fuel_types::canonical::Serialize;
196 <Self as Serialize>::to_bytes(self)
197 }
198
199 #[wasm_bindgen(js_name = from_bytes)]
200 pub fn typescript_from_bytes(value: &[u8]) -> Result<TxPointer, js_sys::Error> {
201 use fuel_types::canonical::Deserialize;
202 <Self as Deserialize>::from_bytes(value)
203 .map_err(|e| js_sys::Error::new(&format!("{:?}", e)))
204 }
205 }
206}
207
208#[cfg(not(feature = "u32-tx-pointer"))]
209#[test]
210fn fmt_encode_decode() {
211 use core::str::FromStr;
212
213 let cases = vec![(83473, 3829)];
214
215 for (block_height, tx_index) in cases {
216 let tx_pointer = TxPointer::new(block_height.into(), tx_index);
217
218 let lower = format!("{tx_pointer:x}");
219 let upper = format!("{tx_pointer:X}");
220
221 assert_eq!(lower, format!("{block_height:08x}{tx_index:04x}"));
222 assert_eq!(upper, format!("{block_height:08X}{tx_index:04X}"));
223
224 let x = TxPointer::from_str(&lower).expect("failed to decode from str");
225 assert_eq!(tx_pointer, x);
226
227 let x = TxPointer::from_str(&upper).expect("failed to decode from str");
228 assert_eq!(tx_pointer, x);
229
230 let bytes = tx_pointer.clone().to_bytes();
231 let tx_pointer_p = TxPointer::from_bytes(&bytes).expect("failed to deserialize");
232
233 assert_eq!(tx_pointer, tx_pointer_p);
234 }
235}
236
237#[cfg(feature = "u32-tx-pointer")]
238#[test]
239fn fmt_encode_decode_u32() {
240 use core::str::FromStr;
241
242 let cases = vec![(83473, u32::from(u16::MAX) + 478930)];
243
244 for (block_height, tx_index) in cases {
245 let tx_pointer = TxPointer::new(block_height.into(), tx_index);
246
247 let lower = format!("{tx_pointer:x}");
248 let upper = format!("{tx_pointer:X}");
249
250 assert_eq!(lower, format!("{block_height:08x}{tx_index:08x}"));
251 assert_eq!(upper, format!("{block_height:08X}{tx_index:08X}"));
252
253 let x = TxPointer::from_str(&lower).expect("failed to decode from str");
254 assert_eq!(tx_pointer, x);
255
256 let x = TxPointer::from_str(&upper).expect("failed to decode from str");
257 assert_eq!(tx_pointer, x);
258
259 let bytes = tx_pointer.clone().to_bytes();
260 let tx_pointer_p = TxPointer::from_bytes(&bytes).expect("failed to deserialize");
261
262 assert_eq!(tx_pointer, tx_pointer_p);
263 }
264}
265
266#[cfg(feature = "u32-tx-pointer")]
267#[test]
268fn fmt_backward_compatibility_u32() {
269 use core::str::FromStr;
270
271 let cases = vec![(83473, 647)];
272
273 for (block_height, tx_index) in cases {
274 let tx_pointer = TxPointer::new(block_height.into(), tx_index);
275
276 let lower = format!("{tx_pointer:x}");
277 let upper = format!("{tx_pointer:X}");
278
279 assert_eq!(lower, format!("{block_height:08x}{tx_index:04x}"));
280 assert_eq!(upper, format!("{block_height:08X}{tx_index:04X}"));
281
282 let x = TxPointer::from_str(&lower).expect("failed to decode from str");
283 assert_eq!(tx_pointer, x);
284
285 let x = TxPointer::from_str(&upper).expect("failed to decode from str");
286 assert_eq!(tx_pointer, x);
287
288 let bytes = tx_pointer.clone().to_bytes();
289 let tx_pointer_p = TxPointer::from_bytes(&bytes).expect("failed to deserialize");
290
291 assert_eq!(tx_pointer, tx_pointer_p);
292 }
293}
294
295#[test]
297fn decode_bug() {
298 use core::str::FromStr;
299 TxPointer::from_str("00000😎000").expect_err("Should fail on incorrect input");
300}