sui_move_natives_latest/
dynamic_field.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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
40// Used for pre-existing values
41const PRE_EXISTING_ABSTRACT_SIZE: u64 = 2;
42// Used for borrowing pre-existing values
43const 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/***************************************************************************************************
85 * native fun hash_type_and_key
86 * Implementation of the Move native function `hash_type_and_key<K: copy + drop + store>(parent: address, k: K): address`
87 *   gas cost: dynamic_field_hash_type_and_key_cost_base                            | covers various fixed costs in the oper
88 *              + dynamic_field_hash_type_and_key_type_cost_per_byte * size_of(K)   | covers cost of operating on the type `K`
89 *              + dynamic_field_hash_type_and_key_value_cost_per_byte * size_of(k)  | covers cost of operating on the value `k`
90 *              + dynamic_field_hash_type_and_key_type_tag_cost_per_byte * size_of(type_tag(k))    | covers cost of operating on the type tag of `K`
91 **************************************************************************************************/
92#[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    // Charge base fee
106    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    // Get size info for costing for derivations, serializations, etc
116    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/***************************************************************************************************
166 * native fun add_child_object
167 * throws `E_KEY_ALREADY_EXISTS` if a child already exists with that ID
168 * Implementation of the Move native function `add_child_object<Child: key>(parent: address, child: Child)`
169 *   gas cost: dynamic_field_add_child_object_cost_base                    | covers various fixed costs in the oper
170 *              + dynamic_field_add_child_object_type_cost_per_byte * size_of(Child)        | covers cost of operating on the type `Child`
171 *              + dynamic_field_add_child_object_value_cost_per_byte * size_of(child)       | covers cost of operating on the value `child`
172 *              + dynamic_field_add_child_object_struct_tag_cost_per_byte * size_of(struct)tag(Child))  | covers cost of operating on the struct tag of `Child`
173 **************************************************************************************************/
174#[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    // Charge base fee
188    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    // The value already exists, the size of the value is irrelevant
198    let child_value_size = PRE_EXISTING_ABSTRACT_SIZE;
199    // ID extraction step
200    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    // TODO remove this copy_value, which will require VM changes
208    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/***************************************************************************************************
261 * native fun borrow_child_object
262 * throws `E_KEY_DOES_NOT_EXIST` if a child does not exist with that ID at that type
263 * or throws `E_FIELD_TYPE_MISMATCH` if the type does not match (as the runtime does not distinguish different reference types)
264 * Implementation of the Move native function `borrow_child_object_mut<Child: key>(parent: &mut UID, id: address): &mut Child`
265 *   gas cost: dynamic_field_borrow_child_object_cost_base                    | covers various fixed costs in the oper
266 *              + dynamic_field_borrow_child_object_child_ref_cost_per_byte  * size_of(&Child)  | covers cost of fetching and returning `&Child`
267 *              + dynamic_field_borrow_child_object_type_cost_per_byte  * size_of(Child)        | covers cost of operating on type `Child`
268 **************************************************************************************************/
269#[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    // UID { id: ID { bytes: address } }
290    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            // The value already existed
329            BORROW_ABSTRACT_SIZE.into()
330        }
331        // The Move value had to be created. We traverse references to get the full size of the
332        // borrowed value
333        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/***************************************************************************************************
358 * native fun remove_child_object
359 * throws `E_KEY_DOES_NOT_EXIST` if a child does not exist with that ID at that type
360 * or throws `E_FIELD_TYPE_MISMATCH` if the type does not match
361 * Implementation of the Move native function `remove_child_object<Child: key>(parent: address, id: address): Child`
362 *   gas cost: dynamic_field_remove_child_object_cost_base                    | covers various fixed costs in the oper
363 *              + dynamic_field_remove_child_object_type_cost_per_byte * size_of(Child)      | covers cost of operating on type `Child`
364 *              + dynamic_field_remove_child_object_child_cost_per_byte  * size_of(child)     | covers cost of fetching and returning value of type `Child`
365 **************************************************************************************************/
366#[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            // The value already existed
421            PRE_EXISTING_ABSTRACT_SIZE.into()
422        }
423        // The Move value had to be created. The value isn't a reference so traverse_references
424        // doesn't matter
425        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    // All inputs are constant same size. No need for special costing as this is a lookup
445    pub dynamic_field_has_child_object_cost_base: InternalGas,
446}
447/***************************************************************************************************
448 * native fun has_child_object
449 * Implementation of the Move native function `has_child_object(parent: address, id: address): bool`
450 *   gas cost: dynamic_field_has_child_object_cost_base                    | covers various fixed costs in the oper
451 **************************************************************************************************/
452#[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/***************************************************************************************************
487 * native fun has_child_object_with_ty
488 * Implementation of the Move native function `has_child_object_with_ty<Child: key>(parent: address, id: address): bool`
489 *   gas cost: dynamic_field_has_child_object_with_ty_cost_base               | covers various fixed costs in the oper
490 *              + dynamic_field_has_child_object_with_ty_type_cost_per_byte * size_of(Child)        | covers cost of operating on type `Child`
491 *              + dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte * size_of(Child)    | covers cost of fetching and returning value of type tag for `Child`
492 **************************************************************************************************/
493#[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}