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
use std::fmt::{Debug, Formatter};
use std::marker::PhantomData;
use std::mem;
use async_trait::async_trait;
use multiversx_sc::codec::TopEncodeMulti;
use multiversx_sc_scenario::scenario_model::{ScCallStep, ScDeployStep, TypedScCall, TypedScDeploy};
use multiversx_sc_snippets::Interactor;
use multiversx_sdk::wallet::Wallet;
use crate::base::deploy::DeployExecutor;
use crate::base::transaction::TransactionExecutor;
use crate::error::executor::ExecutorError;
use crate::network::interactor::BlockchainInteractor;

/// Alias for the `BaseTransactionNetworkExecutor` struct, parameterized with the `Interactor` type.
pub type NetworkExecutor = BaseTransactionNetworkExecutor<Interactor>;

/// A struct representing the executor for handling transactions in a real blockchain environment.
///
/// This executor is designed to interact with a blockchain network via a specified gateway URL and a wallet
/// for signing transactions. It is parameterized by a type `Interactor` that encapsulates the blockchain interaction logic.
pub struct BaseTransactionNetworkExecutor<Interactor: BlockchainInteractor> {
    /// The URL of the blockchain network gateway through which transactions will be sent.
    pub gateway_url: String,
    /// The wallet used for signing transactions before they are sent to the blockchain network.
    pub wallet: Wallet,
    /// Phantom data to allow the generic parameter `Interactor`.
    /// This field does not occupy any space in memory.
    _phantom_data: PhantomData<Interactor>,
}

/// Custom implementation of `Clone` for `BaseTransactionNetworkExecutor`.
///
/// This implementation is necessary because the `Interactor` generic parameter might not
/// implement `Clone`. However, since `Interactor` is used only as phantom data (it does not
/// affect the state of `BaseTransactionNetworkExecutor`), we can safely implement `Clone`
/// without the `Interactor` needing to be `Clone`.
impl<Interactor> Clone for BaseTransactionNetworkExecutor<Interactor>
where
    Interactor: BlockchainInteractor
{
    fn clone(&self) -> Self {
        Self {
            gateway_url: self.gateway_url.clone(),
            wallet: self.wallet,
            _phantom_data: Default::default(),
        }
    }
}

/// Custom implementation of `Debug` for `BaseTransactionNetworkExecutor`.
///
/// This implementation is necessary because the `Interactor` generic parameter might not
/// implement `Debug`. As with `Clone`, since `Interactor` is only used as phantom data,
/// it does not impact the debug representation of `BaseTransactionNetworkExecutor`. This
/// implementation ensures that instances of `BaseTransactionNetworkExecutor` can be
/// formatted using the `Debug` trait regardless of whether `Interactor` implements `Debug`.
impl<Interactor> Debug for BaseTransactionNetworkExecutor<Interactor>
    where
        Interactor: BlockchainInteractor
{
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("BaseTransactionNetworkExecutor")
            .field("gateway_url", &self.gateway_url)
            .field("wallet", &self.wallet)
            .finish()
    }
}

impl<Interactor: BlockchainInteractor> BaseTransactionNetworkExecutor<Interactor> {
    /// Creates a new instance of `BaseTransactionNetworkExecutor`.
    ///
    /// # Parameters
    /// - `gateway_url`: The URL of the blockchain network gateway.
    /// - `wallet`: A reference to the wallet used for signing transactions.
    ///
    /// # Returns
    /// A new `BaseTransactionNetworkExecutor` instance.
    pub fn new(gateway_url: &str, wallet: &Wallet) -> Self {
        BaseTransactionNetworkExecutor {
            gateway_url: gateway_url.to_string(),
            wallet: *wallet,
            _phantom_data: PhantomData,
        }
    }
}

#[async_trait]
impl<Interactor: BlockchainInteractor> TransactionExecutor for BaseTransactionNetworkExecutor<Interactor> {
    /// Executes a smart contract call on the blockchain.
    ///
    /// # Parameters
    /// - `sc_call_step`: A mutable reference to 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` 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> {
        let owned_sc_call_step = mem::replace(sc_call_step, ScCallStep::new().into());
        let mut interactor = Interactor::new(&self.gateway_url).await;
        let sender_address = interactor.register_wallet(self.wallet);
        *sc_call_step = owned_sc_call_step.from(&multiversx_sc::types::Address::from(sender_address.to_bytes()));

        interactor.sc_call(sc_call_step).await;

        Ok(())
    }

    /// Indicates whether deserialization should be skipped during smart contract call execution.
    ///
    /// In the context of a real blockchain 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
    }
}

/// Implementation of the `DeployExecutor` trait for the `BaseTransactionNetworkExecutor` struct.
/// This implementation enables the deployment of smart contracts on the blockchain
/// using a specified blockchain interactor.
#[async_trait]
impl<Interactor: BlockchainInteractor> DeployExecutor for BaseTransactionNetworkExecutor<Interactor> {

    /// Asynchronously deploys a smart contract to the blockchain.
    ///
    /// # 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.
    async fn sc_deploy<OriginalResult>(&mut self, sc_deploy_step: &mut TypedScDeploy<OriginalResult>) -> Result<(), ExecutorError>
        where
            OriginalResult: TopEncodeMulti + Send + Sync,
    {
        let sc_deploy_step = sc_deploy_step.as_mut();
        let owned_sc_deploy_step = mem::replace(sc_deploy_step, ScDeployStep::new());
        let mut interactor = Interactor::new(&self.gateway_url).await;
        let sender_address = interactor.register_wallet(self.wallet);
        *sc_deploy_step = owned_sc_deploy_step.from(&multiversx_sc::types::Address::from(sender_address.to_bytes()));

        interactor.sc_deploy(sc_deploy_step).await;

        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
    }
}