1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
use std::sync::Arc;
use async_trait::async_trait;
use multiversx_sc_scenario::scenario_model::TypedScCall;
use tokio::sync::Mutex;
use crate::error::executor::ExecutorError;

/// A trait defining the necessary operations for executing smart contract transactions.
///
/// Implementations of this trait can vary based on the specific environment (e.g., real blockchain, mock blockchain).
#[async_trait]
pub trait TransactionExecutor: Send + Sync {
    /// Executes a smart contract call with the specified parameters.
    ///
    /// # Parameters
    /// - `sc_call_step`: A mutable reference to the typed smart contract call step.
    ///
    /// # Type Parameters
    /// - `OriginalResult`: The type of the result expected from the smart contract call. Must implement the `Send` trait.
    ///
    /// # Returns
    /// - A `Result` with an empty `Ok(())` value if the call is successful, or an `Err(ExecutorError)` if the call fails.
    async fn sc_call<OriginalResult: Send>(&mut self, sc_call_step: &mut TypedScCall<OriginalResult>) -> Result<(), ExecutorError>;

    /// Determines whether deserialization should be skipped during the smart contract call execution.
    ///
    /// This method is particularly useful for implementations like `DummyExecutor` which do not perform
    /// any actual calls, thus deserializing a non-existent result would lead to an error. In such cases,
    /// this method should return `true` to skip deserialization, preventing potential errors.
    ///
    /// # Returns
    /// - A `bool` indicating whether deserialization should be skipped.
    async fn should_skip_deserialization(&self) -> bool;
}

/// An implementation of `TransactionExecutor` trait for types wrapped in `Arc<Mutex<T>>`.
///
/// This implementation allows shared access to a transaction executor instance across multiple threads.
#[async_trait]
impl<T: TransactionExecutor> TransactionExecutor for Arc<Mutex<T>> {
    /// Executes a smart contract call using the underlying `TransactionExecutor` implementation.
    async fn sc_call<OriginalResult: Send>(&mut self, sc_call_step: &mut TypedScCall<OriginalResult>) -> Result<(), ExecutorError> {
        {
            // Acquire a lock to ensure exclusive access to the executor during the call execution.
            let mut executor = self.lock().await;
            executor.sc_call(sc_call_step).await
        }
    }

    /// Determines whether deserialization should be skipped during the smart contract call execution.
    async fn should_skip_deserialization(&self) -> bool {
        {
            // Acquire a lock to access the underlying executor.
            // Note: The lock here could lead to some performance penalty. A potential solution could be using
            // another type of locking mechanism like `RwLock`.
            let executor = self.lock().await;
            executor.should_skip_deserialization().await
        }
    }
}