multiversx_chain_vm/host/execution/
exec_call.rs

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