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_millis
142            .to_seconds()
143            .as_u64_seconds() as i64)
144    }
145
146    pub fn get_block_timestamp_ms(&mut self) -> Result<i64, VMHooksEarlyExit> {
147        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_time_stamp)?;
148
149        Ok(self
150            .context
151            .get_block_config()
152            .current_block_info
153            .block_timestamp_millis
154            .as_u64_millis() as i64)
155    }
156
157    pub fn get_block_nonce(&mut self) -> Result<i64, VMHooksEarlyExit> {
158        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_nonce)?;
159
160        Ok(self
161            .context
162            .get_block_config()
163            .current_block_info
164            .block_nonce as i64)
165    }
166
167    pub fn get_block_round(&mut self) -> Result<i64, VMHooksEarlyExit> {
168        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_round)?;
169
170        Ok(self
171            .context
172            .get_block_config()
173            .current_block_info
174            .block_round as i64)
175    }
176
177    pub fn get_block_epoch(&mut self) -> Result<i64, VMHooksEarlyExit> {
178        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_epoch)?;
179
180        Ok(self
181            .context
182            .get_block_config()
183            .current_block_info
184            .block_epoch as i64)
185    }
186
187    pub fn get_block_random_seed(&mut self, dest: RawHandle) -> Result<(), VMHooksEarlyExit> {
188        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_random_seed)?;
189
190        self.context.m_types_lock().mb_set(
191            dest,
192            self.context
193                .get_block_config()
194                .current_block_info
195                .block_random_seed
196                .to_vec(),
197        );
198        Ok(())
199    }
200
201    pub fn get_prev_block_timestamp(&mut self) -> Result<i64, VMHooksEarlyExit> {
202        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_time_stamp)?;
203
204        Ok(self
205            .context
206            .get_block_config()
207            .previous_block_info
208            .block_timestamp_millis
209            .to_seconds()
210            .as_u64_seconds() as i64)
211    }
212
213    pub fn get_prev_block_timestamp_ms(&mut self) -> Result<i64, VMHooksEarlyExit> {
214        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_time_stamp)?;
215
216        Ok(self
217            .context
218            .get_block_config()
219            .previous_block_info
220            .block_timestamp_millis
221            .as_u64_millis() as i64)
222    }
223
224    pub fn get_prev_block_nonce(&mut self) -> Result<i64, VMHooksEarlyExit> {
225        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_nonce)?;
226
227        Ok(self
228            .context
229            .get_block_config()
230            .previous_block_info
231            .block_nonce as i64)
232    }
233
234    pub fn get_prev_block_round(&mut self) -> Result<i64, VMHooksEarlyExit> {
235        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_round)?;
236
237        Ok(self
238            .context
239            .get_block_config()
240            .previous_block_info
241            .block_round as i64)
242    }
243
244    pub fn get_prev_block_epoch(&mut self) -> Result<i64, VMHooksEarlyExit> {
245        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_epoch)?;
246
247        Ok(self
248            .context
249            .get_block_config()
250            .previous_block_info
251            .block_epoch as i64)
252    }
253
254    pub fn get_epoch_start_block_timestamp_ms(&mut self) -> Result<i64, VMHooksEarlyExit> {
255        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_time_stamp)?;
256
257        Ok(self
258            .context
259            .get_block_config()
260            .epoch_start_block_info
261            .block_timestamp_millis
262            .as_u64_millis() as i64)
263    }
264
265    pub fn get_epoch_start_block_nonce(&mut self) -> Result<i64, VMHooksEarlyExit> {
266        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_nonce)?;
267
268        Ok(self
269            .context
270            .get_block_config()
271            .epoch_start_block_info
272            .block_nonce as i64)
273    }
274
275    pub fn get_epoch_start_block_round(&mut self) -> Result<i64, VMHooksEarlyExit> {
276        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_round)?;
277
278        Ok(self
279            .context
280            .get_block_config()
281            .epoch_start_block_info
282            .block_round as i64)
283    }
284
285    pub fn get_prev_block_random_seed(&mut self, dest: RawHandle) -> Result<(), VMHooksEarlyExit> {
286        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_random_seed)?;
287
288        self.context.m_types_lock().mb_set(
289            dest,
290            self.context
291                .get_block_config()
292                .previous_block_info
293                .block_random_seed
294                .to_vec(),
295        );
296        Ok(())
297    }
298
299    pub fn get_block_round_time_ms(&mut self) -> Result<i64, VMHooksEarlyExit> {
300        self.use_gas(self.gas_schedule().base_ops_api_cost.get_block_round)?;
301
302        Ok(self.context.get_block_config().block_round_time_ms as i64)
303    }
304
305    pub fn get_current_esdt_nft_nonce(
306        &mut self,
307        address_bytes: &[u8],
308        token_id_bytes: &[u8],
309    ) -> Result<i64, VMHooksEarlyExit> {
310        assert!(
311            self.is_contract_address(address_bytes)?,
312            "get_current_esdt_nft_nonce not yet implemented for accounts other than the contract itself"
313        );
314
315        Ok(self
316            .current_account_data()
317            .esdt
318            .get_by_identifier_or_default(token_id_bytes)
319            .last_nonce as i64)
320    }
321
322    pub fn big_int_get_esdt_external_balance(
323        &mut self,
324        address_bytes: &[u8],
325        token_id_bytes: &[u8],
326        nonce: u64,
327        dest: RawHandle,
328    ) -> Result<(), VMHooksEarlyExit> {
329        self.use_gas(self.gas_schedule().big_int_api_cost.big_int_set_int_64)?;
330
331        assert!(
332            self.is_contract_address(address_bytes)?,
333            "get_esdt_balance not yet implemented for accounts other than the contract itself"
334        );
335
336        let esdt_balance = self
337            .current_account_data()
338            .esdt
339            .get_esdt_balance(token_id_bytes, nonce);
340        self.context
341            .m_types_lock()
342            .bi_overwrite(dest, esdt_balance.into());
343
344        Ok(())
345    }
346
347    pub fn managed_get_code_metadata(
348        &mut self,
349        address_handle: i32,
350        response_handle: i32,
351    ) -> Result<(), VMHooksEarlyExit> {
352        self.use_gas(
353            self.gas_schedule()
354                .managed_buffer_api_cost
355                .m_buffer_get_bytes,
356        )?;
357        self.use_gas(
358            self.gas_schedule()
359                .managed_buffer_api_cost
360                .m_buffer_set_bytes,
361        )?;
362
363        let address = VMAddress::from_slice(self.context.m_types_lock().mb_get(address_handle));
364        let Some(data) = self.context.account_data(&address) else {
365            return Err(
366                VMHooksEarlyExit::new(ReturnCode::ExecutionFailed.as_u64()).with_message(format!(
367                    "account not found: {}",
368                    hex::encode(address.as_bytes())
369                )),
370            );
371        };
372        let code_metadata_bytes = data.code_metadata.to_byte_array();
373        self.context
374            .m_types_lock()
375            .mb_set(response_handle, code_metadata_bytes.to_vec());
376
377        Ok(())
378    }
379
380    pub fn managed_is_builtin_function(
381        &mut self,
382        function_name_handle: i32,
383    ) -> Result<bool, VMHooksEarlyExit> {
384        self.use_gas(self.gas_schedule().base_ops_api_cost.is_builtin_function)?;
385
386        Ok(VM_BUILTIN_FUNCTION_NAMES.contains(
387            &self
388                .context
389                .m_types_lock()
390                .mb_to_function_name(function_name_handle)
391                .as_str(),
392        ))
393    }
394
395    #[allow(clippy::too_many_arguments)]
396    pub fn managed_get_esdt_token_data(
397        &mut self,
398        address_handle: RawHandle,
399        token_id_handle: RawHandle,
400        nonce: u64,
401        value_handle: RawHandle,
402        properties_handle: RawHandle,
403        hash_handle: RawHandle,
404        name_handle: RawHandle,
405        attributes_handle: RawHandle,
406        creator_handle: RawHandle,
407        royalties_handle: RawHandle,
408        uris_handle: RawHandle,
409    ) -> Result<(), VMHooksEarlyExit> {
410        self.use_gas(
411            self.gas_schedule()
412                .managed_buffer_api_cost
413                .m_buffer_get_bytes,
414        )?;
415        let address = VMAddress::from_slice(self.context.m_types_lock().mb_get(address_handle));
416        let token_id_bytes = self.context.m_types_lock().mb_get(token_id_handle).to_vec();
417
418        if let Some(account) = self.context.account_data(&address) {
419            if let Some(esdt_data) = account.esdt.get_by_identifier(token_id_bytes.as_slice()) {
420                if let Some(instance) = esdt_data.instances.get_by_nonce(nonce) {
421                    self.set_esdt_data_values(
422                        esdt_data,
423                        instance,
424                        value_handle,
425                        properties_handle,
426                        hash_handle,
427                        name_handle,
428                        attributes_handle,
429                        creator_handle,
430                        royalties_handle,
431                        uris_handle,
432                    )?
433                }
434            }
435        }
436
437        // missing account/token identifier/nonce
438        self.reset_esdt_data_values(
439            value_handle,
440            properties_handle,
441            hash_handle,
442            name_handle,
443            attributes_handle,
444            creator_handle,
445            royalties_handle,
446            uris_handle,
447        )?;
448
449        Ok(())
450    }
451
452    pub fn managed_get_esdt_token_type(
453        &mut self,
454        _address_handle: i32,
455        _token_id_handle: i32,
456        nonce: i64,
457        type_handle: i32,
458    ) -> Result<(), VMHooksEarlyExit> {
459        // TODO: model the token type properly in the VM
460        let token_type = EsdtTokenType::based_on_token_nonce(nonce as u64);
461        self.context.m_types_lock().bi_overwrite(
462            type_handle,
463            num_bigint::BigInt::from(token_type.as_u8() as i32),
464        );
465        Ok(())
466    }
467
468    pub fn managed_get_back_transfers(
469        &mut self,
470        esdt_transfer_value_handle: RawHandle,
471        call_value_handle: RawHandle,
472    ) -> Result<(), VMHooksEarlyExit> {
473        self.use_gas(self.gas_schedule().big_int_api_cost.big_int_set_int_64)?;
474
475        let back_transfers = self.context.back_transfers_lock();
476        let mut m_types = self.context.m_types_lock();
477        m_types.bi_overwrite(call_value_handle, back_transfers.call_value.clone().into());
478        let num_bytes_copied = m_types.mb_set_vec_of_esdt_payments(
479            esdt_transfer_value_handle,
480            &back_transfers.esdt_transfers,
481        );
482        std::mem::drop(m_types);
483        std::mem::drop(back_transfers);
484        self.use_gas_for_data_copy(num_bytes_copied)?;
485
486        Ok(())
487    }
488
489    pub fn check_esdt_frozen(
490        &mut self,
491        address_handle: RawHandle,
492        token_id_handle: RawHandle,
493        _nonce: u64,
494    ) -> Result<bool, VMHooksEarlyExit> {
495        self.use_gas(
496            2 * self
497                .gas_schedule()
498                .managed_buffer_api_cost
499                .m_buffer_get_bytes,
500        )?;
501
502        let address = VMAddress::from_slice(self.context.m_types_lock().mb_get(address_handle));
503        let token_id_bytes = self.context.m_types_lock().mb_get(token_id_handle).to_vec();
504        if let Some(account) = self.context.account_data(&address) {
505            if let Some(esdt_data) = account.esdt.get_by_identifier(token_id_bytes.as_slice()) {
506                return Ok(esdt_data.frozen);
507            }
508        }
509
510        // Might be better to return Err and check
511        Ok(false)
512    }
513
514    pub fn get_esdt_local_roles_bits(
515        &mut self,
516        token_id_handle: RawHandle,
517    ) -> Result<i64, VMHooksEarlyExit> {
518        self.use_gas(
519            self.gas_schedule()
520                .managed_buffer_api_cost
521                .m_buffer_get_bytes,
522        )?;
523
524        let token_id_bytes = self.context.m_types_lock().mb_get(token_id_handle).to_vec();
525        let account = self.current_account_data();
526        let mut result = EsdtLocalRoleFlags::NONE;
527        if let Some(esdt_data) = account.esdt.get_by_identifier(token_id_bytes.as_slice()) {
528            for role_name in esdt_data.roles.get() {
529                result |= EsdtLocalRole::from(role_name.as_slice()).to_flag();
530            }
531        }
532        Ok(result.bits() as i64)
533    }
534
535    pub fn validate_token_identifier(
536        &mut self,
537        token_id_handle: RawHandle,
538    ) -> Result<bool, VMHooksEarlyExit> {
539        self.use_gas(self.gas_schedule().base_ops_api_cost.get_argument)?;
540
541        let m_types = self.context.m_types_lock();
542        let token_id = m_types.mb_get(token_id_handle);
543        Ok(multiversx_chain_core::token_identifier_util::validate_token_identifier(token_id))
544    }
545
546    #[allow(clippy::too_many_arguments)]
547    fn set_esdt_data_values(
548        &mut self,
549        esdt_data: &EsdtData,
550        instance: &EsdtInstance,
551        value_handle: RawHandle,
552        properties_handle: RawHandle,
553        hash_handle: RawHandle,
554        name_handle: RawHandle,
555        attributes_handle: RawHandle,
556        creator_handle: RawHandle,
557        royalties_handle: RawHandle,
558        uris_handle: RawHandle,
559    ) -> Result<(), VMHooksEarlyExit> {
560        let mut m_types = self.context.m_types_lock();
561        m_types.bi_overwrite(value_handle, instance.balance.clone().into());
562        if esdt_data.frozen {
563            m_types.mb_set(properties_handle, vec![1, 0]);
564        } else {
565            m_types.mb_set(properties_handle, vec![0, 0]);
566        }
567        m_types.mb_set(
568            hash_handle,
569            instance.metadata.hash.clone().unwrap_or_default(),
570        );
571        m_types.mb_set(name_handle, instance.metadata.name.clone());
572        m_types.mb_set(attributes_handle, instance.metadata.attributes.clone());
573        if let Some(creator) = &instance.metadata.creator {
574            m_types.mb_set(creator_handle, creator.to_vec());
575        } else {
576            m_types.mb_set(creator_handle, vec![0u8; 32]);
577        };
578        m_types.bi_overwrite(
579            royalties_handle,
580            num_bigint::BigInt::from(instance.metadata.royalties),
581        );
582
583        let num_bytes_copied =
584            m_types.mb_set_vec_of_bytes(uris_handle, instance.metadata.uri.clone());
585        std::mem::drop(m_types);
586        self.use_gas_for_data_copy(num_bytes_copied)?;
587
588        Ok(())
589    }
590
591    #[allow(clippy::too_many_arguments)]
592    fn reset_esdt_data_values(
593        &mut self,
594        value_handle: RawHandle,
595        properties_handle: RawHandle,
596        hash_handle: RawHandle,
597        name_handle: RawHandle,
598        attributes_handle: RawHandle,
599        creator_handle: RawHandle,
600        royalties_handle: RawHandle,
601        uris_handle: RawHandle,
602    ) -> Result<(), VMHooksEarlyExit> {
603        if ESDT_TOKEN_DATA_FUNC_RESETS_VALUES {
604            self.use_gas(3 * self.gas_schedule().big_int_api_cost.big_int_set_int_64)?;
605            self.use_gas(
606                5 * self
607                    .gas_schedule()
608                    .managed_buffer_api_cost
609                    .m_buffer_set_bytes,
610            )?;
611
612            let mut m_types = self.context.m_types_lock();
613            m_types.bi_overwrite(value_handle, BigInt::zero());
614            m_types.mb_set(properties_handle, vec![0, 0]);
615            m_types.mb_set(hash_handle, vec![]);
616            m_types.mb_set(name_handle, vec![]);
617            m_types.mb_set(attributes_handle, vec![]);
618            m_types.mb_set(creator_handle, vec![0u8; 32]);
619            m_types.bi_overwrite(royalties_handle, BigInt::zero());
620            m_types.bi_overwrite(uris_handle, BigInt::zero());
621        }
622
623        Ok(())
624    }
625}