multiversx_chain_vm/host/vm_hooks/vh_handler/
vh_blockchain.rs

1use crate::{
2    blockchain::state::{EsdtData, EsdtInstance},
3    chain_core::builtin_func_names::*,
4    host::vm_hooks::VMHooksContext,
5    types::{EsdtLocalRole, EsdtLocalRoleFlags, RawHandle, VMAddress},
6};
7use multiversx_chain_core::types::{EsdtTokenType, ReturnCode};
8use multiversx_chain_vm_executor::VMHooksEarlyExit;
9use num_bigint::BigInt;
10use num_traits::Zero;
11
12use super::VMHooksHandler;
13
14// The Go VM doesn't do it, but if we change that, we can enable it easily here too via this constant.
15const ESDT_TOKEN_DATA_FUNC_RESETS_VALUES: bool = false;
16const VM_BUILTIN_FUNCTION_NAMES: [&str; 16] = [
17    ESDT_LOCAL_MINT_FUNC_NAME,
18    ESDT_LOCAL_BURN_FUNC_NAME,
19    ESDT_MULTI_TRANSFER_FUNC_NAME,
20    ESDT_NFT_TRANSFER_FUNC_NAME,
21    ESDT_NFT_CREATE_FUNC_NAME,
22    ESDT_NFT_ADD_QUANTITY_FUNC_NAME,
23    ESDT_NFT_ADD_URI_FUNC_NAME,
24    ESDT_NFT_UPDATE_ATTRIBUTES_FUNC_NAME,
25    ESDT_NFT_BURN_FUNC_NAME,
26    ESDT_TRANSFER_FUNC_NAME,
27    CHANGE_OWNER_BUILTIN_FUNC_NAME,
28    CLAIM_DEVELOPER_REWARDS_FUNC_NAME,
29    SET_USERNAME_FUNC_NAME,
30    MIGRATE_USERNAME_FUNC_NAME,
31    DELETE_USERNAME_FUNC_NAME,
32    UPGRADE_CONTRACT_FUNC_NAME,
33];
34
35impl<C: VMHooksContext> VMHooksHandler<C> {
36    pub fn is_contract_address(&mut self, address_bytes: &[u8]) -> Result<bool, VMHooksEarlyExit> {
37        self.use_gas(self.gas_schedule().base_ops_api_cost.is_smart_contract)?;
38
39        let address = VMAddress::from_slice(address_bytes);
40        Ok(&address == self.context.current_address())
41    }
42
43    pub fn managed_caller(&mut self, dest_handle: RawHandle) -> Result<(), VMHooksEarlyExit> {
44        self.use_gas(
45            self.gas_schedule()
46                .managed_buffer_api_cost
47                .m_buffer_set_bytes,
48        )?;
49
50        self.context
51            .m_types_lock()
52            .mb_set(dest_handle, self.context.input_ref().from.to_vec());
53        Ok(())
54    }
55
56    pub fn managed_sc_address(&mut self, dest_handle: RawHandle) -> Result<(), VMHooksEarlyExit> {
57        self.use_gas(
58            self.gas_schedule()
59                .managed_buffer_api_cost
60                .m_buffer_set_bytes,
61        )?;
62
63        self.context
64            .m_types_lock()
65            .mb_set(dest_handle, self.context.current_address().to_vec());
66        Ok(())
67    }
68
69    pub fn managed_owner_address(
70        &mut self,
71        dest_handle: RawHandle,
72    ) -> Result<(), VMHooksEarlyExit> {
73        self.use_gas(
74            self.gas_schedule()
75                .managed_buffer_api_cost
76                .m_buffer_set_bytes,
77        )?;
78
79        self.context.m_types_lock().mb_set(
80            dest_handle,
81            self.current_account_data()
82                .contract_owner
83                .unwrap_or_else(|| panic!("contract owner address not set"))
84                .to_vec(),
85        );
86        Ok(())
87    }
88
89    pub fn get_shard_of_address(&mut self, address_bytes: &[u8]) -> Result<i32, VMHooksEarlyExit> {
90        self.use_gas(self.gas_schedule().base_ops_api_cost.get_shard_of_address)?;
91
92        Ok((address_bytes[address_bytes.len() - 1] % 3).into())
93    }
94
95    pub fn is_smart_contract(&mut self, address_bytes: &[u8]) -> Result<bool, VMHooksEarlyExit> {
96        self.use_gas(self.gas_schedule().base_ops_api_cost.is_smart_contract)?;
97
98        Ok(VMAddress::from_slice(address_bytes).is_smart_contract_address())
99    }
100
101    pub fn load_balance(
102        &mut self,
103        address_bytes: &[u8],
104        dest: RawHandle,
105    ) -> Result<(), VMHooksEarlyExit> {
106        self.use_gas(self.gas_schedule().big_int_api_cost.big_int_set_int_64)?;
107
108        assert!(
109            self.is_contract_address(address_bytes)?,
110            "get balance not yet implemented for accounts other than the contract itself"
111        );
112        self.context
113            .m_types_lock()
114            .bi_overwrite(dest, self.current_account_data().egld_balance.into());
115
116        Ok(())
117    }
118
119    pub fn get_tx_hash(&mut self, dest: RawHandle) -> Result<(), VMHooksEarlyExit> {
120        self.use_gas(self.gas_schedule().base_ops_api_cost.get_current_tx_hash)?;
121
122        self.context
123            .m_types_lock()
124            .mb_set(dest, self.context.input_ref().tx_hash.to_vec());
125        Ok(())
126    }
127
128    pub fn get_gas_left(&mut self) -> Result<i64, VMHooksEarlyExit> {
129        self.use_gas(self.gas_schedule().base_ops_api_cost.get_gas_left)?;
130
131        Ok(self.context.input_ref().gas_limit as i64)
132    }
133
134    pub fn get_block_timestamp(&mut self) -> Result<i64, VMHooksEarlyExit> {
135        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_time_stamp)?;
136
137        Ok(self
138            .context
139            .get_block_config()
140            .current_block_info
141            .block_timestamp_ms as i64
142            / 1000)
143    }
144
145    pub fn get_block_timestamp_ms(&mut self) -> Result<i64, VMHooksEarlyExit> {
146        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_time_stamp)?;
147
148        Ok(self
149            .context
150            .get_block_config()
151            .current_block_info
152            .block_timestamp_ms as i64)
153    }
154
155    pub fn get_block_nonce(&mut self) -> Result<i64, VMHooksEarlyExit> {
156        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_nonce)?;
157
158        Ok(self
159            .context
160            .get_block_config()
161            .current_block_info
162            .block_nonce as i64)
163    }
164
165    pub fn get_block_round(&mut self) -> Result<i64, VMHooksEarlyExit> {
166        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_round)?;
167
168        Ok(self
169            .context
170            .get_block_config()
171            .current_block_info
172            .block_round as i64)
173    }
174
175    pub fn get_block_epoch(&mut self) -> Result<i64, VMHooksEarlyExit> {
176        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_epoch)?;
177
178        Ok(self
179            .context
180            .get_block_config()
181            .current_block_info
182            .block_epoch as i64)
183    }
184
185    pub fn get_block_random_seed(&mut self, dest: RawHandle) -> Result<(), VMHooksEarlyExit> {
186        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_random_seed)?;
187
188        self.context.m_types_lock().mb_set(
189            dest,
190            self.context
191                .get_block_config()
192                .current_block_info
193                .block_random_seed
194                .to_vec(),
195        );
196        Ok(())
197    }
198
199    pub fn get_prev_block_timestamp(&mut self) -> Result<i64, VMHooksEarlyExit> {
200        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_time_stamp)?;
201
202        Ok(self
203            .context
204            .get_block_config()
205            .previous_block_info
206            .block_timestamp_ms as i64
207            / 1000)
208    }
209
210    pub fn get_prev_block_timestamp_ms(&mut self) -> Result<i64, VMHooksEarlyExit> {
211        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_time_stamp)?;
212
213        Ok(self
214            .context
215            .get_block_config()
216            .previous_block_info
217            .block_timestamp_ms as i64)
218    }
219
220    pub fn get_prev_block_nonce(&mut self) -> Result<i64, VMHooksEarlyExit> {
221        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_nonce)?;
222
223        Ok(self
224            .context
225            .get_block_config()
226            .previous_block_info
227            .block_nonce as i64)
228    }
229
230    pub fn get_prev_block_round(&mut self) -> Result<i64, VMHooksEarlyExit> {
231        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_round)?;
232
233        Ok(self
234            .context
235            .get_block_config()
236            .previous_block_info
237            .block_round as i64)
238    }
239
240    pub fn get_prev_block_epoch(&mut self) -> Result<i64, VMHooksEarlyExit> {
241        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_epoch)?;
242
243        Ok(self
244            .context
245            .get_block_config()
246            .previous_block_info
247            .block_epoch as i64)
248    }
249
250    pub fn get_epoch_start_block_timestamp_ms(&mut self) -> Result<i64, VMHooksEarlyExit> {
251        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_time_stamp)?;
252
253        Ok(self
254            .context
255            .get_block_config()
256            .epoch_start_block_info
257            .block_timestamp_ms as i64)
258    }
259
260    pub fn get_epoch_start_block_nonce(&mut self) -> Result<i64, VMHooksEarlyExit> {
261        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_nonce)?;
262
263        Ok(self
264            .context
265            .get_block_config()
266            .epoch_start_block_info
267            .block_nonce as i64)
268    }
269
270    pub fn get_epoch_start_block_round(&mut self) -> Result<i64, VMHooksEarlyExit> {
271        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_round)?;
272
273        Ok(self
274            .context
275            .get_block_config()
276            .epoch_start_block_info
277            .block_round as i64)
278    }
279
280    pub fn get_prev_block_random_seed(&mut self, dest: RawHandle) -> Result<(), VMHooksEarlyExit> {
281        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_random_seed)?;
282
283        self.context.m_types_lock().mb_set(
284            dest,
285            self.context
286                .get_block_config()
287                .previous_block_info
288                .block_random_seed
289                .to_vec(),
290        );
291        Ok(())
292    }
293
294    pub fn get_block_round_time_ms(&mut self) -> Result<i64, VMHooksEarlyExit> {
295        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_round)?;
296
297        Ok(self.context.get_block_config().block_round_time_ms as i64)
298    }
299
300    pub fn get_current_esdt_nft_nonce(
301        &mut self,
302        address_bytes: &[u8],
303        token_id_bytes: &[u8],
304    ) -> Result<i64, VMHooksEarlyExit> {
305        assert!(
306            self.is_contract_address(address_bytes)?,
307            "get_current_esdt_nft_nonce not yet implemented for accounts other than the contract itself"
308        );
309
310        Ok(self
311            .current_account_data()
312            .esdt
313            .get_by_identifier_or_default(token_id_bytes)
314            .last_nonce as i64)
315    }
316
317    pub fn big_int_get_esdt_external_balance(
318        &mut self,
319        address_bytes: &[u8],
320        token_id_bytes: &[u8],
321        nonce: u64,
322        dest: RawHandle,
323    ) -> Result<(), VMHooksEarlyExit> {
324        self.use_gas(self.gas_schedule().big_int_api_cost.big_int_set_int_64)?;
325
326        assert!(
327            self.is_contract_address(address_bytes)?,
328            "get_esdt_balance not yet implemented for accounts other than the contract itself"
329        );
330
331        let esdt_balance = self
332            .current_account_data()
333            .esdt
334            .get_esdt_balance(token_id_bytes, nonce);
335        self.context
336            .m_types_lock()
337            .bi_overwrite(dest, esdt_balance.into());
338
339        Ok(())
340    }
341
342    pub fn managed_get_code_metadata(
343        &mut self,
344        address_handle: i32,
345        response_handle: i32,
346    ) -> Result<(), VMHooksEarlyExit> {
347        self.use_gas(
348            self.gas_schedule()
349                .managed_buffer_api_cost
350                .m_buffer_get_bytes,
351        )?;
352        self.use_gas(
353            self.gas_schedule()
354                .managed_buffer_api_cost
355                .m_buffer_set_bytes,
356        )?;
357
358        let address = VMAddress::from_slice(self.context.m_types_lock().mb_get(address_handle));
359        let Some(data) = self.context.account_data(&address) else {
360            return Err(
361                VMHooksEarlyExit::new(ReturnCode::ExecutionFailed.as_u64()).with_message(format!(
362                    "account not found: {}",
363                    hex::encode(address.as_bytes())
364                )),
365            );
366        };
367        let code_metadata_bytes = data.code_metadata.to_byte_array();
368        self.context
369            .m_types_lock()
370            .mb_set(response_handle, code_metadata_bytes.to_vec());
371
372        Ok(())
373    }
374
375    pub fn managed_is_builtin_function(
376        &mut self,
377        function_name_handle: i32,
378    ) -> Result<bool, VMHooksEarlyExit> {
379        self.use_gas(self.gas_schedule().base_ops_api_cost.is_builtin_function)?;
380
381        Ok(VM_BUILTIN_FUNCTION_NAMES.contains(
382            &self
383                .context
384                .m_types_lock()
385                .mb_to_function_name(function_name_handle)
386                .as_str(),
387        ))
388    }
389
390    #[allow(clippy::too_many_arguments)]
391    pub fn managed_get_esdt_token_data(
392        &mut self,
393        address_handle: RawHandle,
394        token_id_handle: RawHandle,
395        nonce: u64,
396        value_handle: RawHandle,
397        properties_handle: RawHandle,
398        hash_handle: RawHandle,
399        name_handle: RawHandle,
400        attributes_handle: RawHandle,
401        creator_handle: RawHandle,
402        royalties_handle: RawHandle,
403        uris_handle: RawHandle,
404    ) -> Result<(), VMHooksEarlyExit> {
405        self.use_gas(
406            self.gas_schedule()
407                .managed_buffer_api_cost
408                .m_buffer_get_bytes,
409        )?;
410        let address = VMAddress::from_slice(self.context.m_types_lock().mb_get(address_handle));
411        let token_id_bytes = self.context.m_types_lock().mb_get(token_id_handle).to_vec();
412
413        if let Some(account) = self.context.account_data(&address) {
414            if let Some(esdt_data) = account.esdt.get_by_identifier(token_id_bytes.as_slice()) {
415                if let Some(instance) = esdt_data.instances.get_by_nonce(nonce) {
416                    self.set_esdt_data_values(
417                        esdt_data,
418                        instance,
419                        value_handle,
420                        properties_handle,
421                        hash_handle,
422                        name_handle,
423                        attributes_handle,
424                        creator_handle,
425                        royalties_handle,
426                        uris_handle,
427                    )?
428                }
429            }
430        }
431
432        // missing account/token identifier/nonce
433        self.reset_esdt_data_values(
434            value_handle,
435            properties_handle,
436            hash_handle,
437            name_handle,
438            attributes_handle,
439            creator_handle,
440            royalties_handle,
441            uris_handle,
442        )?;
443
444        Ok(())
445    }
446
447    pub fn managed_get_esdt_token_type(
448        &mut self,
449        _address_handle: i32,
450        _token_id_handle: i32,
451        nonce: i64,
452        type_handle: i32,
453    ) -> Result<(), VMHooksEarlyExit> {
454        // TODO: model the token type properly in the VM
455        let token_type = EsdtTokenType::based_on_token_nonce(nonce as u64);
456        self.context.m_types_lock().bi_overwrite(
457            type_handle,
458            num_bigint::BigInt::from(token_type.as_u8() as i32),
459        );
460        Ok(())
461    }
462
463    pub fn managed_get_back_transfers(
464        &mut self,
465        esdt_transfer_value_handle: RawHandle,
466        call_value_handle: RawHandle,
467    ) -> Result<(), VMHooksEarlyExit> {
468        self.use_gas(self.gas_schedule().big_int_api_cost.big_int_set_int_64)?;
469
470        let back_transfers = self.context.back_transfers_lock();
471        let mut m_types = self.context.m_types_lock();
472        m_types.bi_overwrite(call_value_handle, back_transfers.call_value.clone().into());
473        let num_bytes_copied = m_types.mb_set_vec_of_esdt_payments(
474            esdt_transfer_value_handle,
475            &back_transfers.esdt_transfers,
476        );
477        std::mem::drop(m_types);
478        std::mem::drop(back_transfers);
479        self.use_gas_for_data_copy(num_bytes_copied)?;
480
481        Ok(())
482    }
483
484    pub fn check_esdt_frozen(
485        &mut self,
486        address_handle: RawHandle,
487        token_id_handle: RawHandle,
488        _nonce: u64,
489    ) -> Result<bool, VMHooksEarlyExit> {
490        self.use_gas(
491            2 * self
492                .gas_schedule()
493                .managed_buffer_api_cost
494                .m_buffer_get_bytes,
495        )?;
496
497        let address = VMAddress::from_slice(self.context.m_types_lock().mb_get(address_handle));
498        let token_id_bytes = self.context.m_types_lock().mb_get(token_id_handle).to_vec();
499        if let Some(account) = self.context.account_data(&address) {
500            if let Some(esdt_data) = account.esdt.get_by_identifier(token_id_bytes.as_slice()) {
501                return Ok(esdt_data.frozen);
502            }
503        }
504
505        // Might be better to return Err and check
506        Ok(false)
507    }
508
509    pub fn get_esdt_local_roles_bits(
510        &mut self,
511        token_id_handle: RawHandle,
512    ) -> Result<i64, VMHooksEarlyExit> {
513        self.use_gas(
514            self.gas_schedule()
515                .managed_buffer_api_cost
516                .m_buffer_get_bytes,
517        )?;
518
519        let token_id_bytes = self.context.m_types_lock().mb_get(token_id_handle).to_vec();
520        let account = self.current_account_data();
521        let mut result = EsdtLocalRoleFlags::NONE;
522        if let Some(esdt_data) = account.esdt.get_by_identifier(token_id_bytes.as_slice()) {
523            for role_name in esdt_data.roles.get() {
524                result |= EsdtLocalRole::from(role_name.as_slice()).to_flag();
525            }
526        }
527        Ok(result.bits() as i64)
528    }
529
530    pub fn validate_token_identifier(
531        &mut self,
532        token_id_handle: RawHandle,
533    ) -> Result<bool, VMHooksEarlyExit> {
534        self.use_gas(self.gas_schedule().base_ops_api_cost.get_argument)?;
535
536        let m_types = self.context.m_types_lock();
537        let token_id = m_types.mb_get(token_id_handle);
538        Ok(multiversx_chain_core::token_identifier_util::validate_token_identifier(token_id))
539    }
540
541    #[allow(clippy::too_many_arguments)]
542    fn set_esdt_data_values(
543        &mut self,
544        esdt_data: &EsdtData,
545        instance: &EsdtInstance,
546        value_handle: RawHandle,
547        properties_handle: RawHandle,
548        hash_handle: RawHandle,
549        name_handle: RawHandle,
550        attributes_handle: RawHandle,
551        creator_handle: RawHandle,
552        royalties_handle: RawHandle,
553        uris_handle: RawHandle,
554    ) -> Result<(), VMHooksEarlyExit> {
555        let mut m_types = self.context.m_types_lock();
556        m_types.bi_overwrite(value_handle, instance.balance.clone().into());
557        if esdt_data.frozen {
558            m_types.mb_set(properties_handle, vec![1, 0]);
559        } else {
560            m_types.mb_set(properties_handle, vec![0, 0]);
561        }
562        m_types.mb_set(
563            hash_handle,
564            instance.metadata.hash.clone().unwrap_or_default(),
565        );
566        m_types.mb_set(name_handle, instance.metadata.name.clone());
567        m_types.mb_set(attributes_handle, instance.metadata.attributes.clone());
568        if let Some(creator) = &instance.metadata.creator {
569            m_types.mb_set(creator_handle, creator.to_vec());
570        } else {
571            m_types.mb_set(creator_handle, vec![0u8; 32]);
572        };
573        m_types.bi_overwrite(
574            royalties_handle,
575            num_bigint::BigInt::from(instance.metadata.royalties),
576        );
577
578        let num_bytes_copied =
579            m_types.mb_set_vec_of_bytes(uris_handle, instance.metadata.uri.clone());
580        std::mem::drop(m_types);
581        self.use_gas_for_data_copy(num_bytes_copied)?;
582
583        Ok(())
584    }
585
586    #[allow(clippy::too_many_arguments)]
587    fn reset_esdt_data_values(
588        &mut self,
589        value_handle: RawHandle,
590        properties_handle: RawHandle,
591        hash_handle: RawHandle,
592        name_handle: RawHandle,
593        attributes_handle: RawHandle,
594        creator_handle: RawHandle,
595        royalties_handle: RawHandle,
596        uris_handle: RawHandle,
597    ) -> Result<(), VMHooksEarlyExit> {
598        if ESDT_TOKEN_DATA_FUNC_RESETS_VALUES {
599            self.use_gas(3 * self.gas_schedule().big_int_api_cost.big_int_set_int_64)?;
600            self.use_gas(
601                5 * self
602                    .gas_schedule()
603                    .managed_buffer_api_cost
604                    .m_buffer_set_bytes,
605            )?;
606
607            let mut m_types = self.context.m_types_lock();
608            m_types.bi_overwrite(value_handle, BigInt::zero());
609            m_types.mb_set(properties_handle, vec![0, 0]);
610            m_types.mb_set(hash_handle, vec![]);
611            m_types.mb_set(name_handle, vec![]);
612            m_types.mb_set(attributes_handle, vec![]);
613            m_types.mb_set(creator_handle, vec![0u8; 32]);
614            m_types.bi_overwrite(royalties_handle, BigInt::zero());
615            m_types.bi_overwrite(uris_handle, BigInt::zero());
616        }
617
618        Ok(())
619    }
620}