diff --git a/language/move-stdlib/docs/overview.md b/language/move-stdlib/docs/overview.md
index e8aff3f041..72b6def118 100644
--- a/language/move-stdlib/docs/overview.md
+++ b/language/move-stdlib/docs/overview.md
@@ -21,6 +21,7 @@ This is the root document for the Move stdlib module documentation. The Move std
- [`0x1::option`](option.md#0x1_option)
- [`0x1::signer`](signer.md#0x1_signer)
- [`0x1::string`](string.md#0x1_string)
+- [`0x1::struct_tag`](struct_tag.md#0x1_struct_tag)
- [`0x1::type_name`](type_name.md#0x1_type_name)
- [`0x1::vector`](vector.md#0x1_vector)
diff --git a/language/move-stdlib/docs/struct_tag.md b/language/move-stdlib/docs/struct_tag.md
new file mode 100644
index 0000000000..abd9bc9dd8
--- /dev/null
+++ b/language/move-stdlib/docs/struct_tag.md
@@ -0,0 +1,155 @@
+
+
+
+# Module `0x1::struct_tag`
+
+Module to decompose a move struct into it's components.
+
+
+- [Struct `StructTag`](#0x1_struct_tag_StructTag)
+- [Function `get`](#0x1_struct_tag_get)
+- [Function `into`](#0x1_struct_tag_into)
+- [Function `module_authority`](#0x1_struct_tag_module_authority)
+
+
+
use 0x1::ascii ;
+
+
+
+
+
+
+## Struct `StructTag`
+
+
+
+struct StructTag has copy , drop, store
+
+
+
+
+
+Fields
+
+
+
+
+address_: address
+
+
+ Address of the entity that the struct belongs to.
+ taking 00000000000000000000000000000001::option::Option<u64>
for example,
+ the address will be 00000000000000000000000000000001
+
+
+module_name: ascii::String
+
+
+ the name of the module where the struct is defined.
+ using the example struct above the module name should be option
+
+
+struct_name: ascii::String
+
+
+ the name of the struct itself.
+ using the example struct above the module name should be Option
+
+
+generics: vector <ascii::String >
+
+
+ the generics or tyepe params of the struct.
+ using the example struct above the module name should be vector ["u64"]
+
+
+
+
+
+
+
+
+## Function `get`
+
+Returns the tag of the struct of type T
+
+
+public fun get <T>(): struct_tag::StructTag
+
+
+
+
+
+Implementation
+
+
+public native fun get <T>(): StructTag ;
+
+
+
+
+
+
+
+
+## Function `into`
+
+
+
+public fun into (self: &struct_tag::StructTag ): (address , ascii::String , ascii::String , vector <ascii::String >)
+
+
+
+
+
+Implementation
+
+
+public fun into (self: &StructTag ): (address , String, String, vector <String>) {
+ (self.address_, self.module_name, self.struct_name, self.generics)
+}
+
+
+
+
+
+
+
+
+## Function `module_authority`
+
+Returns the module authority for the struct of type T
+
+
+public fun module_authority <T>(): struct_tag::StructTag
+
+
+
+
+
+Implementation
+
+
+public fun module_authority <T>(): StructTag {
+ let StructTag {
+ address_,
+ module_name,
+ struct_name: _,
+ generics: _
+ } = get <T>();
+
+ StructTag {
+ address_,
+ module_name,
+ struct_name: ascii::string (b"Witness"),
+ generics: vector []
+ }
+}
+
+
+
+
+
+
+
+[//]: # ("File containing references which can be used from documentation")
diff --git a/language/move-stdlib/sources/struct_tag.move b/language/move-stdlib/sources/struct_tag.move
new file mode 100644
index 0000000000..9d659b232d
--- /dev/null
+++ b/language/move-stdlib/sources/struct_tag.move
@@ -0,0 +1,55 @@
+/// Module to decompose a move struct into it's components.
+module std::struct_tag {
+ use std::ascii::{Self, String};
+
+ struct StructTag has copy, store, drop {
+ /// Address of the entity that the struct belongs to.
+ /// taking `00000000000000000000000000000001::option::Option` for example,
+ /// the address will be `00000000000000000000000000000001`
+ address_: address,
+ /// the name of the module where the struct is defined.
+ /// using the example struct above the module name should be `option`
+ module_name: String,
+ /// the name of the struct itself.
+ /// using the example struct above the module name should be `Option`
+ struct_name: String,
+ /// the generics or tyepe params of the struct.
+ /// using the example struct above the module name should be `vector["u64"]`
+ generics: vector
+ }
+
+ /// Returns the tag of the struct of type `T`
+ public native fun get(): StructTag;
+
+ // Converts `self` into a tuple of it's inner values
+ public fun into(self: &StructTag): (address, String, String, vector) {
+ (self.address_, self.module_name, self.struct_name, self.generics)
+ }
+
+ /// Returns the module authority for the struct of type `T`
+ public fun module_authority(): StructTag {
+ let StructTag {
+ address_,
+ module_name,
+ struct_name: _,
+ generics: _
+ } = get();
+
+ StructTag {
+ address_,
+ module_name,
+ struct_name: ascii::string(b"Witness"),
+ generics: vector[]
+ }
+ }
+
+ #[test_only]
+ public fun new_for_testing(address_: address, module_name: String, struct_name: String, generics: vector): StructTag {
+ StructTag {
+ address_,
+ module_name,
+ struct_name,
+ generics
+ }
+ }
+}
diff --git a/language/move-stdlib/src/natives/mod.rs b/language/move-stdlib/src/natives/mod.rs
index fe97636884..910a4b3c9e 100644
--- a/language/move-stdlib/src/natives/mod.rs
+++ b/language/move-stdlib/src/natives/mod.rs
@@ -8,6 +8,7 @@ pub mod event;
pub mod hash;
pub mod signer;
pub mod string;
+pub mod struct_tag;
pub mod type_name;
#[cfg(feature = "testing")]
pub mod unit_test;
@@ -26,6 +27,7 @@ pub struct GasParameters {
pub string: string::GasParameters,
pub type_name: type_name::GasParameters,
pub vector: vector::GasParameters,
+ pub struct_tag: struct_tag::GasParameters,
#[cfg(feature = "testing")]
pub unit_test: unit_test::GasParameters,
@@ -91,6 +93,12 @@ impl GasParameters {
destroy_empty: vector::DestroyEmptyGasParameters { base: 0.into() },
swap: vector::SwapGasParameters { base: 0.into() },
},
+ struct_tag: struct_tag::GasParameters {
+ get: struct_tag::GetGasParameters {
+ base: 0.into(),
+ per_byte: 0.into(),
+ },
+ },
#[cfg(feature = "testing")]
unit_test: unit_test::GasParameters {
create_signers_for_testing: unit_test::CreateSignersForTestingGasParameters {
@@ -122,6 +130,7 @@ pub fn all_natives(
add_natives!("string", string::make_all(gas_params.string));
add_natives!("type_name", type_name::make_all(gas_params.type_name));
add_natives!("vector", vector::make_all(gas_params.vector));
+ add_natives!("struct_tag", struct_tag::make_all(gas_params.struct_tag));
#[cfg(feature = "testing")]
{
add_natives!("unit_test", unit_test::make_all(gas_params.unit_test));
diff --git a/language/move-stdlib/src/natives/struct_tag.rs b/language/move-stdlib/src/natives/struct_tag.rs
new file mode 100644
index 0000000000..cf6e9943fa
--- /dev/null
+++ b/language/move-stdlib/src/natives/struct_tag.rs
@@ -0,0 +1,92 @@
+// Copyright (c) The Move Contributors
+// SPDX-License-Identifier: Apache-2.0
+
+use move_binary_format::errors::PartialVMResult;
+use move_core_types::{
+ gas_algebra::{InternalGas, InternalGasPerByte, NumBytes},
+ language_storage::TypeTag,
+};
+use move_vm_runtime::native_functions::{NativeContext, NativeFunction};
+use move_vm_types::{
+ loaded_data::runtime_types::Type,
+ natives::function::NativeResult,
+ values::{Struct, Value},
+};
+
+use smallvec::smallvec;
+use std::{collections::VecDeque, sync::Arc};
+
+#[derive(Debug, Clone)]
+pub struct GetGasParameters {
+ pub base: InternalGas,
+ pub per_byte: InternalGasPerByte,
+}
+
+fn native_get(
+ gas_params: &GetGasParameters,
+ context: &mut NativeContext,
+ ty_args: Vec,
+ arguments: VecDeque,
+) -> PartialVMResult {
+ debug_assert!(ty_args.len() == 1);
+ debug_assert!(arguments.is_empty());
+
+ let type_tag = context.type_to_type_tag(&ty_args[0])?;
+ let type_name = type_tag.to_canonical_string();
+
+ let mut cost = gas_params.base;
+
+ if let TypeTag::Struct(tag) = type_tag {
+ let address = Value::address(tag.address);
+
+ // make std::ascii::String for the module name
+ let module = Value::struct_(Struct::pack(vec![Value::vector_u8(
+ tag.module.into_bytes(),
+ )]));
+
+ // make std::ascii::String for the struct name
+ let name = Value::struct_(Struct::pack(vec![Value::vector_u8(tag.name.into_bytes())]));
+
+ // make a vector of std::ascii::String for the generics
+ let generics_vec = tag
+ .type_params
+ .iter()
+ .map(|ty| {
+ Value::struct_(Struct::pack(vec![Value::vector_u8(
+ ty.to_canonical_string().into_bytes(),
+ )]))
+ })
+ .collect::>();
+
+ // convert the generics vector into move supported value.
+ // using the `vector_for_testing_only` which can break as it's currently the easiest way to do this without altering the existing `Value` struct.
+ // it should the replaced when the proper API is ready.
+ let generics = Value::vector_for_testing_only(generics_vec);
+
+ cost += gas_params.per_byte * NumBytes::new(type_name.len() as u64);
+
+ Ok(NativeResult::ok(
+ cost,
+ smallvec![Value::struct_(Struct::pack(vec![
+ address, module, name, generics
+ ]))],
+ ))
+ } else {
+ Ok(NativeResult::err(cost, 0))
+ }
+}
+
+pub fn make_native_get(gas_params: GetGasParameters) -> NativeFunction {
+ Arc::new(move |context, ty_args, args| native_get(&gas_params, context, ty_args, args))
+}
+
+#[derive(Debug, Clone)]
+pub struct GasParameters {
+ pub get: GetGasParameters,
+}
+
+pub fn make_all(gas_params: GasParameters) -> impl Iterator- {
+ let natives = [("get", make_native_get(gas_params.get))];
+
+ crate::natives::helpers::make_module_natives(natives)
+}
diff --git a/language/move-stdlib/tests/struct_tag_tests.move b/language/move-stdlib/tests/struct_tag_tests.move
new file mode 100644
index 0000000000..abf05ff4c1
--- /dev/null
+++ b/language/move-stdlib/tests/struct_tag_tests.move
@@ -0,0 +1,171 @@
+#[test_only]
+module std::struct_tag_tests {
+ use std::ascii::{Self, String};
+ use std::option::{Option};
+ use std::struct_tag;
+
+ struct TestStruct has drop {}
+ struct TestStructGeneric1
has drop {}
+ struct TestStructGeneric2 has drop {}
+ struct TestStructGeneric3 has drop {}
+
+ struct Witness has drop {}
+
+ #[test]
+ fun test_plain_struct() {
+ assert!(struct_tag::get() == struct_tag::new_for_testing(@0x1, ascii::string(b"struct_tag_tests"), ascii::string(b"TestStruct"), vector[]), 0);
+ assert!(struct_tag::get() == struct_tag::new_for_testing(@0x1, ascii::string(b"ascii"), ascii::string(b"String"), vector[]), 0);
+ }
+
+ #[test]
+ fun test_generic_struct() {
+ // testing withbasic single generic
+ let new_test_struct_1 = struct_tag::new_for_testing(
+ @0x1,
+ ascii::string(b"struct_tag_tests"),
+ ascii::string(b"TestStructGeneric1"),
+ vector[ascii::string(b"00000000000000000000000000000001::ascii::String")]
+ );
+
+ // testing with two generics
+ let new_test_struct_2_a = struct_tag::new_for_testing(
+ @0x1,
+ ascii::string(b"struct_tag_tests"),
+ ascii::string(b"TestStructGeneric2"),
+ vector[
+ ascii::string(b"00000000000000000000000000000001::ascii::String"),
+ ascii::string(b"address")
+ ]
+ );
+
+ // testing with two generics with nested generic
+ let new_test_struct_2_b = struct_tag::new_for_testing(
+ @0x1,
+ ascii::string(b"struct_tag_tests"),
+ ascii::string(b"TestStructGeneric2"),
+ vector[
+ ascii::string(b"00000000000000000000000000000001::ascii::String"),
+ ascii::string(b"00000000000000000000000000000001::option::Option")
+ ]
+ );
+
+ // testing with multiple(two or more) generics with nested generic
+ let new_test_struct_3_a = struct_tag::new_for_testing(
+ @0x1,
+ ascii::string(b"struct_tag_tests"),
+ ascii::string(b"TestStructGeneric3"),
+ vector[
+ ascii::string(b"00000000000000000000000000000001::ascii::String"),
+ ascii::string(b"address"),
+ ascii::string(b"00000000000000000000000000000001::option::Option")
+ ]
+ );
+
+ // testing with multiple generics with nested two or more generics
+ let new_test_struct_3_b = struct_tag::new_for_testing(
+ @0x1,
+ ascii::string(b"struct_tag_tests"),
+ ascii::string(b"TestStructGeneric3"),
+ vector[
+ ascii::string(b"00000000000000000000000000000001::ascii::String"),
+ ascii::string(b"address"),
+ ascii::string(b"00000000000000000000000000000001::struct_tag_tests::TestStructGeneric2<00000000000000000000000000000001::ascii::String,00000000000000000000000000000001::option::Option>")
+ ]
+ );
+
+ assert!(struct_tag::get>() == new_test_struct_1, 0);
+ assert!(struct_tag::get>() == new_test_struct_2_a, 0);
+ assert!(struct_tag::get>>() == new_test_struct_2_b, 0);
+ assert!(struct_tag::get>>() == new_test_struct_3_a, 0);
+ assert!(struct_tag::get>>>() == new_test_struct_3_b, 0);
+ }
+
+ #[test]
+ fun test_module_authority() {
+ assert!(struct_tag::module_authority() == struct_tag::get(), 0);
+ assert!(struct_tag::module_authority>>() == struct_tag::get(), 0);
+ assert!(struct_tag::module_authority>>>() == struct_tag::get(), 0);
+ }
+
+ #[test]
+ #[expected_failure(abort_code = 0, location = std::struct_tag_tests)]
+ fun test_module_authority_failure() {
+ assert!(struct_tag::module_authority() == struct_tag::get(), 0);
+ assert!(struct_tag::module_authority>() == struct_tag::get(), 0);
+ assert!(struct_tag::module_authority>>() == struct_tag::get(), 0);
+ }
+
+ #[test]
+ #[expected_failure(abort_code = 0, location = std::struct_tag_tests)]
+ fun test_invalid_properties_failure() {
+ // supplying invalid address
+ let new_test_struct_1_a = struct_tag::new_for_testing(
+ @0x2,
+ ascii::string(b"struct_tag_tests"),
+ ascii::string(b"TestStructGeneric1"),
+ vector[ascii::string(b"00000000000000000000000000000001::ascii::String")]
+ );
+
+ // supplying invalid module name
+ let new_test_struct_1_b = struct_tag::new_for_testing(
+ @0x1,
+ ascii::string(b"fake_module_name"),
+ ascii::string(b"TestStructGeneric1"),
+ vector[ascii::string(b"00000000000000000000000000000001::ascii::String")]
+ );
+
+ // supplying invalid struct name
+ let new_test_struct_1_c = struct_tag::new_for_testing(
+ @0x1,
+ ascii::string(b"struct_tag_tests"),
+ ascii::string(b"TestStructGeneric"),
+ vector[ascii::string(b"00000000000000000000000000000001::ascii::String")]
+ );
+
+ // supplying invalid generic
+ let new_test_struct_1_d = struct_tag::new_for_testing(
+ @0x1,
+ ascii::string(b"fake_module_name"),
+ ascii::string(b"TestStructGeneric1"),
+ vector[ascii::string(b"00000000000000000000000000000001::string::String")]
+ );
+
+ // supplying incorrectly positioned generics
+ let new_test_struct_3_a = struct_tag::new_for_testing(
+ @0x1,
+ ascii::string(b"struct_tag_tests"),
+ ascii::string(b"TestStructGeneric3"),
+ vector[
+ ascii::string(b"address"),
+ ascii::string(b"00000000000000000000000000000001::option::Option"),
+ ascii::string(b"00000000000000000000000000000001::ascii::String"),
+ ]
+ );
+
+ // supplying incomplete generics
+ let new_test_struct_3_b = struct_tag::new_for_testing(
+ @0x1,
+ ascii::string(b"struct_tag_tests"),
+ ascii::string(b"TestStructGeneric3"),
+ vector[
+ ascii::string(b"00000000000000000000000000000001::ascii::String"),
+ ascii::string(b"00000000000000000000000000000001::struct_tag_tests::TestStructGeneric2<00000000000000000000000000000001::ascii::String,00000000000000000000000000000001::option::Option>")
+ ]
+ );
+
+ assert!(struct_tag::get>() == new_test_struct_1_a, 0);
+ assert!(struct_tag::get>() == new_test_struct_1_b, 0);
+ assert!(struct_tag::get>() == new_test_struct_1_c, 0);
+ assert!(struct_tag::get>() == new_test_struct_1_d, 0);
+ assert!(struct_tag::get>>() == new_test_struct_3_a, 0);
+ assert!(struct_tag::get>>>() == new_test_struct_3_b, 0);
+ }
+
+ #[test]
+ #[expected_failure(abort_code = 0, location = std::struct_tag)]
+ fun test_invalid_struct_type_failure() {
+ // supplying type that is not a struct
+ struct_tag::get();
+ struct_tag::get();
+ }
+}