multiversx_chain_vm/vm_hooks/vh_impl/
vh_debug_api.rs

1use 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/// A simple wrapper around a managed type container RefCell.
26///
27/// Implements `VMHooksManagedTypes` and thus can be used as a basis of a minimal static API.
28#[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        // the cell is no longer needed, since we end in a panic
111        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); // this avoid to poison the mutex
115        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            // also kill current execution
138            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            // also kill current execution
163            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), // TODO: not sure it's the right condition, it catches insufficient funds
205            _ => 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), // TODO: not sure it's the right condition, it catches insufficient funds
236            _ => 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    /// TODO: only checked on storage writes, needs more checks for calls, transfers, etc.
285    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 {}