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