multiversx_chain_vm/host/execution/
exec_call.rs1use 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
22pub 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 tx_result.append_internal_vm_errors_event_log(&tx_input);
46
47 tx_result
48}
49
50pub 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 let mut tx_result = commit_call(tx_input, state, runtime, f);
64
65 let pending_calls = std::mem::replace(&mut tx_result.pending_calls, TxResultCalls::empty());
67
68 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 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
95fn 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
139fn 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
191fn 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}