iota_graphql_rpc/types/
move_type.rs

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