CVE-2023-36184 (published 2023-09-08T02:15Z)

CMysten Labs Sui blockchain v1.2.0 was discovered to contain a stack overflow via the component /spec/openrpc.json.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-787
cwe_name: Out-of-bounds Write
description: The product writes data past the end, or before the beginning, of the intended buffer.
extended_description: 
            
            
             
			 
         
         

Fix information:
repo_url: https://github.com/aptos-labs/aptos-core
commit_details: repo_url: https://github.com/aptos-labs/aptos-core
author: Victor Gao
author_date: 2023-06-02 10:00:14-07:00
msg: cherry-pick: improved value depth checks in the VM (#8488)
repository: repo_url: https://github.com/aptos-labs/aptos-core
repo_name: aptos-labs/aptos-core
description: Aptos is a layer 1 blockchain built to support the widespread use of blockchain through better technology and user experience.
repo_language: Rust
owner: aptos-labs
file_changes: filename: vm.rs
change_type: ModificationType.MODIFY
diff: @@ -67,6 +67,7 @@ impl MoveVmExt {
                     paranoid_type_checks: crate::AptosVM::get_paranoid_checks(),
                     enable_invariant_violation_check_in_swap_loc,
                     type_size_limit,
+                    max_value_nest_depth: Some(128),
                 },
             )?,
             chain_id,

programming_language: Rust
filename: bcs_tests.move
change_type: ModificationType.MODIFY
diff: @@ -79,9 +79,11 @@ module std::bcs_tests {
         bcs::to_bytes(&box127(true));
     }
 
+    /* Deactivated because we now limit the depth of values you could create inside the VM
     #[test]
     #[expected_failure(abort_code = 453, location = std::bcs)]
     fun encode_129() {
         bcs::to_bytes(&Box { x: box127(true) });
     }
+    */
 }

programming_language: Rust
filename: event_tests.move
change_type: ModificationType.MODIFY
diff: @@ -73,7 +73,7 @@ module std::event_tests {
     }
 
     #[test(s = @0x42)]
-    #[expected_failure(abort_code = 0, location = std::event)]
+    #[expected_failure] // VM_MAX_VALUE_DEPTH_REACHED
     fun test_event_129(s: signer) acquires MyEvent {
         event_129(&s);
     }

programming_language: Rust
filename: bcs_tests.move
change_type: ModificationType.MODIFY
diff: @@ -96,7 +96,7 @@ module std::bcs_tests {
     }
 
     #[test]
-    #[expected_failure(abort_code = 453, location = std::bcs)]
+    #[expected_failure] // VM_MAX_VALUE_DEPTH_REACHED
     fun encode_129() {
         bcs::to_bytes(&Box { x: box127(true) });
     }

programming_language: Rust
filename: config.rs
change_type: ModificationType.MODIFY
diff: @@ -4,6 +4,8 @@
 use move_binary_format::file_format_common::VERSION_MAX;
 use move_bytecode_verifier::VerifierConfig;
 
+pub const DEFAULT_MAX_VALUE_NEST_DEPTH: u64 = 128;
+
 /// Dynamic config options for the Move VM.
 pub struct VMConfig {
     pub verifier: VerifierConfig,
@@ -14,6 +16,8 @@ pub struct VMConfig {
     // When this flag is set to true, MoveVM will check invariant violation in swap_loc
     pub enable_invariant_violation_check_in_swap_loc: bool,
     pub type_size_limit: bool,
+    /// Maximum value nest depth for structs
+    pub max_value_nest_depth: Option<u64>,
 }
 
 impl Default for VMConfig {
@@ -24,6 +28,7 @@ impl Default for VMConfig {
             paranoid_type_checks: false,
             enable_invariant_violation_check_in_swap_loc: true,
             type_size_limit: false,
+            max_value_nest_depth: Some(DEFAULT_MAX_VALUE_NEST_DEPTH),
         }
     }
 }

programming_language: Rust
filename: interpreter.rs
change_type: ModificationType.MODIFY
diff: @@ -1006,6 +1006,91 @@ impl CallStack {
     }
 }
 
+fn check_depth_of_type(resolver: &Resolver, ty: &Type) -> PartialVMResult<()> {
+    // Start at 1 since we always call this right before we add a new node to the value's depth.
+    let max_depth = match resolver.loader().vm_config().max_value_nest_depth {
+        Some(max_depth) => max_depth,
+        None => return Ok(()),
+    };
+    check_depth_of_type_impl(resolver, ty, max_depth, 1)?;
+    Ok(())
+}
+
+fn check_depth_of_type_impl(
+    resolver: &Resolver,
+    ty: &Type,
+    max_depth: u64,
+    depth: u64,
+) -> PartialVMResult<u64> {
+    macro_rules! check_depth {
+        ($additional_depth:expr) => {{
+            let new_depth = depth.saturating_add($additional_depth);
+            if new_depth > max_depth {
+                return Err(PartialVMError::new(StatusCode::VM_MAX_VALUE_DEPTH_REACHED));
+            } else {
+                new_depth
+            }
+        }};
+    }
+
+    // Calculate depth of the type itself
+    let ty_depth = match ty {
+        Type::Bool
+        | Type::U8
+        | Type::U16
+        | Type::U32
+        | Type::U64
+        | Type::U128
+        | Type::U256
+        | Type::Address
+        | Type::Signer => check_depth!(0),
+        // Even though this is recursive this is OK since the depth of this recursion is
+        // bounded by the depth of the type arguments, which we have already checked.
+        Type::Reference(ty) | Type::MutableReference(ty) | Type::Vector(ty) => {
+            check_depth_of_type_impl(resolver, ty, max_depth, check_depth!(1))?
+        },
+        Type::Struct(si) => {
+            let struct_type = resolver.loader().get_struct_type(*si).ok_or_else(|| {
+                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
+                    .with_message("Struct Definition not resolved".to_string())
+            })?;
+            check_depth!(struct_type
+                .depth
+                .as_ref()
+                .ok_or_else(|| { PartialVMError::new(StatusCode::VM_MAX_VALUE_DEPTH_REACHED) })?
+                .solve(&[]))
+        },
+        // NB: substitution must be performed before calling this function
+        Type::StructInstantiation(si, ty_args) => {
+            // Calculate depth of all type arguments, and make sure they themselves are not too deep.
+            let ty_arg_depths = ty_args
+                .iter()
+                .map(|ty| {
+                    // Ty args should be fully resolved and not need any type arguments
+                    check_depth_of_type_impl(resolver, ty, max_depth, check_depth!(0))
+                })
+                .collect::<PartialVMResult<Vec<_>>>()?;
+            let struct_type = resolver.loader().get_struct_type(*si).ok_or_else(|| {
+                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
+                    .with_message("Struct Definition not resolved".to_string())
+            })?;
+            check_depth!(struct_type
+                .depth
+                .as_ref()
+                .ok_or_else(|| { PartialVMError::new(StatusCode::VM_MAX_VALUE_DEPTH_REACHED) })?
+                .solve(&ty_arg_depths))
+        },
+        Type::TyParam(_) => {
+            return Err(
+                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
+                    .with_message("Type parameter should be fully resolved".to_string()),
+            )
+        },
+    };
+
+    Ok(ty_depth)
+}
+
 /// A `Frame` is the execution context for a function. It holds the locals of the function and
 /// the function itself.
 // #[derive(Debug)]
@@ -1870,6 +1955,8 @@ impl Frame {
                     },
                     Bytecode::Pack(sd_idx) => {
                         let field_count = resolver.field_count(*sd_idx);
+                        let struct_type = resolver.get_struct_type(*sd_idx);
+                        check_depth_of_type(resolver, &struct_type)?;
                         gas_meter.charge_pack(
                             false,
                             interpreter.operand_stack.last_n(field_count as usize)?,
@@ -1881,6 +1968,8 @@ impl Frame {
                     },
                     Bytecode::PackGeneric(si_idx) => {
                         let field_count = resolver.field_instantiation_count(*si_idx);
+                        let ty = resolver.instantiate_generic_type(*si_idx, self.ty_args())?;
+                        check_depth_of_type(resolver, &ty)?;
                         gas_meter.charge_pack(
                             true,
                             interpreter.operand_stack.last_n(field_count as usize)?,
@@ -2197,6 +2286,7 @@ impl Frame {
                     },
                     Bytecode::VecPack(si, num) => {
                         let ty = resolver.instantiate_single_type(*si, self.ty_args())?;
+                        check_depth_of_type(resolver, &ty)?;
                         gas_meter.charge_vec_pack(
                             make_ty!(&ty),
                             interpreter.operand_stack.last_n(*num as usize)?,

programming_language: Rust
filename: loader.rs
change_type: ModificationType.MODIFY
diff: @@ -17,7 +17,7 @@ use move_binary_format::{
         FieldHandleIndex, FieldInstantiationIndex, FunctionDefinition, FunctionDefinitionIndex,
         FunctionHandleIndex, FunctionInstantiationIndex, Signature, SignatureIndex, SignatureToken,
         StructDefInstantiationIndex, StructDefinition, StructDefinitionIndex,
-        StructFieldInformation, TableIndex, Visibility,
+        StructFieldInformation, TableIndex, TypeParameterIndex, Visibility,
     },
     IndexKind,
 };
@@ -31,7 +31,7 @@ use move_core_types::{
 };
 use move_vm_types::{
     data_store::DataStore,
-    loaded_data::runtime_types::{CachedStructIndex, StructType, Type},
+    loaded_data::runtime_types::{CachedStructIndex, DepthFormula, StructType, Type},
 };
 use parking_lot::RwLock;
 use sha3::{Digest, Sha3_256};
@@ -203,6 +203,30 @@ impl ModuleCache {
             self.structs.truncate(starting_idx);
             err.finish(Location::Undefined)
         })?;
+
+        let struct_defs_len = module.struct_defs.len();
+
+        let mut depth_cache = BTreeMap::new();
+
+        for cached_idx in starting_idx..(starting_idx + struct_defs_len) {
+            self.calculate_depth_of_struct(CachedStructIndex(cached_idx), &mut depth_cache)
+                .map_err(|err| err.finish(Location::Undefined))?;
+        }
+        debug_assert!(depth_cache.len() == struct_defs_len);
+        for (cache_idx, depth) in depth_cache {
+            match Arc::get_mut(self.structs.get_mut(cache_idx.0).unwrap()) {
+                Some(struct_type) => struct_type.depth = Some(depth),
+                None => {
+                    // we have pending references to the `Arc` which is impossible,
+                    // given the code that adds the `Arc` is above and no reference to
+                    // it should exist.
+                    // So in the spirit of not crashing we just leave it as None and
+                    // log the issue.
+                    error!("Arc<StructType> cannot have any live reference while publishing");
+                },
+            }
+        }
+
         for (idx, func) in module.function_defs().iter().enumerate() {
             let findex = FunctionDefinitionIndex(idx as TableIndex);
             let mut function = Function::new(natives, findex, func, module);
@@ -258,6 +282,7 @@ impl ModuleCache {
             name,
             module,
             struct_def: idx,
+            depth: None,
         }
     }
 
@@ -368,7 +393,7 @@ impl ModuleCache {
             SignatureToken::U256 => Type::U256,
             SignatureToken::Address => Type::Address,
             SignatureToken::Signer => Type::Signer,
-            SignatureToken::TypeParameter(idx) => Type::TyParam(*idx as usize),
+            SignatureToken::TypeParameter(idx) => Type::TyParam(*idx),
             SignatureToken::Vector(inner_tok) => {
                 let inner_type = Self::make_type_internal(module, inner_tok, resolver)?;
                 Type::Vector(Box::new(inner_type))
@@ -459,6 +484,81 @@ impl ModuleCache {
             ),
         }
     }
+
+    fn calculate_depth_of_struct(
+        &self,
+        def_idx: CachedStructIndex,
+        depth_cache: &mut BTreeMap<CachedStructIndex, DepthFormula>,
+    ) -> PartialVMResult<DepthFormula> {
+        let struct_type = &self.struct_at(def_idx);
+
+        // If we've already computed this structs depth, no more work remains to be done.
+        if let Some(form) = &struct_type.depth {
+            return Ok(form.clone());
+        }
+        if let Some(form) = depth_cache.get(&def_idx) {
+            return Ok(form.clone());
+        }
+
+        let formulas = struct_type
+            .fields
+            .iter()
+            .map(|field_type| self.calculate_depth_of_type(field_type, depth_cache))
+            .collect::<PartialVMResult<Vec<_>>>()?;
+        let formula = DepthFormula::normalize(formulas);
+        let prev = depth_cache.insert(def_idx, formula.clone());
+        if prev.is_some() {
+            return Err(
+                PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)
+                    .with_message("Recursive type?".to_owned()),
+            );
+        }
+        Ok(formula)
+    }
+
+    fn calculate_depth_of_type(
+        &self,
+        ty: &Type,
+        depth_cache: &mut BTreeMap<CachedStructIndex, DepthFormula>,
+    ) -> PartialVMResult<DepthFormula> {
+        Ok(match ty {
+            Type::Bool
+            | Type::U8
+            | Type::U64
+            | Type::U128
+            | Type::Address
+            | Type::Signer
+            | Type::U16
+            | Type::U32
+            | Type::U256 => DepthFormula::constant(1),
+            Type::Vector(ty) | Type::Reference(ty) | Type::MutableReference(ty) => {
+                let mut inner = self.calculate_depth_of_type(ty, depth_cache)?;
+                inner.scale(1);
+                inner
+            },
+            Type::TyParam(ty_idx) => DepthFormula::type_parameter(*ty_idx),
+            Type::Struct(cache_idx) => {
+                let mut struct_formula = self.calculate_depth_of_struct(*cache_idx, depth_cache)?;
+                debug_assert!(struct_formula.terms.is_empty());
+                struct_formula.scale(1);
+                struct_formula
+            },
+            Type::StructInstantiation(cache_idx, ty_args) => {
+                let ty_arg_map = ty_args
+                    .iter()
+                    .enumerate()
+                    .map(|(idx, ty)| {
+                        let var = idx as TypeParameterIndex;
+                        Ok((var, self.calculate_depth_of_type(ty, depth_cache)?))
+                    })
+                    .collect::<PartialVMResult<BTreeMap<_, _>>>()?;
+                let struct_formula = self.calculate_depth_of_struct(*cache_idx, depth_cache)?;
+                let mut subst_struct_formula = struct_formula.subst(ty_arg_map)?;
+                subst_struct_formula.scale(1);
+                subst_struct_formula
+            },
+        })
+    }
 }
 
 //
@@ -630,7 +730,7 @@ impl Loader {
             && type_arguments
                 .iter()
                 .map(|loaded_ty| self.count_type_nodes(loaded_ty))
-                .sum::<usize>()
+                .sum::<u64>()
                 > MAX_TYPE_INSTANTIATION_NODES
         {
             return Err(
@@ -757,7 +857,7 @@ impl Loader {
     fn match_return_type<'a>(
         returned: &Type,
         expected: &'a Type,
-        map: &mut BTreeMap<usize, &'a Type>,
+        map: &mut BTreeMap<u16, &'a Type>,
     ) -> bool {
         match (returned, expected) {
             // The important case, deduce the type params
@@ -851,7 +951,7 @@ impl Loader {
         let mut type_arguments = vec![];
         let type_param_len = func.type_parameters().len();
         for i in 0..type_param_len {
-            if let Option::Some(t) = map.get(&i) {
+            if let Option::Some(t) = map.get(&(i as u16)) {
                 type_arguments.push((*t).clone());
             } else {
                 // Unknown type argument we are not able to infer the type arguments.
@@ -1409,7 +1509,7 @@ impl Loader {
                 }
             },
             Type::StructInstantiation(_, struct_inst) => {
-                let mut sum_nodes: usize = 1;
+                let mut sum_nodes = 1u64;
                 for ty in ty_args.iter().chain(struct_inst.iter()) {
                     sum_nodes = sum_nodes.saturating_add(self.count_type_nodes(ty));
                     if sum_nodes > MAX_TYPE_INSTANTIATION_NODES {
@@ -1609,7 +1709,7 @@ impl<'a> Resolver<'a> {
         }
         // Check if the function instantiation over all generics is larger
         // than MAX_TYPE_INSTANTIATION_NODES.
-        let mut sum_nodes: usize = 1;
+        let mut sum_nodes = 1u64;
         for ty in type_params.iter().chain(instantiation.iter()) {
             sum_nodes = sum_nodes.saturating_add(self.loader.count_type_nodes(ty));
             if sum_nodes > MAX_TYPE_INSTANTIATION_NODES {
@@ -1653,8 +1753,8 @@ impl<'a> Resolver<'a> {
         // Before instantiating the type, count the # of nodes of all type arguments plus
         // existing type instantiation.
         // If that number is larger than MAX_TYPE_INSTANTIATION_NODES, refuse to construct this type.
-        // This prevents constructing larger and lager types via struct instantiation.
-        let mut sum_nodes: usize = 1;
+        // This prevents constructing larger and larger types via struct instantiation.
+        let mut sum_nodes = 1u64;
         for ty in ty_args.iter().chain(struct_inst.instantiation.iter()) {
             sum_nodes = sum_nodes.saturating_add(self.loader.count_type_nodes(ty));
             if sum_nodes > MAX_TYPE_INSTANTIATION_NODES {
@@ -2603,8 +2703,8 @@ struct StructInfo {
     struct_tag: Option<StructTag>,
     struct_layout: Option<MoveStructLayout>,
     annotated_struct_layout: Option<MoveStructLayout>,
-    node_count: Option<usize>,
-    annotated_node_count: Option<usize>,
+    node_count: Option<u64>,
+    annotated_node_count: Option<u64>,
 }
 
 impl StructInfo {
@@ -2632,15 +2732,15 @@ impl TypeCache {
 }
 
 /// Maximal depth of a value in terms of type depth.
-const VALUE_DEPTH_MAX: usize = 128;
+pub const VALUE_DEPTH_MAX: u64 = 128;
 
 /// Maximal nodes which are allowed when converting to layout. This includes the the types of
 /// fields for struct types.
-const MAX_TYPE_TO_LAYOUT_NODES: usize = 256;
+const MAX_TYPE_TO_LAYOUT_NODES: u64 = 256;
 
 /// Maximal nodes which are all allowed when instantiating a generic type. This does not include
 /// field types of structs.
-const MAX_TYPE_INSTANTIATION_NODES: usize = 128;
+const MAX_TYPE_INSTANTIATION_NODES: u64 = 128;
 
 impl Loader {
     fn struct_gidx_to_type_tag(
@@ -2707,7 +2807,7 @@ impl Loader {
         })
     }
 
-    fn count_type_nodes(&self, ty: &Type) -> usize {
+    fn count_type_nodes(&self, ty: &Type) -> u64 {
         let mut todo = vec![ty];
         let mut result = 0;
         while let Some(ty) = todo.pop() {
@@ -2732,8 +2832,8 @@ impl Loader {
         &self,
         gidx: CachedStructIndex,
         ty_args: &[Type],
-        count: &mut usize,
-        depth: usize,
+        count: &mut u64,
+        depth: u64,
     ) -> PartialVMResult<MoveStructLayout> {
         if let Some(struct_map) = self.type_cache.read().structs.get(&gidx) {
             if let Some(struct_info) = struct_map.get(ty_args) {
@@ -2777,8 +2877,8 @@ impl Loader {
     fn type_to_type_layout_impl(
         &self,
         ty: &Type,
-        count: &mut usize,
-        depth: usize,
+        count: &mut u64,
+        depth: u64,
     ) -> PartialVMResult<MoveTypeLayout> {
         if *count > MAX_TYPE_TO_LAYOUT_NODES {
             return Err(PartialVMError::new(StatusCode::TOO_MANY_TYPE_NODES));
@@ -2854,8 +2954,8 @@ impl Loader {
         &self,
         gidx: CachedStructIndex,
         ty_args: &[Type],
-        count: &mut usize,
-        depth: usize,
+        count: &mut u64,
+        depth: u64,
     ) -> PartialVMResult<MoveStructLayout> {
         if let Some(struct_map) = self.type_cache.read().structs.get(&gidx) {
             if let Some(struct_info) = struct_map.get(ty_args) {
@@ -2908,8 +3008,8 @@ impl Loader {
     fn type_to_fully_annotated_layout_impl(
         &self,
         ty: &Type,
-        count: &mut usize,
-        depth: usize,
+        count: &mut u64,
+        depth: u64,
     ) -> PartialVMResult<MoveTypeLayout> {
         if *count > MAX_TYPE_TO_LAYOUT_NODES {
             return Err(PartialVMError::new(StatusCode::TOO_MANY_TYPE_NODES));

programming_language: Rust
filename: runtime_layout_deeply_nested.exp
change_type: ModificationType.MODIFY
diff: @@ -6,14 +6,14 @@ Error: Script execution failed with VMError: {
     sub_status: None,
     location: 0x42::M,
     indices: [],
-    offsets: [(FunctionDefinitionIndex(8), 3)],
+    offsets: [(FunctionDefinitionIndex(0), 1)],
 }
 
-task 3 'run'. lines 89-97:
+task 3 'run'. lines 89-96:
 Error: Script execution failed with VMError: {
     major_status: VM_MAX_VALUE_DEPTH_REACHED,
     sub_status: None,
     location: 0x42::M,
     indices: [],
-    offsets: [(FunctionDefinitionIndex(9), 4)],
+    offsets: [(FunctionDefinitionIndex(0), 1)],
 }

programming_language: Rust
filename: runtime_layout_deeply_nested.mvir
change_type: ModificationType.MODIFY
diff: @@ -71,11 +71,11 @@ import 0x42.M;
 
 main(account: signer) {
 label b0:
+    // hits VM_MAX_VALUE_DEPTH_REACHED
     M.publish_128(&account);
     return;
 }
 
-
 //# run --args @0x2
 import 0x42.M;
 
@@ -91,7 +91,6 @@ import 0x42.M;
 
 main(account: signer) {
 label b0:
-    // hits VM_MAX_VALUE_DEPTH_REACHED
     M.publish_257(&account);
     return;
 }

programming_language: Rust
filename: runtime_types.rs
change_type: ModificationType.MODIFY
diff: @@ -4,15 +4,108 @@
 
 use move_binary_format::{
     errors::{PartialVMError, PartialVMResult},
-    file_format::{AbilitySet, SignatureToken, StructDefinitionIndex, StructTypeParameter},
+    file_format::{
+        AbilitySet, SignatureToken, StructDefinitionIndex, StructTypeParameter, TypeParameterIndex,
+    },
 };
 use move_core_types::{
     gas_algebra::AbstractMemorySize, identifier::Identifier, language_storage::ModuleId,
     vm_status::StatusCode,
 };
+use std::{cmp::max, collections::BTreeMap, fmt::Debug};
 
 pub const TYPE_DEPTH_MAX: usize = 256;
 
+#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)]
+/// A formula describing the value depth of a type, using (the depths of) the type parameters as inputs.
+///
+/// It has the form of `max(CBase, T1 + C1, T2 + C2, ..)` where `Ti` is the depth of the ith type parameter
+/// and `Ci` is just some constant.
+///
+/// This form has a special property: when you compute the max of multiple formulae, you can normalize
+/// them into a single formula.
+pub struct DepthFormula {
+    pub terms: Vec<(TypeParameterIndex, u64)>, // Ti + Ci
+    pub constant: Option<u64>,                 // Cbase
+}
+
+impl DepthFormula {
+    pub fn constant(constant: u64) -> Self {
+        Self {
+            terms: vec![],
+            constant: Some(constant),
+        }
+    }
+
+    pub fn type_parameter(tparam: TypeParameterIndex) -> Self {
+        Self {
+            terms: vec![(tparam, 0)],
+            constant: None,
+        }
+    }
+
+    pub fn normalize(formulas: Vec<Self>) -> Self {
+        let mut var_map = BTreeMap::new();
+        let mut constant_acc = None;
+        for formula in formulas {
+            let Self { terms, constant } = formula;
+            for (var, cur_factor) in terms {
+                var_map
+                    .entry(var)
+                    .and_modify(|prev_factor| *prev_factor = max(cur_factor, *prev_factor))
+                    .or_insert(cur_factor);
+            }
+            match (constant_acc, constant) {
+                (_, None) => (),
+                (None, Some(_)) => constant_acc = constant,
+                (Some(c1), Some(c2)) => constant_acc = Some(max(c1, c2)),
+            }
+        }
+        Self {
+            terms: var_map.into_iter().collect(),
+            constant: constant_acc,
+        }
+    }
+
+    pub fn subst(
+        &self,
+        mut map: BTreeMap<TypeParameterIndex, DepthFormula>,
+    ) -> PartialVMResult<DepthFormula> {
+        let Self { terms, constant } = self;
+        let mut formulas = vec![];
+        if let Some(constant) = constant {
+            formulas.push(DepthFormula::constant(*constant))
+        }
+        for (t_i, c_i) in terms {
+            let Some(mut u_form) = map.remove(t_i) else {
+                return Err(PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message(format!("{t_i:?} missing mapping")))
+            };
+            u_form.scale(*c_i);
+            formulas.push(u_form)
+        }
+        Ok(DepthFormula::normalize(formulas))
+    }
+
+    pub fn solve(&self, tparam_depths: &[u64]) -> u64 {
+        let Self { terms, constant } = self;
+        let mut depth = constant.as_ref().copied().unwrap_or(0);
+        for (t_i, c_i) in terms {
+            depth = max(depth, tparam_depths[*t_i as usize].saturating_add(*c_i))
+        }
+        depth
+    }
+
+    pub fn scale(&mut self, c: u64) {
+        let Self { terms, constant } = self;
+        for (_t_i, c_i) in terms {
+            *c_i = (*c_i).saturating_add(c);
+        }
+        if let Some(cbase) = constant.as_mut() {
+            *cbase = (*cbase).saturating_add(c);
+        }
+    }
+}
+
 #[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
 pub struct StructType {
     pub fields: Vec<Type>,
@@ -22,6 +115,7 @@ pub struct StructType {
     pub name: Identifier,
     pub module: ModuleId,
     pub struct_def: StructDefinitionIndex,
+    pub depth: Option<DepthFormula>,
 }
 
 impl StructType {
@@ -46,7 +140,7 @@ pub enum Type {
     StructInstantiation(CachedStructIndex, Vec<Type>),
     Reference(Box<Type>),
     MutableReference(Box<Type>),
-    TyParam(usize),
+    TyParam(u16),
     U16,
     U32,
     U256,
@@ -62,7 +156,7 @@ impl Type {
 
     fn apply_subst<F>(&self, subst: F, depth: usize) -> PartialVMResult<Type>
     where
-        F: Fn(usize, usize) -> PartialVMResult<Type> + Copy,
+        F: Fn(u16, usize) -> PartialVMResult<Type> + Copy,
     {
         if depth > TYPE_DEPTH_MAX {
             return Err(PartialVMError::new(StatusCode::VM_MAX_TYPE_DEPTH_REACHED));
@@ -97,7 +191,7 @@ impl Type {
 
     pub fn subst(&self, ty_args: &[Type]) -> PartialVMResult<Type> {
         self.apply_subst(
-            |idx, depth| match ty_args.get(idx) {
+            |idx, depth| match ty_args.get(idx as usize) {
                 Some(ty) => ty.clone_impl(depth),
                 None => Err(
                     PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR)

programming_language: Rust
filename: timed_features.rs
change_type: ModificationType.MODIFY
diff: @@ -91,6 +91,7 @@ impl TimedFeatureFlag {
 
             (EntryTypeSizeLimit, TESTNET) => 1680231600000, /* Thu Mar 30 2023 20:00:00 GMT-0700 */
             (EntryTypeSizeLimit, MAINNET) => 1680318000000, /* Fri Mar 31 2023 20:00:00 GMT-0700 */
+
             // If unspecified, a timed feature is considered enabled from the very beginning of time.
             _ => 0,
         }

programming_language: Rust