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
14const 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 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 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 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}