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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
use std::mem;
use std::ops::Deref;
use std::sync::Arc;
use async_trait::async_trait;
use multiversx_sc::codec::{TopDecodeMulti, TopEncodeMulti};
use crate::{ScCallStep, ScDeployStep, ScQueryStep, TxQuery, TypedScCall, TypedScDeploy, TypedScQuery};
use crate::ScenarioWorld;
use tokio::sync::{Mutex, MutexGuard};
use novax_data::Address;
use novax_data::NativeConvertible;
use novax_data::parse_query_return_bytes_data;
use crate::base::deploy::DeployExecutor;
use crate::base::query::QueryExecutor;
use crate::base::transaction::TransactionExecutor;
use crate::error::executor::ExecutorError;
use crate::error::mock_deploy::MockDeployError;

/// A convenient type alias for `MockExecutor` with `String` as the generic type.
pub type StandardMockExecutor = MockExecutor<String>;

/// A structure to execute smart contract queries, transactions, and deployments in a mocked blockchain environment.
///
/// This executor utilizes the scenario engine from the MultiversX Rust Testing Framework for executing smart contract interactions.
#[derive(Clone)]
pub struct MockExecutor<A>
    where
        A: Deref + Send + Sync,
        Address: for<'a> From<&'a A::Target>
{
    /// The mocked world where the smart contract interactions are executed.
    world: Arc<Mutex<ScenarioWorld>>,
    /// Optional caller address. If not provided, the executor uses the address from the smart contract call or deployment request.
    opt_caller: Option<A>,
}

impl<A> MockExecutor<A>
    where
        A: Deref + Send + Sync,
        Address: for<'a> From<&'a A::Target>
{
    /// Constructs a new `MockExecutor` with the specified mocked world and an optional caller address.
    ///
    /// # Parameters
    /// - `world`: The mocked world where the smart contract interactions are executed.
    /// - `opt_caller`: Optional caller address.
    ///
    /// # Returns
    /// A new instance of `MockExecutor`.
    pub fn new(world: Arc<Mutex<ScenarioWorld>>, opt_caller: Option<A>) -> MockExecutor<A> {
        MockExecutor {
            world,
            opt_caller,
        }
    }
}

#[async_trait]
impl<A> TransactionExecutor for MockExecutor<A>
    where
        A: Deref + Send + Sync,
        Address: for<'a> From<&'a A::Target>
{
    /// Executes a smart contract call within a mocked environment.
    ///
    /// This method extracts or determines the caller's address, performs a smart contract call,
    /// and updates the world state accordingly, all within a controlled, mocked environment.
    ///
    /// # Parameters
    /// - `sc_call_step`: A mutable reference to a `TypedScCall` object representing the 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` object with an empty `Ok(())` value if the call is successful,
    ///   or an `Err(ExecutorError)` if the call fails for any reason.
    async fn sc_call<OriginalResult: Send>(&mut self, sc_call_step: &mut TypedScCall<OriginalResult>) -> Result<(), ExecutorError> {
        let caller: Address = if let Some(caller) = self.opt_caller.as_deref() {
            caller.into()
        } else {
            (&sc_call_step.sc_call_step.tx.to.value).into()
        };

        let owned_sc_call_step = mem::replace(sc_call_step, ScCallStep::new().into());
        *sc_call_step = owned_sc_call_step.from(&caller);

        {
            let mut world = self.world.lock().await;
            world.sc_call(sc_call_step);
        }

        Ok(())
    }

    /// Specifies whether deserialization should be skipped during the smart contract call execution.
    ///
    /// In the context of the mocked environment, deserialization is not skipped,
    /// hence this method returns `false`.
    ///
    /// # Returns
    /// - A boolean value `false`, indicating that deserialization should not be skipped.
    async fn should_skip_deserialization(&self) -> bool {
        false
    }
}

/// Mock implementation of the `DeployExecutor` trait for testing and development purposes.
/// This implementation uses a mock executor to simulate the deployment of smart contracts
/// on the blockchain without actually interacting with a real blockchain network.
///
/// The `MockExecutor` struct encapsulates the state and behavior necessary for simulating
/// blockchain interactions.
///
/// # Type Parameters
///
/// * `A`: A type implementing `Deref`, `Send`, and `Sync`. This type is used to derive an
///   `Address` type instance representing a blockchain address.
#[async_trait]
impl<A> DeployExecutor for MockExecutor<A>
    where
        A: Deref + Send + Sync,
        Address: for<'a> From<&'a A::Target>
{
    /// Asynchronously deploys a smart contract to the mock blockchain environment.
    ///
    /// # Type Parameters
    ///
    /// * `OriginalResult`: Represents the result type expected from the smart contract deployment.
    ///   This type must implement `TopEncodeMulti`, `Send`, and `Sync`.
    /// * `S`: Represents the type encapsulating the smart contract deployment step.
    ///   This type must implement `AsMut<TypedScDeploy<OriginalResult>>` and `Send`.
    ///
    /// # Parameters
    ///
    /// * `sc_deploy_step`: A mutable reference to the smart contract deployment step to be executed.
    ///
    /// # Returns
    ///
    /// A `Result` with an empty `Ok(())` value indicating success, or an `Err(ExecutorError)` indicating failure,
    /// specifically a `MockDeployError::WalletAddressNotPresent` error if the wallet address is not present.
    async fn sc_deploy<OriginalResult>(&mut self, sc_deploy_step: &mut TypedScDeploy<OriginalResult>) -> Result<(), ExecutorError>
        where
            OriginalResult: TopEncodeMulti + Send + Sync,
    {
        let caller: Address = {
            let Some(caller) = self.opt_caller.as_deref() else {
                return Err(ExecutorError::MockDeploy(MockDeployError::WalletAddressNotPresent))
            };

            caller.into()
        };

        let sc_deploy_step = sc_deploy_step.as_mut();

        let owned_sc_deploy_step = mem::replace(sc_deploy_step, ScDeployStep::new());
        *sc_deploy_step = owned_sc_deploy_step.from(&caller);

        {
            let mut world = self.world.lock().await;
            world.sc_deploy(sc_deploy_step);
        }

        Ok(())
    }

    /// Specifies whether deserialization should be skipped during the deployment execution.
    /// In this implementation, deserialization is not skipped.
    ///
    /// # Returns
    ///
    /// A `bool` value of `false`, indicating that deserialization should not be skipped.
    async fn should_skip_deserialization(&self) -> bool {
        false
    }
}

/// The `MockExecutor` implementation for the `QueryExecutor` trait, used to simulate smart contract queries in a mock environment.
/// This struct is typically utilized in testing and development scenarios where interaction with a real blockchain is undesirable or unnecessary.
///
/// # Type Parameters
///
/// * `A`: A type that implements `Clone`, `Deref`, `Send`, and `Sync` traits. This type is used to derive an `Address` instance representing a blockchain address.
#[async_trait]
impl<A> QueryExecutor for MockExecutor<A>
    where
        A: Clone + Deref + Send + Sync,
        Address: for<'a> From<&'a A::Target>
{
    /// Executes a simulated smart contract query in the mock environment.
    ///
    /// # Type Parameters
    ///
    /// * `OutputManaged`: The type representing the expected output of the smart contract query. It should implement `TopDecodeMulti`, `NativeConvertible`, `Send`, and `Sync`.
    ///
    /// # Parameters
    ///
    /// * `request`: A reference to the `ScCallStep` detailing the smart contract query to be executed.
    ///
    /// # Returns
    ///
    /// * `Result<OutputManaged::Native, ExecutorError>`: On successful execution, returns a `Result` containing the native converted query output.
    ///   On failure, returns a `Result` containing an `ExecutorError`.
    async fn execute<OutputManaged>(&self, request: &ScCallStep) -> Result<OutputManaged::Native, ExecutorError>
        where
            OutputManaged: TopDecodeMulti + NativeConvertible + Send + Sync
    {
        // Convert the smart contract query step to a call step.
        let query = convert_sc_query_step_to_call_step(request);
        // Create a TypedScQuery from the query.
        let mut typed = TypedScQuery::<OutputManaged>::from(query);
        {
            // Lock the mock world state for exclusive access.
            let mut world: MutexGuard<ScenarioWorld> = self.world.lock().await;
            // Execute the smart contract query in the mock world.
            world.sc_query(&mut typed);
        }
        // Retrieve the response from the typed query.
        let response = typed.response();
        // Clone the output from the response.
        let mut out = response.out.clone();

        // Parse the query return bytes data.
        let parsed = parse_query_return_bytes_data::<OutputManaged>(&mut out)?;
        // Convert the parsed data to its native type and return it.
        Ok(parsed.to_native())
    }
}

/// Converts a smart contract query step to a call step.
///
/// This conversion is needed to accommodate the types expected by the scenario engine from the MultiversX Rust Testing Framework.
fn convert_sc_query_step_to_call_step(query_step: &ScCallStep) -> ScQueryStep {
    let query_tx = TxQuery {
        to: query_step.tx.to.clone(),
        function: query_step.tx.function.clone(),
        arguments: query_step.tx.arguments.clone(),
    };

    ScQueryStep {
        id: query_step.id.clone(),
        tx_id: query_step.tx_id.clone(),
        explicit_tx_hash: query_step.explicit_tx_hash.clone(),
        comment: query_step.comment.clone(),
        tx: Box::new(query_tx),
        expect: query_step.expect.clone(),
        response: query_step.response.clone(),
    }
}