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_core_types::{
14 account_address::AccountAddress,
15 gas_algebra::InternalGas,
16 language_storage::{StructTag, TypeTag},
17 vm_status::StatusCode,
18};
19use move_vm_runtime::native_charge_gas_early_exit;
20use move_vm_runtime::natives::functions::NativeContext;
21use move_vm_runtime::{
22 execution::{
23 Type,
24 values::{StructRef, Value},
25 },
26 natives::functions::NativeResult,
27 pop_arg,
28 shared::views::{SizeConfig, ValueView},
29};
30use smallvec::smallvec;
31use std::collections::VecDeque;
32use sui_types::{base_types::MoveObjectType, dynamic_field::derive_dynamic_field_id};
33use tracing::instrument;
34
35const E_KEY_DOES_NOT_EXIST: u64 = 1;
36const E_FIELD_TYPE_MISMATCH: u64 = 2;
37const E_BCS_SERIALIZATION_FAILURE: u64 = 3;
38
39const PRE_EXISTING_ABSTRACT_SIZE: u64 = 2;
41const BORROW_ABSTRACT_SIZE: u64 = 8;
43
44macro_rules! get_or_fetch_object {
45 ($context:ident, $ty_args:ident, $parent:ident, $child_id:ident, $ty_cost_per_byte:expr) => {{
46 let child_ty = $ty_args.pop().unwrap();
47 native_charge_gas_early_exit!(
48 $context,
49 $ty_cost_per_byte * u64::from(child_ty.size()).into()
50 );
51
52 assert!($ty_args.is_empty());
53 let (tag, layout, annotated_layout) = match crate::get_tag_and_layouts($context, &child_ty)?
54 {
55 Some(res) => res,
56 None => {
57 return Ok(NativeResult::err(
58 $context.gas_used(),
59 E_BCS_SERIALIZATION_FAILURE,
60 ));
61 }
62 };
63
64 let object_runtime: &mut ObjectRuntime = $crate::get_extension_mut!($context)?;
65 object_runtime.get_or_fetch_child_object(
66 $parent,
67 $child_id,
68 &layout,
69 &annotated_layout,
70 MoveObjectType::from(tag),
71 )?
72 }};
73}
74
75#[derive(Clone)]
76pub struct DynamicFieldHashTypeAndKeyCostParams {
77 pub dynamic_field_hash_type_and_key_cost_base: InternalGas,
78 pub dynamic_field_hash_type_and_key_type_cost_per_byte: InternalGas,
79 pub dynamic_field_hash_type_and_key_value_cost_per_byte: InternalGas,
80 pub dynamic_field_hash_type_and_key_type_tag_cost_per_byte: InternalGas,
81}
82
83#[instrument(level = "trace", skip_all)]
92pub fn hash_type_and_key(
93 context: &mut NativeContext,
94 mut ty_args: Vec<Type>,
95 mut args: VecDeque<Value>,
96) -> PartialVMResult<NativeResult> {
97 assert_eq!(ty_args.len(), 1);
98 assert_eq!(args.len(), 2);
99
100 let dynamic_field_hash_type_and_key_cost_params = get_extension!(context, NativesCostTable)?
101 .dynamic_field_hash_type_and_key_cost_params
102 .clone();
103
104 native_charge_gas_early_exit!(
106 context,
107 dynamic_field_hash_type_and_key_cost_params.dynamic_field_hash_type_and_key_cost_base
108 );
109
110 let k_ty = ty_args.pop().unwrap();
111 let k: Value = args.pop_back().unwrap();
112 let parent = pop_arg!(args, AccountAddress);
113
114 let k_ty_size = u64::from(k_ty.size());
116 let k_value_size = u64::from(abstract_size(
117 get_extension!(context, ObjectRuntime)?.protocol_config,
118 &k,
119 )?);
120 native_charge_gas_early_exit!(
121 context,
122 dynamic_field_hash_type_and_key_cost_params
123 .dynamic_field_hash_type_and_key_type_cost_per_byte
124 * k_ty_size.into()
125 + dynamic_field_hash_type_and_key_cost_params
126 .dynamic_field_hash_type_and_key_value_cost_per_byte
127 * k_value_size.into()
128 );
129
130 let k_tag = context.type_to_type_tag(&k_ty)?;
131 let k_tag_size = u64::from(k_tag.abstract_size_for_gas_metering());
132
133 native_charge_gas_early_exit!(
134 context,
135 dynamic_field_hash_type_and_key_cost_params
136 .dynamic_field_hash_type_and_key_type_tag_cost_per_byte
137 * k_tag_size.into()
138 );
139
140 let cost = context.gas_used();
141
142 let k_layout = match context.type_to_type_layout(&k_ty) {
143 Ok(Some(layout)) => layout,
144 _ => return Ok(NativeResult::err(cost, E_BCS_SERIALIZATION_FAILURE)),
145 };
146 let Some(k_bytes) = k.typed_serialize(&k_layout) else {
147 return Ok(NativeResult::err(cost, E_BCS_SERIALIZATION_FAILURE));
148 };
149 let Ok(id) = derive_dynamic_field_id(parent, &k_tag, &k_bytes) else {
150 return Ok(NativeResult::err(cost, E_BCS_SERIALIZATION_FAILURE));
151 };
152
153 Ok(NativeResult::ok(cost, smallvec![Value::address(id.into())]))
154}
155
156#[derive(Clone)]
157pub struct DynamicFieldAddChildObjectCostParams {
158 pub dynamic_field_add_child_object_cost_base: InternalGas,
159 pub dynamic_field_add_child_object_type_cost_per_byte: InternalGas,
160 pub dynamic_field_add_child_object_value_cost_per_byte: InternalGas,
161 pub dynamic_field_add_child_object_struct_tag_cost_per_byte: InternalGas,
162}
163
164#[instrument(level = "trace", skip_all)]
174pub fn add_child_object(
175 context: &mut NativeContext,
176 mut ty_args: Vec<Type>,
177 mut args: VecDeque<Value>,
178) -> PartialVMResult<NativeResult> {
179 assert!(ty_args.len() == 1);
180 assert!(args.len() == 2);
181
182 let dynamic_field_add_child_object_cost_params = get_extension!(context, NativesCostTable)?
183 .dynamic_field_add_child_object_cost_params
184 .clone();
185
186 native_charge_gas_early_exit!(
188 context,
189 dynamic_field_add_child_object_cost_params.dynamic_field_add_child_object_cost_base
190 );
191
192 let child = args.pop_back().unwrap();
193 let parent = pop_arg!(args, AccountAddress).into();
194 assert!(args.is_empty());
195
196 let child_value_size = PRE_EXISTING_ABSTRACT_SIZE;
198 native_charge_gas_early_exit!(
200 context,
201 dynamic_field_add_child_object_cost_params
202 .dynamic_field_add_child_object_value_cost_per_byte
203 * child_value_size.into()
204 );
205
206 let child_id = get_object_id(child.copy_value())
208 .unwrap()
209 .value_as::<AccountAddress>()
210 .unwrap()
211 .into();
212 let child_ty = ty_args.pop().unwrap();
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 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 assert!(ty_args.len() == 1);
276 assert!(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 = pop_arg!(args, StructRef).read_ref().unwrap();
289 let parent = get_nested_struct_field(parent_uid, &[0, 0])
291 .unwrap()
292 .value_as::<AccountAddress>()
293 .unwrap()
294 .into();
295
296 assert!(args.is_empty());
297 let global_value_result = get_or_fetch_object!(
298 context,
299 ty_args,
300 parent,
301 child_id,
302 dynamic_field_borrow_child_object_cost_params
303 .dynamic_field_borrow_child_object_type_cost_per_byte
304 );
305 let (cache_info, global_value) = match global_value_result {
306 ObjectResult::MismatchedType => {
307 return Ok(NativeResult::err(context.gas_used(), E_FIELD_TYPE_MISMATCH));
308 }
309 ObjectResult::Loaded(gv) => gv,
310 };
311 if !global_value.exists()? {
312 return Ok(NativeResult::err(context.gas_used(), E_KEY_DOES_NOT_EXIST));
313 }
314 let child_ref = global_value.borrow_global().inspect_err(|err| {
315 assert!(err.major_status() != StatusCode::MISSING_DATA);
316 })?;
317
318 charge_cache_or_load_gas!(context, cache_info);
319 let child_ref_size = match cache_info {
320 CacheInfo::CachedValue => {
321 BORROW_ABSTRACT_SIZE.into()
323 }
324 CacheInfo::CachedObject | CacheInfo::Loaded(_) => {
327 child_ref.abstract_memory_size(&SizeConfig {
328 include_vector_size: true,
329 traverse_references: true,
330 })?
331 }
332 };
333
334 native_charge_gas_early_exit!(
335 context,
336 dynamic_field_borrow_child_object_cost_params
337 .dynamic_field_borrow_child_object_child_ref_cost_per_byte
338 * u64::from(child_ref_size).into()
339 );
340
341 Ok(NativeResult::ok(context.gas_used(), smallvec![child_ref]))
342}
343
344#[derive(Clone)]
345pub struct DynamicFieldRemoveChildObjectCostParams {
346 pub dynamic_field_remove_child_object_cost_base: InternalGas,
347 pub dynamic_field_remove_child_object_child_cost_per_byte: InternalGas,
348 pub dynamic_field_remove_child_object_type_cost_per_byte: InternalGas,
349}
350#[instrument(level = "trace", skip_all)]
360pub fn remove_child_object(
361 context: &mut NativeContext,
362 mut ty_args: Vec<Type>,
363 mut args: VecDeque<Value>,
364) -> PartialVMResult<NativeResult> {
365 assert!(ty_args.len() == 1);
366 assert!(args.len() == 2);
367
368 let dynamic_field_remove_child_object_cost_params = get_extension!(context, NativesCostTable)?
369 .dynamic_field_remove_child_object_cost_params
370 .clone();
371 native_charge_gas_early_exit!(
372 context,
373 dynamic_field_remove_child_object_cost_params.dynamic_field_remove_child_object_cost_base
374 );
375
376 let child_id = pop_arg!(args, AccountAddress).into();
377 let parent = pop_arg!(args, AccountAddress).into();
378 assert!(args.is_empty());
379 let global_value_result = get_or_fetch_object!(
380 context,
381 ty_args,
382 parent,
383 child_id,
384 dynamic_field_remove_child_object_cost_params
385 .dynamic_field_remove_child_object_type_cost_per_byte
386 );
387 let (cache_info, global_value) = match global_value_result {
388 ObjectResult::MismatchedType => {
389 return Ok(NativeResult::err(context.gas_used(), E_FIELD_TYPE_MISMATCH));
390 }
391 ObjectResult::Loaded(gv) => gv,
392 };
393
394 if !global_value.exists()? {
395 return Ok(NativeResult::err(context.gas_used(), E_KEY_DOES_NOT_EXIST));
396 }
397 let child = global_value.move_from().inspect_err(|err| {
398 assert!(err.major_status() != StatusCode::MISSING_DATA);
399 })?;
400
401 charge_cache_or_load_gas!(context, cache_info);
402
403 let child_size = match cache_info {
404 CacheInfo::CachedValue => {
405 PRE_EXISTING_ABSTRACT_SIZE.into()
407 }
408 CacheInfo::CachedObject | CacheInfo::Loaded(_) => {
411 child.abstract_memory_size(&SizeConfig {
412 include_vector_size: true,
413 traverse_references: false,
414 })?
415 }
416 };
417 native_charge_gas_early_exit!(
418 context,
419 dynamic_field_remove_child_object_cost_params
420 .dynamic_field_remove_child_object_child_cost_per_byte
421 * u64::from(child_size).into()
422 );
423
424 Ok(NativeResult::ok(context.gas_used(), smallvec![child]))
425}
426
427#[derive(Clone)]
428pub struct DynamicFieldHasChildObjectCostParams {
429 pub dynamic_field_has_child_object_cost_base: InternalGas,
431}
432#[instrument(level = "trace", skip_all)]
438pub fn has_child_object(
439 context: &mut NativeContext,
440 ty_args: Vec<Type>,
441 mut args: VecDeque<Value>,
442) -> PartialVMResult<NativeResult> {
443 assert!(ty_args.is_empty());
444 assert!(args.len() == 2);
445
446 let dynamic_field_has_child_object_cost_params = get_extension!(context, NativesCostTable)?
447 .dynamic_field_has_child_object_cost_params
448 .clone();
449 native_charge_gas_early_exit!(
450 context,
451 dynamic_field_has_child_object_cost_params.dynamic_field_has_child_object_cost_base
452 );
453
454 let child_id = pop_arg!(args, AccountAddress).into();
455 let parent = pop_arg!(args, AccountAddress).into();
456 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
457 let (cache_info, has_child) = object_runtime.child_object_exists(parent, child_id)?;
458 charge_cache_or_load_gas!(context, cache_info);
459 Ok(NativeResult::ok(
460 context.gas_used(),
461 smallvec![Value::bool(has_child)],
462 ))
463}
464
465#[derive(Clone)]
466pub struct DynamicFieldHasChildObjectWithTyCostParams {
467 pub dynamic_field_has_child_object_with_ty_cost_base: InternalGas,
468 pub dynamic_field_has_child_object_with_ty_type_cost_per_byte: InternalGas,
469 pub dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: InternalGas,
470}
471#[instrument(level = "trace", skip_all)]
479pub fn has_child_object_with_ty(
480 context: &mut NativeContext,
481 mut ty_args: Vec<Type>,
482 mut args: VecDeque<Value>,
483) -> PartialVMResult<NativeResult> {
484 assert!(ty_args.len() == 1);
485 assert!(args.len() == 2);
486
487 let dynamic_field_has_child_object_with_ty_cost_params =
488 get_extension!(context, NativesCostTable)?
489 .dynamic_field_has_child_object_with_ty_cost_params
490 .clone();
491 native_charge_gas_early_exit!(
492 context,
493 dynamic_field_has_child_object_with_ty_cost_params
494 .dynamic_field_has_child_object_with_ty_cost_base
495 );
496
497 let child_id = pop_arg!(args, AccountAddress).into();
498 let parent = pop_arg!(args, AccountAddress).into();
499 assert!(args.is_empty());
500 let ty = ty_args.pop().unwrap();
501
502 native_charge_gas_early_exit!(
503 context,
504 dynamic_field_has_child_object_with_ty_cost_params
505 .dynamic_field_has_child_object_with_ty_type_cost_per_byte
506 * u64::from(ty.size()).into()
507 );
508
509 let tag: StructTag = match context.type_to_type_tag(&ty)? {
510 TypeTag::Struct(s) => *s,
511 _ => {
512 return Err(
513 PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
514 .with_message("Sui verifier guarantees this is a struct".to_string()),
515 );
516 }
517 };
518
519 native_charge_gas_early_exit!(
520 context,
521 dynamic_field_has_child_object_with_ty_cost_params
522 .dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte
523 * u64::from(tag.abstract_size_for_gas_metering()).into()
524 );
525
526 let object_runtime: &mut ObjectRuntime = get_extension_mut!(context)?;
527 let (cache_info, has_child) = object_runtime.child_object_exists_and_has_type(
528 parent,
529 child_id,
530 &MoveObjectType::from(tag),
531 )?;
532 charge_cache_or_load_gas!(context, cache_info);
533 Ok(NativeResult::ok(
534 context.gas_used(),
535 smallvec![Value::bool(has_child)],
536 ))
537}