1use crate::{
5 NativesCostTable, abstract_size, charge_cache_or_load_gas, get_extension, get_extension_mut,
6 get_nested_struct_field, get_object_id,
7 object_runtime::{
8 ObjectRuntime,
9 object_store::{CacheInfo, ObjectResult},
10 },
11};
12use move_binary_format::errors::{PartialVMError, PartialVMResult};
13use move_binary_format::{partial_vm_error, safe_assert, safe_assert_eq, safe_unwrap};
14use move_core_types::{
15 account_address::AccountAddress,
16 gas_algebra::InternalGas,
17 language_storage::{StructTag, TypeTag},
18 vm_status::StatusCode,
19};
20use move_vm_runtime::native_charge_gas_early_exit;
21use move_vm_runtime::natives::functions::NativeContext;
22use move_vm_runtime::{
23 execution::{
24 Type,
25 values::{StructRef, Value},
26 },
27 natives::functions::NativeResult,
28 pop_arg,
29 shared::views::{SizeConfig, ValueView},
30};
31use smallvec::smallvec;
32use std::collections::VecDeque;
33use sui_types::{base_types::MoveObjectType, dynamic_field::derive_dynamic_field_id};
34use tracing::instrument;
35
36const E_KEY_DOES_NOT_EXIST: u64 = 1;
37const E_FIELD_TYPE_MISMATCH: u64 = 2;
38const E_BCS_SERIALIZATION_FAILURE: u64 = 3;
39
40const PRE_EXISTING_ABSTRACT_SIZE: u64 = 2;
42const BORROW_ABSTRACT_SIZE: u64 = 8;
44
45macro_rules! get_or_fetch_object {
46 ($context:ident, $ty_args:ident, $parent:ident, $child_id:ident, $ty_cost_per_byte:expr) => {{
47 let child_ty = safe_unwrap!($ty_args.pop());
48 native_charge_gas_early_exit!(
49 $context,
50 $ty_cost_per_byte * u64::from(child_ty.size()?).into()
51 );
52
53 safe_assert!($ty_args.is_empty());
54 let (tag, layout, annotated_layout) = match crate::get_tag_and_layouts($context, &child_ty)?
55 {
56 Some(res) => res,
57 None => {
58 return Ok(NativeResult::err(
59 $context.gas_used(),
60 E_BCS_SERIALIZATION_FAILURE,
61 ));
62 }
63 };
64
65 let object_runtime: &mut ObjectRuntime = $crate::get_extension_mut!($context)?;
66 object_runtime.get_or_fetch_child_object(
67 $parent,
68 $child_id,
69 &layout,
70 &annotated_layout,
71 MoveObjectType::from(tag),
72 )?
73 }};
74}
75
76#[derive(Clone)]
77pub struct DynamicFieldHashTypeAndKeyCostParams {
78 pub dynamic_field_hash_type_and_key_cost_base: InternalGas,
79 pub dynamic_field_hash_type_and_key_type_cost_per_byte: InternalGas,
80 pub dynamic_field_hash_type_and_key_value_cost_per_byte: InternalGas,
81 pub dynamic_field_hash_type_and_key_type_tag_cost_per_byte: InternalGas,
82}
83
84#[instrument(level = "trace", skip_all)]
93pub fn hash_type_and_key(
94 context: &mut NativeContext,
95 mut ty_args: Vec<Type>,
96 mut args: VecDeque<Value>,
97) -> PartialVMResult<NativeResult> {
98 safe_assert_eq!(ty_args.len(), 1);
99 safe_assert_eq!(args.len(), 2);
100
101 let dynamic_field_hash_type_and_key_cost_params = get_extension!(context, NativesCostTable)?
102 .dynamic_field_hash_type_and_key_cost_params
103 .clone();
104
105 native_charge_gas_early_exit!(
107 context,
108 dynamic_field_hash_type_and_key_cost_params.dynamic_field_hash_type_and_key_cost_base
109 );
110
111 let k_ty = safe_unwrap!(ty_args.pop());
112 let k: Value = safe_unwrap!(args.pop_back());
113 let parent = pop_arg!(args, AccountAddress);
114
115 let k_ty_size = u64::from(k_ty.size()?);
117 let k_value_size = u64::from(abstract_size(
118 get_extension!(context, ObjectRuntime)?.protocol_config,
119 &k,
120 )?);
121 native_charge_gas_early_exit!(
122 context,
123 dynamic_field_hash_type_and_key_cost_params
124 .dynamic_field_hash_type_and_key_type_cost_per_byte
125 * k_ty_size.into()
126 + dynamic_field_hash_type_and_key_cost_params
127 .dynamic_field_hash_type_and_key_value_cost_per_byte
128 * k_value_size.into()
129 );
130
131 let k_tag = context.type_to_type_tag(&k_ty)?;
132 let k_tag_size = u64::from(k_tag.abstract_size_for_gas_metering());
133
134 native_charge_gas_early_exit!(
135 context,
136 dynamic_field_hash_type_and_key_cost_params
137 .dynamic_field_hash_type_and_key_type_tag_cost_per_byte
138 * k_tag_size.into()
139 );
140
141 let cost = context.gas_used();
142
143 let k_layout = match context.type_to_type_layout(&k_ty) {
144 Ok(Some(layout)) => layout,
145 _ => return Ok(NativeResult::err(cost, E_BCS_SERIALIZATION_FAILURE)),
146 };
147 let Some(k_bytes) = k.typed_serialize(&k_layout) else {
148 return Ok(NativeResult::err(cost, E_BCS_SERIALIZATION_FAILURE));
149 };
150 let Ok(id) = derive_dynamic_field_id(parent, &k_tag, &k_bytes) else {
151 return Ok(NativeResult::err(cost, E_BCS_SERIALIZATION_FAILURE));
152 };
153
154 Ok(NativeResult::ok(cost, smallvec![Value::address(id.into())]))
155}
156
157#[derive(Clone)]
158pub struct DynamicFieldAddChildObjectCostParams {
159 pub dynamic_field_add_child_object_cost_base: InternalGas,
160 pub dynamic_field_add_child_object_type_cost_per_byte: InternalGas,
161 pub dynamic_field_add_child_object_value_cost_per_byte: InternalGas,
162 pub dynamic_field_add_child_object_struct_tag_cost_per_byte: InternalGas,
163}
164
165#[instrument(level = "trace", skip_all)]
175pub fn add_child_object(
176 context: &mut NativeContext,
177 mut ty_args: Vec<Type>,
178 mut args: VecDeque<Value>,
179) -> PartialVMResult<NativeResult> {
180 safe_assert_eq!(ty_args.len(), 1);
181 safe_assert_eq!(args.len(), 2);
182
183 let dynamic_field_add_child_object_cost_params = get_extension!(context, NativesCostTable)?
184 .dynamic_field_add_child_object_cost_params
185 .clone();
186
187 native_charge_gas_early_exit!(
189 context,
190 dynamic_field_add_child_object_cost_params.dynamic_field_add_child_object_cost_base
191 );
192
193 let child = safe_unwrap!(args.pop_back());
194 let parent = pop_arg!(args, AccountAddress).into();
195 safe_assert!(args.is_empty());
196
197 let child_value_size = PRE_EXISTING_ABSTRACT_SIZE;
199 native_charge_gas_early_exit!(
201 context,
202 dynamic_field_add_child_object_cost_params
203 .dynamic_field_add_child_object_value_cost_per_byte
204 * child_value_size.into()
205 );
206
207 let child_id = safe_unwrap!(
209 get_object_id(child.copy_value()).and_then(|v| v.value_as::<AccountAddress>())
210 )
211 .into();
212 let child_ty = safe_unwrap!(ty_args.pop());
213 let child_type_size = u64::from(child_ty.size()?);
214
215 native_charge_gas_early_exit!(
216 context,
217 dynamic_field_add_child_object_cost_params
218 .dynamic_field_add_child_object_type_cost_per_byte
219 * child_type_size.into()
220 );
221
222 safe_assert!(ty_args.is_empty());
223 let tag = match context.type_to_type_tag(&child_ty)? {
224 TypeTag::Struct(s) => *s,
225 _ => {
226 return Err(
227 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
228 .with_message("Sui verifier guarantees this is a struct".to_string()),
229 );
230 }
231 };
232
233 let struct_tag_size = u64::from(tag.abstract_size_for_gas_metering());
234 native_charge_gas_early_exit!(
235 context,
236 dynamic_field_add_child_object_cost_params
237 .dynamic_field_add_child_object_struct_tag_cost_per_byte
238 * struct_tag_size.into()
239 );
240
241 if get_extension!(context, ObjectRuntime)?
242 .protocol_config
243 .generate_df_type_layouts()
244 {
245 context.type_to_type_layout(&child_ty)?;
246 }
247
248 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
249 object_runtime.add_child_object(parent, child_id, MoveObjectType::from(tag), child)?;
250 Ok(NativeResult::ok(context.gas_used(), smallvec![]))
251}
252
253#[derive(Clone)]
254pub struct DynamicFieldBorrowChildObjectCostParams {
255 pub dynamic_field_borrow_child_object_cost_base: InternalGas,
256 pub dynamic_field_borrow_child_object_child_ref_cost_per_byte: InternalGas,
257 pub dynamic_field_borrow_child_object_type_cost_per_byte: InternalGas,
258}
259
260#[instrument(level = "trace", skip_all)]
270pub fn borrow_child_object(
271 context: &mut NativeContext,
272 mut ty_args: Vec<Type>,
273 mut args: VecDeque<Value>,
274) -> PartialVMResult<NativeResult> {
275 safe_assert_eq!(ty_args.len(), 1);
276 safe_assert_eq!(args.len(), 2);
277
278 let dynamic_field_borrow_child_object_cost_params = get_extension!(context, NativesCostTable)?
279 .dynamic_field_borrow_child_object_cost_params
280 .clone();
281 native_charge_gas_early_exit!(
282 context,
283 dynamic_field_borrow_child_object_cost_params.dynamic_field_borrow_child_object_cost_base
284 );
285
286 let child_id = pop_arg!(args, AccountAddress).into();
287
288 let parent_uid = safe_unwrap!(pop_arg!(args, StructRef).read_ref());
289 let parent = safe_unwrap!(
291 get_nested_struct_field(parent_uid, &[0, 0]).and_then(|v| v.value_as::<AccountAddress>())
292 )
293 .into();
294
295 safe_assert!(args.is_empty());
296 let global_value_result = get_or_fetch_object!(
297 context,
298 ty_args,
299 parent,
300 child_id,
301 dynamic_field_borrow_child_object_cost_params
302 .dynamic_field_borrow_child_object_type_cost_per_byte
303 );
304 let (cache_info, global_value) = match global_value_result {
305 ObjectResult::MismatchedType => {
306 return Ok(NativeResult::err(context.gas_used(), E_FIELD_TYPE_MISMATCH));
307 }
308 ObjectResult::Loaded(gv) => gv,
309 };
310 if !global_value.exists()? {
311 return Ok(NativeResult::err(context.gas_used(), E_KEY_DOES_NOT_EXIST));
312 }
313 let child_ref = global_value.borrow_global().map_err(|err| {
314 if err.major_status() == StatusCode::MISSING_DATA {
315 debug_assert!(false);
316 partial_vm_error!(
317 UNKNOWN_INVARIANT_VIOLATION_ERROR,
318 "borrow_global returned MISSING_DATA after exists() was true"
319 )
320 } else {
321 err
322 }
323 })?;
324
325 charge_cache_or_load_gas!(context, cache_info);
326 let child_ref_size = match cache_info {
327 CacheInfo::CachedValue => {
328 BORROW_ABSTRACT_SIZE.into()
330 }
331 CacheInfo::CachedObject | CacheInfo::Loaded(_) => {
334 child_ref.abstract_memory_size(&SizeConfig {
335 include_vector_size: true,
336 traverse_references: true,
337 })?
338 }
339 };
340
341 native_charge_gas_early_exit!(
342 context,
343 dynamic_field_borrow_child_object_cost_params
344 .dynamic_field_borrow_child_object_child_ref_cost_per_byte
345 * u64::from(child_ref_size).into()
346 );
347
348 Ok(NativeResult::ok(context.gas_used(), smallvec![child_ref]))
349}
350
351#[derive(Clone)]
352pub struct DynamicFieldRemoveChildObjectCostParams {
353 pub dynamic_field_remove_child_object_cost_base: InternalGas,
354 pub dynamic_field_remove_child_object_child_cost_per_byte: InternalGas,
355 pub dynamic_field_remove_child_object_type_cost_per_byte: InternalGas,
356}
357#[instrument(level = "trace", skip_all)]
367pub fn remove_child_object(
368 context: &mut NativeContext,
369 mut ty_args: Vec<Type>,
370 mut args: VecDeque<Value>,
371) -> PartialVMResult<NativeResult> {
372 safe_assert_eq!(ty_args.len(), 1);
373 safe_assert_eq!(args.len(), 2);
374
375 let dynamic_field_remove_child_object_cost_params = get_extension!(context, NativesCostTable)?
376 .dynamic_field_remove_child_object_cost_params
377 .clone();
378 native_charge_gas_early_exit!(
379 context,
380 dynamic_field_remove_child_object_cost_params.dynamic_field_remove_child_object_cost_base
381 );
382
383 let child_id = pop_arg!(args, AccountAddress).into();
384 let parent = pop_arg!(args, AccountAddress).into();
385 safe_assert!(args.is_empty());
386 let global_value_result = get_or_fetch_object!(
387 context,
388 ty_args,
389 parent,
390 child_id,
391 dynamic_field_remove_child_object_cost_params
392 .dynamic_field_remove_child_object_type_cost_per_byte
393 );
394 let (cache_info, global_value) = match global_value_result {
395 ObjectResult::MismatchedType => {
396 return Ok(NativeResult::err(context.gas_used(), E_FIELD_TYPE_MISMATCH));
397 }
398 ObjectResult::Loaded(gv) => gv,
399 };
400
401 if !global_value.exists()? {
402 return Ok(NativeResult::err(context.gas_used(), E_KEY_DOES_NOT_EXIST));
403 }
404 let child = global_value.move_from().map_err(|err| {
405 if err.major_status() == StatusCode::MISSING_DATA {
406 debug_assert!(false);
407 partial_vm_error!(
408 UNKNOWN_INVARIANT_VIOLATION_ERROR,
409 "move_from returned MISSING_DATA after exists() was true"
410 )
411 } else {
412 err
413 }
414 })?;
415
416 charge_cache_or_load_gas!(context, cache_info);
417
418 let child_size = match cache_info {
419 CacheInfo::CachedValue => {
420 PRE_EXISTING_ABSTRACT_SIZE.into()
422 }
423 CacheInfo::CachedObject | CacheInfo::Loaded(_) => {
426 child.abstract_memory_size(&SizeConfig {
427 include_vector_size: true,
428 traverse_references: false,
429 })?
430 }
431 };
432 native_charge_gas_early_exit!(
433 context,
434 dynamic_field_remove_child_object_cost_params
435 .dynamic_field_remove_child_object_child_cost_per_byte
436 * u64::from(child_size).into()
437 );
438
439 Ok(NativeResult::ok(context.gas_used(), smallvec![child]))
440}
441
442#[derive(Clone)]
443pub struct DynamicFieldHasChildObjectCostParams {
444 pub dynamic_field_has_child_object_cost_base: InternalGas,
446}
447#[instrument(level = "trace", skip_all)]
453pub fn has_child_object(
454 context: &mut NativeContext,
455 ty_args: Vec<Type>,
456 mut args: VecDeque<Value>,
457) -> PartialVMResult<NativeResult> {
458 safe_assert!(ty_args.is_empty());
459 safe_assert_eq!(args.len(), 2);
460
461 let dynamic_field_has_child_object_cost_params = get_extension!(context, NativesCostTable)?
462 .dynamic_field_has_child_object_cost_params
463 .clone();
464 native_charge_gas_early_exit!(
465 context,
466 dynamic_field_has_child_object_cost_params.dynamic_field_has_child_object_cost_base
467 );
468
469 let child_id = pop_arg!(args, AccountAddress).into();
470 let parent = pop_arg!(args, AccountAddress).into();
471 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
472 let (cache_info, has_child) = object_runtime.child_object_exists(parent, child_id)?;
473 charge_cache_or_load_gas!(context, cache_info);
474 Ok(NativeResult::ok(
475 context.gas_used(),
476 smallvec![Value::bool(has_child)],
477 ))
478}
479
480#[derive(Clone)]
481pub struct DynamicFieldHasChildObjectWithTyCostParams {
482 pub dynamic_field_has_child_object_with_ty_cost_base: InternalGas,
483 pub dynamic_field_has_child_object_with_ty_type_cost_per_byte: InternalGas,
484 pub dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: InternalGas,
485}
486#[instrument(level = "trace", skip_all)]
494pub fn has_child_object_with_ty(
495 context: &mut NativeContext,
496 mut ty_args: Vec<Type>,
497 mut args: VecDeque<Value>,
498) -> PartialVMResult<NativeResult> {
499 safe_assert_eq!(ty_args.len(), 1);
500 safe_assert_eq!(args.len(), 2);
501
502 let dynamic_field_has_child_object_with_ty_cost_params =
503 get_extension!(context, NativesCostTable)?
504 .dynamic_field_has_child_object_with_ty_cost_params
505 .clone();
506 native_charge_gas_early_exit!(
507 context,
508 dynamic_field_has_child_object_with_ty_cost_params
509 .dynamic_field_has_child_object_with_ty_cost_base
510 );
511
512 let child_id = pop_arg!(args, AccountAddress).into();
513 let parent = pop_arg!(args, AccountAddress).into();
514 safe_assert!(args.is_empty());
515 let ty = safe_unwrap!(ty_args.pop());
516
517 native_charge_gas_early_exit!(
518 context,
519 dynamic_field_has_child_object_with_ty_cost_params
520 .dynamic_field_has_child_object_with_ty_type_cost_per_byte
521 * u64::from(ty.size()?).into()
522 );
523
524 let tag: StructTag = match context.type_to_type_tag(&ty)? {
525 TypeTag::Struct(s) => *s,
526 _ => {
527 return Err(
528 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
529 .with_message("Sui verifier guarantees this is a struct".to_string()),
530 );
531 }
532 };
533
534 native_charge_gas_early_exit!(
535 context,
536 dynamic_field_has_child_object_with_ty_cost_params
537 .dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte
538 * u64::from(tag.abstract_size_for_gas_metering()).into()
539 );
540
541 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
542 let (cache_info, has_child) = object_runtime.child_object_exists_and_has_type(
543 parent,
544 child_id,
545 &MoveObjectType::from(tag),
546 )?;
547 charge_cache_or_load_gas!(context, cache_info);
548 Ok(NativeResult::ok(
549 context.gas_used(),
550 smallvec![Value::bool(has_child)],
551 ))
552}