diff --git a/third_party/move/move-vm/runtime/src/loader/mod.rs b/third_party/move/move-vm/runtime/src/loader/mod.rs index 0bafef00c1f06..11a9eda168eb7 100644 --- a/third_party/move/move-vm/runtime/src/loader/mod.rs +++ b/third_party/move/move-vm/runtime/src/loader/mod.rs @@ -56,6 +56,7 @@ use crate::{ storage::{ loader::LoaderV2, module_storage::FunctionValueExtensionAdapter, struct_name_index_map::StructNameIndexMap, ty_cache::StructInfoCache, + ty_tag_cache::TypeTagBuilder, }, }; pub use function::{Function, LoadedFunction}; @@ -1671,15 +1672,42 @@ pub const VALUE_DEPTH_MAX: u64 = 128; /// fields for struct types. const MAX_TYPE_TO_LAYOUT_NODES: u64 = 256; -struct PseudoGasContext { +pub(crate) struct PseudoGasContext { + // Parameters for metering type tag construction: + // - maximum allowed cost, + // - base cost for any type to tag conversion, + // - cost for size of a struct tag. max_cost: u64, - cost: u64, + pub(crate) cost: u64, cost_base: u64, cost_per_byte: u64, } impl PseudoGasContext { - fn charge(&mut self, amount: u64) -> PartialVMResult<()> { + pub(crate) fn new(vm_config: &VMConfig) -> Self { + Self { + max_cost: vm_config.type_max_cost, + cost: 0, + cost_base: vm_config.type_base_cost, + cost_per_byte: vm_config.type_byte_cost, + } + } + + pub(crate) fn current_cost(&mut self) -> u64 { + self.cost + } + + pub(crate) fn charge_base(&mut self) -> PartialVMResult<()> { + self.charge(self.cost_base) + } + + pub(crate) fn charge_struct_tag(&mut self, struct_tag: &StructTag) -> PartialVMResult<()> { + let size = + (struct_tag.address.len() + struct_tag.module.len() + struct_tag.name.len()) as u64; + self.charge(size * self.cost_per_byte) + } + + pub(crate) fn charge(&mut self, amount: u64) -> PartialVMResult<()> { self.cost += amount; if self.cost > self.max_cost { Err( @@ -1694,38 +1722,34 @@ impl PseudoGasContext { } } -impl Loader { +impl LoaderV1 { fn struct_name_to_type_tag( &self, struct_name_idx: StructNameIndex, ty_args: &[Type], gas_context: &mut PseudoGasContext, - module_storage: &dyn ModuleStorage, ) -> PartialVMResult { - let ty_cache = self.ty_cache(module_storage); - if let Some((struct_tag, gas)) = ty_cache.get_struct_tag(&struct_name_idx, ty_args) { + if let Some((struct_tag, gas)) = self.type_cache.get_struct_tag(&struct_name_idx, ty_args) { gas_context.charge(gas)?; return Ok(struct_tag.clone()); } - let cur_cost = gas_context.cost; + let cur_cost = gas_context.current_cost(); let type_args = ty_args .iter() - .map(|ty| self.type_to_type_tag_impl(ty, gas_context, module_storage)) + .map(|ty| self.type_to_type_tag_impl(ty, gas_context)) .collect::>>()?; + let struct_tag = self + .name_cache + .idx_to_struct_tag(struct_name_idx, type_args)?; - let struct_name_index_map = self.struct_name_index_map(module_storage); - let struct_tag = struct_name_index_map.idx_to_struct_tag(struct_name_idx, type_args)?; - - let size = - (struct_tag.address.len() + struct_tag.module.len() + struct_tag.name.len()) as u64; - gas_context.charge(size * gas_context.cost_per_byte)?; - ty_cache.store_struct_tag( + gas_context.charge_struct_tag(&struct_tag)?; + self.type_cache.store_struct_tag( struct_name_idx, ty_args.to_vec(), struct_tag.clone(), - gas_context.cost - cur_cost, + gas_context.current_cost() - cur_cost, ); Ok(struct_tag) } @@ -1734,9 +1758,8 @@ impl Loader { &self, ty: &Type, gas_context: &mut PseudoGasContext, - module_storage: &dyn ModuleStorage, ) -> PartialVMResult { - gas_context.charge(gas_context.cost_base)?; + gas_context.charge_base()?; Ok(match ty { Type::Bool => TypeTag::Bool, Type::U8 => TypeTag::U8, @@ -1748,17 +1771,16 @@ impl Loader { Type::Address => TypeTag::Address, Type::Signer => TypeTag::Signer, Type::Vector(ty) => { - let el_ty_tag = self.type_to_type_tag_impl(ty, gas_context, module_storage)?; + let el_ty_tag = self.type_to_type_tag_impl(ty, gas_context)?; TypeTag::Vector(Box::new(el_ty_tag)) }, Type::Struct { idx, .. } => TypeTag::Struct(Box::new(self.struct_name_to_type_tag( *idx, &[], gas_context, - module_storage, )?)), Type::StructInstantiation { idx, ty_args, .. } => TypeTag::Struct(Box::new( - self.struct_name_to_type_tag(*idx, ty_args, gas_context, module_storage)?, + self.struct_name_to_type_tag(*idx, ty_args, gas_context)?, )), Type::Reference(_) | Type::MutableReference(_) | Type::TyParam(_) => { return Err( @@ -1768,7 +1790,9 @@ impl Loader { }, }) } +} +impl Loader { fn struct_name_to_type_layout( &self, struct_name_idx: StructNameIndex, @@ -2057,18 +2081,16 @@ impl Loader { } let count_before = *count; - let mut gas_context = PseudoGasContext { - cost: 0, - max_cost: self.vm_config().type_max_cost, - cost_base: self.vm_config().type_base_cost, - cost_per_byte: self.vm_config().type_byte_cost, + let struct_tag = match self { + Loader::V1(loader) => { + let mut gas_context = PseudoGasContext::new(loader.vm_config()); + loader.struct_name_to_type_tag(struct_name_idx, ty_args, &mut gas_context)? + }, + Loader::V2(_) => { + let ty_tag_builder = TypeTagBuilder::new(module_storage.runtime_environment()); + ty_tag_builder.struct_name_idx_to_struct_tag(&struct_name_idx, ty_args)? + }, }; - let struct_tag = self.struct_name_to_type_tag( - struct_name_idx, - ty_args, - &mut gas_context, - module_storage, - )?; let fields = struct_type.fields(None)?; let field_layouts = fields @@ -2263,13 +2285,16 @@ impl Loader { ty: &Type, module_storage: &dyn ModuleStorage, ) -> PartialVMResult { - let mut gas_context = PseudoGasContext { - cost: 0, - max_cost: self.vm_config().type_max_cost, - cost_base: self.vm_config().type_base_cost, - cost_per_byte: self.vm_config().type_byte_cost, - }; - self.type_to_type_tag_impl(ty, &mut gas_context, module_storage) + match self { + Loader::V1(loader) => { + let mut gas_context = PseudoGasContext::new(self.vm_config()); + loader.type_to_type_tag_impl(ty, &mut gas_context) + }, + Loader::V2(_) => { + let ty_tag_builder = TypeTagBuilder::new(module_storage.runtime_environment()); + ty_tag_builder.ty_to_ty_tag(ty) + }, + } } pub(crate) fn type_to_type_layout_with_identifier_mappings( diff --git a/third_party/move/move-vm/runtime/src/storage/environment.rs b/third_party/move/move-vm/runtime/src/storage/environment.rs index 9a813576dbdd4..515188d04e2af 100644 --- a/third_party/move/move-vm/runtime/src/storage/environment.rs +++ b/third_party/move/move-vm/runtime/src/storage/environment.rs @@ -7,7 +7,7 @@ use crate::{ native_functions::{NativeFunction, NativeFunctions}, storage::{ struct_name_index_map::StructNameIndexMap, ty_cache::StructInfoCache, - verified_module_cache::VERIFIED_MODULES_V2, + ty_tag_cache::TypeTagCache, verified_module_cache::VERIFIED_MODULES_V2, }, Module, Script, }; @@ -52,6 +52,10 @@ pub struct RuntimeEnvironment { /// We wrap the index map into an [Arc] so that on republishing these clones are cheap. struct_name_index_map: Arc, + /// Caches struct tags for instantiated types. This cache can be used concurrently and + /// speculatively because type tag information does not change with module publishes. + ty_tag_cache: Arc, + /// Type cache for struct layouts, tags and depths, shared across multiple threads. /// /// SAFETY: @@ -100,6 +104,7 @@ impl RuntimeEnvironment { vm_config, natives, struct_name_index_map: Arc::new(StructNameIndexMap::empty()), + ty_tag_cache: Arc::new(TypeTagCache::empty()), ty_cache: StructInfoCache::empty(), } } @@ -261,6 +266,12 @@ impl RuntimeEnvironment { &self.struct_name_index_map } + /// Returns the type tag cache used by this environment to store already constructed struct + /// tags. + pub(crate) fn ty_tag_cache(&self) -> &TypeTagCache { + &self.ty_tag_cache + } + /// Returns the type cache owned by this runtime environment which stores information about /// struct layouts, tags and depth formulae. pub(crate) fn ty_cache(&self) -> &StructInfoCache { @@ -273,9 +284,10 @@ impl RuntimeEnvironment { self.struct_name_index_map.checked_len() } - /// Flushes the struct information (type) cache. Flushing this cache does not invalidate struct - /// name index map or module cache. + /// Flushes the struct information (type and tag) caches. Flushing this cache does not + /// invalidate struct name index map or module cache. pub fn flush_struct_info_cache(&self) { + self.ty_tag_cache.flush(); self.ty_cache.flush(); } @@ -307,14 +319,13 @@ impl RuntimeEnvironment { } impl Clone for RuntimeEnvironment { - /// Returns the cloned environment. Struct re-indexing map and type caches are cloned and no - /// longer shared with the original environment. fn clone(&self) -> Self { Self { vm_config: self.vm_config.clone(), natives: self.natives.clone(), - struct_name_index_map: self.struct_name_index_map.clone(), ty_cache: self.ty_cache.clone(), + struct_name_index_map: Arc::clone(&self.struct_name_index_map), + ty_tag_cache: Arc::clone(&self.ty_tag_cache), } } } diff --git a/third_party/move/move-vm/runtime/src/storage/mod.rs b/third_party/move/move-vm/runtime/src/storage/mod.rs index 86d2040757d18..b10a70b52a156 100644 --- a/third_party/move/move-vm/runtime/src/storage/mod.rs +++ b/third_party/move/move-vm/runtime/src/storage/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod loader; pub(crate) mod struct_name_index_map; pub(crate) mod ty_cache; +pub(crate) mod ty_tag_cache; mod verified_module_cache; pub mod code_storage; diff --git a/third_party/move/move-vm/runtime/src/storage/ty_tag_cache.rs b/third_party/move/move-vm/runtime/src/storage/ty_tag_cache.rs new file mode 100644 index 0000000000000..c6b70e8edbc58 --- /dev/null +++ b/third_party/move/move-vm/runtime/src/storage/ty_tag_cache.rs @@ -0,0 +1,440 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::{loader::PseudoGasContext, RuntimeEnvironment}; +use move_binary_format::errors::{PartialVMError, PartialVMResult}; +use move_core_types::{ + language_storage::{StructTag, TypeTag}, + vm_status::StatusCode, +}; +use move_vm_types::loaded_data::runtime_types::{StructNameIndex, Type}; +use parking_lot::RwLock; +use std::collections::{hash_map::Entry, HashMap}; + +/// An entry in [TypeTagCache] that also stores a "cost" of the tag. The cost is proportional to +/// the size of the tag, which includes the number of inner nodes and the sum of the sizes in bytes +/// of addresses and identifiers. +#[derive(Debug, Clone)] +pub(crate) struct PricedStructTag { + pub(crate) struct_tag: StructTag, + pub(crate) pseudo_gas_cost: u64, +} + +/// Cache for struct tags, that can be used safely for concurrent and speculative execution. +/// +/// # Speculative execution safety +/// +/// A struct name corresponds to a unique [StructNameIndex]. So all non-generic structs with same +/// names have the same struct tags. If structs are generic, the number of type parameters cannot +/// be changed by the upgrade, so the tags stay the same for different "upgraded" struct versions. +/// The type parameters themselves (vector of [Type]s in this cache used as keys) are also not +/// changing. +/// +/// Note: even if we allow to add more type parameters (e.g., for enums), it still does not affect +/// safety because different number of type parameters will correspond to a different entries in +/// cache. For example, suppose we have 3 threads, where threads 1 and 2 cache different versions +/// of an enum (see below). +/// ``` +/// // Tag cached by thread 1. +/// enum Foo { V1(A), } +/// ``` +/// ``` +/// // Tag cached by thread 2, where the version of Foo is upgraded with a new variant and new +/// // type parameter (artificial example). +/// enum Foo { V1(A), V2(B), } +/// ``` +/// If thread 3 reads the tag of this enum, the read result is always **deterministic** for the +/// fixed type parameters used by thread 3. +pub(crate) struct TypeTagCache { + cache: RwLock, PricedStructTag>>>, +} + +impl TypeTagCache { + /// Creates a new empty cache without any entries. + pub(crate) fn empty() -> Self { + Self { + cache: RwLock::new(HashMap::new()), + } + } + + /// Removes all entries from the cache. + pub(crate) fn flush(&self) { + self.cache.write().clear(); + } + + /// Returns cached struct tag and its pseudo-gas cost if it exists, and [None] otherwise. + pub(crate) fn get_struct_tag( + &self, + idx: &StructNameIndex, + ty_args: &[Type], + ) -> Option { + self.cache.read().get(idx)?.get(ty_args).cloned() + } + + /// Inserts the struct tag and its pseudo-gas cost ([PricedStructTag]) into the cache. Returns + /// true if the tag was not cached before, and false otherwise. + pub(crate) fn insert_struct_tag( + &self, + idx: &StructNameIndex, + ty_args: &[Type], + priced_struct_tag: &PricedStructTag, + ) -> bool { + // Check if already cached. + if let Some(inner_cache) = self.cache.read().get(idx) { + if inner_cache.contains_key(ty_args) { + return false; + } + } + + let priced_struct_tag = priced_struct_tag.clone(); + let ty_args = ty_args.to_vec(); + + // Otherwise, we need to insert. We did the clones outside the lock, and also avoid the + // double insertion. + let mut cache = self.cache.write(); + if let Entry::Vacant(entry) = cache.entry(*idx).or_default().entry(ty_args) { + entry.insert(priced_struct_tag); + true + } else { + false + } + } +} + +/// Responsible for building type tags, while also doing the metering in order to bound space and +/// time complexity. +pub(crate) struct TypeTagBuilder<'a> { + /// Stores caches for struct names and tags, as well as pseudo-gas metering configs. + runtime_environment: &'a RuntimeEnvironment, +} + +impl<'a> TypeTagBuilder<'a> { + /// Creates a new builder for the specified environment and configs. + pub(crate) fn new(runtime_environment: &'a RuntimeEnvironment) -> Self { + Self { + runtime_environment, + } + } + + /// Converts a runtime type into a type tag. If the type is too complex (e.g., struct name size + /// too large, or type too deeply nested), an error is returned. + pub(crate) fn ty_to_ty_tag(&self, ty: &Type) -> PartialVMResult { + let mut gas_context = PseudoGasContext::new(self.runtime_environment.vm_config()); + self.ty_to_ty_tag_impl(ty, &mut gas_context) + } + + /// Converts the struct type (based on its indexed name and type arguments) into a struct tag. + /// If the tag has not been previously cached, it will be cached. Just like for types, if the + /// type arguments are too complex, etc. the tag construction fails. + pub(crate) fn struct_name_idx_to_struct_tag( + &self, + struct_name_idx: &StructNameIndex, + ty_args: &[Type], + ) -> PartialVMResult { + let mut gas_context = PseudoGasContext::new(self.runtime_environment.vm_config()); + self.struct_name_idx_to_struct_tag_impl(struct_name_idx, ty_args, &mut gas_context) + } + + fn ty_to_ty_tag_impl( + &self, + ty: &Type, + gas_context: &mut PseudoGasContext, + ) -> PartialVMResult { + // Charge base cost at the start. + gas_context.charge_base()?; + + Ok(match ty { + // Primitive types. + Type::Bool => TypeTag::Bool, + Type::U8 => TypeTag::U8, + Type::U16 => TypeTag::U16, + Type::U32 => TypeTag::U32, + Type::U64 => TypeTag::U64, + Type::U128 => TypeTag::U128, + Type::U256 => TypeTag::U256, + Type::Address => TypeTag::Address, + Type::Signer => TypeTag::Signer, + + // Vector types: recurse. + Type::Vector(elem_ty) => { + let elem_ty_tag = self.ty_to_ty_tag_impl(elem_ty, gas_context)?; + TypeTag::Vector(Box::new(elem_ty_tag)) + }, + + // Structs: we need to convert indices to names, possibly caching struct tags. + Type::Struct { idx, .. } => { + let struct_tag = self.struct_name_idx_to_struct_tag_impl(idx, &[], gas_context)?; + TypeTag::Struct(Box::new(struct_tag)) + }, + Type::StructInstantiation { idx, ty_args, .. } => { + let struct_tag = + self.struct_name_idx_to_struct_tag_impl(idx, ty_args, gas_context)?; + TypeTag::Struct(Box::new(struct_tag)) + }, + + // References and type parameters cannot be converted to tags. + Type::Reference(_) | Type::MutableReference(_) | Type::TyParam(_) => { + return Err( + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message(format!("No type tag for {:?}", ty)), + ); + }, + }) + } + + fn struct_name_idx_to_struct_tag_impl( + &self, + struct_name_idx: &StructNameIndex, + ty_args: &[Type], + gas_context: &mut PseudoGasContext, + ) -> PartialVMResult { + let ty_tag_cache = self.runtime_environment.ty_tag_cache(); + + // If cached, charge pseudo-gas cost and return. + if let Some(priced_tag) = ty_tag_cache.get_struct_tag(struct_name_idx, ty_args) { + gas_context.charge(priced_tag.pseudo_gas_cost)?; + return Ok(priced_tag.struct_tag); + } + + // If not cached, record the current cost and construct tags for type arguments. + let cur_cost = gas_context.current_cost(); + + let type_args = ty_args + .iter() + .map(|ty| self.ty_to_ty_tag_impl(ty, gas_context)) + .collect::>>()?; + + // Construct the struct tag as well. + let struct_name_index_map = self.runtime_environment.struct_name_index_map(); + let struct_tag = struct_name_index_map.idx_to_struct_tag(*struct_name_idx, type_args)?; + gas_context.charge_struct_tag(&struct_tag)?; + + // Cache the struct tag. Record its gas cost as well. + let priced_tag = PricedStructTag { + struct_tag, + pseudo_gas_cost: gas_context.current_cost() - cur_cost, + }; + ty_tag_cache.insert_struct_tag(struct_name_idx, ty_args, &priced_tag); + + Ok(priced_tag.struct_tag) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::config::VMConfig; + use claims::{assert_err, assert_none, assert_ok, assert_ok_eq, assert_some}; + use move_binary_format::file_format::{AbilitySet, StructTypeParameter}; + use move_core_types::{ + account_address::AccountAddress, identifier::Identifier, language_storage::ModuleId, + }; + use move_vm_types::loaded_data::runtime_types::{ + AbilityInfo, StructIdentifier, StructLayout, StructType, TypeBuilder, + }; + use std::str::FromStr; + + #[test] + fn test_type_tag_cache() { + let cache = TypeTagCache::empty(); + assert!(cache.cache.read().is_empty()); + assert!(cache.get_struct_tag(&StructNameIndex(0), &[]).is_none()); + + let tag = PricedStructTag { + struct_tag: StructTag::from_str("0x1::foo::Foo").unwrap(), + pseudo_gas_cost: 10, + }; + assert!(cache.insert_struct_tag(&StructNameIndex(0), &[], &tag)); + + let tag = PricedStructTag { + struct_tag: StructTag::from_str("0x1::foo::Foo").unwrap(), + // Set different cost to check. + pseudo_gas_cost: 100, + }; + assert!(!cache.insert_struct_tag(&StructNameIndex(0), &[], &tag)); + + assert_eq!(cache.cache.read().len(), 1); + let cost = cache + .get_struct_tag(&StructNameIndex(0), &[]) + .unwrap() + .pseudo_gas_cost; + assert_eq!(cost, 10); + } + + #[test] + fn test_ty_to_ty_tag() { + let ty_builder = TypeBuilder::with_limits(10, 10); + + let runtime_environment = RuntimeEnvironment::new(vec![]); + let ty_tag_builder = TypeTagBuilder::new(&runtime_environment); + + let disallowed_tys = [ + Type::TyParam(0), + ty_builder + .create_ref_ty(&ty_builder.create_u8_ty(), true) + .unwrap(), + ty_builder + .create_ref_ty(&ty_builder.create_u8_ty(), false) + .unwrap(), + ]; + for ty in disallowed_tys { + assert_err!(ty_tag_builder.ty_to_ty_tag(&ty)); + } + + let allowed_primitive_tys = [ + (ty_builder.create_bool_ty(), TypeTag::Bool), + (ty_builder.create_u8_ty(), TypeTag::U8), + (ty_builder.create_u16_ty(), TypeTag::U16), + (ty_builder.create_u32_ty(), TypeTag::U32), + (ty_builder.create_u64_ty(), TypeTag::U64), + (ty_builder.create_u128_ty(), TypeTag::U128), + (ty_builder.create_u256_ty(), TypeTag::U256), + (ty_builder.create_address_ty(), TypeTag::Address), + (ty_builder.create_signer_ty(), TypeTag::Signer), + ]; + for (ty, expected_tag) in allowed_primitive_tys { + let actual_tag = assert_ok!(ty_tag_builder.ty_to_ty_tag(&ty)); + assert_eq!(actual_tag, expected_tag); + } + + // Vectors. + let bool_vec_ty = ty_builder + .create_vec_ty(&ty_builder.create_bool_ty()) + .unwrap(); + let bool_vec_tag = TypeTag::Vector(Box::new(TypeTag::Bool)); + assert_ok_eq!( + ty_tag_builder.ty_to_ty_tag(&bool_vec_ty), + bool_vec_tag.clone() + ); + + // Structs. + let bar_idx = runtime_environment + .struct_name_index_map() + .struct_name_to_idx(&StructIdentifier { + module: ModuleId::new(AccountAddress::ONE, Identifier::new("foo").unwrap()), + name: Identifier::new("Bar").unwrap(), + }) + .unwrap(); + let foo_idx = runtime_environment + .struct_name_index_map() + .struct_name_to_idx(&StructIdentifier { + module: ModuleId::new(AccountAddress::TWO, Identifier::new("foo").unwrap()), + name: Identifier::new("Foo").unwrap(), + }) + .unwrap(); + + let struct_ty = + ty_builder.create_struct_ty(bar_idx, AbilityInfo::struct_(AbilitySet::EMPTY)); + let struct_tag = StructTag::from_str("0x1::foo::Bar").unwrap(); + assert_ok_eq!( + ty_tag_builder.ty_to_ty_tag(&struct_ty), + TypeTag::Struct(Box::new(struct_tag)) + ); + + let struct_ty = StructType { + idx: foo_idx, + layout: StructLayout::Single(vec![( + Identifier::new("field").unwrap(), + Type::TyParam(0), + )]), + phantom_ty_params_mask: Default::default(), + abilities: AbilitySet::EMPTY, + ty_params: vec![StructTypeParameter { + constraints: AbilitySet::EMPTY, + is_phantom: false, + }], + name: Identifier::new("Foo").unwrap(), + module: ModuleId::new(AccountAddress::TWO, Identifier::new("foo").unwrap()), + }; + let generic_struct_ty = ty_builder + .create_struct_instantiation_ty(&struct_ty, &[Type::TyParam(0)], &[bool_vec_ty]) + .unwrap(); + let struct_tag = StructTag::from_str("0x2::foo::Foo>").unwrap(); + assert_ok_eq!( + ty_tag_builder.ty_to_ty_tag(&generic_struct_ty), + TypeTag::Struct(Box::new(struct_tag)) + ); + } + + #[test] + fn test_ty_to_ty_tag_too_complex() { + let ty_builder = TypeBuilder::with_limits(10, 10); + + let vm_config = VMConfig { + type_base_cost: 1, + type_max_cost: 2, + ..Default::default() + }; + let runtime_environment = RuntimeEnvironment::new_with_config(vec![], vm_config); + let ty_tag_builder = TypeTagBuilder::new(&runtime_environment); + + let bool_ty = ty_builder.create_bool_ty(); + assert_ok_eq!(ty_tag_builder.ty_to_ty_tag(&bool_ty), TypeTag::Bool); + + let vec_ty = ty_builder.create_vec_ty(&bool_ty).unwrap(); + assert_ok_eq!( + ty_tag_builder.ty_to_ty_tag(&vec_ty), + TypeTag::Vector(Box::new(TypeTag::Bool)) + ); + + let vec_ty = ty_builder.create_vec_ty(&vec_ty).unwrap(); + let err = assert_err!(ty_tag_builder.ty_to_ty_tag(&vec_ty)); + assert_eq!(err.major_status(), StatusCode::TYPE_TAG_LIMIT_EXCEEDED); + } + + #[test] + fn test_ty_to_ty_tag_struct_metering() { + let type_max_cost = 76; + let vm_config = VMConfig { + type_base_cost: 1, + type_byte_cost: 2, + type_max_cost, + ..Default::default() + }; + let runtime_environment = RuntimeEnvironment::new_with_config(vec![], vm_config); + let ty_tag_builder = TypeTagBuilder::new(&runtime_environment); + + let id = StructIdentifier { + module: ModuleId::new(AccountAddress::ONE, Identifier::new("foo").unwrap()), + name: Identifier::new("Foo").unwrap(), + }; + let idx = runtime_environment + .struct_name_index_map() + .struct_name_to_idx(&id) + .unwrap(); + let struct_tag = StructTag::from_str("0x1::foo::Foo").unwrap(); + + let mut gas_context = PseudoGasContext::new(runtime_environment.vm_config()); + assert_ok_eq!( + ty_tag_builder.struct_name_idx_to_struct_tag_impl(&idx, &[], &mut gas_context), + struct_tag.clone() + ); + + // Address size, plus module name and struct name each taking 3 characters. + let expected_cost = 2 * (32 + 3 + 3); + assert_eq!(gas_context.current_cost(), expected_cost); + + let priced_tag = assert_some!(runtime_environment.ty_tag_cache().get_struct_tag(&idx, &[])); + assert_eq!(priced_tag.pseudo_gas_cost, expected_cost); + assert_eq!(priced_tag.struct_tag, struct_tag); + + // Now + let vm_config = VMConfig { + type_base_cost: 1, + type_byte_cost: 2, + // Use smaller limit, to test metering. + type_max_cost: type_max_cost - 1, + ..Default::default() + }; + let runtime_environment = RuntimeEnvironment::new_with_config(vec![], vm_config); + let mut gas_context = PseudoGasContext::new(runtime_environment.vm_config()); + + let err = assert_err!(ty_tag_builder.struct_name_idx_to_struct_tag_impl( + &idx, + &[], + &mut gas_context + )); + assert_eq!(err.major_status(), StatusCode::TYPE_TAG_LIMIT_EXCEEDED); + assert_none!(runtime_environment.ty_tag_cache().get_struct_tag(&idx, &[])); + } +} 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 d96d01a23161a..9cce895bb6a57 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 @@ -817,6 +817,14 @@ impl TypeBuilder { Type::U256 } + pub fn create_address_ty(&self) -> Type { + Type::Address + } + + pub fn create_signer_ty(&self) -> Type { + Type::Signer + } + /// Creates a (possibly mutable) reference type from the given inner type. /// Returns an error if the type size or depth are too large. #[inline]