test_tube/runner/
result.rs

1use crate::runner::error::{DecodeError, RunnerError};
2use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
3use base64::Engine;
4use cosmrs::proto::cosmos::base::abci::v1beta1::{GasInfo, TxMsgData};
5use cosmrs::proto::tendermint::abci::ResponseFinalizeBlock;
6use cosmrs::proto::tendermint::v0_37::abci::ResponseDeliverTx;
7use cosmrs::rpc::endpoint::broadcast::tx_commit::Response as TxCommitResponse;
8use cosmrs::tendermint::abci::types::ExecTxResult;
9use cosmwasm_std::{Attribute, Event};
10use prost::Message;
11use std::ffi::CString;
12use std::str::Utf8Error;
13
14pub type RunnerResult<T> = Result<T, RunnerError>;
15pub type RunnerExecuteResult<R> = Result<ExecuteResponse<R>, RunnerError>;
16
17#[derive(Debug, Clone, PartialEq)]
18pub struct ExecuteResponse<R>
19where
20    R: prost::Message + Default,
21{
22    pub data: R,
23    pub raw_data: Vec<u8>,
24    pub events: Vec<Event>,
25    pub gas_info: GasInfo,
26}
27
28impl<R> TryFrom<ExecTxResult> for ExecuteResponse<R>
29where
30    R: prost::Message + Default,
31{
32    type Error = RunnerError;
33
34    fn try_from(res: ExecTxResult) -> Result<Self, Self::Error> {
35        let tx_msg_data =
36            TxMsgData::decode(res.data.as_ref()).map_err(DecodeError::ProtoDecodeError)?;
37
38        let msg_data = tx_msg_data
39            .msg_responses
40            // since this tx contains exactly 1 msg
41            // when getting none of them, that means error
42            .first()
43            .ok_or(RunnerError::ExecuteError { msg: res.log })?;
44
45        let data = R::decode(msg_data.value.as_slice()).map_err(DecodeError::ProtoDecodeError)?;
46
47        let events = res
48            .events
49            .into_iter()
50            .map(|e| -> Result<Event, DecodeError> {
51                Ok(Event::new(e.kind).add_attributes(
52                    e.attributes
53                        .into_iter()
54                        .map(|a| -> Result<Attribute, Utf8Error> {
55                            Ok(Attribute {
56                                key: std::str::from_utf8(a.key_bytes())?.to_string(),
57                                value: std::str::from_utf8(a.value_bytes())?.to_string(),
58                            })
59                        })
60                        .collect::<Result<Vec<Attribute>, Utf8Error>>()?,
61                ))
62            })
63            .collect::<Result<Vec<Event>, DecodeError>>()?;
64
65        Ok(ExecuteResponse {
66            data,
67            raw_data: res.data.to_vec(),
68            events,
69            gas_info: GasInfo {
70                gas_wanted: res.gas_wanted as u64,
71                gas_used: res.gas_used as u64,
72            },
73        })
74    }
75}
76
77impl<R> TryFrom<TxCommitResponse> for ExecuteResponse<R>
78where
79    R: prost::Message + Default,
80{
81    type Error = RunnerError;
82
83    fn try_from(tx_commit_response: TxCommitResponse) -> Result<Self, Self::Error> {
84        let res = tx_commit_response.tx_result;
85        let tx_msg_data =
86            TxMsgData::decode(res.data.as_ref()).map_err(DecodeError::ProtoDecodeError)?;
87
88        let msg_data = tx_msg_data
89            .msg_responses
90            // since this tx contains exactly 1 msg
91            // when getting none of them, that means error
92            .first()
93            .ok_or(RunnerError::ExecuteError { msg: res.log })?;
94
95        let data = R::decode(msg_data.value.as_slice()).map_err(DecodeError::ProtoDecodeError)?;
96
97        let events = res
98            .events
99            .into_iter()
100            .map(|e| -> Result<Event, DecodeError> {
101                Ok(Event::new(e.kind).add_attributes(
102                    e.attributes
103                        .into_iter()
104                        .map(|a| -> Result<Attribute, Utf8Error> {
105                            Ok(Attribute {
106                                key: std::str::from_utf8(a.key_bytes())?.to_string(),
107                                value: std::str::from_utf8(a.value_bytes())?.to_string(),
108                            })
109                        })
110                        .collect::<Result<Vec<Attribute>, Utf8Error>>()?,
111                ))
112            })
113            .collect::<Result<Vec<Event>, DecodeError>>()?;
114
115        Ok(Self {
116            data,
117            raw_data: res.data.to_vec(),
118            events,
119            gas_info: GasInfo {
120                gas_wanted: res.gas_wanted as u64,
121                gas_used: res.gas_used as u64,
122            },
123        })
124    }
125}
126
127impl<R> TryFrom<ResponseDeliverTx> for ExecuteResponse<R>
128where
129    R: prost::Message + Default,
130{
131    type Error = RunnerError;
132
133    fn try_from(res: ResponseDeliverTx) -> Result<Self, Self::Error> {
134        let tx_msg_data =
135            TxMsgData::decode(res.data.as_ref()).map_err(DecodeError::ProtoDecodeError)?;
136
137        let msg_data = tx_msg_data
138            .msg_responses
139            // since this tx contains exactly 1 msg
140            // when getting none of them, that means error
141            .first()
142            .ok_or(RunnerError::ExecuteError { msg: res.log })?;
143
144        let data = R::decode(msg_data.value.as_slice()).map_err(DecodeError::ProtoDecodeError)?;
145
146        let events = res
147            .events
148            .into_iter()
149            .map(|e| -> Result<Event, DecodeError> {
150                Ok(Event::new(e.r#type).add_attributes(
151                    e.attributes
152                        .into_iter()
153                        .map(|a| -> Result<Attribute, Utf8Error> {
154                            Ok(Attribute {
155                                key: a.key.to_string(),
156                                value: a.value.to_string(),
157                            })
158                        })
159                        .collect::<Result<Vec<Attribute>, Utf8Error>>()?,
160                ))
161            })
162            .collect::<Result<Vec<Event>, DecodeError>>()?;
163
164        Ok(Self {
165            data,
166            raw_data: res.data.to_vec(),
167            events,
168            gas_info: GasInfo {
169                gas_wanted: res.gas_wanted as u64,
170                gas_used: res.gas_used as u64,
171            },
172        })
173    }
174}
175
176impl<R> TryFrom<ResponseFinalizeBlock> for ExecuteResponse<R>
177where
178    R: prost::Message + Default,
179{
180    type Error = RunnerError;
181
182    fn try_from(res: ResponseFinalizeBlock) -> Result<Self, Self::Error> {
183        let tx_results = res.tx_results;
184        let first_tx_result = tx_results
185            .first()
186            .expect("tx_results should have at least one element");
187
188        let tx_msg_data = TxMsgData::decode(first_tx_result.data.clone().as_ref())
189            .map_err(DecodeError::ProtoDecodeError)?;
190
191        let msg_data = tx_msg_data
192            .msg_responses
193            // since this tx contains exactly 1 msg
194            // when getting none of them, that means error
195            .first()
196            .ok_or(RunnerError::ExecuteError {
197                msg: first_tx_result.log.clone(),
198            })?;
199
200        let data = R::decode(msg_data.value.as_slice()).map_err(DecodeError::ProtoDecodeError)?;
201
202        let events = first_tx_result
203            .events
204            .clone()
205            .into_iter()
206            .map(|e| -> Result<Event, DecodeError> {
207                Ok(Event::new(e.r#type).add_attributes(
208                    e.attributes
209                        .into_iter()
210                        .map(|a| -> Result<Attribute, Utf8Error> {
211                            Ok(Attribute {
212                                key: a.key.to_string(),
213                                value: a.value.to_string(),
214                            })
215                        })
216                        .collect::<Result<Vec<Attribute>, Utf8Error>>()?,
217                ))
218            })
219            .collect::<Result<Vec<Event>, DecodeError>>()?;
220
221        let gas_wanted = first_tx_result.gas_used as u64;
222        let gas_used = first_tx_result.gas_used as u64;
223
224        Ok(Self {
225            data,
226            raw_data: msg_data.value.to_vec(),
227            events,
228            gas_info: GasInfo {
229                gas_wanted,
230                gas_used,
231            },
232        })
233    }
234}
235
236/// `RawResult` facilitates type conversions between Go and Rust,
237///
238/// Since Go struct could not be exposed via cgo due to limitations on
239/// its unstable behavior of its memory layout.
240/// So, apart from passing primitive types, we need to:
241///
242///   Go { T -> bytes(T) -> base64 -> *c_char }
243///                      ↓
244///   Rust { *c_char -> base64 -> bytes(T') -> T' }
245///
246/// Where T and T' are corresponding data structures, regardless of their encoding
247/// in their respective language plus error information.
248///
249/// Resulted bytes are tagged by prepending 4 bytes to byte array
250/// before base64 encoded. The prepended byte represents
251///   0 -> Ok
252///   1 -> QueryError
253///   2 -> ExecuteError
254///
255/// The rest are undefined and remaining spaces are reserved for future use.
256#[derive(Debug)]
257pub struct RawResult(Result<Vec<u8>, RunnerError>);
258
259impl RawResult {
260    /// Convert ptr to AppResult. Check the first byte tag before decoding the rest of the bytes into expected type
261    ///
262    /// # Safety
263    ///
264    /// `ptr` must contain a valid pointer to a null-terminated C string with base64 encoded bytes.
265    /// The decoded content must be a valid utf-8 string.
266    pub unsafe fn from_ptr(ptr: *mut std::os::raw::c_char) -> Option<Self> {
267        if ptr.is_null() {
268            return None;
269        }
270
271        let c_string = unsafe { CString::from_raw(ptr) };
272        let base64_bytes = c_string.to_bytes();
273        let bytes = BASE64_STANDARD.decode(base64_bytes).unwrap();
274        let code = bytes[0];
275        let content = &bytes[1..];
276
277        if code == 0 {
278            Some(Self(Ok(content.to_vec())))
279        } else {
280            let content_string = CString::new(content).unwrap().to_string_lossy().to_string();
281
282            let error = match code {
283                1 => RunnerError::QueryError {
284                    msg: content_string,
285                },
286                2 => RunnerError::ExecuteError {
287                    msg: content_string,
288                },
289                _ => panic!("undefined code: {}", code),
290            };
291            Some(Self(Err(error)))
292        }
293    }
294
295    /// Convert ptr to AppResult. Use this function only when it is sure that the
296    /// pointer is not a null pointer.
297    ///
298    /// # Safety
299    /// There is a potential null pointer here, need to be extra careful before
300    /// calling this function
301    pub unsafe fn from_non_null_ptr(ptr: *mut std::os::raw::c_char) -> Self {
302        Self::from_ptr(ptr).expect("Must ensure that the pointer is not null")
303    }
304
305    pub fn into_result(self) -> Result<Vec<u8>, RunnerError> {
306        self.0
307    }
308}