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