fuel_core_wasm_executor/
utils.rs

1use fuel_core_executor::executor::ExecutionOptions;
2use fuel_core_storage::transactional::Changes;
3use fuel_core_types::{
4    blockchain::block::Block,
5    services::{
6        Uncommitted,
7        block_producer::Components,
8        executor::{
9            Error as ExecutorError,
10            ExecutionResult,
11            UncommittedResult,
12            ValidationResult,
13        },
14    },
15};
16#[cfg(feature = "std")]
17use fuel_core_types_v0::services::executor::Error as ExecutorErrorV0;
18
19/// Pack a pointer and length into an `u64`.
20pub fn pack_ptr_and_len(ptr: u32, len: u32) -> u64 {
21    (u64::from(len) << 32) | u64::from(ptr)
22}
23
24/// Unpacks an `u64` into the pointer and length.
25pub fn unpack_ptr_and_len(val: u64) -> (u32, u32) {
26    let ptr = u32::try_from(val & (u32::MAX as u64))
27        .expect("It only contains first 32 bytes; qed");
28    let len = u32::try_from(val >> 32).expect("It only contains first 32 bytes; qed");
29
30    (ptr, len)
31}
32
33/// Pack a `exists`, `size` and `result` into one `u64`.
34pub fn pack_exists_size_result(exists: bool, size: u32, result: u16) -> u64 {
35    ((u64::from(result) << 33) | (u64::from(size) << 1)) | u64::from(exists)
36}
37
38/// Unpacks an `u64` into `exists`, `size` and `result`.
39pub fn unpack_exists_size_result(val: u64) -> (bool, u32, u16) {
40    let exists = (val & 1u64) != 0;
41    let size = u32::try_from((val >> 1) & (u32::MAX as u64))
42        .expect("It only contains first 32 bytes; qed");
43    let result = u16::try_from((val >> 33) & (u16::MAX as u64))
44        .expect("It only contains first 16 bytes; qed");
45
46    (exists, size, result)
47}
48
49/// The input type for the WASM executor. Enum allows handling different
50/// versions of the input without introducing new host functions.
51#[derive(Debug, serde::Serialize)]
52pub enum InputSerializationType<'a> {
53    V1 {
54        block: WasmSerializationBlockTypes<'a, ()>,
55        options: ExecutionOptions,
56    },
57}
58
59#[derive(Debug, serde::Deserialize)]
60pub enum InputDeserializationType {
61    V1 {
62        block: WasmDeserializationBlockTypes<()>,
63        options: ExecutionOptions,
64    },
65}
66
67#[derive(Debug, serde::Serialize)]
68pub enum WasmSerializationBlockTypes<'a, TxSource> {
69    /// DryRun mode where P is being produced.
70    DryRun(Components<TxSource>),
71    /// Production mode where P is being produced.
72    Production(Components<TxSource>),
73    /// Validation of a produced block.
74    Validation(&'a Block),
75}
76
77#[derive(Debug, serde::Deserialize)]
78pub enum WasmDeserializationBlockTypes<TxSource> {
79    /// DryRun mode where P is being produced.
80    DryRun(Components<TxSource>),
81    /// Production mode where P is being produced.
82    Production(Components<TxSource>),
83    /// Validation of a produced block.
84    Validation(Block),
85}
86
87/// The JSON version of the executor error. The serialization and deserialization
88/// of the JSON error are less sensitive to the order of the variants in the enum.
89/// It simplifies the error conversion between different versions of the execution.
90///
91/// If deserialization fails, it returns a string representation of the error that
92/// still has useful information, even if the error is not supported by the native executor.
93#[derive(Debug, serde::Serialize, serde::Deserialize)]
94pub struct JSONError(String);
95
96#[cfg(feature = "std")]
97impl From<ExecutorErrorV0> for JSONError {
98    fn from(value: ExecutorErrorV0) -> Self {
99        let json = serde_json::to_string(&value).unwrap_or_else(|e| {
100            anyhow::anyhow!("Failed to serialize the V0 error: {:?}", e).to_string()
101        });
102        JSONError(json)
103    }
104}
105
106impl From<ExecutorError> for JSONError {
107    fn from(value: ExecutorError) -> Self {
108        let json = serde_json::to_string(&value).unwrap_or_else(|e| {
109            anyhow::anyhow!("Failed to serialize the error: {:?}", e).to_string()
110        });
111        JSONError(json)
112    }
113}
114
115impl From<JSONError> for ExecutorError {
116    fn from(value: JSONError) -> Self {
117        serde_json::from_str(&value.0).unwrap_or(ExecutorError::Other(value.0))
118    }
119}
120
121/// The return type for the WASM executor. Enum allows handling different
122/// versions of the return without introducing new host functions.
123#[cfg(feature = "std")]
124#[derive(Debug, serde::Serialize, serde::Deserialize)]
125pub enum ReturnType {
126    ExecutionV0(
127        Result<Uncommitted<ExecutionResult<ExecutorErrorV0>, Changes>, ExecutorErrorV0>,
128    ),
129    ExecutionV1(Result<Uncommitted<ExecutionResult<JSONError>, Changes>, JSONError>),
130    Validation(Result<Uncommitted<ValidationResult, Changes>, JSONError>),
131}
132
133/// The return type for the WASM executor. Enum allows handling different
134/// versions of the return without introducing new host functions.
135#[cfg(not(feature = "std"))]
136#[derive(Debug, serde::Serialize, serde::Deserialize)]
137#[allow(clippy::large_enum_variant)]
138pub enum ReturnType {
139    /// WASM executor doesn't use this variant, so from its perspective it is empty.
140    ExecutionV0,
141    ExecutionV1(Result<Uncommitted<ExecutionResult<JSONError>, Changes>, JSONError>),
142    Validation(Result<Uncommitted<ValidationResult, Changes>, JSONError>),
143}
144
145/// Converts the latest execution result to the `ExecutionV1`.
146pub fn convert_to_v1_execution_result(
147    result: Result<UncommittedResult<Changes>, ExecutorError>,
148) -> Result<Uncommitted<ExecutionResult<JSONError>, Changes>, JSONError> {
149    result
150        .map(|result| {
151            let (result, changes) = result.into();
152            let ExecutionResult {
153                block,
154                skipped_transactions,
155                tx_status,
156                events,
157            } = result;
158
159            let skipped_transactions: Vec<_> = skipped_transactions
160                .into_iter()
161                .map(|(id, error)| (id, JSONError::from(error)))
162                .collect();
163
164            let result = ExecutionResult {
165                block,
166                skipped_transactions,
167                tx_status,
168                events,
169            };
170
171            Uncommitted::new(result, changes)
172        })
173        .map_err(JSONError::from)
174}
175
176/// Converts the `ExecutionV1` to latest execution result.
177pub fn convert_from_v1_execution_result(
178    result: Result<Uncommitted<ExecutionResult<JSONError>, Changes>, JSONError>,
179) -> Result<UncommittedResult<Changes>, ExecutorError> {
180    result
181        .map(|result| {
182            let (result, changes) = result.into();
183            let ExecutionResult {
184                block,
185                skipped_transactions,
186                tx_status,
187                events,
188            } = result;
189
190            let skipped_transactions: Vec<_> = skipped_transactions
191                .into_iter()
192                .map(|(id, error)| (id, ExecutorError::from(error)))
193                .collect();
194
195            let result = ExecutionResult {
196                block,
197                skipped_transactions,
198                tx_status,
199                events,
200            };
201
202            Uncommitted::new(result, changes)
203        })
204        .map_err(ExecutorError::from)
205}
206
207/// Converts the `ExecutionV0` to latest execution result.
208#[cfg(feature = "std")]
209pub fn convert_from_v0_execution_result(
210    result: Result<
211        Uncommitted<ExecutionResult<ExecutorErrorV0>, Changes>,
212        ExecutorErrorV0,
213    >,
214) -> Result<UncommittedResult<Changes>, ExecutorError> {
215    result
216        .map(|result| {
217            let (result, changes) = result.into();
218            let ExecutionResult {
219                block,
220                skipped_transactions,
221                tx_status,
222                events,
223            } = result;
224
225            let skipped_transactions: Vec<_> = skipped_transactions
226                .into_iter()
227                .map(|(id, error)| (id, ExecutorError::from(JSONError::from(error))))
228                .collect();
229
230            let result = ExecutionResult {
231                block,
232                skipped_transactions,
233                tx_status,
234                events,
235            };
236
237            Uncommitted::new(result, changes)
238        })
239        .map_err(JSONError::from)
240        .map_err(ExecutorError::from)
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246    use fuel_core_types::services::executor::TransactionValidityError;
247    #[cfg(feature = "std")]
248    use fuel_core_types_v0::services::executor::TransactionValidityError as TransactionValidityErrorV0;
249    use proptest::prelude::prop::*;
250
251    proptest::proptest! {
252        #[test]
253        fn can_pack_any_values(exists: bool, size: u32, result: u16) {
254            pack_exists_size_result(exists, size, result);
255        }
256
257        #[test]
258        fn can_unpack_any_values(value: u64) {
259            let _ = unpack_exists_size_result(value);
260        }
261
262
263        #[test]
264        fn unpacks_packed_values(exists: bool, size: u32, result: u16) {
265            let packed = pack_exists_size_result(exists, size, result);
266            let (unpacked_exists, unpacked_size, unpacked_result) =
267                unpack_exists_size_result(packed);
268
269            proptest::prop_assert_eq!(exists, unpacked_exists);
270            proptest::prop_assert_eq!(size, unpacked_size);
271            proptest::prop_assert_eq!(result, unpacked_result);
272        }
273    }
274
275    #[cfg(feature = "std")]
276    #[test]
277    fn can_convert_v0_error_to_v1() {
278        // Given
279        let v0 = ExecutorErrorV0::TransactionValidity(
280            TransactionValidityErrorV0::CoinDoesNotExist(Default::default()),
281        );
282
283        // When
284        let json: JSONError = v0.into();
285        let v1: ExecutorError = json.into();
286
287        // Then
288        assert_eq!(
289            v1,
290            ExecutorError::TransactionValidity(
291                TransactionValidityError::CoinDoesNotExist(Default::default())
292            )
293        );
294    }
295}