multiversx_chain_vm/host/vm_hooks/vh_handler/
vh_send.rs

1use crate::{
2    chain_core::builtin_func_names::{
3        ESDT_MULTI_TRANSFER_FUNC_NAME, ESDT_NFT_TRANSFER_FUNC_NAME, ESDT_TRANSFER_FUNC_NAME,
4        UPGRADE_CONTRACT_FUNC_NAME,
5    },
6    host::{
7        context::{AsyncCallTxData, Promise, TxFunctionName, TxTokenTransfer},
8        vm_hooks::{vh_early_exit::early_exit_vm_error, VMHooksContext},
9    },
10    types::{top_encode_big_uint, top_encode_u64, RawHandle, VMAddress, VMCodeMetadata},
11    vm_err_msg,
12};
13use multiversx_chain_core::types::ReturnCode;
14use multiversx_chain_vm_executor::VMHooksEarlyExit;
15use num_traits::Zero;
16
17use super::VMHooksHandler;
18
19fn append_endpoint_name_and_args(
20    args: &mut Vec<Vec<u8>>,
21    endpoint_name: TxFunctionName,
22    arg_buffer: Vec<Vec<u8>>,
23) {
24    if !endpoint_name.is_empty() {
25        args.push(endpoint_name.into_bytes());
26        args.extend(arg_buffer);
27    }
28}
29
30impl<C: VMHooksContext> VMHooksHandler<C> {
31    fn perform_transfer_execute_esdt(
32        &mut self,
33        to: VMAddress,
34        token: Vec<u8>,
35        amount: num_bigint::BigUint,
36        _gas_limit: u64,
37        func_name: TxFunctionName,
38        arguments: Vec<Vec<u8>>,
39    ) -> Result<(), VMHooksEarlyExit> {
40        let mut args = vec![token, amount.to_bytes_be()];
41        append_endpoint_name_and_args(&mut args, func_name, arguments);
42
43        self.context.perform_transfer_execute(
44            to,
45            num_bigint::BigUint::zero(),
46            ESDT_TRANSFER_FUNC_NAME.into(),
47            args,
48        )
49    }
50
51    #[allow(clippy::too_many_arguments)]
52    fn perform_transfer_execute_nft(
53        &mut self,
54        to: VMAddress,
55        token: Vec<u8>,
56        nonce: u64,
57        amount: num_bigint::BigUint,
58        _gas_limit: u64,
59        func_name: TxFunctionName,
60        arguments: Vec<Vec<u8>>,
61    ) -> Result<(), VMHooksEarlyExit> {
62        let contract_address = self.context.current_address().clone();
63
64        let mut args = vec![
65            token,
66            top_encode_u64(nonce),
67            top_encode_big_uint(&amount),
68            to.to_vec(),
69        ];
70
71        append_endpoint_name_and_args(&mut args, func_name, arguments);
72
73        self.context.perform_transfer_execute(
74            contract_address,
75            num_bigint::BigUint::zero(),
76            ESDT_NFT_TRANSFER_FUNC_NAME.into(),
77            args,
78        )
79    }
80
81    fn perform_transfer_execute_multi(
82        &mut self,
83        to: VMAddress,
84        payments: Vec<TxTokenTransfer>,
85        _gas_limit: u64,
86        endpoint_name: TxFunctionName,
87        arguments: Vec<Vec<u8>>,
88    ) -> Result<(), VMHooksEarlyExit> {
89        let contract_address = self.context.current_address().clone();
90
91        let mut args = vec![to.to_vec(), top_encode_u64(payments.len() as u64)];
92
93        for payment in payments.into_iter() {
94            let token_bytes = payment.token_identifier;
95            args.push(token_bytes);
96            let nonce_bytes = top_encode_u64(payment.nonce);
97            args.push(nonce_bytes);
98            let amount_bytes = top_encode_big_uint(&payment.value);
99            args.push(amount_bytes);
100        }
101
102        append_endpoint_name_and_args(&mut args, endpoint_name, arguments);
103
104        self.context.perform_transfer_execute(
105            contract_address,
106            num_bigint::BigUint::zero(),
107            ESDT_MULTI_TRANSFER_FUNC_NAME.into(),
108            args,
109        )
110    }
111
112    fn perform_upgrade_contract(
113        &mut self,
114        to: VMAddress,
115        egld_value: num_bigint::BigUint,
116        contract_code: Vec<u8>,
117        code_metadata: VMCodeMetadata,
118        args: Vec<Vec<u8>>,
119    ) -> Result<(), VMHooksEarlyExit> {
120        let mut arguments = vec![contract_code, code_metadata.to_vec()];
121        arguments.extend(args);
122        self.context.perform_async_call(
123            to,
124            egld_value,
125            UPGRADE_CONTRACT_FUNC_NAME.into(),
126            arguments,
127        )
128    }
129
130    pub fn transfer_value_execute(
131        &mut self,
132        to_handle: RawHandle,
133        amount_handle: RawHandle,
134        _gas_limit: u64,
135        endpoint_name_handle: RawHandle,
136        arg_buffer_handle: RawHandle,
137    ) -> Result<(), VMHooksEarlyExit> {
138        let recipient = self.context.m_types_lock().mb_to_address(to_handle);
139        let egld_value = self.context.m_types_lock().bu_get(amount_handle);
140        let endpoint_name = self
141            .context
142            .m_types_lock()
143            .mb_to_function_name(endpoint_name_handle);
144        let arg_buffer = self.load_arg_data(arg_buffer_handle)?;
145
146        self.context
147            .perform_transfer_execute(recipient, egld_value, endpoint_name, arg_buffer)
148    }
149
150    pub fn multi_transfer_esdt_nft_execute(
151        &mut self,
152        to_handle: RawHandle,
153        payments_handle: RawHandle,
154        gas_limit: u64,
155        endpoint_name_handle: RawHandle,
156        arg_buffer_handle: RawHandle,
157    ) -> Result<(), VMHooksEarlyExit> {
158        let to = self.context.m_types_lock().mb_to_address(to_handle);
159        let (payments, num_bytes_copied) = self
160            .context
161            .m_types_lock()
162            .mb_get_vec_of_esdt_payments(payments_handle);
163        self.use_gas_for_data_copy(num_bytes_copied)?;
164        let endpoint_name = self
165            .context
166            .m_types_lock()
167            .mb_to_function_name(endpoint_name_handle);
168        let arg_buffer = self.load_arg_data(arg_buffer_handle)?;
169
170        if payments.len() == 1 {
171            let payment = payments[0].clone();
172            if payment.nonce == 0 {
173                self.perform_transfer_execute_esdt(
174                    to,
175                    payment.token_identifier,
176                    payment.value,
177                    gas_limit,
178                    endpoint_name,
179                    arg_buffer,
180                )
181            } else {
182                self.perform_transfer_execute_nft(
183                    to,
184                    payment.token_identifier,
185                    payment.nonce,
186                    payment.value,
187                    gas_limit,
188                    endpoint_name,
189                    arg_buffer,
190                )
191            }
192        } else {
193            self.perform_transfer_execute_multi(to, payments, gas_limit, endpoint_name, arg_buffer)
194        }
195    }
196
197    pub fn managed_multi_transfer_esdt_nft_execute_with_return(
198        &mut self,
199        to_handle: i32,
200        payments_handle: i32,
201        gas_limit: u64,
202        function_handle: i32,
203        arguments_handle: i32,
204    ) -> Result<i32, VMHooksEarlyExit> {
205        self.multi_transfer_esdt_nft_execute(
206            to_handle,
207            payments_handle,
208            gas_limit,
209            function_handle,
210            arguments_handle,
211        )?;
212        // TODO: fallibility
213        Ok(0)
214    }
215
216    pub fn async_call_raw(
217        &mut self,
218        to_handle: RawHandle,
219        egld_value_handle: RawHandle,
220        endpoint_name_handle: RawHandle,
221        arg_buffer_handle: RawHandle,
222    ) -> Result<(), VMHooksEarlyExit> {
223        let to = self.context.m_types_lock().mb_to_address(to_handle);
224        let egld_value = self.context.m_types_lock().bu_get(egld_value_handle);
225        let endpoint_name = self
226            .context
227            .m_types_lock()
228            .mb_to_function_name(endpoint_name_handle);
229        let arg_buffer = self.load_arg_data(arg_buffer_handle)?;
230
231        self.context
232            .perform_async_call(to, egld_value, endpoint_name, arg_buffer)
233    }
234
235    #[allow(clippy::too_many_arguments)]
236    pub fn create_async_call_raw(
237        &mut self,
238        to_handle: RawHandle,
239        egld_value_handle: RawHandle,
240        endpoint_name_handle: RawHandle,
241        arg_buffer_handle: RawHandle,
242        success_callback: &[u8],
243        error_callback: &[u8],
244        _gas: u64,
245        _extra_gas_for_callback: u64,
246        callback_closure_handle: RawHandle,
247    ) -> Result<(), VMHooksEarlyExit> {
248        let contract_address = self.context.current_address().clone();
249        let to = self.context.m_types_lock().mb_to_address(to_handle);
250        let egld_value = self.context.m_types_lock().bu_get(egld_value_handle);
251        let endpoint_name = self
252            .context
253            .m_types_lock()
254            .mb_to_function_name(endpoint_name_handle);
255        if endpoint_name.is_empty() {
256            // imitating the behavior of the VM
257            // TODO: lift limitation from the VM, then also remove this condition here
258            return Err(early_exit_vm_error(vm_err_msg::PROMISES_TOKENIZE_FAILED));
259        }
260        let arg_buffer = self.load_arg_data(arg_buffer_handle)?;
261        let tx_hash = self.context.tx_hash();
262        let callback_closure_data = self
263            .context
264            .m_types_lock()
265            .mb_get(callback_closure_handle)
266            .to_vec();
267
268        let call = AsyncCallTxData {
269            from: contract_address,
270            to,
271            call_value: egld_value,
272            endpoint_name,
273            arguments: arg_buffer,
274            tx_hash,
275        };
276
277        let promise = Promise {
278            call,
279            success_callback: success_callback.into(),
280            error_callback: error_callback.into(),
281            callback_closure_data,
282        };
283
284        let mut tx_result = self.context.result_lock();
285        tx_result.all_calls.push(promise.call.clone());
286        tx_result.pending_calls.promises.push(promise);
287
288        Ok(())
289    }
290
291    #[allow(clippy::too_many_arguments)]
292    pub fn deploy_contract(
293        &mut self,
294        _gas: u64,
295        egld_value_handle: RawHandle,
296        code_handle: RawHandle,
297        code_metadata_handle: RawHandle,
298        arg_buffer_handle: RawHandle,
299        new_address_handle: RawHandle,
300        result_handle: RawHandle,
301    ) -> Result<(), VMHooksEarlyExit> {
302        let egld_value = self.context.m_types_lock().bu_get(egld_value_handle);
303        let code = self.context.m_types_lock().mb_get(code_handle).to_vec();
304        let code_metadata = self
305            .context
306            .m_types_lock()
307            .mb_to_code_metadata(code_metadata_handle);
308        let arg_buffer = self.load_arg_data(arg_buffer_handle)?;
309
310        let (new_address, result) =
311            self.context
312                .perform_deploy(egld_value, code, code_metadata, arg_buffer)?;
313
314        self.context
315            .m_types_lock()
316            .mb_set(new_address_handle, new_address.to_vec());
317        self.set_return_data(result_handle, result)?;
318
319        Ok(())
320    }
321
322    #[allow(clippy::too_many_arguments)]
323    pub fn deploy_from_source_contract(
324        &mut self,
325        _gas: u64,
326        egld_value_handle: RawHandle,
327        source_contract_address_handle: RawHandle,
328        code_metadata_handle: RawHandle,
329        arg_buffer_handle: RawHandle,
330        new_address_handle: RawHandle,
331        result_handle: RawHandle,
332    ) -> Result<(), VMHooksEarlyExit> {
333        let egld_value = self.context.m_types_lock().bu_get(egld_value_handle);
334        let source_contract_address = self
335            .context
336            .m_types_lock()
337            .mb_to_address(source_contract_address_handle);
338        let source_contract_code = self.context.account_code(&source_contract_address);
339        let code_metadata = self
340            .context
341            .m_types_lock()
342            .mb_to_code_metadata(code_metadata_handle);
343        let arg_buffer = self.load_arg_data(arg_buffer_handle)?;
344
345        let (new_address, result) = self.context.perform_deploy(
346            egld_value,
347            source_contract_code,
348            code_metadata,
349            arg_buffer,
350        )?;
351
352        self.context
353            .m_types_lock()
354            .mb_set(new_address_handle, new_address.to_vec());
355
356        self.set_return_data(result_handle, result)?;
357
358        Ok(())
359    }
360
361    pub fn upgrade_from_source_contract(
362        &mut self,
363        sc_address_handle: RawHandle,
364        _gas: u64,
365        egld_value_handle: RawHandle,
366        source_contract_address_handle: RawHandle,
367        code_metadata_handle: RawHandle,
368        arg_buffer_handle: RawHandle,
369    ) -> Result<(), VMHooksEarlyExit> {
370        self.use_gas(self.gas_schedule().base_ops_api_cost.create_contract)?;
371
372        let to = self.context.m_types_lock().mb_to_address(sc_address_handle);
373        let egld_value = self.context.m_types_lock().bu_get(egld_value_handle);
374        let source_contract_address = self
375            .context
376            .m_types_lock()
377            .mb_to_address(source_contract_address_handle);
378        let source_contract_code = self.context.account_code(&source_contract_address);
379        let code_metadata = self
380            .context
381            .m_types_lock()
382            .mb_to_code_metadata(code_metadata_handle);
383        let arg_buffer = self.load_arg_data(arg_buffer_handle)?;
384
385        self.perform_upgrade_contract(
386            to,
387            egld_value,
388            source_contract_code,
389            code_metadata,
390            arg_buffer,
391        )
392    }
393
394    pub fn upgrade_contract(
395        &mut self,
396        sc_address_handle: RawHandle,
397        _gas: u64,
398        egld_value_handle: RawHandle,
399        code_handle: RawHandle,
400        code_metadata_handle: RawHandle,
401        arg_buffer_handle: RawHandle,
402    ) -> Result<(), VMHooksEarlyExit> {
403        self.use_gas(self.gas_schedule().base_ops_api_cost.create_contract)?;
404
405        let to = self.context.m_types_lock().mb_to_address(sc_address_handle);
406        let egld_value = self.context.m_types_lock().bu_get(egld_value_handle);
407        let code = self.context.m_types_lock().mb_get(code_handle).to_vec();
408        let code_metadata = self
409            .context
410            .m_types_lock()
411            .mb_to_code_metadata(code_metadata_handle);
412        let arg_buffer = self.load_arg_data(arg_buffer_handle)?;
413
414        self.perform_upgrade_contract(to, egld_value, code, code_metadata, arg_buffer)
415    }
416
417    /// Executes a sync call, and returns the TxResult, as-is.
418    ///
419    /// It is also used in the fallible variant, so it will not kill the current execution on execution error.
420    ///
421    /// However, it will cause a failure if any other failure occurs, such as parsing the arguments.
422    ///
423    /// Gas usage should also cause an immediate failure, but it isn't yet implemented.
424    fn execute_on_dest_context_fallible_raw(
425        &mut self,
426        _gas: u64,
427        to_handle: RawHandle,
428        egld_value_handle: RawHandle,
429        endpoint_name_handle: RawHandle,
430        arg_buffer_handle: RawHandle,
431        result_handle: RawHandle,
432    ) -> Result<(ReturnCode, String), VMHooksEarlyExit> {
433        let to = self.context.m_types_lock().mb_to_address(to_handle);
434        let egld_value = self.context.m_types_lock().bu_get(egld_value_handle);
435        let endpoint_name = self
436            .context
437            .m_types_lock()
438            .mb_to_function_name(endpoint_name_handle);
439        let arg_buffer = self.load_arg_data(arg_buffer_handle)?;
440
441        let tx_result = self.context.perform_execute_on_dest_context(
442            to,
443            egld_value,
444            endpoint_name,
445            arg_buffer,
446        )?;
447
448        if tx_result.result_status.is_success() {
449            self.set_return_data(result_handle, tx_result.result_values)?;
450        }
451
452        Ok((tx_result.result_status, tx_result.result_message))
453    }
454
455    pub fn execute_on_dest_context_raw(
456        &mut self,
457        gas: u64,
458        to_handle: RawHandle,
459        egld_value_handle: RawHandle,
460        endpoint_name_handle: RawHandle,
461        arg_buffer_handle: RawHandle,
462        result_handle: RawHandle,
463    ) -> Result<(), VMHooksEarlyExit> {
464        let (result_status, result_message) = self.execute_on_dest_context_fallible_raw(
465            gas,
466            to_handle,
467            egld_value_handle,
468            endpoint_name_handle,
469            arg_buffer_handle,
470            result_handle,
471        )?;
472
473        if result_status.is_success() {
474            Ok(())
475        } else {
476            // kill current execution
477            Err(VMHooksEarlyExit::new(result_status.as_u64()).with_message(result_message.clone()))
478        }
479    }
480
481    pub fn execute_on_dest_context_fallible(
482        &mut self,
483        gas: u64,
484        to_handle: RawHandle,
485        egld_value_handle: RawHandle,
486        endpoint_name_handle: RawHandle,
487        arg_buffer_handle: RawHandle,
488        result_handle: RawHandle,
489    ) -> Result<i32, VMHooksEarlyExit> {
490        let (result_status, _) = self.execute_on_dest_context_fallible_raw(
491            gas,
492            to_handle,
493            egld_value_handle,
494            endpoint_name_handle,
495            arg_buffer_handle,
496            result_handle,
497        )?;
498
499        if result_status.is_success() {
500            Ok(0)
501        } else {
502            Ok(1)
503        }
504    }
505
506    pub fn execute_on_dest_context_readonly_raw(
507        &mut self,
508        _gas: u64,
509        to_handle: RawHandle,
510        endpoint_name_handle: RawHandle,
511        arg_buffer_handle: RawHandle,
512        result_handle: RawHandle,
513    ) -> Result<(), VMHooksEarlyExit> {
514        let to = self.context.m_types_lock().mb_to_address(to_handle);
515        let endpoint_name = self
516            .context
517            .m_types_lock()
518            .mb_to_function_name(endpoint_name_handle);
519        let arg_buffer = self.load_arg_data(arg_buffer_handle)?;
520
521        let result =
522            self.context
523                .perform_execute_on_dest_context_readonly(to, endpoint_name, arg_buffer)?;
524
525        self.set_return_data(result_handle, result)?;
526
527        Ok(())
528    }
529
530    fn load_arg_data(
531        &mut self,
532        arg_buffer_handle: RawHandle,
533    ) -> Result<Vec<Vec<u8>>, VMHooksEarlyExit> {
534        let (arg_buffer, num_bytes_copied) = self
535            .context
536            .m_types_lock()
537            .mb_get_vec_of_bytes(arg_buffer_handle);
538
539        self.use_gas_for_data_copy(num_bytes_copied)?;
540        Ok(arg_buffer)
541    }
542
543    fn set_return_data(
544        &mut self,
545        result_handle: RawHandle,
546        result: Vec<Vec<u8>>,
547    ) -> Result<(), VMHooksEarlyExit> {
548        let num_bytes_copied = self
549            .context
550            .m_types_lock()
551            .mb_set_vec_of_bytes(result_handle, result);
552
553        self.use_gas_for_data_copy(num_bytes_copied)
554    }
555
556    pub fn clean_return_data(&mut self) -> Result<(), VMHooksEarlyExit> {
557        let mut tx_result = self.context.result_lock();
558        tx_result.result_values.clear();
559        Ok(())
560    }
561
562    pub fn delete_from_return_data(&mut self, index: usize) -> Result<(), VMHooksEarlyExit> {
563        let mut tx_result = self.context.result_lock();
564        if index > tx_result.result_values.len() {
565            return Ok(());
566        }
567
568        let _ = tx_result.result_values.remove(index);
569        Ok(())
570    }
571}