CVE-2023-36184: Out-of-bounds Write (CWE-787)
value: CMysten Labs Sui blockchain v1.2.0 was discovered to contain a stack overflow via the component /spec/openrpc.json.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 7.5
Published: 2023-09-08T02:15Z
Repository: https://github.com/aptos-labs/aptos-core
Language: Rust
Files changed: aptos-move/aptos-vm/src/move_vm_ext/vm.rs, aptos-move/framework/move-stdlib/tests/bcs_tests.move, third_party/move/move-stdlib/nursery/tests/event_tests.move, third_party/move/move-stdlib/tests/bcs_tests.move, third_party/move/move-vm/runtime/src/config.rs, third_party/move/move-vm/runtime/src/interpreter.rs, third_party/move/move-vm/runtime/src/loader.rs, third_party/move/move-vm/transactional-tests/tests/recursion/runtime_layout_deeply_nested.exp, third_party/move/move-vm/transactional-tests/tests/recursion/runtime_layout_deeply_nested.mvir, third_party/move/move-vm/types/src/loaded_data/runtime_types.rs, types/src/on_chain_config/timed_features.rs
Commit date: 2023-06-02 10:00:14 -0700
Commit message: cherry-pick: improved value depth checks in the VM (#8488)


Diff: diff --git a/aptos-move/aptos-vm/src/move_vm_ext/vm.rs b/aptos-move/aptos-vm/src/move_vm_ext/vm.rs
index 337ba645c3..c9244c32a0 100644
--- a/aptos-move/aptos-vm/src/move_vm_ext/vm.rs
+++ b/aptos-move/aptos-vm/src/move_vm_ext/vm.rs
@@ -65,10 +65,11 @@ impl MoveVmExt {
                     verifier: verifier_config,
                     max_binary_format_version,
                     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,
         })
     }
diff --git a/aptos-move/framework/move-stdlib/tests/bcs_tests.move b/aptos-move/framework/move-stdlib/tests/bcs_tests.move
index 09266136ea..72437ebb00 100644
--- a/aptos-move/framework/move-stdlib/tests/bcs_tests.move
+++ b/aptos-move/framework/move-stdlib/tests/bcs_tests.move
@@ -77,11 +77,13 @@ module std::bcs_tests {
     #[test]
     fun encode_128() {
         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) });
     }
+    */
 }
diff --git a/third_party/move/move-stdlib/nursery/tests/event_tests.move b/third_party/move/move-stdlib/nursery/tests/event_tests.move
index df5a9e3e05..064ebb7877 100644
--- a/third_party/move/move-stdlib/nursery/tests/event_tests.move
+++ b/third_party/move/move-stdlib/nursery/tests/event_tests.move
@@ -71,11 +71,11 @@ module std::event_tests {
     fun test_event_128(s: signer) acquires MyEvent {
         event_128(&s);
     }
 
     #[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);
     }
 
     // More detailed version of the above--test BCS compatibility between the old event
diff --git a/third_party/move/move-stdlib/tests/bcs_tests.move b/third_party/move/move-stdlib/tests/bcs_tests.move
index 9564f5a1c5..cf16ce1113 100644
--- a/third_party/move/move-stdlib/tests/bcs_tests.move
+++ b/third_party/move/move-stdlib/tests/bcs_tests.move
@@ -94,10 +94,10 @@ module std::bcs_tests {
     fun encode_128() {
         bcs::to_bytes(&box127(true));
     }
 
     #[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) });
     }
 }
diff --git a/third_party/move/move-vm/runtime/src/config.rs b/third_party/move/move-vm/runtime/src/config.rs
index faa54c80c7..5c23b87133 100644
--- a/third_party/move/move-vm/runtime/src/config.rs
+++ b/third_party/move/move-vm/runtime/src/config.rs
@@ -2,30 +2,35 @@
 // SPDX-License-Identifier: Apache-2.0
 
 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,
     pub max_binary_format_version: u32,
     // When this flag is set to true, MoveVM will perform type check at every instruction
     // execution to ensure that type safety cannot be violated at runtime.
     pub paranoid_type_checks: bool,
     // 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 {
     fn default() -> Self {
         Self {
             verifier: VerifierConfig::default(),
             max_binary_format_version: VERSION_MAX,
             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),
         }
     }
 }
 
 impl VMConfig {
diff --git a/third_party/move/move-vm/runtime/src/interpreter.rs b/third_party/move/move-vm/runtime/src/interpreter.rs
index 21e79707ad..fb497a3646 100644
--- a/third_party/move/move-vm/runtime/src/interpreter.rs
+++ b/third_party/move/move-vm/runtime/src/interpreter.rs
@@ -1004,10 +1004,95 @@ impl CallStack {
         let location_opt = self.0.last().map(|frame| frame.location());
         location_opt.unwrap_or(Location::Undefined)
     }
 }
 
+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)]
 struct Frame {
     pc: u16,
@@ -1868,10 +1953,12 @@ impl Frame {
                         let field_ref = reference.borrow_field(offset)?;
                         interpreter.operand_stack.push(field_ref)?;
                     },
                     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)?,
                         )?;
                         let args = interpreter.operand_stack.popn(field_count)?;
@@ -1879,10 +1966,12 @@ impl Frame {
                             .operand_stack
                             .push(Value::struct_(Struct::pack(args)))?;
                     },
                     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)?,
                         )?;
                         let args = interpreter.operand_stack.popn(field_count)?;
@@ -2195,10 +2284,11 @@ impl Frame {
                     Bytecode::Nop => {
                         gas_meter.charge_simple_instr(S::Nop)?;
                     },
                     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)?,
                         )?;
                         let elements = interpreter.operand_stack.popn(*num as u16)?;
diff --git a/third_party/move/move-vm/runtime/src/loader.rs b/third_party/move/move-vm/runtime/src/loader.rs
index fa6dc5744a..da194d7055 100644
--- a/third_party/move/move-vm/runtime/src/loader.rs
+++ b/third_party/move/move-vm/runtime/src/loader.rs
@@ -15,11 +15,11 @@ use move_binary_format::{
     file_format::{
         AbilitySet, Bytecode, CompiledModule, CompiledScript, Constant, ConstantPoolIndex,
         FieldHandleIndex, FieldInstantiationIndex, FunctionDefinition, FunctionDefinitionIndex,
         FunctionHandleIndex, FunctionInstantiationIndex, Signature, SignatureIndex, SignatureToken,
         StructDefInstantiationIndex, StructDefinition, StructDefinitionIndex,
-        StructFieldInformation, TableIndex, Visibility,
+        StructFieldInformation, TableIndex, TypeParameterIndex, Visibility,
     },
     IndexKind,
 };
 use move_bytecode_verifier::{self, cyclic_dependencies, dependencies};
 use move_core_types::{
@@ -29,11 +29,11 @@ use move_core_types::{
     value::{MoveFieldLayout, MoveStructLayout, MoveTypeLayout},
     vm_status::StatusCode,
 };
 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};
 use std::{
     collections::{btree_map, BTreeMap, BTreeSet, HashMap},
@@ -201,10 +201,34 @@ impl ModuleCache {
         self.load_field_types(module, starting_idx).map_err(|err| {
             // clean up the structs that were cached
             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);
             function.return_types = function
                 .return_
@@ -256,10 +280,11 @@ impl ModuleCache {
             abilities,
             type_parameters,
             name,
             module,
             struct_def: idx,
+            depth: None,
         }
     }
 
     fn load_field_types(
         &mut self,
@@ -366,11 +391,11 @@ impl ModuleCache {
             SignatureToken::U64 => Type::U64,
             SignatureToken::U128 => Type::U128,
             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))
             },
             SignatureToken::Reference(inner_tok) => {
@@ -457,10 +482,85 @@ impl ModuleCache {
                     module_id, func_name
                 )),
             ),
         }
     }
+
+    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
+            },
+        })
+    }
 }
 
 //
 // Loader
 //
@@ -628,11 +728,11 @@ impl Loader {
 
         if self.vm_config.type_size_limit
             && type_arguments
                 .iter()
                 .map(|loaded_ty| self.count_type_nodes(loaded_ty))
-                .sum::<usize>()
+                .sum::<u64>()
                 > MAX_TYPE_INSTANTIATION_NODES
         {
             return Err(
                 PartialVMError::new(StatusCode::TOO_MANY_TYPE_NODES).finish(Location::Script)
             );
@@ -755,11 +855,11 @@ impl Loader {
     // necessary type as stored in the map. The expected type must be a concrete type (no TyParam).
     // Returns true if a successful match is made.
     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
             (Type::TyParam(idx), _) => match map.entry(*idx) {
                 btree_map::Entry::Vacant(vacant_entry) => {
@@ -849,11 +949,11 @@ impl Loader {
 
         // Construct the type arguments from the match
         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.
                 // For functions that are marked constructor this should not happen.
                 return Err(
@@ -1407,11 +1507,11 @@ impl Loader {
                 {
                     return Err(PartialVMError::new(StatusCode::TOO_MANY_TYPE_NODES));
                 }
             },
             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 {
                         return Err(PartialVMError::new(StatusCode::TOO_MANY_TYPE_NODES));
                     }
@@ -1607,11 +1707,11 @@ impl<'a> Resolver<'a> {
         for ty in &func_inst.instantiation {
             instantiation.push(self.subst(ty, type_params)?);
         }
         // 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 {
                 return Err(PartialVMError::new(StatusCode::TOO_MANY_TYPE_NODES));
             }
@@ -1651,12 +1751,12 @@ 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 {
                 return Err(PartialVMError::new(StatusCode::TOO_MANY_TYPE_NODES));
             }
@@ -2601,12 +2701,12 @@ struct FieldInstantiation {
 
 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 {
     fn new() -> Self {
         Self {
@@ -2630,19 +2730,19 @@ 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(
         &self,
         gidx: CachedStructIndex,
@@ -2705,11 +2805,11 @@ 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() {
             match ty {
                 Type::Vector(ty) | Type::Reference(ty) | Type::MutableReference(ty) => {
@@ -2730,12 +2830,12 @@ impl Loader {
 
     fn struct_gidx_to_type_layout(
         &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) {
                 if let Some(node_count) = &struct_info.node_count {
                     *count += *node_count
@@ -2775,12 +2875,12 @@ 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));
         }
         if depth > VALUE_DEPTH_MAX {
@@ -2852,12 +2952,12 @@ impl Loader {
 
     fn struct_gidx_to_fully_annotated_layout(
         &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) {
                 if let Some(annotated_node_count) = &struct_info.annotated_node_count {
                     *count += *annotated_node_count
@@ -2906,12 +3006,12 @@ 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));
         }
         if depth > VALUE_DEPTH_MAX {
diff --git a/third_party/move/move-vm/transactional-tests/tests/recursion/runtime_layout_deeply_nested.exp b/third_party/move/move-vm/transactional-tests/tests/recursion/runtime_layout_deeply_nested.exp
index a7ad267396..ff2cde123e 100644
--- a/third_party/move/move-vm/transactional-tests/tests/recursion/runtime_layout_deeply_nested.exp
+++ b/third_party/move/move-vm/transactional-tests/tests/recursion/runtime_layout_deeply_nested.exp
@@ -4,16 +4,16 @@ task 2 'run'. lines 79-86:
 Error: Script execution failed with VMError: {
     major_status: VM_MAX_VALUE_DEPTH_REACHED,
     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)],
 }
diff --git a/third_party/move/move-vm/transactional-tests/tests/recursion/runtime_layout_deeply_nested.mvir b/third_party/move/move-vm/transactional-tests/tests/recursion/runtime_layout_deeply_nested.mvir
index 7f2bc48e6b..9044139ecd 100644
--- a/third_party/move/move-vm/transactional-tests/tests/recursion/runtime_layout_deeply_nested.mvir
+++ b/third_party/move/move-vm/transactional-tests/tests/recursion/runtime_layout_deeply_nested.mvir
@@ -69,15 +69,15 @@ module 0x42.M {
 //# run --args @0x1
 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;
 
 main(account: signer) {
 label b0:
@@ -89,9 +89,8 @@ label b0:
 //# run --args @0x3
 import 0x42.M;
 
 main(account: signer) {
 label b0:
-    // hits VM_MAX_VALUE_DEPTH_REACHED
     M.publish_257(&account);
     return;
 }
diff --git a/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs b/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs
index d8c9a99697..e8ed89f2e5 100644
--- a/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs
+++ b/third_party/move/move-vm/types/src/loaded_data/runtime_types.rs
@@ -2,28 +2,122 @@
 // Copyright (c) The Move Contributors
 // SPDX-License-Identifier: Apache-2.0
 
 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>,
     pub field_names: Vec<Identifier>,
     pub abilities: AbilitySet,
     pub type_parameters: Vec<StructTypeParameter>,
     pub name: Identifier,
     pub module: ModuleId,
     pub struct_def: StructDefinitionIndex,
+    pub depth: Option<DepthFormula>,
 }
 
 impl StructType {
     pub fn type_param_constraints(&self) -> impl ExactSizeIterator<Item = &AbilitySet> {
         self.type_parameters.iter().map(|param| &param.constraints)
@@ -44,11 +138,11 @@ pub enum Type {
     Vector(Box<Type>),
     Struct(CachedStructIndex),
     StructInstantiation(CachedStructIndex, Vec<Type>),
     Reference(Box<Type>),
     MutableReference(Box<Type>),
-    TyParam(usize),
+    TyParam(u16),
     U16,
     U32,
     U256,
 }
 
@@ -60,11 +154,11 @@ impl Type {
         self.apply_subst(|idx, _| Ok(Type::TyParam(idx)), depth)
     }
 
     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));
         }
         let res = match self {
@@ -95,11 +189,11 @@ impl Type {
         Ok(res)
     }
 
     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)
                         .with_message(format!(
                             "type substitution failed: index out of bounds -- len {} got {}",
diff --git a/types/src/on_chain_config/timed_features.rs b/types/src/on_chain_config/timed_features.rs
index 4d5766b410..ab675bdd42 100644
--- a/types/src/on_chain_config/timed_features.rs
+++ b/types/src/on_chain_config/timed_features.rs
@@ -89,10 +89,11 @@ impl TimedFeatureFlag {
             (DisableInvariantViolationCheckInSwapLoc, TESTNET) => NOT_YET_SPECIFIED,
             (DisableInvariantViolationCheckInSwapLoc, MAINNET) => NOT_YET_SPECIFIED,
 
             (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,
         }
     }
 }

Vulnerable code:     #[expected_failure(abort_code = 0, location = std::event)]
    #[expected_failure(abort_code = 453, location = std::bcs)]
        StructFieldInformation, TableIndex, Visibility,
    loaded_data::runtime_types::{CachedStructIndex, StructType, Type},
            SignatureToken::TypeParameter(idx) => Type::TyParam(*idx as usize),
                .sum::<usize>()
        map: &mut BTreeMap<usize, &'a Type>,
            if let Option::Some(t) = map.get(&i) {
                let mut sum_nodes: usize = 1;
        let mut sum_nodes: usize = 1;
        // This prevents constructing larger and lager types via struct instantiation.
        let mut sum_nodes: usize = 1;
    node_count: Option<usize>,
    annotated_node_count: Option<usize>,
const VALUE_DEPTH_MAX: usize = 128;
const MAX_TYPE_TO_LAYOUT_NODES: usize = 256;
const MAX_TYPE_INSTANTIATION_NODES: usize = 128;
    fn count_type_nodes(&self, ty: &Type) -> usize {
        count: &mut usize,
        depth: usize,
        count: &mut usize,
        depth: usize,
        count: &mut usize,
        depth: usize,
        count: &mut usize,
        depth: usize,
    offsets: [(FunctionDefinitionIndex(8), 3)],
task 3 'run'. lines 89-97:
    offsets: [(FunctionDefinitionIndex(9), 4)],

    // hits VM_MAX_VALUE_DEPTH_REACHED
    file_format::{AbilitySet, SignatureToken, StructDefinitionIndex, StructTypeParameter},
    TyParam(usize),
        F: Fn(usize, usize) -> PartialVMResult<Type> + Copy,
            |idx, depth| match ty_args.get(idx) {
Fixed code:                     max_value_nest_depth: Some(128),
    /* Deactivated because we now limit the depth of values you could create inside the VM
    */
    #[expected_failure] // VM_MAX_VALUE_DEPTH_REACHED
    #[expected_failure] // VM_MAX_VALUE_DEPTH_REACHED
pub const DEFAULT_MAX_VALUE_NEST_DEPTH: u64 = 128;

    /// Maximum value nest depth for structs
    pub max_value_nest_depth: Option<u64>,
            max_value_nest_depth: Some(DEFAULT_MAX_VALUE_NEST_DEPTH),
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)
}

                        let struct_type = resolver.get_struct_type(*sd_idx);
                        check_depth_of_type(resolver, &struct_type)?;
                        let ty = resolver.instantiate_generic_type(*si_idx, self.ty_args())?;
                        check_depth_of_type(resolver, &ty)?;
                        check_depth_of_type(resolver, &ty)?;
        StructFieldInformation, TableIndex, TypeParameterIndex, Visibility,
    loaded_data::runtime_types::{CachedStructIndex, DepthFormula, StructType, Type},

        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");
                },
            }
        }

            depth: None,
            SignatureToken::TypeParameter(idx) => Type::TyParam(*idx),

    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
            },
        })
    }
                .sum::<u64>()
        map: &mut BTreeMap<u16, &'a Type>,
            if let Option::Some(t) = map.get(&(i as u16)) {
                let mut sum_nodes = 1u64;
        let mut sum_nodes = 1u64;
        // This prevents constructing larger and larger types via struct instantiation.
        let mut sum_nodes = 1u64;
    node_count: Option<u64>,
    annotated_node_count: Option<u64>,
pub const VALUE_DEPTH_MAX: u64 = 128;
const MAX_TYPE_TO_LAYOUT_NODES: u64 = 256;
const MAX_TYPE_INSTANTIATION_NODES: u64 = 128;
    fn count_type_nodes(&self, ty: &Type) -> u64 {
        count: &mut u64,
        depth: u64,
        count: &mut u64,
        depth: u64,
        count: &mut u64,
        depth: u64,
        count: &mut u64,
        depth: u64,
    offsets: [(FunctionDefinitionIndex(0), 1)],
task 3 'run'. lines 89-96:
    offsets: [(FunctionDefinitionIndex(0), 1)],
    // hits VM_MAX_VALUE_DEPTH_REACHED
    file_format::{
        AbilitySet, SignatureToken, StructDefinitionIndex, StructTypeParameter, TypeParameterIndex,
    },
use std::{cmp::max, collections::BTreeMap, fmt::Debug};
#[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);
        }
    }
}

    pub depth: Option<DepthFormula>,
    TyParam(u16),
        F: Fn(u16, usize) -> PartialVMResult<Type> + Copy,
            |idx, depth| match ty_args.get(idx as usize) {