multiversx_chain_vm/host/execution/
exec_call.rs1use 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
19pub 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
44pub 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 let mut tx_result = commit_call(tx_input, state, runtime, f);
58
59 let pending_calls = std::mem::replace(&mut tx_result.pending_calls, TxResultCalls::empty());
61
62 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 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
89fn 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
133fn 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
185fn 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}