multiversx_chain_vm/host/vm_hooks/
vh_tx_context.rs1use 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 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 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 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 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 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 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}