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