multiversx_chain_vm/vm_hooks/vh_impl/
vh_debug_api.rs1use std::sync::{Arc, MutexGuard};
2
3use multiversx_chain_core::types::ReturnCode;
4use multiversx_chain_vm_executor::BreakpointValue;
5use num_bigint::BigUint;
6use num_traits::Zero;
7
8use crate::{
9 tx_execution::execute_current_tx_context_input,
10 tx_mock::{
11 async_call_tx_input, AsyncCallTxData, BackTransfers, BlockchainUpdate, CallType, TxCache,
12 TxContext, TxFunctionName, TxInput, TxManagedTypes, TxPanic, TxResult,
13 },
14 types::{VMAddress, VMCodeMetadata},
15 vm_err_msg,
16 vm_hooks::{
17 VMHooksBigFloat, VMHooksBigInt, VMHooksBlockchain, VMHooksCallValue, VMHooksCrypto,
18 VMHooksEndpointArgument, VMHooksEndpointFinish, VMHooksError, VMHooksErrorManaged,
19 VMHooksHandler, VMHooksHandlerSource, VMHooksLog, VMHooksManagedBuffer, VMHooksManagedMap,
20 VMHooksManagedTypes, VMHooksSend, VMHooksStorageRead, VMHooksStorageWrite,
21 },
22 world_mock::{reserved::STORAGE_RESERVED_PREFIX, AccountData, BlockInfo},
23};
24
25#[derive(Debug)]
29pub struct DebugApiVMHooksHandler(Arc<TxContext>);
30
31impl DebugApiVMHooksHandler {
32 pub fn new(tx_context_arc: Arc<TxContext>) -> Self {
33 DebugApiVMHooksHandler(tx_context_arc)
34 }
35}
36
37impl VMHooksHandlerSource for DebugApiVMHooksHandler {
38 fn m_types_lock(&self) -> MutexGuard<TxManagedTypes> {
39 self.0.m_types_lock()
40 }
41
42 fn halt_with_error(&self, status: ReturnCode, message: &str) -> ! {
43 *self.0.result_lock() = TxResult::from_panic_obj(&TxPanic::new(status, message));
44 let breakpoint = match status {
45 ReturnCode::UserError => BreakpointValue::SignalError,
46 _ => BreakpointValue::ExecutionFailed,
47 };
48 std::panic::panic_any(breakpoint);
49 }
50
51 fn input_ref(&self) -> &TxInput {
52 self.0.input_ref()
53 }
54
55 fn random_next_bytes(&self, length: usize) -> Vec<u8> {
56 self.0.rng_lock().next_bytes(length)
57 }
58
59 fn result_lock(&self) -> MutexGuard<TxResult> {
60 self.0.result_lock()
61 }
62
63 fn storage_read_any_address(&self, address: &VMAddress, key: &[u8]) -> Vec<u8> {
64 self.0.with_account_mut(address, |account| {
65 account.storage.get(key).cloned().unwrap_or_default()
66 })
67 }
68
69 fn storage_write(&self, key: &[u8], value: &[u8]) {
70 self.check_reserved_key(key);
71 self.check_not_readonly();
72
73 self.0.with_contract_account_mut(|account| {
74 account.storage.insert(key.to_vec(), value.to_vec());
75 });
76 }
77
78 fn get_previous_block_info(&self) -> &BlockInfo {
79 &self.0.blockchain_ref().previous_block_info
80 }
81
82 fn get_current_block_info(&self) -> &BlockInfo {
83 &self.0.blockchain_ref().current_block_info
84 }
85
86 fn back_transfers_lock(&self) -> MutexGuard<BackTransfers> {
87 self.0.back_transfers_lock()
88 }
89
90 fn account_data(&self, address: &VMAddress) -> Option<AccountData> {
91 self.0
92 .with_account_or_else(address, |account| Some(account.clone()), || None)
93 }
94
95 fn account_code(&self, address: &VMAddress) -> Vec<u8> {
96 self.0
97 .blockchain_cache()
98 .with_account(address, |account| account.contract_path.clone())
99 .unwrap_or_else(|| panic!("Account is not a smart contract, it has no code"))
100 }
101
102 fn perform_async_call(
103 &self,
104 to: VMAddress,
105 egld_value: num_bigint::BigUint,
106 func_name: TxFunctionName,
107 arguments: Vec<Vec<u8>>,
108 ) -> ! {
109 let async_call_data = self.create_async_call_data(to, egld_value, func_name, arguments);
110 let mut tx_result = self.result_lock();
112 tx_result.all_calls.push(async_call_data.clone());
113 tx_result.pending_calls.async_call = Some(async_call_data);
114 drop(tx_result); std::panic::panic_any(BreakpointValue::AsyncCall);
116 }
117
118 fn perform_execute_on_dest_context(
119 &self,
120 to: VMAddress,
121 egld_value: num_bigint::BigUint,
122 func_name: TxFunctionName,
123 arguments: Vec<Vec<u8>>,
124 ) -> Vec<Vec<u8>> {
125 let async_call_data = self.create_async_call_data(to, egld_value, func_name, arguments);
126 let tx_input = async_call_tx_input(&async_call_data, CallType::ExecuteOnDestContext);
127 let tx_cache = TxCache::new(self.0.blockchain_cache_arc());
128 let (tx_result, blockchain_updates) = self.0.vm_ref.execute_builtin_function_or_default(
129 tx_input,
130 tx_cache,
131 execute_current_tx_context_input,
132 );
133
134 if tx_result.result_status.is_success() {
135 self.sync_call_post_processing(tx_result, blockchain_updates)
136 } else {
137 self.halt_with_error(tx_result.result_status, &tx_result.result_message)
139 }
140 }
141
142 fn perform_execute_on_dest_context_readonly(
143 &self,
144 to: VMAddress,
145 func_name: TxFunctionName,
146 arguments: Vec<Vec<u8>>,
147 ) -> Vec<Vec<u8>> {
148 let async_call_data =
149 self.create_async_call_data(to, BigUint::zero(), func_name, arguments);
150 let mut tx_input = async_call_tx_input(&async_call_data, CallType::ExecuteOnDestContext);
151 tx_input.readonly = true;
152 let tx_cache = TxCache::new(self.0.blockchain_cache_arc());
153 let (tx_result, blockchain_updates) = self.0.vm_ref.execute_builtin_function_or_default(
154 tx_input,
155 tx_cache,
156 execute_current_tx_context_input,
157 );
158
159 if tx_result.result_status.is_success() {
160 self.sync_call_post_processing(tx_result, blockchain_updates)
161 } else {
162 self.halt_with_error(tx_result.result_status, &tx_result.result_message)
164 }
165 }
166
167 fn perform_deploy(
168 &self,
169 egld_value: num_bigint::BigUint,
170 contract_code: Vec<u8>,
171 code_metadata: VMCodeMetadata,
172 args: Vec<Vec<u8>>,
173 ) -> (VMAddress, Vec<Vec<u8>>) {
174 let contract_address = self.current_address();
175 let tx_hash = self.tx_hash();
176 let tx_input = TxInput {
177 from: contract_address.clone(),
178 to: VMAddress::zero(),
179 egld_value,
180 esdt_values: Vec::new(),
181 func_name: TxFunctionName::EMPTY,
182 args,
183 gas_limit: 1000,
184 gas_price: 0,
185 tx_hash,
186 ..Default::default()
187 };
188
189 let tx_cache = TxCache::new(self.0.blockchain_cache_arc());
190 tx_cache.increase_acount_nonce(contract_address);
191 let (tx_result, new_address, blockchain_updates) = self.0.vm_ref.deploy_contract(
192 tx_input,
193 contract_code,
194 code_metadata,
195 tx_cache,
196 execute_current_tx_context_input,
197 );
198
199 match tx_result.result_status {
200 ReturnCode::Success => (
201 new_address,
202 self.sync_call_post_processing(tx_result, blockchain_updates),
203 ),
204 ReturnCode::ExecutionFailed => self.vm_error(&tx_result.result_message), _ => self.vm_error(vm_err_msg::ERROR_SIGNALLED_BY_SMARTCONTRACT),
206 }
207 }
208
209 fn perform_transfer_execute(
210 &self,
211 to: VMAddress,
212 egld_value: num_bigint::BigUint,
213 func_name: TxFunctionName,
214 arguments: Vec<Vec<u8>>,
215 ) {
216 let async_call_data = self.create_async_call_data(to, egld_value, func_name, arguments);
217 let mut tx_input = async_call_tx_input(&async_call_data, CallType::TransferExecute);
218 if self.is_back_transfer(&tx_input) {
219 tx_input.call_type = CallType::BackTransfer;
220 }
221
222 let tx_cache = TxCache::new(self.0.blockchain_cache_arc());
223 let (tx_result, blockchain_updates) = self.0.vm_ref.execute_builtin_function_or_default(
224 tx_input,
225 tx_cache,
226 execute_current_tx_context_input,
227 );
228
229 match tx_result.result_status {
230 ReturnCode::Success => {
231 self.0.result_lock().all_calls.push(async_call_data);
232
233 let _ = self.sync_call_post_processing(tx_result, blockchain_updates);
234 },
235 ReturnCode::ExecutionFailed => self.vm_error(&tx_result.result_message), _ => self.vm_error(vm_err_msg::ERROR_SIGNALLED_BY_SMARTCONTRACT),
237 }
238 }
239}
240
241impl DebugApiVMHooksHandler {
242 fn create_async_call_data(
243 &self,
244 to: VMAddress,
245 egld_value: num_bigint::BigUint,
246 func_name: TxFunctionName,
247 arguments: Vec<Vec<u8>>,
248 ) -> AsyncCallTxData {
249 let contract_address = &self.0.input_ref().to;
250 let tx_hash = self.tx_hash();
251 AsyncCallTxData {
252 from: contract_address.clone(),
253 to,
254 call_value: egld_value,
255 endpoint_name: func_name,
256 arguments,
257 tx_hash,
258 }
259 }
260
261 fn sync_call_post_processing(
262 &self,
263 tx_result: TxResult,
264 blockchain_updates: BlockchainUpdate,
265 ) -> Vec<Vec<u8>> {
266 self.0.blockchain_cache().commit_updates(blockchain_updates);
267
268 self.0.result_lock().merge_after_sync_call(&tx_result);
269
270 let contract_address = &self.0.input_ref().to;
271 let builtin_functions = &self.0.vm_ref.builtin_functions;
272 self.back_transfers_lock()
273 .new_from_result(contract_address, &tx_result, builtin_functions);
274
275 tx_result.result_values
276 }
277
278 fn check_reserved_key(&self, key: &[u8]) {
279 if key.starts_with(STORAGE_RESERVED_PREFIX) {
280 self.vm_error(vm_err_msg::WRITE_RESERVED);
281 }
282 }
283
284 fn check_not_readonly(&self) {
286 if self.0.input_ref().readonly {
287 self.vm_error(vm_err_msg::WRITE_READONLY);
288 }
289 }
290
291 fn is_back_transfer(&self, tx_input: &TxInput) -> bool {
292 let caller_address = &self.0.input_ref().from;
293 if !caller_address.is_smart_contract_address() {
294 return false;
295 }
296
297 let builtin_functions = &self.0.vm_ref.builtin_functions;
298 let token_transfers = builtin_functions.extract_token_transfers(tx_input);
299 &token_transfers.real_recipient == caller_address
300 }
301}
302
303impl VMHooksBigInt for DebugApiVMHooksHandler {}
304impl VMHooksManagedBuffer for DebugApiVMHooksHandler {}
305impl VMHooksManagedMap for DebugApiVMHooksHandler {}
306impl VMHooksBigFloat for DebugApiVMHooksHandler {}
307impl VMHooksManagedTypes for DebugApiVMHooksHandler {}
308
309impl VMHooksCallValue for DebugApiVMHooksHandler {}
310impl VMHooksEndpointArgument for DebugApiVMHooksHandler {}
311impl VMHooksEndpointFinish for DebugApiVMHooksHandler {}
312impl VMHooksError for DebugApiVMHooksHandler {}
313impl VMHooksErrorManaged for DebugApiVMHooksHandler {}
314impl VMHooksStorageRead for DebugApiVMHooksHandler {}
315impl VMHooksStorageWrite for DebugApiVMHooksHandler {}
316impl VMHooksCrypto for DebugApiVMHooksHandler {}
317impl VMHooksBlockchain for DebugApiVMHooksHandler {}
318impl VMHooksLog for DebugApiVMHooksHandler {}
319impl VMHooksSend for DebugApiVMHooksHandler {}
320
321impl VMHooksHandler for DebugApiVMHooksHandler {}