Skip to content

Commit

Permalink
[GraphQL] MoveType and MoveTypeSignature (MystenLabs#14294)
Browse files Browse the repository at this point in the history
## Description

Implement `MoveType` and `MoveTypeSignature` from the RFC in MystenLabs#13955 and
add `type` as a field to `MoveValue`.

`MoveType` is missing its layout field, because this requires support
for converting a type tag into a type layout which will land in an
upcoming PR.

## Test Plan

New unit tests:

```
sui-graphql-rpc$ cargo nextest run  -- move_type
```
  • Loading branch information
amnn authored Oct 18, 2023
1 parent 66a4302 commit 9381fbd
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 28 deletions.
33 changes: 32 additions & 1 deletion crates/sui-graphql-rpc/schema/current_progress_schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,6 @@ type MoveData =
| { Vector: [MoveData] }
| { Option: MoveData? }
| { Struct: [{ name: string, value: MoveData }] }
"""
scalar MoveData

Expand Down Expand Up @@ -372,11 +371,43 @@ type MovePackage {
asObject: Object
}

"""
Represents concrete types (no type parameters, no references)
"""
type MoveType {
"""
Flat representation of the type signature, as a displayable string.
"""
repr: String!
"""
Structured representation of the type signature.
"""
signature: MoveTypeSignature!
}

"""
The signature of a concrete Move Type (a type with all its type
parameters instantiated with concrete types, that contains no
references), corresponding to the following recursive type:
type MoveTypeSignature =
"address"
| "bool"
| "u8" | "u16" | ... | "u256"
| { vector: MoveTypeSignature }
| {
struct: {
package: string,
module: string,
type: string,
typeParameters: [MoveTypeSignature],
}
}
"""
scalar MoveTypeSignature

type MoveValue {
type: MoveType!
bcs: Base64!
data: MoveData!
}
Expand Down
10 changes: 6 additions & 4 deletions crates/sui-graphql-rpc/schema/draft_target_schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,12 @@ scalar MoveData
# | "u8" | "u16" | ... | "u256"
# | { vector: MoveTypeSignature }
# | {
# package: string,
# module: string,
# type: string,
# typeParameters: [MoveTypeSignature]?,
# struct: {
# package: string,
# module: string,
# type: string,
# typeParameters: [MoveTypeSignature],
# }
# }
scalar MoveTypeSignature

Expand Down
4 changes: 1 addition & 3 deletions crates/sui-graphql-rpc/src/context_data/db_data_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1341,9 +1341,7 @@ impl PgManager {
package: SuiAddress::from_array(**e.package_id),
name: e.transaction_module.to_string(),
}),
event_type: Some(MoveType {
repr: e.type_.to_string(),
}),
event_type: Some(MoveType::new(e.type_.to_string())),
senders: Some(vec![Address {
address: SuiAddress::from_array(e.sender.to_inner()),
}]),
Expand Down
1 change: 1 addition & 0 deletions crates/sui-graphql-rpc/src/types/move_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ impl MoveObject {
.extend()?;

return Ok(Some(MoveValue::new(
type_tag.to_string(),
type_layout,
self.native_object
.data
Expand Down
184 changes: 181 additions & 3 deletions crates/sui-graphql-rpc/src/types/move_type.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,189 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use std::str::FromStr;

use async_graphql::*;
use move_core_types::language_storage::TypeTag;
use serde::{Deserialize, Serialize};

use crate::error::{code, graphql_error};

/// Represents concrete types (no type parameters, no references)
#[derive(SimpleObject)]
#[graphql(complex)]
pub(crate) struct MoveType {
pub repr: String,
// typeName: MoveTypeName!
// typeParameters: [MoveType]
/// Flat representation of the type signature, as a displayable string.
repr: String,
}

scalar!(
MoveTypeSignature,
"MoveTypeSignature",
r#"The signature of a concrete Move Type (a type with all its type
parameters instantiated with concrete types, that contains no
references), corresponding to the following recursive type:
type MoveTypeSignature =
"address"
| "bool"
| "u8" | "u16" | ... | "u256"
| { vector: MoveTypeSignature }
| {
struct: {
package: string,
module: string,
type: string,
typeParameters: [MoveTypeSignature],
}
}"#
);

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub(crate) enum MoveTypeSignature {
Address,
Bool,
U8,
U16,
U32,
U64,
U128,
U256,
Vector(Box<MoveTypeSignature>),
Struct {
package: String,
module: String,
#[serde(rename = "type")]
type_: String,
type_parameters: Vec<MoveTypeSignature>,
},
}

#[ComplexObject]
impl MoveType {
/// Structured representation of the type signature.
async fn signature(&self) -> Result<MoveTypeSignature> {
// Factor out into its own non-GraphQL, non-async function for better testability
self.signature_impl()
}
}

impl MoveType {
pub(crate) fn new(repr: String) -> MoveType {
Self { repr }
}

fn signature_impl(&self) -> Result<MoveTypeSignature> {
let tag = TypeTag::from_str(&self.repr).map_err(|e| {
graphql_error(
code::INTERNAL_SERVER_ERROR,
format!("Error parsing type '{}': {e}", self.repr),
)
})?;

MoveTypeSignature::try_from(tag)
}
}

impl TryFrom<TypeTag> for MoveTypeSignature {
type Error = async_graphql::Error;

fn try_from(tag: TypeTag) -> Result<Self> {
use TypeTag as T;

Ok(match tag {
T::Signer => return Err(unexpected_signer_error()),

T::U8 => Self::U8,
T::U16 => Self::U16,
T::U32 => Self::U32,
T::U64 => Self::U64,
T::U128 => Self::U128,
T::U256 => Self::U256,

T::Bool => Self::Bool,
T::Address => Self::Address,

T::Vector(v) => Self::Vector(Box::new(MoveTypeSignature::try_from(*v)?)),

T::Struct(s) => Self::Struct {
package: format!("0x{}", s.address.to_canonical_string()),
module: s.module.to_string(),
type_: s.name.to_string(),
type_parameters: s
.type_params
.into_iter()
.map(MoveTypeSignature::try_from)
.collect::<Result<Vec<_>>>()?,
},
})
}
}

/// Error from seeing a `signer` value or type, which shouldn't be possible in Sui Move.
pub(crate) fn unexpected_signer_error() -> Error {
graphql_error(
code::INTERNAL_SERVER_ERROR,
"Unexpected value of type: signer.",
)
.into()
}

#[cfg(test)]
mod tests {
use super::*;

use expect_test::expect;

fn signature(repr: impl Into<String>) -> Result<MoveTypeSignature> {
MoveType::new(repr.into()).signature_impl()
}

#[test]
fn complex_type() {
let sig = signature("vector<0x42::foo::Bar<address, u32, bool, u256>>").unwrap();
let expect = expect![[r#"
Vector(
Struct {
package: "0x0000000000000000000000000000000000000000000000000000000000000042",
module: "foo",
type_: "Bar",
type_parameters: [
Address,
U32,
Bool,
U256,
],
},
)"#]];
expect.assert_eq(&format!("{sig:#?}"));
}

#[test]
fn tag_parse_error() {
let err = signature("not_a_type").unwrap_err();
let expect = expect![[
r#"Error { message: "Error parsing type 'not_a_type': unexpected token Name(\"not_a_type\"), expected type tag", extensions: None }"#
]];
expect.assert_eq(&format!("{err:?}"));
}

#[test]
fn signer_type() {
let err = signature("signer").unwrap_err();
let expect = expect![[
r#"Error { message: "Unexpected value of type: signer.", extensions: None }"#
]];
expect.assert_eq(&format!("{err:?}"));
}

#[test]
fn nested_signer_type() {
let err = signature("0x42::baz::Qux<u32, vector<signer>>").unwrap_err();
let expect = expect![[
r#"Error { message: "Unexpected value of type: signer.", extensions: None }"#
]];
expect.assert_eq(&format!("{err:?}"));
}
}
45 changes: 29 additions & 16 deletions crates/sui-graphql-rpc/src/types/move_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ use move_core_types::{
};
use serde::{Deserialize, Serialize};

use crate::error::{code, graphql_error};
use crate::{
error::{code, graphql_error},
types::move_type::unexpected_signer_error,
};

use super::{base64::Base64, big_int::BigInt, sui_address::SuiAddress};
use super::{base64::Base64, big_int::BigInt, move_type::MoveType, sui_address::SuiAddress};

const STD: AccountAddress = AccountAddress::ONE;
const SUI: AccountAddress = AccountAddress::TWO;
Expand All @@ -31,6 +34,8 @@ const TYP_UID: &IdentStr = ident_str!("UID");
#[derive(SimpleObject)]
#[graphql(complex)]
pub(crate) struct MoveValue {
#[graphql(name = "type")]
type_: MoveType,
#[graphql(skip)]
layout: MoveTypeLayout,
bcs: Base64,
Expand All @@ -49,8 +54,7 @@ type MoveData =
| { String: string }
| { Vector: [MoveData] }
| { Option: MoveData? }
| { Struct: [{ name: string, value: MoveData }] }
"#
| { Struct: [{ name: string, value: MoveData }] }"#
);

#[derive(Serialize, Deserialize, Debug)]
Expand Down Expand Up @@ -81,8 +85,9 @@ impl MoveValue {
}

impl MoveValue {
pub fn new(layout: MoveTypeLayout, bcs: Base64) -> Self {
Self { layout, bcs }
pub fn new(repr: String, layout: MoveTypeLayout, bcs: Base64) -> Self {
let type_ = MoveType::new(repr);
Self { type_, layout, bcs }
}

fn data_impl(&self) -> Result<MoveData> {
Expand Down Expand Up @@ -149,13 +154,7 @@ impl TryFrom<value::MoveValue> for MoveData {
}

// Sui does not support `signer` as a type.
V::Signer(_) => {
return Err(graphql_error(
code::INTERNAL_SERVER_ERROR,
"Unexpected value of type: signer.",
)
.into())
}
V::Signer(_) => return Err(unexpected_signer_error()),
})
}
}
Expand Down Expand Up @@ -367,8 +366,22 @@ mod tests {
}

fn data<T: Serialize>(layout: MoveTypeLayout, data: T) -> Result<MoveData> {
let tag: TypeTag = (&layout).try_into().expect("Error fetching type tag");

// The format for type from its `Display` impl does not technically match the format that
// the RPC expects from the data layer (where a type's package should be canonicalized), but
// it will suffice.
data_with_tag(format!("{}", tag), layout, data)
}

fn data_with_tag<T: Serialize>(
tag: impl Into<String>,
layout: MoveTypeLayout,
data: T,
) -> Result<MoveData> {
let type_ = MoveType::new(tag.into());
let bcs = Base64(bcs::to_bytes(&data).unwrap());
MoveValue { layout, bcs }.data_impl()
MoveValue { type_, layout, bcs }.data_impl()
}

#[test]
Expand Down Expand Up @@ -666,7 +679,7 @@ mod tests {
layout: vector_layout!(L::U8),
}]));

let v = data(l, "Hello, world!");
let v = data_with_tag("0x1::string::String", l, "Hello, world!");
let expect = expect![[r#"
Err(
Error {
Expand All @@ -681,7 +694,7 @@ mod tests {
fn no_field_information() {
// Even less information about the layout -- even less likely to succeed.
let l = L::Struct(S::Runtime(vec![vector_layout!(L::U8)]));
let v = data(l, "Hello, world!");
let v = data_with_tag("0x1::string::String", l, "Hello, world!");
let expect = expect![[r#"
Err(
Error {
Expand Down
Loading

0 comments on commit 9381fbd

Please sign in to comment.