sui_move_natives_latest/
config.rs

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