soroban_rs/
response.rs

1use stellar_rpc_client::GetTransactionResponse;
2use stellar_xdr::curr::{ScVal, SorobanTransactionMeta, TransactionMeta, TransactionMetaV3};
3
4use crate::SorobanHelperError;
5
6/// Extended transaction response with methods to extract Soroban-specific data
7#[derive(Debug, Clone)]
8pub struct SorobanTransactionResponse {
9    /// The underlying RPC transaction response
10    pub response: GetTransactionResponse,
11}
12
13impl From<GetTransactionResponse> for SorobanTransactionResponse {
14    fn from(response: GetTransactionResponse) -> Self {
15        Self { response }
16    }
17}
18
19impl SorobanTransactionResponse {
20    /// Creates a new SorobanTransactionResponse from a GetTransactionResponse
21    pub fn new(response: GetTransactionResponse) -> Self {
22        Self { response }
23    }
24
25    /// Extracts the Soroban transaction return value from the transaction metadata
26    ///
27    /// # Returns
28    ///
29    /// The Soroban return value as an ScVal or an error if:
30    /// - The transaction result is not available
31    /// - The transaction metadata is not available
32    /// - The transaction metadata is not in V3 format
33    /// - The Soroban metadata is not available
34    pub fn get_return_value(&self) -> Result<ScVal, SorobanHelperError> {
35        // Check if result_meta exists
36        let result_meta = self.response.result_meta.as_ref().ok_or_else(|| {
37            SorobanHelperError::InvalidArgument("Transaction metadata not available".to_string())
38        })?;
39
40        // Extract the Soroban metadata from the transaction metadata
41        match result_meta {
42            TransactionMeta::V3(meta_v3) => self.extract_soroban_return_value(meta_v3),
43            _ => Err(SorobanHelperError::InvalidArgument(
44                "Transaction metadata is not in V3 format (not a Soroban transaction)".to_string(),
45            )),
46        }
47    }
48
49    /// Extracts the Soroban transaction events from the transaction metadata
50    ///
51    /// # Returns
52    ///
53    /// A vector of contract events or an error if:
54    /// - The transaction result is not available
55    /// - The transaction metadata is not available
56    /// - The transaction metadata is not in V3 format
57    /// - The Soroban metadata is not available
58    pub fn get_events(&self) -> Result<Vec<stellar_xdr::curr::ContractEvent>, SorobanHelperError> {
59        // Check if result_meta exists
60        let result_meta = self.response.result_meta.as_ref().ok_or_else(|| {
61            SorobanHelperError::InvalidArgument("Transaction metadata not available".to_string())
62        })?;
63
64        // Extract the Soroban metadata from the transaction metadata
65        match result_meta {
66            TransactionMeta::V3(meta_v3) => self.extract_soroban_events(meta_v3),
67            _ => Err(SorobanHelperError::InvalidArgument(
68                "Transaction metadata is not in V3 format (not a Soroban transaction)".to_string(),
69            )),
70        }
71    }
72
73    /// Helper method to extract the Soroban return value from a TransactionMetaV3
74    fn extract_soroban_return_value(
75        &self,
76        meta_v3: &TransactionMetaV3,
77    ) -> Result<ScVal, SorobanHelperError> {
78        // Extract the Soroban metadata
79        let soroban_meta = meta_v3.soroban_meta.as_ref().ok_or_else(|| {
80            SorobanHelperError::InvalidArgument("Soroban metadata not available".to_string())
81        })?;
82
83        // Return a clone of the return value
84        Ok(soroban_meta.return_value.clone())
85    }
86
87    /// Helper method to extract the Soroban events from a TransactionMetaV3
88    fn extract_soroban_events(
89        &self,
90        meta_v3: &TransactionMetaV3,
91    ) -> Result<Vec<stellar_xdr::curr::ContractEvent>, SorobanHelperError> {
92        // Extract the Soroban metadata
93        let soroban_meta = meta_v3.soroban_meta.as_ref().ok_or_else(|| {
94            SorobanHelperError::InvalidArgument("Soroban metadata not available".to_string())
95        })?;
96
97        // Convert the VecM to a Vec
98        let events = soroban_meta.events.to_vec();
99        Ok(events)
100    }
101
102    /// Extracts the full Soroban transaction metadata
103    ///
104    /// # Returns
105    ///
106    /// The Soroban transaction metadata or an error if:
107    /// - The transaction result is not available
108    /// - The transaction metadata is not available
109    /// - The transaction metadata is not in V3 format
110    /// - The Soroban metadata is not available
111    pub fn get_soroban_meta(&self) -> Result<SorobanTransactionMeta, SorobanHelperError> {
112        // Check if result_meta exists
113        let result_meta = self.response.result_meta.as_ref().ok_or_else(|| {
114            SorobanHelperError::InvalidArgument("Transaction metadata not available".to_string())
115        })?;
116
117        // Extract the Soroban metadata from the transaction metadata
118        match result_meta {
119            TransactionMeta::V3(meta_v3) => {
120                // Extract the Soroban metadata
121                let soroban_meta = meta_v3.soroban_meta.as_ref().ok_or_else(|| {
122                    SorobanHelperError::InvalidArgument(
123                        "Soroban metadata not available".to_string(),
124                    )
125                })?;
126
127                // Return a clone of the Soroban metadata
128                Ok(soroban_meta.clone())
129            }
130            _ => Err(SorobanHelperError::InvalidArgument(
131                "Transaction metadata is not in V3 format (not a Soroban transaction)".to_string(),
132            )),
133        }
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use crate::mock::create_mock_contract_event;
140
141    use super::*;
142    use stellar_xdr::curr::{
143        ContractEvent, ExtensionPoint, LedgerEntryChanges, SorobanTransactionMetaExt,
144        TransactionResult, TransactionResultExt, TransactionResultResult, VecM,
145    };
146
147    #[test]
148    fn test_get_return_value_success() {
149        // Create a mock GetTransactionResponse with a V3 transaction meta
150        let response = create_mock_response(Some(ScVal::U32(42)));
151        let soroban_response = SorobanTransactionResponse::new(response);
152
153        // Test extracting the return value
154        let return_value = soroban_response.get_return_value().unwrap();
155        assert_eq!(return_value, ScVal::U32(42));
156    }
157
158    #[test]
159    fn test_get_return_value_no_meta() {
160        // Create a mock GetTransactionResponse with no transaction meta
161        let response = GetTransactionResponse {
162            status: "success".to_string(),
163            envelope: None,
164            result: None,
165            result_meta: None,
166            ledger: None,
167            events: stellar_rpc_client::GetTransactionEvents {
168                contract_events: vec![],
169                diagnostic_events: vec![],
170                transaction_events: vec![],
171            },
172        };
173        let soroban_response = SorobanTransactionResponse::new(response);
174
175        // Test extracting the return value - should fail
176        let result = soroban_response.get_return_value();
177        assert!(result.is_err());
178    }
179
180    // Helper function to create a mock GetTransactionResponse
181    fn create_mock_response(return_value: Option<ScVal>) -> GetTransactionResponse {
182        // Create a mock Soroban transaction meta
183        let soroban_meta = SorobanTransactionMeta {
184            ext: SorobanTransactionMetaExt::V0,
185            events: VecM::default(),
186            return_value: return_value.unwrap_or(ScVal::Void),
187            diagnostic_events: VecM::default(),
188        };
189
190        // Create a mock V3 transaction meta
191        let meta_v3 = TransactionMetaV3 {
192            ext: ExtensionPoint::V0,
193            tx_changes_before: LedgerEntryChanges::default(),
194            operations: VecM::default(),
195            tx_changes_after: LedgerEntryChanges::default(),
196            soroban_meta: Some(soroban_meta),
197        };
198
199        let transaction_result = TransactionResult {
200            fee_charged: 0,
201            result: TransactionResultResult::TxSuccess(VecM::default()),
202            ext: TransactionResultExt::V0,
203        };
204
205        // Create a mock GetTransactionResponse
206        GetTransactionResponse {
207            status: "success".to_string(),
208            envelope: None,
209            result: Some(transaction_result),
210            result_meta: Some(TransactionMeta::V3(meta_v3)),
211            ledger: Some(123456), // Example ledger number
212            events: stellar_rpc_client::GetTransactionEvents {
213                contract_events: vec![],
214                diagnostic_events: vec![],
215                transaction_events: vec![],
216            },
217        }
218    }
219
220    #[test]
221    fn test_get_events_success() {
222        // Create a mock ContractEvent
223        let event1 = create_mock_contract_event();
224        let event2 = create_mock_contract_event();
225
226        // Create a VecM with the events
227        let events: VecM<ContractEvent> = vec![event1.clone(), event2.clone()].try_into().unwrap();
228
229        // Create a mock GetTransactionResponse with events
230        let response = create_mock_response_with_events(Some(ScVal::Void), events);
231        let soroban_response = SorobanTransactionResponse::new(response);
232
233        // Test extracting the events
234        let extracted_events = soroban_response.get_events().unwrap();
235        assert_eq!(extracted_events.len(), 2);
236    }
237
238    #[test]
239    fn test_get_events_no_meta() {
240        // Create a mock GetTransactionResponse with no transaction meta
241        let response = GetTransactionResponse {
242            status: "success".to_string(),
243            envelope: None,
244            result: None,
245            result_meta: None,
246            ledger: None,
247            events: stellar_rpc_client::GetTransactionEvents {
248                contract_events: vec![],
249                diagnostic_events: vec![],
250                transaction_events: vec![],
251            },
252        };
253        let soroban_response = SorobanTransactionResponse::new(response);
254
255        // Test extracting the events - should fail
256        let result = soroban_response.get_events();
257        assert!(result.is_err());
258    }
259
260    #[test]
261    fn test_get_events_empty() {
262        // Create a mock GetTransactionResponse with empty events
263        let response = create_mock_response(Some(ScVal::Void));
264        let soroban_response = SorobanTransactionResponse::new(response);
265
266        // Test extracting the events - should return empty vector
267        let events = soroban_response.get_events().unwrap();
268        assert_eq!(events.len(), 0);
269    }
270
271    #[test]
272    fn test_get_soroban_meta_success() {
273        // Create a mock GetTransactionResponse
274        let return_value = ScVal::U32(42);
275        let response = create_mock_response(Some(return_value.clone()));
276        let soroban_response = SorobanTransactionResponse::new(response);
277
278        // Test extracting the Soroban metadata
279        let soroban_meta = soroban_response.get_soroban_meta().unwrap();
280
281        // Verify the metadata contents
282        assert_eq!(soroban_meta.return_value, return_value);
283        assert_eq!(soroban_meta.events.len(), 0);
284        assert_eq!(soroban_meta.diagnostic_events.len(), 0);
285        assert!(matches!(soroban_meta.ext, SorobanTransactionMetaExt::V0));
286    }
287
288    #[test]
289    fn test_get_soroban_meta_no_meta() {
290        // Create a mock GetTransactionResponse with no transaction meta
291        let response = GetTransactionResponse {
292            status: "success".to_string(),
293            envelope: None,
294            result: None,
295            result_meta: None,
296            ledger: None,
297            events: stellar_rpc_client::GetTransactionEvents {
298                contract_events: vec![],
299                diagnostic_events: vec![],
300                transaction_events: vec![],
301            },
302        };
303        let soroban_response = SorobanTransactionResponse::new(response);
304
305        // Test extracting the Soroban metadata - should fail
306        let result = soroban_response.get_soroban_meta();
307        assert!(result.is_err());
308    }
309
310    // Helper function to create a mock GetTransactionResponse with custom events
311    fn create_mock_response_with_events(
312        return_value: Option<ScVal>,
313        events: VecM<stellar_xdr::curr::ContractEvent>,
314    ) -> GetTransactionResponse {
315        // Create a mock Soroban transaction meta
316        let soroban_meta = SorobanTransactionMeta {
317            ext: SorobanTransactionMetaExt::V0,
318            events,
319            return_value: return_value.unwrap_or(ScVal::Void),
320            diagnostic_events: VecM::default(),
321        };
322
323        // Create a mock V3 transaction meta
324        let meta_v3 = TransactionMetaV3 {
325            ext: ExtensionPoint::V0,
326            tx_changes_before: LedgerEntryChanges::default(),
327            operations: VecM::default(),
328            tx_changes_after: LedgerEntryChanges::default(),
329            soroban_meta: Some(soroban_meta),
330        };
331
332        let transaction_result = TransactionResult {
333            fee_charged: 0,
334            result: TransactionResultResult::TxSuccess(VecM::default()),
335            ext: TransactionResultExt::V0,
336        };
337
338        // Create a mock GetTransactionResponse
339        GetTransactionResponse {
340            status: "success".to_string(),
341            envelope: None,
342            result: Some(transaction_result),
343            result_meta: Some(TransactionMeta::V3(meta_v3)),
344            ledger: Some(123456), // Example ledger number
345            events: stellar_rpc_client::GetTransactionEvents {
346                contract_events: vec![],
347                diagnostic_events: vec![],
348                transaction_events: vec![],
349            },
350        }
351    }
352}