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        };
167        let soroban_response = SorobanTransactionResponse::new(response);
168
169        // Test extracting the return value - should fail
170        let result = soroban_response.get_return_value();
171        assert!(result.is_err());
172    }
173
174    // Helper function to create a mock GetTransactionResponse
175    fn create_mock_response(return_value: Option<ScVal>) -> GetTransactionResponse {
176        // Create a mock Soroban transaction meta
177        let soroban_meta = SorobanTransactionMeta {
178            ext: SorobanTransactionMetaExt::V0,
179            events: VecM::default(),
180            return_value: return_value.unwrap_or(ScVal::Void),
181            diagnostic_events: VecM::default(),
182        };
183
184        // Create a mock V3 transaction meta
185        let meta_v3 = TransactionMetaV3 {
186            ext: ExtensionPoint::V0,
187            tx_changes_before: LedgerEntryChanges::default(),
188            operations: VecM::default(),
189            tx_changes_after: LedgerEntryChanges::default(),
190            soroban_meta: Some(soroban_meta),
191        };
192
193        let transaction_result = TransactionResult {
194            fee_charged: 0,
195            result: TransactionResultResult::TxSuccess(VecM::default()),
196            ext: TransactionResultExt::V0,
197        };
198
199        // Create a mock GetTransactionResponse
200        GetTransactionResponse {
201            status: "success".to_string(),
202            envelope: None,
203            result: Some(transaction_result),
204            result_meta: Some(TransactionMeta::V3(meta_v3)),
205        }
206    }
207
208    #[test]
209    fn test_get_events_success() {
210        // Create a mock ContractEvent
211        let event1 = create_mock_contract_event();
212        let event2 = create_mock_contract_event();
213
214        // Create a VecM with the events
215        let events: VecM<ContractEvent> = vec![event1.clone(), event2.clone()].try_into().unwrap();
216
217        // Create a mock GetTransactionResponse with events
218        let response = create_mock_response_with_events(Some(ScVal::Void), events);
219        let soroban_response = SorobanTransactionResponse::new(response);
220
221        // Test extracting the events
222        let extracted_events = soroban_response.get_events().unwrap();
223        assert_eq!(extracted_events.len(), 2);
224    }
225
226    #[test]
227    fn test_get_events_no_meta() {
228        // Create a mock GetTransactionResponse with no transaction meta
229        let response = GetTransactionResponse {
230            status: "success".to_string(),
231            envelope: None,
232            result: None,
233            result_meta: None,
234        };
235        let soroban_response = SorobanTransactionResponse::new(response);
236
237        // Test extracting the events - should fail
238        let result = soroban_response.get_events();
239        assert!(result.is_err());
240    }
241
242    #[test]
243    fn test_get_events_empty() {
244        // Create a mock GetTransactionResponse with empty events
245        let response = create_mock_response(Some(ScVal::Void));
246        let soroban_response = SorobanTransactionResponse::new(response);
247
248        // Test extracting the events - should return empty vector
249        let events = soroban_response.get_events().unwrap();
250        assert_eq!(events.len(), 0);
251    }
252
253    #[test]
254    fn test_get_soroban_meta_success() {
255        // Create a mock GetTransactionResponse
256        let return_value = ScVal::U32(42);
257        let response = create_mock_response(Some(return_value.clone()));
258        let soroban_response = SorobanTransactionResponse::new(response);
259
260        // Test extracting the Soroban metadata
261        let soroban_meta = soroban_response.get_soroban_meta().unwrap();
262
263        // Verify the metadata contents
264        assert_eq!(soroban_meta.return_value, return_value);
265        assert_eq!(soroban_meta.events.len(), 0);
266        assert_eq!(soroban_meta.diagnostic_events.len(), 0);
267        assert!(matches!(soroban_meta.ext, SorobanTransactionMetaExt::V0));
268    }
269
270    #[test]
271    fn test_get_soroban_meta_no_meta() {
272        // Create a mock GetTransactionResponse with no transaction meta
273        let response = GetTransactionResponse {
274            status: "success".to_string(),
275            envelope: None,
276            result: None,
277            result_meta: None,
278        };
279        let soroban_response = SorobanTransactionResponse::new(response);
280
281        // Test extracting the Soroban metadata - should fail
282        let result = soroban_response.get_soroban_meta();
283        assert!(result.is_err());
284    }
285
286    // Helper function to create a mock GetTransactionResponse with custom events
287    fn create_mock_response_with_events(
288        return_value: Option<ScVal>,
289        events: VecM<stellar_xdr::curr::ContractEvent>,
290    ) -> GetTransactionResponse {
291        // Create a mock Soroban transaction meta
292        let soroban_meta = SorobanTransactionMeta {
293            ext: SorobanTransactionMetaExt::V0,
294            events,
295            return_value: return_value.unwrap_or(ScVal::Void),
296            diagnostic_events: VecM::default(),
297        };
298
299        // Create a mock V3 transaction meta
300        let meta_v3 = TransactionMetaV3 {
301            ext: ExtensionPoint::V0,
302            tx_changes_before: LedgerEntryChanges::default(),
303            operations: VecM::default(),
304            tx_changes_after: LedgerEntryChanges::default(),
305            soroban_meta: Some(soroban_meta),
306        };
307
308        let transaction_result = TransactionResult {
309            fee_charged: 0,
310            result: TransactionResultResult::TxSuccess(VecM::default()),
311            ext: TransactionResultExt::V0,
312        };
313
314        // Create a mock GetTransactionResponse
315        GetTransactionResponse {
316            status: "success".to_string(),
317            envelope: None,
318            result: Some(transaction_result),
319            result_meta: Some(TransactionMeta::V3(meta_v3)),
320        }
321    }
322}