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<Vec<Vec<u8>>, 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 Ok(self.sync_call_post_processing(tx_result, blockchain_updates))
170 } else {
171 Err(VMHooksEarlyExit::new(tx_result.result_status.as_u64())
173 .with_message(tx_result.result_message.clone()))
174 }
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 Ok(self.sync_call_post_processing(tx_result, blockchain_updates))
197 } else {
198 Err(VMHooksEarlyExit::new(tx_result.result_status.as_u64())
200 .with_message(tx_result.result_message.clone()))
201 }
202 }
203
204 fn perform_deploy(
205 &mut self,
206 egld_value: num_bigint::BigUint,
207 contract_code: Vec<u8>,
208 code_metadata: VMCodeMetadata,
209 args: Vec<Vec<u8>>,
210 ) -> Result<(VMAddress, Vec<Vec<u8>>), VMHooksEarlyExit> {
211 let contract_address = self.current_address();
212 let tx_hash = self.tx_hash();
213 let tx_input = TxInput {
214 from: contract_address.clone(),
215 to: VMAddress::zero(),
216 egld_value,
217 esdt_values: Vec::new(),
218 func_name: TxFunctionName::INIT,
219 args,
220 gas_limit: 1000,
221 gas_price: 0,
222 tx_hash,
223 ..Default::default()
224 };
225
226 let tx_cache = TxCache::new(self.tx_context_ref.blockchain_cache_arc());
227 tx_cache.increase_account_nonce(contract_address);
228 let (tx_result, new_address, blockchain_updates) = execution::execute_deploy(
229 tx_input,
230 contract_code,
231 code_metadata,
232 tx_cache,
233 &self.tx_context_ref.runtime_ref,
234 RuntimeInstanceCallLambdaDefault,
235 );
236
237 match tx_result.result_status {
238 ReturnCode::Success => Ok((
239 new_address,
240 self.sync_call_post_processing(tx_result, blockchain_updates),
241 )),
242 ReturnCode::ExecutionFailed => {
243 Err(VMHooksEarlyExit::new(ReturnCode::ExecutionFailed.as_u64())
245 .with_message(tx_result.result_message.clone()))
246 }
247 _ => Err(VMHooksEarlyExit::new(ReturnCode::ExecutionFailed.as_u64())
248 .with_const_message(vm_err_msg::ERROR_SIGNALLED_BY_SMARTCONTRACT)),
249 }
250 }
251
252 fn perform_transfer_execute(
253 &mut self,
254 to: VMAddress,
255 egld_value: num_bigint::BigUint,
256 func_name: TxFunctionName,
257 arguments: Vec<Vec<u8>>,
258 ) -> Result<(), VMHooksEarlyExit> {
259 let async_call_data = self.create_async_call_data(to, egld_value, func_name, arguments);
260 let mut tx_input = async_call_tx_input(&async_call_data, CallType::TransferExecute);
261 if self.is_back_transfer(&tx_input) {
262 tx_input.call_type = CallType::BackTransfer;
263 }
264
265 let tx_cache = TxCache::new(self.tx_context_ref.blockchain_cache_arc());
266 let (tx_result, blockchain_updates) = execution::execute_builtin_function_or_default(
267 tx_input,
268 tx_cache,
269 &self.tx_context_ref.runtime_ref,
270 RuntimeInstanceCallLambdaDefault,
271 );
272
273 match tx_result.result_status {
274 ReturnCode::Success => {
275 self.tx_context_ref
276 .result_lock()
277 .all_calls
278 .push(async_call_data);
279
280 let _ = self.sync_call_post_processing(tx_result, blockchain_updates);
281 Ok(())
282 }
283 ReturnCode::ExecutionFailed => {
284 Err(VMHooksEarlyExit::new(ReturnCode::ExecutionFailed.as_u64())
286 .with_message(tx_result.result_message.clone()))
287 }
288 _ => Err(VMHooksEarlyExit::new(ReturnCode::ExecutionFailed.as_u64())
289 .with_const_message(vm_err_msg::ERROR_SIGNALLED_BY_SMARTCONTRACT)),
290 }
291 }
292}
293
294impl<S: InstanceState> TxVMHooksContext<S> {
295 fn create_async_call_data(
296 &self,
297 to: VMAddress,
298 egld_value: num_bigint::BigUint,
299 func_name: TxFunctionName,
300 arguments: Vec<Vec<u8>>,
301 ) -> AsyncCallTxData {
302 let contract_address = &self.tx_context_ref.input_ref().to;
303 let tx_hash = self.tx_hash();
304 AsyncCallTxData {
305 from: contract_address.clone(),
306 to,
307 call_value: egld_value,
308 endpoint_name: func_name,
309 arguments,
310 tx_hash,
311 }
312 }
313
314 fn sync_call_post_processing(
315 &self,
316 tx_result: TxResult,
317 blockchain_updates: BlockchainUpdate,
318 ) -> Vec<Vec<u8>> {
319 self.tx_context_ref
320 .blockchain_cache()
321 .commit_updates(blockchain_updates);
322
323 self.tx_context_ref
324 .result_lock()
325 .merge_after_sync_call(&tx_result);
326
327 let contract_address = &self.tx_context_ref.input_ref().to;
328 let builtin_functions = &self.tx_context_ref.runtime_ref.vm_ref.builtin_functions;
329 self.back_transfers_lock()
330 .new_from_result(contract_address, &tx_result, builtin_functions);
331
332 tx_result.result_values
333 }
334
335 fn check_reserved_key(&mut self, key: &[u8]) -> Result<(), VMHooksEarlyExit> {
336 if key.starts_with(STORAGE_RESERVED_PREFIX) {
337 return Err(early_exit_vm_error(vm_err_msg::WRITE_RESERVED));
338 }
339 Ok(())
340 }
341
342 fn check_not_readonly(&mut self) -> Result<(), VMHooksEarlyExit> {
344 if self.tx_context_ref.input_ref().readonly {
345 return Err(early_exit_vm_error(vm_err_msg::WRITE_READONLY));
346 }
347 Ok(())
348 }
349
350 fn is_back_transfer(&self, tx_input: &TxInput) -> bool {
351 let caller_address = &self.tx_context_ref.input_ref().from;
352 if !caller_address.is_smart_contract_address() {
353 return false;
354 }
355
356 let builtin_functions = &self.tx_context_ref.runtime_ref.vm_ref.builtin_functions;
357 let token_transfers = builtin_functions.extract_token_transfers(tx_input);
358 &token_transfers.real_recipient == caller_address
359 }
360}