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_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
39// Used for pre-existing values
40const PRE_EXISTING_ABSTRACT_SIZE: u64 = 2;
41// Used for borrowing pre-existing values
42const 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/***************************************************************************************************
84 * native fun hash_type_and_key
85 * Implementation of the Move native function `hash_type_and_key<K: copy + drop + store>(parent: address, k: K): address`
86 *   gas cost: dynamic_field_hash_type_and_key_cost_base                            | covers various fixed costs in the oper
87 *              + dynamic_field_hash_type_and_key_type_cost_per_byte * size_of(K)   | covers cost of operating on the type `K`
88 *              + dynamic_field_hash_type_and_key_value_cost_per_byte * size_of(k)  | covers cost of operating on the value `k`
89 *              + 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`
90 **************************************************************************************************/
91#[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    // Charge base fee
105    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    // Get size info for costing for derivations, serializations, etc
115    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/***************************************************************************************************
165 * native fun add_child_object
166 * throws `E_KEY_ALREADY_EXISTS` if a child already exists with that ID
167 * Implementation of the Move native function `add_child_object<Child: key>(parent: address, child: Child)`
168 *   gas cost: dynamic_field_add_child_object_cost_base                    | covers various fixed costs in the oper
169 *              + dynamic_field_add_child_object_type_cost_per_byte * size_of(Child)        | covers cost of operating on the type `Child`
170 *              + dynamic_field_add_child_object_value_cost_per_byte * size_of(child)       | covers cost of operating on the value `child`
171 *              + 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`
172 **************************************************************************************************/
173#[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    // Charge base fee
187    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    // The value already exists, the size of the value is irrelevant
197    let child_value_size = PRE_EXISTING_ABSTRACT_SIZE;
198    // ID extraction step
199    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    // TODO remove this copy_value, which will require VM changes
207    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/***************************************************************************************************
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    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    // UID { id: ID { bytes: address } }
290    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            // The value already existed
322            BORROW_ABSTRACT_SIZE.into()
323        }
324        // The Move value had to be created. We traverse references to get the full size of the
325        // borrowed value
326        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/***************************************************************************************************
351 * native fun remove_child_object
352 * throws `E_KEY_DOES_NOT_EXIST` if a child does not exist with that ID at that type
353 * or throws `E_FIELD_TYPE_MISMATCH` if the type does not match
354 * Implementation of the Move native function `remove_child_object<Child: key>(parent: address, id: address): Child`
355 *   gas cost: dynamic_field_remove_child_object_cost_base                    | covers various fixed costs in the oper
356 *              + dynamic_field_remove_child_object_type_cost_per_byte * size_of(Child)      | covers cost of operating on type `Child`
357 *              + dynamic_field_remove_child_object_child_cost_per_byte  * size_of(child)     | covers cost of fetching and returning value of type `Child`
358 **************************************************************************************************/
359#[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            // The value already existed
406            PRE_EXISTING_ABSTRACT_SIZE.into()
407        }
408        // The Move value had to be created. The value isn't a reference so traverse_references
409        // doesn't matter
410        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    // All inputs are constant same size. No need for special costing as this is a lookup
430    pub dynamic_field_has_child_object_cost_base: InternalGas,
431}
432/***************************************************************************************************
433 * native fun has_child_object
434 * Implementation of the Move native function `has_child_object(parent: address, id: address): bool`
435 *   gas cost: dynamic_field_has_child_object_cost_base                    | covers various fixed costs in the oper
436 **************************************************************************************************/
437#[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/***************************************************************************************************
472 * native fun has_child_object_with_ty
473 * Implementation of the Move native function `has_child_object_with_ty<Child: key>(parent: address, id: address): bool`
474 *   gas cost: dynamic_field_has_child_object_with_ty_cost_base               | covers various fixed costs in the oper
475 *              + dynamic_field_has_child_object_with_ty_type_cost_per_byte * size_of(Child)        | covers cost of operating on type `Child`
476 *              + 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`
477 **************************************************************************************************/
478#[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}