Skip to main content

iota_move_natives_latest/
config.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_sdk_types::{ObjectId, StructTag};
8use iota_types::iota_sdk_types_conversions::struct_tag_core_to_sdk;
9use move_binary_format::errors::{PartialVMError, PartialVMResult};
10use move_core_types::{
11    account_address::AccountAddress, gas_algebra::InternalGas, runtime_value as R,
12    vm_status::StatusCode,
13};
14use move_vm_runtime::{native_charge_gas_early_exit, native_functions::NativeContext};
15use move_vm_types::{
16    loaded_data::runtime_types::Type,
17    natives::function::NativeResult,
18    pop_arg,
19    values::{Struct, Value, Vector},
20};
21use smallvec::smallvec;
22use tracing::{error, instrument};
23
24use crate::{NativesCostTable, object_runtime::ObjectRuntime};
25
26const E_BCS_SERIALIZATION_FAILURE: u64 = 2;
27
28#[derive(Clone)]
29pub struct ConfigReadSettingImplCostParams {
30    pub config_read_setting_impl_cost_base: Option<InternalGas>,
31    pub config_read_setting_impl_cost_per_byte: Option<InternalGas>,
32}
33
34#[instrument(level = "trace", skip_all)]
35pub fn read_setting_impl(
36    context: &mut NativeContext,
37    mut ty_args: Vec<Type>,
38    mut args: VecDeque<Value>,
39) -> PartialVMResult<NativeResult> {
40    assert_eq!(ty_args.len(), 4);
41    assert_eq!(args.len(), 3);
42
43    let ConfigReadSettingImplCostParams {
44        config_read_setting_impl_cost_base,
45        config_read_setting_impl_cost_per_byte,
46    } = context
47        .extensions_mut()
48        .get::<NativesCostTable>()?
49        .config_read_setting_impl_cost_params
50        .clone();
51
52    let config_read_setting_impl_cost_base =
53        config_read_setting_impl_cost_base.ok_or_else(|| {
54            PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
55                .with_message("gas cost is not set".to_string())
56        })?;
57    let config_read_setting_impl_cost_per_byte = config_read_setting_impl_cost_per_byte
58        .ok_or_else(|| {
59            PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
60                .with_message("gas cost is not set".to_string())
61        })?;
62    // Charge base fee
63    native_charge_gas_early_exit!(context, config_read_setting_impl_cost_base);
64
65    let value_ty = ty_args.pop().unwrap();
66    let setting_data_value_ty = ty_args.pop().unwrap();
67    let setting_value_ty = ty_args.pop().unwrap();
68    let field_setting_ty = ty_args.pop().unwrap();
69
70    let current_epoch = pop_arg!(args, u64);
71    let name_df_addr = pop_arg!(args, AccountAddress);
72    let config_addr = pop_arg!(args, AccountAddress);
73
74    let field_setting_tag: StructTag = match context.type_to_type_tag(&field_setting_ty)? {
75        move_core_types::language_storage::TypeTag::Struct(s) => struct_tag_core_to_sdk(&s),
76        _ => {
77            return Err(
78                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
79                    .with_message("IOTA verifier guarantees this is a struct".to_string()),
80            );
81        }
82    };
83    let Some(field_setting_layout) = context.type_to_type_layout(&field_setting_ty)? else {
84        return Ok(NativeResult::err(
85            context.gas_used(),
86            E_BCS_SERIALIZATION_FAILURE,
87        ));
88    };
89    let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut()?;
90
91    let read_value_opt = consistent_value_before_current_epoch(
92        object_runtime,
93        &field_setting_ty,
94        field_setting_tag,
95        &field_setting_layout,
96        &setting_value_ty,
97        &setting_data_value_ty,
98        &value_ty,
99        config_addr,
100        name_df_addr,
101        current_epoch,
102    )?;
103
104    native_charge_gas_early_exit!(
105        context,
106        config_read_setting_impl_cost_per_byte * u64::from(read_value_opt.legacy_size()).into()
107    );
108
109    Ok(NativeResult::ok(
110        context.gas_used(),
111        smallvec![read_value_opt],
112    ))
113}
114
115fn consistent_value_before_current_epoch(
116    object_runtime: &mut ObjectRuntime,
117    field_setting_ty: &Type,
118    field_setting_tag: StructTag,
119    field_setting_layout: &R::MoveTypeLayout,
120    _setting_value_ty: &Type,
121    setting_data_value_ty: &Type,
122    value_ty: &Type,
123    config_addr: AccountAddress,
124    name_df_addr: AccountAddress,
125    current_epoch: u64,
126) -> PartialVMResult<Value> {
127    let Some(field) = object_runtime.config_setting_unsequenced_read(
128        ObjectId::new(config_addr.into_bytes()),
129        ObjectId::new(name_df_addr.into_bytes()),
130        field_setting_ty,
131        field_setting_layout,
132        &field_setting_tag,
133    ) else {
134        return option_none(value_ty);
135    };
136
137    let [_id, _name, setting]: [Value; 3] = unpack_struct(field)?;
138    let [data_opt]: [Value; 1] = unpack_struct(setting)?;
139    let data = match unpack_option(data_opt, setting_data_value_ty)? {
140        None => {
141            error!(
142                "
143                SettingData is none.
144                config_addr: {config_addr},
145                name_df_addr: {name_df_addr},
146                field_setting_tag: {field_setting_tag:?}",
147            );
148            return option_none(value_ty);
149        }
150        Some(data) => data,
151    };
152    let [newer_value_epoch, newer_value, older_value_opt]: [Value; 3] = unpack_struct(data)?;
153    let newer_value_epoch: u64 = newer_value_epoch.value_as()?;
154    debug_assert!(
155        unpack_option(newer_value.copy_value()?, value_ty)?.is_some()
156            || unpack_option(older_value_opt.copy_value()?, value_ty)?.is_some()
157    );
158    Ok(if current_epoch > newer_value_epoch {
159        newer_value
160    } else {
161        older_value_opt
162    })
163}
164
165fn unpack_struct<const N: usize>(s: Value) -> PartialVMResult<[Value; N]> {
166    let s: Struct = s.value_as()?;
167    s.unpack()?.collect::<Vec<_>>().try_into().map_err(|e| {
168        PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
169            .with_message(format!("struct expected to have have {N} fields: {e:?}"))
170    })
171}
172
173fn unpack_option(option: Value, type_param: &Type) -> PartialVMResult<Option<Value>> {
174    let [vec_value]: [Value; 1] = unpack_struct(option)?;
175    let vec: Vector = vec_value.value_as()?;
176    Ok(if vec.elem_len() == 0 {
177        None
178    } else {
179        let [elem]: [Value; 1] = vec.unpack(type_param, 1)?.try_into().map_err(|e| {
180            PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
181                .with_message(format!("vector expected to have one element: {e:?}"))
182        })?;
183        Some(elem)
184    })
185}
186
187fn option_none(type_param: &Type) -> PartialVMResult<Value> {
188    Ok(Value::struct_(Struct::pack(vec![Vector::empty(
189        type_param.try_into()?,
190    )?])))
191}