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#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
58pub struct LogId(ContractId, String);
59
60#[derive(Debug, Clone, Default)]
62pub struct LogDecoder {
63 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 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 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}