multiversx_chain_vm/tx_execution/
exec_call.rs

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
249
250
251
252
253
use crate::{
    tx_mock::{
        async_call_tx_input, async_callback_tx_input, async_promise_callback_tx_input,
        merge_results, AsyncCallTxData, BlockchainUpdate, CallType, Promise, TxCache, TxContext,
        TxContextStack, TxInput, TxPanic, TxResult, TxResultCalls,
    },
    types::VMCodeMetadata,
    with_shared::Shareable,
    world_mock::{AccountData, AccountEsdt, BlockchainState},
};
use num_bigint::BigUint;
use num_traits::Zero;
use std::collections::HashMap;

use super::BlockchainVMRef;

/// Executes the SC endpoint, as given by the current TxInput in the current TxContext.
///
/// Works directly with the top of the execution stack, that is why it takes no arguments.
///
/// It expectes that the stack is properly set up.
pub fn execute_current_tx_context_input() {
    let tx_context_arc = TxContextStack::static_peek();
    let func_name = tx_context_arc.input_ref().func_name.clone();
    let instance = tx_context_arc.vm_ref.get_contract_instance(&tx_context_arc);
    instance.call(func_name.as_str()).expect("execution error");
}

impl BlockchainVMRef {
    pub fn execute_sc_query_lambda<F>(
        &self,
        tx_input: TxInput,
        state: &mut Shareable<BlockchainState>,
        f: F,
    ) -> TxResult
    where
        F: FnOnce(),
    {
        let (tx_result, _) = self.execute_in_debugger(tx_input, state, f);
        tx_result
    }

    pub fn execute_in_debugger<F>(
        &self,
        tx_input: TxInput,
        state: &mut Shareable<BlockchainState>,
        f: F,
    ) -> (TxResult, BlockchainUpdate)
    where
        F: FnOnce(),
    {
        state.with_shared(|state_arc| {
            let tx_cache = TxCache::new(state_arc);
            let mut tx_context_sh =
                Shareable::new(TxContext::new(self.clone(), tx_input, tx_cache));
            TxContextStack::execute_on_vm_stack(&mut tx_context_sh, f);
            tx_context_sh.into_inner().into_results()
        })
    }

    pub fn execute_builtin_function_or_default<F>(
        &self,
        tx_input: TxInput,
        tx_cache: TxCache,
        f: F,
    ) -> (TxResult, BlockchainUpdate)
    where
        F: FnOnce(),
    {
        self.builtin_functions.execute_builtin_function_or_else(
            self,
            tx_input,
            tx_cache,
            f,
            |tx_input, tx_cache, f| self.default_execution(tx_input, tx_cache, f),
        )
    }

    pub fn execute_sc_call_lambda<F>(
        &self,
        tx_input: TxInput,
        state: &mut Shareable<BlockchainState>,
        f: F,
    ) -> TxResult
    where
        F: FnOnce(),
    {
        state.subtract_tx_gas(&tx_input.from, tx_input.gas_limit, tx_input.gas_price);

        let (tx_result, blockchain_updates) = state.with_shared(|state_arc| {
            let tx_cache = TxCache::new(state_arc);
            self.execute_builtin_function_or_default(tx_input, tx_cache, f)
        });

        if tx_result.result_status.is_success() {
            blockchain_updates.apply(state);
        }

        tx_result
    }

    pub fn execute_async_call_and_callback(
        &self,
        async_data: AsyncCallTxData,
        state: &mut Shareable<BlockchainState>,
    ) -> (TxResult, TxResult) {
        if state.accounts.contains_key(&async_data.to) {
            let async_input = async_call_tx_input(&async_data, CallType::AsyncCall);

            let async_result = self.sc_call_with_async_and_callback(
                async_input,
                state,
                execute_current_tx_context_input,
            );

            let callback_input =
                async_callback_tx_input(&async_data, &async_result, &self.builtin_functions);
            let callback_result = self.execute_sc_call_lambda(
                callback_input,
                state,
                execute_current_tx_context_input,
            );
            assert!(
                callback_result.pending_calls.async_call.is_none(),
                "successive asyncs currently not supported"
            );
            (async_result, callback_result)
        } else {
            let result = self.insert_ghost_account(&async_data, state);
            match result {
                Ok(blockchain_updates) => {
                    state.commit_updates(blockchain_updates);
                    (TxResult::empty(), TxResult::empty())
                },
                Err(err) => (TxResult::from_panic_obj(&err), TxResult::empty()),
            }
        }
    }

    // TODO: refactor
    pub fn sc_call_with_async_and_callback<F>(
        &self,
        tx_input: TxInput,
        state: &mut Shareable<BlockchainState>,
        f: F,
    ) -> TxResult
    where
        F: FnOnce(),
    {
        // main call
        let mut tx_result = self.execute_sc_call_lambda(tx_input, state, f);

        // take & clear pending calls
        let pending_calls = std::mem::replace(&mut tx_result.pending_calls, TxResultCalls::empty());

        // legacy async call
        // the async call also gets reset
        if tx_result.result_status.is_success() {
            if let Some(async_data) = pending_calls.async_call {
                let (async_result, callback_result) =
                    self.execute_async_call_and_callback(async_data, state);

                tx_result = merge_results(tx_result, async_result);
                tx_result = merge_results(tx_result, callback_result);

                return tx_result;
            }
        }

        // calling all promises
        // the promises are also reset
        for promise in pending_calls.promises {
            let (async_result, callback_result) =
                self.execute_promise_call_and_callback(&promise, state);

            tx_result = merge_results(tx_result, async_result.clone());
            tx_result = merge_results(tx_result, callback_result.clone());
        }

        tx_result
    }

    pub fn execute_promise_call_and_callback(
        &self,
        promise: &Promise,
        state: &mut Shareable<BlockchainState>,
    ) -> (TxResult, TxResult) {
        if state.accounts.contains_key(&promise.call.to) {
            let async_input = async_call_tx_input(&promise.call, CallType::AsyncCall);
            let async_result = self.sc_call_with_async_and_callback(
                async_input,
                state,
                execute_current_tx_context_input,
            );
            let callback_result = self.execute_promises_callback(&async_result, promise, state);
            (async_result, callback_result)
        } else {
            let result = self.insert_ghost_account(&promise.call, state);
            match result {
                Ok(blockchain_updates) => {
                    state.commit_updates(blockchain_updates);
                    (TxResult::empty(), TxResult::empty())
                },
                Err(err) => (TxResult::from_panic_obj(&err), TxResult::empty()),
            }
        }
    }

    fn execute_promises_callback(
        &self,
        async_result: &TxResult,
        promise: &Promise,
        state: &mut Shareable<BlockchainState>,
    ) -> TxResult {
        if !promise.has_callback() {
            return TxResult::empty();
        }
        let callback_input =
            async_promise_callback_tx_input(promise, async_result, &self.builtin_functions);
        let callback_result =
            self.execute_sc_call_lambda(callback_input, state, execute_current_tx_context_input);
        assert!(
            callback_result.pending_calls.promises.is_empty(),
            "successive promises currently not supported"
        );
        callback_result
    }

    /// When calling a contract that is unknown to the state, we insert a ghost account.
    fn insert_ghost_account(
        &self,
        async_data: &AsyncCallTxData,
        state: &mut Shareable<BlockchainState>,
    ) -> Result<BlockchainUpdate, TxPanic> {
        state.with_shared(|state_arc| {
            let tx_cache = TxCache::new(state_arc);
            tx_cache.subtract_egld_balance(&async_data.from, &async_data.call_value)?;
            tx_cache.insert_account(AccountData {
                address: async_data.to.clone(),
                nonce: 0,
                egld_balance: async_data.call_value.clone(),
                esdt: AccountEsdt::default(),
                username: Vec::new(),
                storage: HashMap::new(),
                contract_path: None,
                code_metadata: VMCodeMetadata::empty(),
                contract_owner: None,
                developer_rewards: BigUint::zero(),
            });
            Ok(tx_cache.into_blockchain_updates())
        })
    }
}