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::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
37// Used for pre-existing values
38const PRE_EXISTING_ABSTRACT_SIZE: u64 = 2;
39// Used for borrowing pre-existing values
40const 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/***************************************************************************************************
82 * native fun hash_type_and_key
83 * Implementation of the Move native function `hash_type_and_key<K: copy + drop + store>(parent: address, k: K): address`
84 *   gas cost: dynamic_field_hash_type_and_key_cost_base                            | covers various fixed costs in the oper
85 *              + dynamic_field_hash_type_and_key_type_cost_per_byte * size_of(K)   | covers cost of operating on the type `K`
86 *              + dynamic_field_hash_type_and_key_value_cost_per_byte * size_of(k)  | covers cost of operating on the value `k`
87 *              + 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`
88 **************************************************************************************************/
89#[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    // Charge base fee
103    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    // Get size info for costing for derivations, serializations, etc
113    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/***************************************************************************************************
163 * native fun add_child_object
164 * throws `E_KEY_ALREADY_EXISTS` if a child already exists with that ID
165 * Implementation of the Move native function `add_child_object<Child: key>(parent: address, child: Child)`
166 *   gas cost: dynamic_field_add_child_object_cost_base                    | covers various fixed costs in the oper
167 *              + dynamic_field_add_child_object_type_cost_per_byte * size_of(Child)        | covers cost of operating on the type `Child`
168 *              + dynamic_field_add_child_object_value_cost_per_byte * size_of(child)       | covers cost of operating on the value `child`
169 *              + 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`
170 **************************************************************************************************/
171#[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    // Charge base fee
185    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        // The value already exists, the size of the value is irrelevant
197        PRE_EXISTING_ABSTRACT_SIZE
198    } else {
199        child.legacy_size().into()
200    };
201    // ID extraction step
202    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    // TODO remove this copy_value, which will require VM changes
210    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/***************************************************************************************************
264 * native fun borrow_child_object
265 * throws `E_KEY_DOES_NOT_EXIST` if a child does not exist with that ID at that type
266 * or throws `E_FIELD_TYPE_MISMATCH` if the type does not match (as the runtime does not distinguish different reference types)
267 * Implementation of the Move native function `borrow_child_object_mut<Child: key>(parent: &mut UID, id: address): &mut Child`
268 *   gas cost: dynamic_field_borrow_child_object_cost_base                    | covers various fixed costs in the oper
269 *              + dynamic_field_borrow_child_object_child_ref_cost_per_byte  * size_of(&Child)  | covers cost of fetching and returning `&Child`
270 *              + dynamic_field_borrow_child_object_type_cost_per_byte  * size_of(Child)        | covers cost of operating on type `Child`
271 **************************************************************************************************/
272#[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    // UID { id: ID { bytes: address } }
293    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            // The value already existed
327            BORROW_ABSTRACT_SIZE.into()
328        }
329        // The Move value had to be created. We traverse references to get the full size of the
330        // borrowed value
331        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/***************************************************************************************************
357 * native fun remove_child_object
358 * throws `E_KEY_DOES_NOT_EXIST` if a child does not exist with that ID at that type
359 * or throws `E_FIELD_TYPE_MISMATCH` if the type does not match
360 * Implementation of the Move native function `remove_child_object<Child: key>(parent: address, id: address): Child`
361 *   gas cost: dynamic_field_remove_child_object_cost_base                    | covers various fixed costs in the oper
362 *              + dynamic_field_remove_child_object_type_cost_per_byte * size_of(Child)      | covers cost of operating on type `Child`
363 *              + dynamic_field_remove_child_object_child_cost_per_byte  * size_of(child)     | covers cost of fetching and returning value of type `Child`
364 **************************************************************************************************/
365#[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            // The value already existed
414            PRE_EXISTING_ABSTRACT_SIZE.into()
415        }
416        // The Move value had to be created. The value isn't a reference so traverse_references
417        // doesn't matter
418        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    // All inputs are constant same size. No need for special costing as this is a lookup
437    pub dynamic_field_has_child_object_cost_base: InternalGas,
438}
439/***************************************************************************************************
440 * native fun has_child_object
441 * Implementation of the Move native function `has_child_object(parent: address, id: address): bool`
442 *   gas cost: dynamic_field_has_child_object_cost_base                    | covers various fixed costs in the oper
443 **************************************************************************************************/
444#[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/***************************************************************************************************
479 * native fun has_child_object_with_ty
480 * Implementation of the Move native function `has_child_object_with_ty<Child: key>(parent: address, id: address): bool`
481 *   gas cost: dynamic_field_has_child_object_with_ty_cost_base               | covers various fixed costs in the oper
482 *              + dynamic_field_has_child_object_with_ty_type_cost_per_byte * size_of(Child)        | covers cost of operating on type `Child`
483 *              + 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`
484 **************************************************************************************************/
485#[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}