fuels_core/codec/
logs.rs

1use std::{
2    any::TypeId,
3    collections::{HashMap, HashSet},
4    fmt::{Debug, Formatter},
5    iter::FilterMap,
6};
7
8use fuel_tx::{ContractId, Receipt};
9
10use crate::{
11    codec::{ABIDecoder, DecoderConfig},
12    traits::{Parameterize, Tokenizable},
13    types::errors::{error, Error, Result},
14};
15
16#[derive(Clone)]
17pub struct LogFormatter {
18    formatter: fn(DecoderConfig, &[u8]) -> Result<String>,
19    type_id: TypeId,
20}
21
22impl LogFormatter {
23    pub fn new<T: Tokenizable + Parameterize + Debug + 'static>() -> Self {
24        Self {
25            formatter: Self::format_log::<T>,
26            type_id: TypeId::of::<T>(),
27        }
28    }
29
30    fn format_log<T: Parameterize + Tokenizable + Debug>(
31        decoder_config: DecoderConfig,
32        bytes: &[u8],
33    ) -> Result<String> {
34        let token = ABIDecoder::new(decoder_config).decode(&T::param_type(), bytes)?;
35
36        Ok(format!("{:?}", T::from_token(token)?))
37    }
38
39    pub fn can_handle_type<T: Tokenizable + Parameterize + 'static>(&self) -> bool {
40        TypeId::of::<T>() == self.type_id
41    }
42
43    pub fn format(&self, decoder_config: DecoderConfig, bytes: &[u8]) -> Result<String> {
44        (self.formatter)(decoder_config, bytes)
45    }
46}
47
48impl Debug for LogFormatter {
49    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
50        f.debug_struct("LogFormatter")
51            .field("type_id", &self.type_id)
52            .finish()
53    }
54}
55
56/// Holds a unique log ID
57#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
58pub struct LogId(ContractId, String);
59
60/// Struct used to pass the log mappings from the Abigen
61#[derive(Debug, Clone, Default)]
62pub struct LogDecoder {
63    /// A mapping of LogId and param-type
64    log_formatters: HashMap<LogId, LogFormatter>,
65    decoder_config: DecoderConfig,
66}
67
68#[derive(Debug)]
69pub struct LogResult {
70    pub results: Vec<Result<String>>,
71}
72
73impl LogResult {
74    pub fn filter_succeeded(&self) -> Vec<&str> {
75        self.results
76            .iter()
77            .filter_map(|result| result.as_deref().ok())
78            .collect()
79    }
80
81    pub fn filter_failed(&self) -> Vec<&Error> {
82        self.results
83            .iter()
84            .filter_map(|result| result.as_ref().err())
85            .collect()
86    }
87}
88
89impl LogDecoder {
90    pub fn new(log_formatters: HashMap<LogId, LogFormatter>) -> Self {
91        Self {
92            log_formatters,
93            decoder_config: Default::default(),
94        }
95    }
96
97    pub fn set_decoder_config(&mut self, decoder_config: DecoderConfig) -> &mut Self {
98        self.decoder_config = decoder_config;
99        self
100    }
101
102    /// Get all logs results from the given receipts as `Result<String>`
103    pub fn decode_logs(&self, receipts: &[Receipt]) -> LogResult {
104        let results = receipts
105            .iter()
106            .extract_log_id_and_data()
107            .map(|(log_id, data)| self.format_log(&log_id, &data))
108            .collect();
109
110        LogResult { results }
111    }
112
113    fn format_log(&self, log_id: &LogId, data: &[u8]) -> Result<String> {
114        self.log_formatters
115            .get(log_id)
116            .ok_or_else(|| {
117                error!(
118                    Codec,
119                    "missing log formatter for log_id: `{:?}`, data: `{:?}`. \
120                     Consider adding external contracts using `with_contracts()`",
121                    log_id,
122                    data
123                )
124            })
125            .and_then(|log_formatter| log_formatter.format(self.decoder_config, data))
126    }
127
128    pub(crate) fn decode_last_log(&self, receipts: &[Receipt]) -> Result<String> {
129        receipts
130            .iter()
131            .rev()
132            .extract_log_id_and_data()
133            .next()
134            .ok_or_else(|| error!(Codec, "no receipts found for decoding last log"))
135            .and_then(|(log_id, data)| self.format_log(&log_id, &data))
136    }
137
138    pub(crate) fn decode_last_two_logs(&self, receipts: &[Receipt]) -> Result<(String, String)> {
139        let res = receipts
140            .iter()
141            .rev()
142            .extract_log_id_and_data()
143            .map(|(log_id, data)| self.format_log(&log_id, &data))
144            .take(2)
145            .collect::<Result<Vec<_>>>();
146
147        match res.as_deref() {
148            Ok([rhs, lhs]) => Ok((lhs.to_string(), rhs.to_string())),
149            Ok(some_slice) => Err(error!(
150                Codec,
151                "expected to have two logs. Found {}",
152                some_slice.len()
153            )),
154            Err(_) => Err(res.expect_err("must be an error")),
155        }
156    }
157
158    /// Get decoded logs with specific type from the given receipts.
159    /// Note that this method returns the actual type and not a `String` representation.
160    pub fn decode_logs_with_type<T: Tokenizable + Parameterize + 'static>(
161        &self,
162        receipts: &[Receipt],
163    ) -> Result<Vec<T>> {
164        let target_ids: HashSet<LogId> = self
165            .log_formatters
166            .iter()
167            .filter(|(_, log_formatter)| log_formatter.can_handle_type::<T>())
168            .map(|(log_id, _)| log_id.clone())
169            .collect();
170
171        receipts
172            .iter()
173            .extract_log_id_and_data()
174            .filter_map(|(log_id, bytes)| {
175                target_ids.contains(&log_id).then(|| {
176                    let token = ABIDecoder::new(self.decoder_config)
177                        .decode(&T::param_type(), bytes.as_slice())?;
178
179                    T::from_token(token)
180                })
181            })
182            .collect()
183    }
184
185    pub fn merge(&mut self, log_decoder: LogDecoder) {
186        self.log_formatters.extend(log_decoder.log_formatters);
187    }
188}
189
190trait ExtractLogIdData {
191    type Output: Iterator<Item = (LogId, Vec<u8>)>;
192    fn extract_log_id_and_data(self) -> Self::Output;
193}
194
195impl<'a, I: Iterator<Item = &'a Receipt>> ExtractLogIdData for I {
196    type Output = FilterMap<Self, fn(&Receipt) -> Option<(LogId, Vec<u8>)>>;
197    fn extract_log_id_and_data(self) -> Self::Output {
198        self.filter_map(|r| match r {
199            Receipt::LogData {
200                rb,
201                data: Some(data),
202                id,
203                ..
204            } => Some((LogId(*id, (*rb).to_string()), data.clone())),
205            Receipt::Log { ra, rb, id, .. } => {
206                Some((LogId(*id, (*rb).to_string()), ra.to_be_bytes().to_vec()))
207            }
208            _ => None,
209        })
210    }
211}
212
213pub fn log_formatters_lookup(
214    log_id_log_formatter_pairs: Vec<(String, LogFormatter)>,
215    contract_id: ContractId,
216) -> HashMap<LogId, LogFormatter> {
217    log_id_log_formatter_pairs
218        .into_iter()
219        .map(|(id, log_formatter)| (LogId(contract_id, id), log_formatter))
220        .collect()
221}