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
(); + } +}