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_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 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 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 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}