sui_graphql_rpc/types/
move_type.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use async_graphql::*;
5use move_binary_format::file_format::AbilitySet;
6use move_core_types::{annotated_value as A, language_storage::TypeTag};
7use serde::{Deserialize, Serialize};
8use sui_types::base_types::MoveObjectType;
9use sui_types::type_input::TypeInput;
10
11use crate::data::package_resolver::PackageResolver;
12use crate::error::Error;
13
14use super::open_move_type::MoveAbility;
15
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub(crate) struct MoveType {
18    pub native: TypeInput,
19}
20
21scalar!(
22    MoveTypeSignature,
23    "MoveTypeSignature",
24    "The signature of a concrete Move Type (a type with all its type parameters instantiated with \
25     concrete types, that contains no references), corresponding to the following recursive type:
26
27type MoveTypeSignature =
28    \"address\"
29  | \"bool\"
30  | \"u8\" | \"u16\" | ... | \"u256\"
31  | { vector: MoveTypeSignature }
32  | {
33      datatype: {
34        package: string,
35        module: string,
36        type: string,
37        typeParameters: [MoveTypeSignature],
38      }
39    }"
40);
41
42scalar!(
43    MoveTypeLayout,
44    "MoveTypeLayout",
45    "The shape of a concrete Move Type (a type with all its type parameters instantiated with \
46     concrete types), corresponding to the following recursive type:
47
48type MoveTypeLayout =
49    \"address\"
50  | \"bool\"
51  | \"u8\" | \"u16\" | ... | \"u256\"
52  | { vector: MoveTypeLayout }
53  | {
54      struct: {
55        type: string,
56        fields: [{ name: string, layout: MoveTypeLayout }],
57      }
58    }
59  | { enum: [{
60          type: string,
61          variants: [{
62              name: string,
63              fields: [{ name: string, layout: MoveTypeLayout }],
64          }]
65      }]
66  }"
67);
68
69#[derive(Serialize, Deserialize, Debug)]
70#[serde(rename_all = "camelCase")]
71pub(crate) enum MoveTypeSignature {
72    Address,
73    Bool,
74    U8,
75    U16,
76    U32,
77    U64,
78    U128,
79    U256,
80    Vector(Box<MoveTypeSignature>),
81    Datatype {
82        package: String,
83        module: String,
84        #[serde(rename = "type")]
85        type_: String,
86        #[serde(rename = "typeParameters")]
87        type_parameters: Vec<MoveTypeSignature>,
88    },
89}
90
91#[derive(Serialize, Deserialize)]
92#[serde(rename_all = "camelCase")]
93pub(crate) enum MoveTypeLayout {
94    Address,
95    Bool,
96    U8,
97    U16,
98    U32,
99    U64,
100    U128,
101    U256,
102    Vector(Box<MoveTypeLayout>),
103    Struct(MoveStructLayout),
104    Enum(MoveEnumLayout),
105}
106
107#[derive(Serialize, Deserialize)]
108pub(crate) struct MoveStructLayout {
109    #[serde(rename = "type")]
110    type_: String,
111    fields: Vec<MoveFieldLayout>,
112}
113
114#[derive(Serialize, Deserialize)]
115pub(crate) struct MoveEnumLayout {
116    variants: Vec<MoveVariantLayout>,
117}
118
119#[derive(Serialize, Deserialize)]
120pub(crate) struct MoveVariantLayout {
121    name: String,
122    layout: Vec<MoveFieldLayout>,
123}
124
125#[derive(Serialize, Deserialize)]
126pub(crate) struct MoveFieldLayout {
127    name: String,
128    layout: MoveTypeLayout,
129}
130
131/// Represents concrete types (no type parameters, no references).
132#[Object]
133impl MoveType {
134    /// Flat representation of the type signature, as a displayable string.
135    async fn repr(&self) -> String {
136        self.native.to_canonical_string(/* with_prefix */ true)
137    }
138
139    /// Structured representation of the type signature.
140    async fn signature(&self) -> Result<MoveTypeSignature> {
141        // Factor out into its own non-GraphQL, non-async function for better testability
142        self.signature_impl().extend()
143    }
144
145    /// Structured representation of the "shape" of values that match this type. May return no
146    /// layout if the type is invalid.
147    async fn layout(&self, ctx: &Context<'_>) -> Result<Option<MoveTypeLayout>> {
148        let resolver: &PackageResolver = ctx
149            .data()
150            .map_err(|_| Error::Internal("Unable to fetch Package Cache.".to_string()))
151            .extend()?;
152
153        let Some(layout) = self.layout_impl(resolver).await.extend()? else {
154            return Ok(None);
155        };
156
157        Ok(Some(MoveTypeLayout::try_from(layout).extend()?))
158    }
159
160    /// The abilities this concrete type has. Returns no abilities if the type is invalid.
161    async fn abilities(&self, ctx: &Context<'_>) -> Result<Option<Vec<MoveAbility>>> {
162        let resolver: &PackageResolver = ctx
163            .data()
164            .map_err(|_| Error::Internal("Unable to fetch Package Cache.".to_string()))
165            .extend()?;
166
167        let Some(abilities) = self.abilities_impl(resolver).await.extend()? else {
168            return Ok(None);
169        };
170
171        Ok(Some(abilities.into_iter().map(MoveAbility::from).collect()))
172    }
173}
174
175impl MoveType {
176    fn signature_impl(&self) -> Result<MoveTypeSignature, Error> {
177        MoveTypeSignature::try_from(self.native.clone())
178    }
179
180    pub(crate) async fn layout_impl(
181        &self,
182        resolver: &PackageResolver,
183    ) -> Result<Option<A::MoveTypeLayout>, Error> {
184        let Ok(tag) = self.native.to_type_tag() else {
185            return Ok(None);
186        };
187
188        Ok(Some(resolver.type_layout(tag).await.map_err(|e| {
189            Error::Internal(format!(
190                "Error calculating layout for {}: {e}",
191                self.native.to_canonical_display(/* with_prefix */ true),
192            ))
193        })?))
194    }
195
196    pub(crate) async fn abilities_impl(
197        &self,
198        resolver: &PackageResolver,
199    ) -> Result<Option<AbilitySet>, Error> {
200        let Ok(tag) = self.native.to_type_tag() else {
201            return Ok(None);
202        };
203
204        Ok(Some(resolver.abilities(tag).await.map_err(|e| {
205            Error::Internal(format!(
206                "Error calculating abilities for {}: {e}",
207                self.native.to_canonical_string(/* with_prefix */ true),
208            ))
209        })?))
210    }
211}
212
213impl From<MoveObjectType> for MoveType {
214    fn from(obj: MoveObjectType) -> Self {
215        let tag: TypeTag = obj.into();
216        Self { native: tag.into() }
217    }
218}
219
220impl From<TypeTag> for MoveType {
221    fn from(tag: TypeTag) -> Self {
222        Self { native: tag.into() }
223    }
224}
225
226impl From<TypeInput> for MoveType {
227    fn from(native: TypeInput) -> Self {
228        Self { native }
229    }
230}
231
232impl TryFrom<TypeInput> for MoveTypeSignature {
233    type Error = Error;
234
235    fn try_from(tag: TypeInput) -> Result<Self, Error> {
236        use TypeInput as T;
237
238        Ok(match tag {
239            T::Signer => return Err(unexpected_signer_error()),
240
241            T::U8 => Self::U8,
242            T::U16 => Self::U16,
243            T::U32 => Self::U32,
244            T::U64 => Self::U64,
245            T::U128 => Self::U128,
246            T::U256 => Self::U256,
247
248            T::Bool => Self::Bool,
249            T::Address => Self::Address,
250
251            T::Vector(v) => Self::Vector(Box::new(Self::try_from(*v)?)),
252
253            T::Struct(s) => Self::Datatype {
254                package: s.address.to_canonical_string(/* with_prefix */ true),
255                module: s.module,
256                type_: s.name,
257                type_parameters: s
258                    .type_params
259                    .into_iter()
260                    .map(Self::try_from)
261                    .collect::<Result<Vec<_>, _>>()?,
262            },
263        })
264    }
265}
266
267impl TryFrom<A::MoveTypeLayout> for MoveTypeLayout {
268    type Error = Error;
269
270    fn try_from(layout: A::MoveTypeLayout) -> Result<Self, Error> {
271        use A::MoveTypeLayout as TL;
272
273        Ok(match layout {
274            TL::Signer => return Err(unexpected_signer_error()),
275
276            TL::U8 => Self::U8,
277            TL::U16 => Self::U16,
278            TL::U32 => Self::U32,
279            TL::U64 => Self::U64,
280            TL::U128 => Self::U128,
281            TL::U256 => Self::U256,
282
283            TL::Bool => Self::Bool,
284            TL::Address => Self::Address,
285
286            TL::Vector(v) => Self::Vector(Box::new(Self::try_from(*v)?)),
287            TL::Struct(s) => Self::Struct((*s).try_into()?),
288            TL::Enum(e) => Self::Enum((*e).try_into()?),
289        })
290    }
291}
292
293impl TryFrom<A::MoveEnumLayout> for MoveEnumLayout {
294    type Error = Error;
295
296    fn try_from(layout: A::MoveEnumLayout) -> Result<Self, Error> {
297        let A::MoveEnumLayout { variants, .. } = layout;
298        let mut variant_layouts = Vec::new();
299        for ((name, _), variant_fields) in variants {
300            let mut field_layouts = Vec::new();
301            for field in variant_fields {
302                field_layouts.push(MoveFieldLayout::try_from(field)?);
303            }
304            variant_layouts.push(MoveVariantLayout {
305                name: name.to_string(),
306                layout: field_layouts,
307            });
308        }
309
310        Ok(MoveEnumLayout {
311            variants: variant_layouts,
312        })
313    }
314}
315
316impl TryFrom<A::MoveStructLayout> for MoveStructLayout {
317    type Error = Error;
318
319    fn try_from(layout: A::MoveStructLayout) -> Result<Self, Error> {
320        Ok(Self {
321            type_: layout.type_.to_canonical_string(/* with_prefix */ true),
322            fields: layout
323                .fields
324                .into_iter()
325                .map(MoveFieldLayout::try_from)
326                .collect::<Result<_, _>>()?,
327        })
328    }
329}
330
331impl TryFrom<A::MoveFieldLayout> for MoveFieldLayout {
332    type Error = Error;
333
334    fn try_from(layout: A::MoveFieldLayout) -> Result<Self, Error> {
335        Ok(Self {
336            name: layout.name.to_string(),
337            layout: layout.layout.try_into()?,
338        })
339    }
340}
341
342/// Error from seeing a `signer` value or type, which shouldn't be possible in Sui Move.
343pub(crate) fn unexpected_signer_error() -> Error {
344    Error::Internal("Unexpected value of type: signer.".to_string())
345}
346
347#[cfg(test)]
348mod tests {
349    use std::str::FromStr;
350
351    use super::*;
352
353    use expect_test::expect;
354
355    fn signature(repr: impl Into<String>) -> Result<MoveTypeSignature, Error> {
356        let tag = TypeTag::from_str(repr.into().as_str()).unwrap();
357        MoveType::from(tag).signature_impl()
358    }
359
360    #[test]
361    fn complex_type() {
362        let sig = signature("vector<0x42::foo::Bar<address, u32, bool, u256>>").unwrap();
363        let expect = expect![[r#"
364            Vector(
365                Datatype {
366                    package: "0x0000000000000000000000000000000000000000000000000000000000000042",
367                    module: "foo",
368                    type_: "Bar",
369                    type_parameters: [
370                        Address,
371                        U32,
372                        Bool,
373                        U256,
374                    ],
375                },
376            )"#]];
377        expect.assert_eq(&format!("{sig:#?}"));
378    }
379
380    #[test]
381    fn signer_type() {
382        let err = signature("signer").unwrap_err();
383        let expect = expect![[r#"Internal("Unexpected value of type: signer.")"#]];
384        expect.assert_eq(&format!("{err:?}"));
385    }
386
387    #[test]
388    fn nested_signer_type() {
389        let err = signature("0x42::baz::Qux<u32, vector<signer>>").unwrap_err();
390        let expect = expect![[r#"Internal("Unexpected value of type: signer.")"#]];
391        expect.assert_eq(&format!("{err:?}"));
392    }
393}