multiversx_chain_vm/host/vm_hooks/
vh_tx_context.rs

1use std::fmt::Debug;
2use std::sync::MutexGuard;
3
4use multiversx_chain_core::types::ReturnCode;
5use multiversx_chain_vm_executor::{InstanceState, MemLength, MemPtr, VMHooksEarlyExit};
6use num_bigint::BigUint;
7use num_traits::Zero;
8
9use crate::blockchain::state::BlockConfig;
10use crate::host::runtime::RuntimeInstanceCallLambdaDefault;
11use crate::schedule::GasSchedule;
12use crate::{
13    blockchain::{reserved::STORAGE_RESERVED_PREFIX, state::AccountData},
14    host::context::{
15        async_call_tx_input, AsyncCallTxData, BackTransfers, BlockchainUpdate, CallType,
16        ManagedTypeContainer, TxCache, TxContextRef, TxFunctionName, TxInput, TxResult,
17    },
18    host::execution,
19    types::{VMAddress, VMCodeMetadata},
20    vm_err_msg,
21};
22
23use super::vh_early_exit::{early_exit_async_call, early_exit_vm_error};
24use super::VMHooksContext;
25
26pub struct TxVMHooksContext<S: InstanceState> {
27    tx_context_ref: TxContextRef,
28    pub(crate) instance_state_ref: S,
29}
30
31impl<S: InstanceState> TxVMHooksContext<S> {
32    pub fn new(tx_context_ref: TxContextRef, instance_state_ref: S) -> Self {
33        TxVMHooksContext {
34            tx_context_ref,
35            instance_state_ref,
36        }
37    }
38}
39
40impl<S: InstanceState> Debug for TxVMHooksContext<S> {
41    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42        f.debug_struct("TxContextVMHooksHandler").finish()
43    }
44}
45
46impl<S: InstanceState> VMHooksContext for TxVMHooksContext<S> {
47    unsafe fn memory_load(&self, offset: MemPtr, length: MemLength) -> Vec<u8> {
48        self.instance_state_ref
49            .memory_load_owned(offset, length)
50            .expect("error loading memory from wasmer instance")
51    }
52
53    unsafe fn memory_store(&self, mem_ptr: MemPtr, data: &[u8]) {
54        self.instance_state_ref
55            .memory_store(mem_ptr, data)
56            .expect("error writing to wasmer instance memory");
57    }
58
59    fn m_types_lock(&self) -> MutexGuard<'_, ManagedTypeContainer> {
60        self.tx_context_ref.m_types_lock()
61    }
62
63    fn gas_schedule(&self) -> &GasSchedule {
64        &self.tx_context_ref.0.runtime_ref.vm_ref.gas_schedule
65    }
66
67    fn use_gas(&mut self, gas: u64) -> Result<(), VMHooksEarlyExit> {
68        let gas_limit = self.input_ref().gas_limit;
69        let state_ref = &mut self.instance_state_ref;
70        let prev_gas_used = state_ref
71            .get_points_used()
72            .expect("error fetching points used from instance state");
73
74        let next_gas_used = prev_gas_used + gas;
75
76        // println!("use gas {gas}: {prev_gas_used} -> {next_gas_used}");
77
78        if next_gas_used > gas_limit {
79            Err(VMHooksEarlyExit::new(ReturnCode::OutOfGas.as_u64()))
80        } else {
81            state_ref
82                .set_points_used(next_gas_used)
83                .expect("error setting points used in instance");
84            Ok(())
85        }
86    }
87
88    fn input_ref(&self) -> &TxInput {
89        self.tx_context_ref.input_ref()
90    }
91
92    fn random_next_bytes(&self, length: usize) -> Vec<u8> {
93        self.tx_context_ref.rng_lock().next_bytes(length)
94    }
95
96    fn result_lock(&self) -> MutexGuard<'_, TxResult> {
97        self.tx_context_ref.result_lock()
98    }
99
100    fn storage_read_any_address(&self, address: &VMAddress, key: &[u8]) -> Vec<u8> {
101        self.tx_context_ref.with_account_mut(address, |account| {
102            account.storage.get(key).cloned().unwrap_or_default()
103        })
104    }
105
106    fn storage_write(&mut self, key: &[u8], value: &[u8]) -> Result<(), VMHooksEarlyExit> {
107        self.check_reserved_key(key)?;
108        self.check_not_readonly()?;
109
110        self.tx_context_ref.with_contract_account_mut(|account| {
111            account.storage.insert(key.to_vec(), value.to_vec());
112        });
113        Ok(())
114    }
115
116    fn get_block_config(&self) -> &BlockConfig {
117        &self.tx_context_ref.blockchain_ref().block_config
118    }
119
120    fn back_transfers_lock(&self) -> MutexGuard<'_, BackTransfers> {
121        self.tx_context_ref.back_transfers_lock()
122    }
123
124    fn account_data(&self, address: &VMAddress) -> Option<AccountData> {
125        self.tx_context_ref
126            .with_account_or_else(address, |account| Some(account.clone()), || None)
127    }
128
129    fn account_code(&self, address: &VMAddress) -> Vec<u8> {
130        self.tx_context_ref
131            .blockchain_cache()
132            .with_account(address, |account| account.contract_path.clone())
133            .unwrap_or_else(|| panic!("Account is not a smart contract, it has no code"))
134    }
135
136    fn perform_async_call(
137        &mut self,
138        to: VMAddress,
139        egld_value: num_bigint::BigUint,
140        func_name: TxFunctionName,
141        arguments: Vec<Vec<u8>>,
142    ) -> Result<(), VMHooksEarlyExit> {
143        let async_call_data = self.create_async_call_data(to, egld_value, func_name, arguments);
144        // the cell is no longer needed, since we end in a panic
145        let mut tx_result = self.result_lock();
146        tx_result.all_calls.push(async_call_data.clone());
147        tx_result.pending_calls.async_call = Some(async_call_data);
148        Err(early_exit_async_call())
149    }
150
151    fn perform_execute_on_dest_context(
152        &mut self,
153        to: VMAddress,
154        egld_value: num_bigint::BigUint,
155        func_name: TxFunctionName,
156        arguments: Vec<Vec<u8>>,
157    ) -> Result<TxResult, VMHooksEarlyExit> {
158        let async_call_data = self.create_async_call_data(to, egld_value, func_name, arguments);
159        let tx_input = async_call_tx_input(&async_call_data, CallType::ExecuteOnDestContext);
160        let tx_cache = TxCache::new(self.tx_context_ref.blockchain_cache_arc());
161        let (tx_result, blockchain_updates) = execution::execute_builtin_function_or_default(
162            tx_input,
163            tx_cache,
164            &self.tx_context_ref.runtime_ref,
165            RuntimeInstanceCallLambdaDefault,
166        );
167
168        if tx_result.result_status.is_success() {
169            self.sync_call_post_processing_ok(&tx_result, blockchain_updates);
170        } else {
171            self.sync_call_post_processing_err(&tx_result);
172        }
173
174        Ok(tx_result)
175    }
176
177    fn perform_execute_on_dest_context_readonly(
178        &mut self,
179        to: VMAddress,
180        func_name: TxFunctionName,
181        arguments: Vec<Vec<u8>>,
182    ) -> Result<Vec<Vec<u8>>, VMHooksEarlyExit> {
183        let async_call_data =
184            self.create_async_call_data(to, BigUint::zero(), func_name, arguments);
185        let mut tx_input = async_call_tx_input(&async_call_data, CallType::ExecuteOnDestContext);
186        tx_input.readonly = true;
187        let tx_cache = TxCache::new(self.tx_context_ref.blockchain_cache_arc());
188        let (tx_result, blockchain_updates) = execution::execute_builtin_function_or_default(
189            tx_input,
190            tx_cache,
191            &self.tx_context_ref.runtime_ref,
192            RuntimeInstanceCallLambdaDefault,
193        );
194
195        if tx_result.result_status.is_success() {
196            self.sync_call_post_processing_ok(&tx_result, blockchain_updates);
197            Ok(tx_result.result_values)
198        } else {
199            self.sync_call_post_processing_err(&tx_result);
200
201            // also kill current execution
202            Err(VMHooksEarlyExit::new(tx_result.result_status.as_u64())
203                .with_message(tx_result.result_message.clone()))
204        }
205    }
206
207    fn perform_deploy(
208        &mut self,
209        egld_value: num_bigint::BigUint,
210        contract_code: Vec<u8>,
211        code_metadata: VMCodeMetadata,
212        args: Vec<Vec<u8>>,
213    ) -> Result<(VMAddress, Vec<Vec<u8>>), VMHooksEarlyExit> {
214        let contract_address = self.current_address();
215        let tx_hash = self.tx_hash();
216        let tx_input = TxInput {
217            from: contract_address.clone(),
218            to: VMAddress::zero(),
219            egld_value,
220            esdt_values: Vec::new(),
221            func_name: TxFunctionName::INIT,
222            args,
223            gas_limit: 1000,
224            gas_price: 0,
225            tx_hash,
226            ..Default::default()
227        };
228
229        let tx_cache = TxCache::new(self.tx_context_ref.blockchain_cache_arc());
230        tx_cache.increase_account_nonce(contract_address);
231        let (tx_result, new_address, blockchain_updates) = execution::execute_deploy(
232            tx_input,
233            contract_code,
234            code_metadata,
235            tx_cache,
236            &self.tx_context_ref.runtime_ref,
237            RuntimeInstanceCallLambdaDefault,
238        );
239
240        match tx_result.result_status {
241            ReturnCode::Success => {
242                self.sync_call_post_processing_ok(&tx_result, blockchain_updates);
243                Ok((new_address, tx_result.result_values))
244            }
245            ReturnCode::ExecutionFailed => {
246                self.sync_call_post_processing_err(&tx_result);
247
248                // TODO: not sure it's the right condition, it catches insufficient funds
249                Err(VMHooksEarlyExit::new(ReturnCode::ExecutionFailed.as_u64())
250                    .with_message(tx_result.result_message.clone()))
251            }
252            _ => Err(VMHooksEarlyExit::new(ReturnCode::ExecutionFailed.as_u64())
253                .with_const_message(vm_err_msg::ERROR_SIGNALLED_BY_SMARTCONTRACT)),
254        }
255    }
256
257    fn perform_transfer_execute(
258        &mut self,
259        to: VMAddress,
260        egld_value: num_bigint::BigUint,
261        func_name: TxFunctionName,
262        arguments: Vec<Vec<u8>>,
263    ) -> Result<(), VMHooksEarlyExit> {
264        let async_call_data = self.create_async_call_data(to, egld_value, func_name, arguments);
265        let mut tx_input = async_call_tx_input(&async_call_data, CallType::TransferExecute);
266        if self.is_back_transfer(&tx_input) {
267            tx_input.call_type = CallType::BackTransfer;
268        }
269
270        let tx_cache = TxCache::new(self.tx_context_ref.blockchain_cache_arc());
271        let (tx_result, blockchain_updates) = execution::execute_builtin_function_or_default(
272            tx_input,
273            tx_cache,
274            &self.tx_context_ref.runtime_ref,
275            RuntimeInstanceCallLambdaDefault,
276        );
277
278        match tx_result.result_status {
279            ReturnCode::Success => {
280                self.tx_context_ref
281                    .result_lock()
282                    .all_calls
283                    .push(async_call_data);
284
285                self.sync_call_post_processing_ok(&tx_result, blockchain_updates);
286                Ok(())
287            }
288            ReturnCode::ExecutionFailed => {
289                self.sync_call_post_processing_err(&tx_result);
290
291                // TODO: not sure it's the right condition, it catches insufficient funds
292                Err(VMHooksEarlyExit::new(ReturnCode::ExecutionFailed.as_u64())
293                    .with_message(tx_result.result_message.clone()))
294            }
295            _ => Err(VMHooksEarlyExit::new(ReturnCode::ExecutionFailed.as_u64())
296                .with_const_message(vm_err_msg::ERROR_SIGNALLED_BY_SMARTCONTRACT)),
297        }
298    }
299}
300
301impl<S: InstanceState> TxVMHooksContext<S> {
302    fn create_async_call_data(
303        &self,
304        to: VMAddress,
305        egld_value: num_bigint::BigUint,
306        func_name: TxFunctionName,
307        arguments: Vec<Vec<u8>>,
308    ) -> AsyncCallTxData {
309        let contract_address = &self.tx_context_ref.input_ref().to;
310        let tx_hash = self.tx_hash();
311        AsyncCallTxData {
312            from: contract_address.clone(),
313            to,
314            call_value: egld_value,
315            endpoint_name: func_name,
316            arguments,
317            tx_hash,
318        }
319    }
320
321    fn sync_call_post_processing_ok(
322        &self,
323        tx_result: &TxResult,
324        blockchain_updates: BlockchainUpdate,
325    ) {
326        self.tx_context_ref
327            .blockchain_cache()
328            .commit_updates(blockchain_updates);
329
330        self.tx_context_ref
331            .result_lock()
332            .merge_after_sync_call(tx_result);
333
334        let contract_address = &self.tx_context_ref.input_ref().to;
335        let builtin_functions = &self.tx_context_ref.runtime_ref.vm_ref.builtin_functions;
336        self.back_transfers_lock()
337            .new_from_result(contract_address, tx_result, builtin_functions);
338    }
339
340    fn sync_call_post_processing_err(&self, tx_result: &TxResult) {
341        let mut own_tx_result = self.tx_context_ref.result_lock();
342        if let Some(transfer_log) = &tx_result.esdt_transfer_log {
343            own_tx_result.result_logs.push(transfer_log.clone());
344        }
345        own_tx_result
346            .error_trace
347            .extend_from_slice(&tx_result.error_trace);
348    }
349
350    fn check_reserved_key(&mut self, key: &[u8]) -> Result<(), VMHooksEarlyExit> {
351        if key.starts_with(STORAGE_RESERVED_PREFIX) {
352            return Err(early_exit_vm_error(vm_err_msg::WRITE_RESERVED));
353        }
354        Ok(())
355    }
356
357    /// TODO: only checked on storage writes, needs more checks for calls, transfers, etc.
358    fn check_not_readonly(&mut self) -> Result<(), VMHooksEarlyExit> {
359        if self.tx_context_ref.input_ref().readonly {
360            return Err(early_exit_vm_error(vm_err_msg::WRITE_READONLY));
361        }
362        Ok(())
363    }
364
365    fn is_back_transfer(&self, tx_input: &TxInput) -> bool {
366        let caller_address = &self.tx_context_ref.input_ref().from;
367        if !caller_address.is_smart_contract_address() {
368            return false;
369        }
370
371        let builtin_functions = &self.tx_context_ref.runtime_ref.vm_ref.builtin_functions;
372        let token_transfers = builtin_functions.extract_token_transfers(tx_input);
373        &token_transfers.real_recipient == caller_address
374    }
375}