fuels_core/codec/
logs.rs

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/// Holds a unique log ID
103#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
104pub struct LogId(ContractId, String);
105
106/// Struct used to pass the log mappings from the Abigen
107#[derive(Debug, Clone, Default)]
108pub struct LogDecoder {
109    /// A mapping of LogId and param-type
110    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    /// Get all logs results from the given receipts as `Result<String>`
158    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    /// Get decoded logs with specific type from the given receipts.
214    /// Note that this method returns the actual type and not a `String` representation.
215    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 merge(&mut self, log_decoder: LogDecoder) {
241        self.log_formatters.extend(log_decoder.log_formatters);
242        self.error_codes.extend(log_decoder.error_codes);
243    }
244}
245
246trait ExtractLogIdData {
247    type Output: Iterator<Item = (LogId, Vec<u8>)>;
248    fn extract_log_id_and_data(self) -> Self::Output;
249}
250
251impl<'a, I: Iterator<Item = &'a Receipt>> ExtractLogIdData for I {
252    type Output = FilterMap<Self, fn(&Receipt) -> Option<(LogId, Vec<u8>)>>;
253    fn extract_log_id_and_data(self) -> Self::Output {
254        self.filter_map(|r| match r {
255            Receipt::LogData {
256                rb,
257                data: Some(data),
258                id,
259                ..
260            } => Some((LogId(*id, (*rb).to_string()), data.clone())),
261            Receipt::Log { ra, rb, id, .. } => {
262                Some((LogId(*id, (*rb).to_string()), ra.to_be_bytes().to_vec()))
263            }
264            _ => None,
265        })
266    }
267}
268
269pub fn log_formatters_lookup(
270    log_id_log_formatter_pairs: Vec<(String, LogFormatter)>,
271    contract_id: ContractId,
272) -> HashMap<LogId, LogFormatter> {
273    log_id_log_formatter_pairs
274        .into_iter()
275        .map(|(id, log_formatter)| (LogId(contract_id, id), log_formatter))
276        .collect()
277}