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