multiversx_chain_vm/host/execution/
exec_call.rs

1use crate::{
2    blockchain::{
3        state::{AccountData, AccountEsdt, BlockchainStateRef},
4        VMConfig,
5    },
6    host::{
7        context::{
8            async_call_tx_input, async_callback_tx_input, async_promise_callback_tx_input,
9            merge_async_results, AsyncCallTxData, BlockchainUpdate, CallType, Promise, TxCache,
10            TxInput, TxPanic, TxResult, TxResultCalls,
11        },
12        runtime::{RuntimeInstanceCallLambda, RuntimeInstanceCallLambdaDefault, RuntimeRef},
13    },
14    types::VMCodeMetadata,
15};
16use num_bigint::BigUint;
17use num_traits::Zero;
18use std::collections::HashMap;
19
20use super::execute_builtin_function_or_default;
21
22/// Executes transaction and commits changes back to the underlying blockchain state.
23///
24/// Does not handle async calls.
25pub fn commit_call<F>(
26    tx_input: TxInput,
27    state: &mut BlockchainStateRef,
28    runtime: &RuntimeRef,
29    f: F,
30) -> TxResult
31where
32    F: RuntimeInstanceCallLambda,
33{
34    state.subtract_tx_gas(&tx_input.from, tx_input.gas_limit, tx_input.gas_price);
35
36    let tx_cache = TxCache::new(state.get_arc());
37    let (mut tx_result, blockchain_updates) =
38        execute_builtin_function_or_default(tx_input.clone(), tx_cache, runtime, f);
39
40    if tx_result.result_status.is_success() {
41        blockchain_updates.apply(state);
42    }
43
44    // TODO: not sure if this is the best place to put this, investigate
45    tx_result.append_internal_vm_errors_event_log(&tx_input);
46
47    tx_result
48}
49
50/// Executes transaction and commits changes back to the underlying blockchain state.
51///
52/// Then executes all asyncs recursively, and commits them as well.
53pub fn commit_call_with_async_and_callback<F>(
54    tx_input: TxInput,
55    state: &mut BlockchainStateRef,
56    runtime: &RuntimeRef,
57    f: F,
58) -> TxResult
59where
60    F: RuntimeInstanceCallLambda,
61{
62    // main call
63    let mut tx_result = commit_call(tx_input, state, runtime, f);
64
65    // take & clear pending calls
66    let pending_calls = std::mem::replace(&mut tx_result.pending_calls, TxResultCalls::empty());
67
68    // legacy async call
69    // the async call also gets reset
70    if tx_result.result_status.is_success() {
71        if let Some(async_data) = pending_calls.async_call {
72            let (async_result, callback_result) =
73                commit_async_call_and_callback(async_data, state, runtime);
74
75            tx_result = merge_async_results(tx_result, async_result);
76            tx_result = merge_async_results(tx_result, callback_result);
77
78            return tx_result;
79        }
80    }
81
82    // calling all promises
83    // the promises are also reset
84    for promise in pending_calls.promises {
85        let (async_result, callback_result) =
86            commit_promise_call_and_callback(&promise, state, runtime);
87
88        tx_result = merge_async_results(tx_result, async_result.clone());
89        tx_result = merge_async_results(tx_result, callback_result.clone());
90    }
91
92    tx_result
93}
94
95/// Asyncs only.
96fn commit_async_call_and_callback(
97    async_data: AsyncCallTxData,
98    state: &mut BlockchainStateRef,
99    runtime: &RuntimeRef,
100) -> (TxResult, TxResult) {
101    if state.account_exists(&async_data.to) {
102        let async_input = async_call_tx_input(&async_data, CallType::AsyncCall);
103
104        let async_result = commit_call_with_async_and_callback(
105            async_input,
106            state,
107            runtime,
108            RuntimeInstanceCallLambdaDefault,
109        );
110
111        let callback_input = async_callback_tx_input(
112            &async_data,
113            &async_result,
114            &runtime.vm_ref.builtin_functions,
115        );
116        let callback_result = commit_call(
117            callback_input,
118            state,
119            runtime,
120            RuntimeInstanceCallLambdaDefault,
121        );
122        assert!(
123            callback_result.pending_calls.async_call.is_none(),
124            "successive asyncs currently not supported"
125        );
126        (async_result, callback_result)
127    } else {
128        let result = insert_ghost_account(&runtime.vm_ref, &async_data, state);
129        match result {
130            Ok(blockchain_updates) => {
131                state.commit_updates(blockchain_updates);
132                (TxResult::empty(), TxResult::empty())
133            }
134            Err(err) => (TxResult::from_panic_obj(&err), TxResult::empty()),
135        }
136    }
137}
138
139/// Promises only.
140fn commit_promise_call_and_callback(
141    promise: &Promise,
142    state: &mut BlockchainStateRef,
143    runtime: &RuntimeRef,
144) -> (TxResult, TxResult) {
145    if state.account_exists(&promise.call.to) {
146        let async_input = async_call_tx_input(&promise.call, CallType::AsyncCall);
147        let async_result = commit_call_with_async_and_callback(
148            async_input,
149            state,
150            runtime,
151            RuntimeInstanceCallLambdaDefault,
152        );
153        let callback_result = commit_promises_callback(&async_result, promise, state, runtime);
154        (async_result, callback_result)
155    } else {
156        let result = insert_ghost_account(&runtime.vm_ref, &promise.call, state);
157        match result {
158            Ok(blockchain_updates) => {
159                state.commit_updates(blockchain_updates);
160                (TxResult::empty(), TxResult::empty())
161            }
162            Err(err) => (TxResult::from_panic_obj(&err), TxResult::empty()),
163        }
164    }
165}
166
167fn commit_promises_callback(
168    async_result: &TxResult,
169    promise: &Promise,
170    state: &mut BlockchainStateRef,
171    runtime: &RuntimeRef,
172) -> TxResult {
173    if !promise.has_callback() {
174        return TxResult::empty();
175    }
176    let callback_input =
177        async_promise_callback_tx_input(promise, async_result, &runtime.vm_ref.builtin_functions);
178    let callback_result = commit_call(
179        callback_input,
180        state,
181        runtime,
182        RuntimeInstanceCallLambdaDefault,
183    );
184    assert!(
185        callback_result.pending_calls.promises.is_empty(),
186        "successive promises currently not supported"
187    );
188    callback_result
189}
190
191/// When calling a contract that is unknown to the state, we insert a ghost account.
192fn insert_ghost_account(
193    vm_config: &VMConfig,
194    async_data: &AsyncCallTxData,
195    state: &mut BlockchainStateRef,
196) -> Result<BlockchainUpdate, TxPanic> {
197    let address = async_data.to.clone();
198
199    if !vm_config.insert_ghost_accounts {
200        panic!("Recipient account not found: {address}");
201    }
202
203    let tx_cache = TxCache::new(state.get_arc());
204    tx_cache.subtract_egld_balance(&async_data.from, &async_data.call_value)?;
205    tx_cache.insert_account(AccountData {
206        address,
207        nonce: 0,
208        egld_balance: async_data.call_value.clone(),
209        esdt: AccountEsdt::default(),
210        username: Vec::new(),
211        storage: HashMap::new(),
212        contract_path: None,
213        code_metadata: VMCodeMetadata::empty(),
214        contract_owner: None,
215        developer_rewards: BigUint::zero(),
216    });
217    Ok(tx_cache.into_blockchain_updates())
218}