iota_move_natives_latest/
dynamic_field.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::collections::VecDeque;
6
7use iota_types::{base_types::MoveObjectType, dynamic_field::derive_dynamic_field_id};
8use move_binary_format::errors::{PartialVMError, PartialVMResult};
9use move_core_types::{
10    account_address::AccountAddress,
11    gas_algebra::InternalGas,
12    language_storage::{StructTag, TypeTag},
13    vm_status::StatusCode,
14};
15use move_vm_runtime::{native_charge_gas_early_exit, native_functions::NativeContext};
16use move_vm_types::{
17    loaded_data::runtime_types::Type,
18    natives::function::NativeResult,
19    pop_arg,
20    values::{StructRef, Value},
21};
22use smallvec::smallvec;
23use tracing::instrument;
24
25use crate::{
26    NativesCostTable, get_nested_struct_field, get_object_id,
27    object_runtime::{ObjectRuntime, object_store::ObjectResult},
28};
29
30const E_KEY_DOES_NOT_EXIST: u64 = 1;
31const E_FIELD_TYPE_MISMATCH: u64 = 2;
32const E_BCS_SERIALIZATION_FAILURE: u64 = 3;
33
34macro_rules! get_or_fetch_object {
35    ($context:ident, $ty_args:ident, $parent:ident, $child_id:ident, $ty_cost_per_byte:expr) => {{
36        let child_ty = $ty_args.pop().unwrap();
37        native_charge_gas_early_exit!(
38            $context,
39            $ty_cost_per_byte * u64::from(child_ty.size()).into()
40        );
41
42        assert!($ty_args.is_empty());
43        let (tag, layout, annotated_layout) = match crate::get_tag_and_layouts($context, &child_ty)?
44        {
45            Some(res) => res,
46            None => {
47                return Ok(NativeResult::err(
48                    $context.gas_used(),
49                    E_BCS_SERIALIZATION_FAILURE,
50                ));
51            }
52        };
53
54        let object_runtime: &mut ObjectRuntime = $context.extensions_mut().get_mut();
55        object_runtime.get_or_fetch_child_object(
56            $parent,
57            $child_id,
58            &child_ty,
59            &layout,
60            &annotated_layout,
61            MoveObjectType::from(tag),
62        )?
63    }};
64}
65
66#[derive(Clone)]
67pub struct DynamicFieldHashTypeAndKeyCostParams {
68    pub dynamic_field_hash_type_and_key_cost_base: InternalGas,
69    pub dynamic_field_hash_type_and_key_type_cost_per_byte: InternalGas,
70    pub dynamic_field_hash_type_and_key_value_cost_per_byte: InternalGas,
71    pub dynamic_field_hash_type_and_key_type_tag_cost_per_byte: InternalGas,
72}
73
74/// ****************************************************************************
75/// ********************* native fun hash_type_and_key
76/// Implementation of the Move native function `hash_type_and_key<K: copy + drop
77/// + store>(parent: address, k: K): address`   gas cost:
78/// dynamic_field_hash_type_and_key_cost_base                            |
79/// covers various fixed costs in the oper
80///              + dynamic_field_hash_type_and_key_type_cost_per_byte *
81///                size_of(K)   | covers cost of operating on the type `K`
82///              + dynamic_field_hash_type_and_key_value_cost_per_byte *
83///                size_of(k)  | covers cost of operating on the value `k`
84///              + dynamic_field_hash_type_and_key_type_tag_cost_per_byte *
85///                size_of(type_tag(k))    | covers cost of operating on the
86///                type tag of `K`
87/// ****************************************************************************
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 = context
99        .extensions_mut()
100        .get::<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(k.legacy_size());
117    native_charge_gas_early_exit!(
118        context,
119        dynamic_field_hash_type_and_key_cost_params
120            .dynamic_field_hash_type_and_key_type_cost_per_byte
121            * k_ty_size.into()
122            + dynamic_field_hash_type_and_key_cost_params
123                .dynamic_field_hash_type_and_key_value_cost_per_byte
124                * k_value_size.into()
125    );
126
127    let k_tag = context.type_to_type_tag(&k_ty)?;
128    let k_tag_size = u64::from(k_tag.abstract_size_for_gas_metering());
129
130    native_charge_gas_early_exit!(
131        context,
132        dynamic_field_hash_type_and_key_cost_params
133            .dynamic_field_hash_type_and_key_type_tag_cost_per_byte
134            * k_tag_size.into()
135    );
136
137    let cost = context.gas_used();
138
139    let k_layout = match context.type_to_type_layout(&k_ty) {
140        Ok(Some(layout)) => layout,
141        _ => return Ok(NativeResult::err(cost, E_BCS_SERIALIZATION_FAILURE)),
142    };
143    let Some(k_bytes) = k.simple_serialize(&k_layout) else {
144        return Ok(NativeResult::err(cost, E_BCS_SERIALIZATION_FAILURE));
145    };
146    let Ok(id) = derive_dynamic_field_id(parent, &k_tag, &k_bytes) else {
147        return Ok(NativeResult::err(cost, E_BCS_SERIALIZATION_FAILURE));
148    };
149
150    Ok(NativeResult::ok(cost, smallvec![Value::address(id.into())]))
151}
152
153#[derive(Clone)]
154pub struct DynamicFieldAddChildObjectCostParams {
155    pub dynamic_field_add_child_object_cost_base: InternalGas,
156    pub dynamic_field_add_child_object_type_cost_per_byte: InternalGas,
157    pub dynamic_field_add_child_object_value_cost_per_byte: InternalGas,
158    pub dynamic_field_add_child_object_struct_tag_cost_per_byte: InternalGas,
159}
160
161/// ****************************************************************************
162/// ********************* native fun add_child_object
163/// throws `E_KEY_ALREADY_EXISTS` if a child already exists with that ID
164/// Implementation of the Move native function `add_child_object<Child:
165/// key>(parent: address, child: Child)`   gas cost:
166/// dynamic_field_add_child_object_cost_base                    | covers various
167/// fixed costs in the oper
168///              + dynamic_field_add_child_object_type_cost_per_byte *
169///                size_of(Child)        | covers cost of operating on the type
170///                `Child`
171///              + dynamic_field_add_child_object_value_cost_per_byte *
172///                size_of(child)       | covers cost of operating on the value
173///                `child`
174///              + dynamic_field_add_child_object_struct_tag_cost_per_byte *
175///                size_of(struct)tag(Child))  | covers cost of operating on the
176///                struct tag of `Child`
177/// ****************************************************************************
178/// *******************
179#[instrument(level = "trace", skip_all)]
180pub fn add_child_object(
181    context: &mut NativeContext,
182    mut ty_args: Vec<Type>,
183    mut args: VecDeque<Value>,
184) -> PartialVMResult<NativeResult> {
185    assert!(ty_args.len() == 1);
186    assert!(args.len() == 2);
187
188    let dynamic_field_add_child_object_cost_params = context
189        .extensions_mut()
190        .get::<NativesCostTable>()
191        .dynamic_field_add_child_object_cost_params
192        .clone();
193
194    // Charge base fee
195    native_charge_gas_early_exit!(
196        context,
197        dynamic_field_add_child_object_cost_params.dynamic_field_add_child_object_cost_base
198    );
199
200    let child = args.pop_back().unwrap();
201    let parent = pop_arg!(args, AccountAddress).into();
202    assert!(args.is_empty());
203
204    let child_value_size = u64::from(child.legacy_size());
205    // ID extraction step
206    native_charge_gas_early_exit!(
207        context,
208        dynamic_field_add_child_object_cost_params
209            .dynamic_field_add_child_object_value_cost_per_byte
210            * child_value_size.into()
211    );
212
213    // TODO remove this copy_value, which will require VM changes
214    let child_id = get_object_id(child.copy_value().unwrap())
215        .unwrap()
216        .value_as::<AccountAddress>()
217        .unwrap()
218        .into();
219    let child_ty = ty_args.pop().unwrap();
220    let child_type_size = u64::from(child_ty.size());
221
222    native_charge_gas_early_exit!(
223        context,
224        dynamic_field_add_child_object_cost_params
225            .dynamic_field_add_child_object_type_cost_per_byte
226            * child_type_size.into()
227    );
228
229    assert!(ty_args.is_empty());
230    let tag = match context.type_to_type_tag(&child_ty)? {
231        TypeTag::Struct(s) => *s,
232        _ => {
233            return Err(
234                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
235                    .with_message("IOTA verifier guarantees this is a struct".to_string()),
236            );
237        }
238    };
239
240    let struct_tag_size = u64::from(tag.abstract_size_for_gas_metering());
241    native_charge_gas_early_exit!(
242        context,
243        dynamic_field_add_child_object_cost_params
244            .dynamic_field_add_child_object_struct_tag_cost_per_byte
245            * struct_tag_size.into()
246    );
247
248    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut();
249    object_runtime.add_child_object(
250        parent,
251        child_id,
252        &child_ty,
253        MoveObjectType::from(tag),
254        child,
255    )?;
256    Ok(NativeResult::ok(context.gas_used(), smallvec![]))
257}
258
259#[derive(Clone)]
260pub struct DynamicFieldBorrowChildObjectCostParams {
261    pub dynamic_field_borrow_child_object_cost_base: InternalGas,
262    pub dynamic_field_borrow_child_object_child_ref_cost_per_byte: InternalGas,
263    pub dynamic_field_borrow_child_object_type_cost_per_byte: InternalGas,
264}
265
266/// ****************************************************************************
267/// ********************* native fun borrow_child_object
268/// throws `E_KEY_DOES_NOT_EXIST` if a child does not exist with that ID at that
269/// type or throws `E_FIELD_TYPE_MISMATCH` if the type does not match (as the
270/// runtime does not distinguish different reference types) Implementation of
271/// the Move native function `borrow_child_object_mut<Child: key>(parent: &mut
272/// UID, id: address): &mut Child`   gas cost:
273/// dynamic_field_borrow_child_object_cost_base                    | covers
274/// various fixed costs in the oper
275///              + dynamic_field_borrow_child_object_child_ref_cost_per_byte  *
276///                size_of(&Child)  | covers cost of fetching and returning
277///                `&Child`
278///              + dynamic_field_borrow_child_object_type_cost_per_byte  *
279///                size_of(Child)        | covers cost of operating on type
280///                `Child`
281/// ****************************************************************************
282/// *******************
283#[instrument(level = "trace", skip_all)]
284pub fn borrow_child_object(
285    context: &mut NativeContext,
286    mut ty_args: Vec<Type>,
287    mut args: VecDeque<Value>,
288) -> PartialVMResult<NativeResult> {
289    assert!(ty_args.len() == 1);
290    assert!(args.len() == 2);
291
292    let dynamic_field_borrow_child_object_cost_params = context
293        .extensions_mut()
294        .get::<NativesCostTable>()
295        .dynamic_field_borrow_child_object_cost_params
296        .clone();
297    native_charge_gas_early_exit!(
298        context,
299        dynamic_field_borrow_child_object_cost_params.dynamic_field_borrow_child_object_cost_base
300    );
301
302    let child_id = pop_arg!(args, AccountAddress).into();
303
304    let parent_uid = pop_arg!(args, StructRef).read_ref().unwrap();
305    // UID { id: ID { bytes: address } }
306    let parent = get_nested_struct_field(parent_uid, &[0, 0])
307        .unwrap()
308        .value_as::<AccountAddress>()
309        .unwrap()
310        .into();
311
312    assert!(args.is_empty());
313    let global_value_result = get_or_fetch_object!(
314        context,
315        ty_args,
316        parent,
317        child_id,
318        dynamic_field_borrow_child_object_cost_params
319            .dynamic_field_borrow_child_object_type_cost_per_byte
320    );
321    let global_value = match global_value_result {
322        ObjectResult::MismatchedType => {
323            return Ok(NativeResult::err(context.gas_used(), E_FIELD_TYPE_MISMATCH));
324        }
325        ObjectResult::Loaded(gv) => gv,
326    };
327    if !global_value.exists()? {
328        return Ok(NativeResult::err(context.gas_used(), E_KEY_DOES_NOT_EXIST));
329    }
330    let child_ref = global_value.borrow_global().inspect_err(|err| {
331        assert!(err.major_status() != StatusCode::MISSING_DATA);
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.legacy_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
353/// type or throws `E_FIELD_TYPE_MISMATCH` if the type does not match
354/// Implementation of the Move native function `remove_child_object<Child:
355/// key>(parent: address, id: address): Child`   gas cost:
356/// dynamic_field_remove_child_object_cost_base                    | covers
357/// various fixed costs in the oper
358///              + dynamic_field_remove_child_object_type_cost_per_byte *
359///                size_of(Child)      | covers cost of operating on type
360///                `Child`
361///              + dynamic_field_remove_child_object_child_cost_per_byte  *
362///                size_of(child)     | covers cost of fetching and returning
363///                value of type `Child`
364/// ****************************************************************************
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    assert!(ty_args.len() == 1);
373    assert!(args.len() == 2);
374
375    let dynamic_field_remove_child_object_cost_params = context
376        .extensions_mut()
377        .get::<NativesCostTable>()
378        .dynamic_field_remove_child_object_cost_params
379        .clone();
380    native_charge_gas_early_exit!(
381        context,
382        dynamic_field_remove_child_object_cost_params.dynamic_field_remove_child_object_cost_base
383    );
384
385    let child_id = pop_arg!(args, AccountAddress).into();
386    let parent = pop_arg!(args, AccountAddress).into();
387    assert!(args.is_empty());
388    let global_value_result = get_or_fetch_object!(
389        context,
390        ty_args,
391        parent,
392        child_id,
393        dynamic_field_remove_child_object_cost_params
394            .dynamic_field_remove_child_object_type_cost_per_byte
395    );
396    let global_value = match global_value_result {
397        ObjectResult::MismatchedType => {
398            return Ok(NativeResult::err(context.gas_used(), E_FIELD_TYPE_MISMATCH));
399        }
400        ObjectResult::Loaded(gv) => gv,
401    };
402    if !global_value.exists()? {
403        return Ok(NativeResult::err(context.gas_used(), E_KEY_DOES_NOT_EXIST));
404    }
405    let child = global_value.move_from().inspect_err(|err| {
406        assert!(err.major_status() != StatusCode::MISSING_DATA);
407    })?;
408
409    native_charge_gas_early_exit!(
410        context,
411        dynamic_field_remove_child_object_cost_params
412            .dynamic_field_remove_child_object_child_cost_per_byte
413            * u64::from(child.legacy_size()).into()
414    );
415
416    Ok(NativeResult::ok(context.gas_used(), smallvec![child]))
417}
418
419#[derive(Clone)]
420pub struct DynamicFieldHasChildObjectCostParams {
421    // All inputs are constant same size. No need for special costing as this is a lookup
422    pub dynamic_field_has_child_object_cost_base: InternalGas,
423}
424/// ****************************************************************************
425/// ********************* native fun has_child_object
426/// Implementation of the Move native function `has_child_object(parent:
427/// address, id: address): bool`   gas cost:
428/// dynamic_field_has_child_object_cost_base                    | covers various
429/// fixed costs in the oper ****************************************************
430/// *******************************************
431#[instrument(level = "trace", skip_all)]
432pub fn has_child_object(
433    context: &mut NativeContext,
434    ty_args: Vec<Type>,
435    mut args: VecDeque<Value>,
436) -> PartialVMResult<NativeResult> {
437    assert!(ty_args.is_empty());
438    assert!(args.len() == 2);
439
440    let dynamic_field_has_child_object_cost_params = context
441        .extensions_mut()
442        .get::<NativesCostTable>()
443        .dynamic_field_has_child_object_cost_params
444        .clone();
445    native_charge_gas_early_exit!(
446        context,
447        dynamic_field_has_child_object_cost_params.dynamic_field_has_child_object_cost_base
448    );
449
450    let child_id = pop_arg!(args, AccountAddress).into();
451    let parent = pop_arg!(args, AccountAddress).into();
452    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut();
453    let has_child = object_runtime.child_object_exists(parent, child_id)?;
454    Ok(NativeResult::ok(
455        context.gas_used(),
456        smallvec![Value::bool(has_child)],
457    ))
458}
459
460#[derive(Clone)]
461pub struct DynamicFieldHasChildObjectWithTyCostParams {
462    pub dynamic_field_has_child_object_with_ty_cost_base: InternalGas,
463    pub dynamic_field_has_child_object_with_ty_type_cost_per_byte: InternalGas,
464    pub dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: InternalGas,
465}
466/// ****************************************************************************
467/// ********************* native fun has_child_object_with_ty
468/// Implementation of the Move native function `has_child_object_with_ty<Child:
469/// key>(parent: address, id: address): bool`   gas cost:
470/// dynamic_field_has_child_object_with_ty_cost_base               | covers
471/// various fixed costs in the oper
472///              + dynamic_field_has_child_object_with_ty_type_cost_per_byte *
473///                size_of(Child)        | covers cost of operating on type
474///                `Child`
475///              + dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte
476///                * size_of(Child)    | covers cost of fetching and returning
477///                value of type tag for `Child`
478/// ****************************************************************************
479/// *******************
480#[instrument(level = "trace", skip_all)]
481pub fn has_child_object_with_ty(
482    context: &mut NativeContext,
483    mut ty_args: Vec<Type>,
484    mut args: VecDeque<Value>,
485) -> PartialVMResult<NativeResult> {
486    assert!(ty_args.len() == 1);
487    assert!(args.len() == 2);
488
489    let dynamic_field_has_child_object_with_ty_cost_params = context
490        .extensions_mut()
491        .get::<NativesCostTable>()
492        .dynamic_field_has_child_object_with_ty_cost_params
493        .clone();
494    native_charge_gas_early_exit!(
495        context,
496        dynamic_field_has_child_object_with_ty_cost_params
497            .dynamic_field_has_child_object_with_ty_cost_base
498    );
499
500    let child_id = pop_arg!(args, AccountAddress).into();
501    let parent = pop_arg!(args, AccountAddress).into();
502    assert!(args.is_empty());
503    let ty = ty_args.pop().unwrap();
504
505    native_charge_gas_early_exit!(
506        context,
507        dynamic_field_has_child_object_with_ty_cost_params
508            .dynamic_field_has_child_object_with_ty_type_cost_per_byte
509            * u64::from(ty.size()).into()
510    );
511
512    let tag: StructTag = match context.type_to_type_tag(&ty)? {
513        TypeTag::Struct(s) => *s,
514        _ => {
515            return Err(
516                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
517                    .with_message("IOTA verifier guarantees this is a struct".to_string()),
518            );
519        }
520    };
521
522    native_charge_gas_early_exit!(
523        context,
524        dynamic_field_has_child_object_with_ty_cost_params
525            .dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte
526            * u64::from(tag.abstract_size_for_gas_metering()).into()
527    );
528
529    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut();
530    let has_child = object_runtime.child_object_exists_and_has_type(
531        parent,
532        child_id,
533        &MoveObjectType::from(tag),
534    )?;
535    Ok(NativeResult::ok(
536        context.gas_used(),
537        smallvec![Value::bool(has_child)],
538    ))
539}