1use fuel_types::{
2 bytes::WORD_SIZE,
3 BlockHeight,
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 distributions::{
19 Distribution,
20 Standard,
21 },
22 Rng,
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 tx_index: u16,
39}
40
41impl TxPointer {
42 pub const LEN: usize = 2 * WORD_SIZE;
43
44 pub const fn new(block_height: BlockHeight, tx_index: u16) -> Self {
45 Self {
46 block_height,
47 tx_index,
48 }
49 }
50
51 pub const fn block_height(&self) -> BlockHeight {
52 self.block_height
53 }
54
55 pub const fn tx_index(&self) -> u16 {
56 self.tx_index
57 }
58}
59
60#[cfg(feature = "random")]
61impl Distribution<TxPointer> for Standard {
62 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> TxPointer {
63 TxPointer::new(rng.gen(), rng.gen())
64 }
65}
66
67impl fmt::Display for TxPointer {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 fmt::LowerHex::fmt(self, f)
70 }
71}
72
73impl fmt::LowerHex for TxPointer {
74 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
75 write!(f, "{:08x}{:04x}", self.block_height, self.tx_index)
76 }
77}
78
79impl fmt::UpperHex for TxPointer {
80 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
81 write!(f, "{:08X}{:04X}", self.block_height, self.tx_index)
82 }
83}
84
85impl str::FromStr for TxPointer {
86 type Err = &'static str;
87
88 fn from_str(s: &str) -> Result<Self, Self::Err> {
92 const ERR: &str = "Invalid encoded byte in TxPointer";
93
94 if s.len() != 12 || !s.is_char_boundary(8) {
95 return Err(ERR)
96 }
97
98 let (block_height, tx_index) = s.split_at(8);
99
100 let block_height = u32::from_str_radix(block_height, 16).map_err(|_| ERR)?;
101 let tx_index = u16::from_str_radix(tx_index, 16).map_err(|_| ERR)?;
102
103 Ok(Self::new(block_height.into(), tx_index))
104 }
105}
106
107#[cfg(feature = "typescript")]
108pub mod typescript {
109 use super::*;
110
111 use wasm_bindgen::prelude::*;
112
113 use alloc::{
114 format,
115 string::String,
116 vec::Vec,
117 };
118
119 #[wasm_bindgen]
120 impl TxPointer {
121 #[wasm_bindgen(constructor)]
122 pub fn typescript_new(value: &str) -> Result<TxPointer, js_sys::Error> {
123 use core::str::FromStr;
124 TxPointer::from_str(value).map_err(js_sys::Error::new)
125 }
126
127 #[wasm_bindgen(js_name = toString)]
128 pub fn typescript_to_string(&self) -> String {
129 format!("{:#x}", self)
130 }
131
132 #[wasm_bindgen(js_name = to_bytes)]
133 pub fn typescript_to_bytes(&self) -> Vec<u8> {
134 use fuel_types::canonical::Serialize;
135 <Self as Serialize>::to_bytes(self)
136 }
137
138 #[wasm_bindgen(js_name = from_bytes)]
139 pub fn typescript_from_bytes(value: &[u8]) -> Result<TxPointer, js_sys::Error> {
140 use fuel_types::canonical::Deserialize;
141 <Self as Deserialize>::from_bytes(value)
142 .map_err(|e| js_sys::Error::new(&format!("{:?}", e)))
143 }
144 }
145}
146
147#[test]
148fn fmt_encode_decode() {
149 use core::str::FromStr;
150
151 let cases = vec![(83473, 3829)];
152
153 for (block_height, tx_index) in cases {
154 let tx_pointer = TxPointer::new(block_height.into(), tx_index);
155
156 let lower = format!("{tx_pointer:x}");
157 let upper = format!("{tx_pointer:X}");
158
159 assert_eq!(lower, format!("{block_height:08x}{tx_index:04x}"));
160 assert_eq!(upper, format!("{block_height:08X}{tx_index:04X}"));
161
162 let x = TxPointer::from_str(&lower).expect("failed to decode from str");
163 assert_eq!(tx_pointer, x);
164
165 let x = TxPointer::from_str(&upper).expect("failed to decode from str");
166 assert_eq!(tx_pointer, x);
167
168 let bytes = tx_pointer.clone().to_bytes();
169 let tx_pointer_p = TxPointer::from_bytes(&bytes).expect("failed to deserialize");
170
171 assert_eq!(tx_pointer, tx_pointer_p);
172 }
173}
174
175#[test]
177fn decode_bug() {
178 use core::str::FromStr;
179 TxPointer::from_str("00000😎000").expect_err("Should fail on incorrect input");
180}