1use std::{
2 any::TypeId,
3 collections::{HashMap, HashSet},
4 fmt::{Debug, Formatter},
5 iter::FilterMap,
6};
7
8#[derive(Debug, Clone)]
9pub struct ErrorDetails {
10 pub(crate) pkg: String,
11 pub(crate) file: String,
12 pub(crate) line: u64,
13 pub(crate) column: u64,
14 pub(crate) log_id: Option<String>,
15 pub(crate) msg: Option<String>,
16}
17
18impl ErrorDetails {
19 pub fn new(
20 pkg: String,
21 file: String,
22 line: u64,
23 column: u64,
24 log_id: Option<String>,
25 msg: Option<String>,
26 ) -> Self {
27 Self {
28 pkg,
29 file,
30 line,
31 column,
32 log_id,
33 msg,
34 }
35 }
36}
37
38use fuel_tx::{ContractId, Receipt};
39
40use crate::{
41 codec::{ABIDecoder, DecoderConfig},
42 traits::{Parameterize, Tokenizable},
43 types::errors::{Error, Result, error},
44};
45
46#[derive(Clone)]
47pub struct LogFormatter {
48 formatter: fn(DecoderConfig, &[u8]) -> Result<String>,
49 type_id: TypeId,
50}
51
52impl LogFormatter {
53 pub fn new_log<T: Tokenizable + Parameterize + Debug + 'static>() -> Self {
54 Self {
55 formatter: Self::format_log::<T>,
56 type_id: TypeId::of::<T>(),
57 }
58 }
59
60 pub fn new_error<T: Tokenizable + Parameterize + std::error::Error + 'static>() -> Self {
61 Self {
62 formatter: Self::format_error::<T>,
63 type_id: TypeId::of::<T>(),
64 }
65 }
66
67 fn format_log<T: Parameterize + Tokenizable + Debug>(
68 decoder_config: DecoderConfig,
69 bytes: &[u8],
70 ) -> Result<String> {
71 let token = ABIDecoder::new(decoder_config).decode(&T::param_type(), bytes)?;
72
73 Ok(format!("{:?}", T::from_token(token)?))
74 }
75
76 fn format_error<T: Parameterize + Tokenizable + std::error::Error>(
77 decoder_config: DecoderConfig,
78 bytes: &[u8],
79 ) -> Result<String> {
80 let token = ABIDecoder::new(decoder_config).decode(&T::param_type(), bytes)?;
81
82 Ok(T::from_token(token)?.to_string())
83 }
84
85 pub fn can_handle_type<T: Tokenizable + Parameterize + 'static>(&self) -> bool {
86 TypeId::of::<T>() == self.type_id
87 }
88
89 pub fn format(&self, decoder_config: DecoderConfig, bytes: &[u8]) -> Result<String> {
90 (self.formatter)(decoder_config, bytes)
91 }
92}
93
94impl Debug for LogFormatter {
95 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
96 f.debug_struct("LogFormatter")
97 .field("type_id", &self.type_id)
98 .finish()
99 }
100}
101
102#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
104pub struct LogId(ContractId, String);
105
106#[derive(Debug, Clone, Default)]
108pub struct LogDecoder {
109 log_formatters: HashMap<LogId, LogFormatter>,
111 error_codes: HashMap<u64, ErrorDetails>,
112 decoder_config: DecoderConfig,
113}
114
115#[derive(Debug)]
116pub struct LogResult {
117 pub results: Vec<Result<String>>,
118}
119
120impl LogResult {
121 pub fn filter_succeeded(&self) -> Vec<&str> {
122 self.results
123 .iter()
124 .filter_map(|result| result.as_deref().ok())
125 .collect()
126 }
127
128 pub fn filter_failed(&self) -> Vec<&Error> {
129 self.results
130 .iter()
131 .filter_map(|result| result.as_ref().err())
132 .collect()
133 }
134}
135
136impl LogDecoder {
137 pub fn new(
138 log_formatters: HashMap<LogId, LogFormatter>,
139 error_codes: HashMap<u64, ErrorDetails>,
140 ) -> Self {
141 Self {
142 log_formatters,
143 error_codes,
144 decoder_config: Default::default(),
145 }
146 }
147
148 pub fn get_error_codes(&self, id: &u64) -> Option<&ErrorDetails> {
149 self.error_codes.get(id)
150 }
151
152 pub fn set_decoder_config(&mut self, decoder_config: DecoderConfig) -> &mut Self {
153 self.decoder_config = decoder_config;
154 self
155 }
156
157 pub fn decode_logs(&self, receipts: &[Receipt]) -> LogResult {
159 let results = receipts
160 .iter()
161 .extract_log_id_and_data()
162 .map(|(log_id, data)| self.format_log(&log_id, &data))
163 .collect();
164
165 LogResult { results }
166 }
167
168 fn format_log(&self, log_id: &LogId, data: &[u8]) -> Result<String> {
169 self.log_formatters
170 .get(log_id)
171 .ok_or_else(|| {
172 error!(
173 Codec,
174 "missing log formatter for log_id: `{:?}`, data: `{:?}`. \
175 Consider adding external contracts using `with_contracts()`",
176 log_id,
177 data
178 )
179 })
180 .and_then(|log_formatter| log_formatter.format(self.decoder_config, data))
181 }
182
183 pub(crate) fn decode_last_log(&self, receipts: &[Receipt]) -> Result<String> {
184 receipts
185 .iter()
186 .rev()
187 .extract_log_id_and_data()
188 .next()
189 .ok_or_else(|| error!(Codec, "no receipts found for decoding last log"))
190 .and_then(|(log_id, data)| self.format_log(&log_id, &data))
191 }
192
193 pub(crate) fn decode_last_two_logs(&self, receipts: &[Receipt]) -> Result<(String, String)> {
194 let res = receipts
195 .iter()
196 .rev()
197 .extract_log_id_and_data()
198 .map(|(log_id, data)| self.format_log(&log_id, &data))
199 .take(2)
200 .collect::<Result<Vec<_>>>();
201
202 match res.as_deref() {
203 Ok([rhs, lhs]) => Ok((lhs.to_string(), rhs.to_string())),
204 Ok(some_slice) => Err(error!(
205 Codec,
206 "expected to have two logs. Found {}",
207 some_slice.len()
208 )),
209 Err(_) => Err(res.expect_err("must be an error")),
210 }
211 }
212
213 pub fn decode_logs_with_type<T: Tokenizable + Parameterize + 'static>(
216 &self,
217 receipts: &[Receipt],
218 ) -> Result<Vec<T>> {
219 let target_ids: HashSet<LogId> = self
220 .log_formatters
221 .iter()
222 .filter(|(_, log_formatter)| log_formatter.can_handle_type::<T>())
223 .map(|(log_id, _)| log_id.clone())
224 .collect();
225
226 receipts
227 .iter()
228 .extract_log_id_and_data()
229 .filter_map(|(log_id, bytes)| {
230 target_ids.contains(&log_id).then(|| {
231 let token = ABIDecoder::new(self.decoder_config)
232 .decode(&T::param_type(), bytes.as_slice())?;
233
234 T::from_token(token)
235 })
236 })
237 .collect()
238 }
239
240 pub fn decode_logs_lazy<'a, T: Tokenizable + Parameterize + 'static>(
242 &'a self,
243 receipt: &'a Receipt,
244 ) -> impl Iterator<Item = impl FnOnce() -> Result<T>> + 'a {
245 let target_ids: HashSet<&LogId> = self
246 .log_formatters
247 .iter()
248 .filter(|(_, log_formatter)| log_formatter.can_handle_type::<T>())
249 .map(|(log_id, _)| log_id)
250 .collect();
251
252 std::iter::once(receipt).extract_matching_logs_lazy::<T>(target_ids, self.decoder_config)
253 }
254
255 pub fn merge(&mut self, log_decoder: LogDecoder) {
256 self.log_formatters.extend(log_decoder.log_formatters);
257 self.error_codes.extend(log_decoder.error_codes);
258 }
259}
260
261trait ExtractLogIdData {
262 type Output: Iterator<Item = (LogId, Vec<u8>)>;
263 fn extract_log_id_and_data(self) -> Self::Output;
264}
265
266trait ExtractLogIdLazy {
267 fn extract_matching_logs_lazy<T: Tokenizable + Parameterize + 'static>(
268 self,
269 target_ids: HashSet<&LogId>,
270 decoder_config: DecoderConfig,
271 ) -> impl Iterator<Item = impl FnOnce() -> Result<T>>;
272}
273
274impl<'a, I: Iterator<Item = &'a Receipt>> ExtractLogIdData for I {
275 type Output = FilterMap<Self, fn(&Receipt) -> Option<(LogId, Vec<u8>)>>;
276 fn extract_log_id_and_data(self) -> Self::Output {
277 self.filter_map(|r| match r {
278 Receipt::LogData {
279 rb,
280 data: Some(data),
281 id,
282 ..
283 } => Some((LogId(*id, (*rb).to_string()), data.clone())),
284 Receipt::Log { ra, rb, id, .. } => {
285 Some((LogId(*id, (*rb).to_string()), ra.to_be_bytes().to_vec()))
286 }
287 _ => None,
288 })
289 }
290}
291
292impl<'a, I: Iterator<Item = &'a Receipt>> ExtractLogIdLazy for I {
293 fn extract_matching_logs_lazy<T: Tokenizable + Parameterize + 'static>(
294 self,
295 target_ids: HashSet<&LogId>,
296 decoder_config: DecoderConfig,
297 ) -> impl Iterator<Item = impl FnOnce() -> Result<T>> {
298 self.filter_map(move |r| {
299 let log_id = match r {
300 Receipt::LogData { rb, id, .. } => LogId(*id, (*rb).to_string()),
301 Receipt::Log { rb, id, .. } => LogId(*id, (*rb).to_string()),
302 _ => return None,
303 };
304
305 if !target_ids.contains(&log_id) {
306 return None;
307 }
308
309 enum Data<'a> {
310 LogData(&'a [u8]),
311 LogRa(u64),
312 }
313
314 let data = match r {
315 Receipt::LogData {
316 data: Some(data), ..
317 } => Some(Data::LogData(data.as_slice())),
318 Receipt::Log { ra, .. } => Some(Data::LogRa(*ra)),
319 _ => None,
320 };
321
322 data.map(move |data| {
323 move || {
324 let normalized_data = match data {
325 Data::LogData(data) => data,
326 Data::LogRa(ra) => &ra.to_be_bytes(),
327 };
328 let token = ABIDecoder::new(decoder_config)
329 .decode(&T::param_type(), normalized_data)?;
330 T::from_token(token)
331 }
332 })
333 })
334 }
335}
336
337pub fn log_formatters_lookup(
338 log_id_log_formatter_pairs: Vec<(String, LogFormatter)>,
339 contract_id: ContractId,
340) -> HashMap<LogId, LogFormatter> {
341 log_id_log_formatter_pairs
342 .into_iter()
343 .map(|(id, log_formatter)| (LogId(contract_id, id), log_formatter))
344 .collect()
345}