1use std::collections::VecDeque;
2
3use fuel_tx::Receipt;
4use fuels_core::{
5 codec::{ABIDecoder, DecoderConfig},
6 types::{
7 ContractId, Token,
8 errors::{Error, Result, error},
9 param_types::ParamType,
10 },
11};
12
13pub struct ReceiptParser {
14 receipts: VecDeque<Receipt>,
15 decoder: ABIDecoder,
16}
17
18impl ReceiptParser {
19 pub fn new(receipts: &[Receipt], decoder_config: DecoderConfig) -> Self {
20 let relevant_receipts = receipts
21 .iter()
22 .filter(|receipt| matches!(receipt, Receipt::ReturnData { .. } | Receipt::Call { .. }))
23 .cloned()
24 .collect();
25
26 Self {
27 receipts: relevant_receipts,
28 decoder: ABIDecoder::new(decoder_config),
29 }
30 }
31
32 pub fn parse_call(
35 &mut self,
36 contract_id: ContractId,
37 output_param: &ParamType,
38 ) -> Result<Token> {
39 let data = self
40 .extract_contract_call_data(contract_id)
41 .ok_or_else(|| Self::missing_receipts_error(output_param))?;
42
43 self.decoder.decode(output_param, data.as_slice())
44 }
45
46 pub fn parse_script(self, output_param: &ParamType) -> Result<Token> {
47 let data = self
48 .extract_script_data()
49 .ok_or_else(|| Self::missing_receipts_error(output_param))?;
50
51 self.decoder.decode(output_param, data.as_slice())
52 }
53
54 fn missing_receipts_error(output_param: &ParamType) -> Error {
55 error!(
56 Codec,
57 "`ReceiptDecoder`: failed to find matching receipts entry for {output_param:?}"
58 )
59 }
60
61 pub fn extract_contract_call_data(&mut self, target_contract: ContractId) -> Option<Vec<u8>> {
62 let mut nested_calls_stack = vec![];
64
65 while let Some(receipt) = self.receipts.pop_front() {
66 if let Receipt::Call { to, .. } = receipt {
67 nested_calls_stack.push(to);
68 } else if let Receipt::ReturnData {
69 data,
70 id: return_id,
71 ..
72 } = receipt
73 {
74 let call_id = nested_calls_stack.pop();
75
76 debug_assert_eq!(call_id.unwrap(), return_id);
78
79 if nested_calls_stack.is_empty() {
80 debug_assert_eq!(target_contract, return_id);
82
83 return data.clone();
84 }
85 }
86 }
87
88 None
89 }
90
91 fn extract_script_data(&self) -> Option<Vec<u8>> {
92 self.receipts.iter().find_map(|receipt| match receipt {
93 Receipt::ReturnData {
94 id,
95 data: Some(data),
96 ..
97 } if *id == ContractId::zeroed() => Some(data.clone()),
98 _ => None,
99 })
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use fuel_tx::ScriptExecutionResult;
106 use fuels_core::traits::{Parameterize, Tokenizable};
107
108 use super::*;
109
110 const RECEIPT_DATA: &[u8; 3] = &[8, 8, 3];
111 const DECODED_DATA: &[u8; 3] = &[8, 8, 3];
112
113 fn target_contract() -> ContractId {
114 ContractId::from([1u8; 32])
115 }
116
117 fn get_return_data_receipt(id: ContractId, data: &[u8]) -> Receipt {
118 Receipt::ReturnData {
119 id,
120 ptr: Default::default(),
121 len: Default::default(),
122 digest: Default::default(),
123 data: Some(data.to_vec()),
124 pc: Default::default(),
125 is: Default::default(),
126 }
127 }
128
129 fn get_call_receipt(to: ContractId) -> Receipt {
130 Receipt::Call {
131 id: Default::default(),
132 to,
133 amount: Default::default(),
134 asset_id: Default::default(),
135 gas: Default::default(),
136 param1: Default::default(),
137 param2: Default::default(),
138 pc: Default::default(),
139 is: Default::default(),
140 }
141 }
142
143 fn get_relevant_receipts() -> Vec<Receipt> {
144 let id = target_contract();
145 vec![
146 get_call_receipt(id),
147 get_return_data_receipt(id, RECEIPT_DATA),
148 ]
149 }
150
151 #[tokio::test]
152 async fn receipt_parser_filters_receipts() -> Result<()> {
153 let mut receipts = vec![
154 Receipt::Revert {
155 id: Default::default(),
156 ra: Default::default(),
157 pc: Default::default(),
158 is: Default::default(),
159 },
160 Receipt::Log {
161 id: Default::default(),
162 ra: Default::default(),
163 rb: Default::default(),
164 rc: Default::default(),
165 rd: Default::default(),
166 pc: Default::default(),
167 is: Default::default(),
168 },
169 Receipt::LogData {
170 id: Default::default(),
171 ra: Default::default(),
172 rb: Default::default(),
173 ptr: Default::default(),
174 len: Default::default(),
175 digest: Default::default(),
176 data: Default::default(),
177 pc: Default::default(),
178 is: Default::default(),
179 },
180 Receipt::ScriptResult {
181 result: ScriptExecutionResult::Success,
182 gas_used: Default::default(),
183 },
184 ];
185 let relevant_receipts = get_relevant_receipts();
186 receipts.extend(relevant_receipts.clone());
187
188 let parser = ReceiptParser::new(&receipts, Default::default());
189
190 assert_eq!(parser.receipts, relevant_receipts);
191
192 Ok(())
193 }
194
195 #[tokio::test]
196 async fn receipt_parser_empty_receipts() -> Result<()> {
197 let receipts = [];
198 let output_param = ParamType::U8;
199
200 let error = ReceiptParser::new(&receipts, Default::default())
201 .parse_call(target_contract(), &output_param)
202 .expect_err("should error");
203
204 let expected_error = ReceiptParser::missing_receipts_error(&output_param);
205 assert_eq!(error.to_string(), expected_error.to_string());
206
207 Ok(())
208 }
209
210 #[tokio::test]
211 async fn receipt_parser_extract_return_data() -> Result<()> {
212 let receipts = get_relevant_receipts();
213
214 let mut parser = ReceiptParser::new(&receipts, Default::default());
215
216 let token = parser
217 .parse_call(target_contract(), &<[u8; 3]>::param_type())
218 .expect("parsing should succeed");
219
220 assert_eq!(&<[u8; 3]>::from_token(token)?, DECODED_DATA);
221
222 Ok(())
223 }
224
225 #[tokio::test]
226 async fn receipt_parser_extracts_top_level_call_receipts() -> Result<()> {
227 const CORRECT_DATA_1: [u8; 3] = [1, 2, 3];
228 const CORRECT_DATA_2: [u8; 3] = [5, 6, 7];
229
230 let contract_top_lvl = target_contract();
231 let contract_nested = ContractId::from([9u8; 32]);
232
233 let receipts = vec![
234 get_call_receipt(contract_top_lvl),
235 get_call_receipt(contract_nested),
236 get_return_data_receipt(contract_nested, &[9, 9, 9]),
237 get_return_data_receipt(contract_top_lvl, &CORRECT_DATA_1),
238 get_call_receipt(contract_top_lvl),
239 get_call_receipt(contract_nested),
240 get_return_data_receipt(contract_nested, &[7, 7, 7]),
241 get_return_data_receipt(contract_top_lvl, &CORRECT_DATA_2),
242 ];
243
244 let mut parser = ReceiptParser::new(&receipts, Default::default());
245
246 let token_1 = parser
247 .parse_call(contract_top_lvl, &<[u8; 3]>::param_type())
248 .expect("parsing should succeed");
249 let token_2 = parser
250 .parse_call(contract_top_lvl, &<[u8; 3]>::param_type())
251 .expect("parsing should succeed");
252
253 assert_eq!(&<[u8; 3]>::from_token(token_1)?, &CORRECT_DATA_1);
254 assert_eq!(&<[u8; 3]>::from_token(token_2)?, &CORRECT_DATA_2);
255
256 Ok(())
257 }
258}