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;
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: TypeTag,
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}
105
106#[derive(Serialize, Deserialize)]
107pub(crate) struct MoveStructLayout {
108    #[serde(rename = "type")]
109    type_: String,
110    fields: Vec<MoveFieldLayout>,
111}
112
113#[derive(Serialize, Deserialize)]
114pub(crate) struct MoveEnumLayout {
115    variants: Vec<MoveVariantLayout>,
116}
117
118#[derive(Serialize, Deserialize)]
119pub(crate) struct MoveVariantLayout {
120    name: String,
121    layout: Vec<MoveFieldLayout>,
122}
123
124#[derive(Serialize, Deserialize)]
125pub(crate) struct MoveFieldLayout {
126    name: String,
127    layout: MoveTypeLayout,
128}
129
130/// Represents concrete types (no type parameters, no references).
131#[Object]
132impl MoveType {
133    /// Flat representation of the type signature, as a displayable string.
134    async fn repr(&self) -> String {
135        self.native.to_canonical_string(/* with_prefix */ true)
136    }
137
138    /// Structured representation of the type signature.
139    async fn signature(&self) -> Result<MoveTypeSignature> {
140        // Factor out into its own non-GraphQL, non-async function for better
141        // testability
142        self.signature_impl().extend()
143    }
144
145    /// Structured representation of the "shape" of values that match this type.
146    async fn layout(&self, ctx: &Context<'_>) -> Result<MoveTypeLayout> {
147        let resolver: &PackageResolver = ctx
148            .data()
149            .map_err(|_| Error::Internal("Unable to fetch Package Cache.".to_string()))
150            .extend()?;
151
152        MoveTypeLayout::try_from(self.layout_impl(resolver).await.extend()?).extend()
153    }
154
155    /// The abilities this concrete type has.
156    async fn abilities(&self, ctx: &Context<'_>) -> Result<Vec<MoveAbility>> {
157        let resolver: &PackageResolver = ctx
158            .data()
159            .map_err(|_| Error::Internal("Unable to fetch Package Cache.".to_string()))
160            .extend()?;
161
162        Ok(self
163            .abilities_impl(resolver)
164            .await
165            .extend()?
166            .into_iter()
167            .map(MoveAbility::from)
168            .collect())
169    }
170}
171
172impl MoveType {
173    pub(crate) fn new(native: TypeTag) -> MoveType {
174        Self { native }
175    }
176
177    fn signature_impl(&self) -> Result<MoveTypeSignature, Error> {
178        MoveTypeSignature::try_from(self.native.clone())
179    }
180
181    pub(crate) async fn layout_impl(
182        &self,
183        resolver: &PackageResolver,
184    ) -> Result<A::MoveTypeLayout, Error> {
185        resolver
186            .type_layout(self.native.clone())
187            .await
188            .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<AbilitySet, Error> {
200        resolver.abilities(self.native.clone()).await.map_err(|e| {
201            Error::Internal(format!(
202                "Error calculating abilities for {}: {e}",
203                self.native.to_canonical_string(/* with_prefix */ true),
204            ))
205        })
206    }
207}
208
209impl From<MoveObjectType> for MoveType {
210    fn from(obj: MoveObjectType) -> Self {
211        let tag: TypeTag = obj.into();
212        Self { native: tag }
213    }
214}
215
216impl From<TypeTag> for MoveType {
217    fn from(tag: TypeTag) -> Self {
218        Self { native: tag }
219    }
220}
221
222impl TryFrom<TypeTag> for MoveTypeSignature {
223    type Error = Error;
224
225    fn try_from(tag: TypeTag) -> Result<Self, Error> {
226        use TypeTag as T;
227
228        Ok(match tag {
229            T::Signer => return Err(unexpected_signer_error()),
230
231            T::U8 => Self::U8,
232            T::U16 => Self::U16,
233            T::U32 => Self::U32,
234            T::U64 => Self::U64,
235            T::U128 => Self::U128,
236            T::U256 => Self::U256,
237
238            T::Bool => Self::Bool,
239            T::Address => Self::Address,
240
241            T::Vector(v) => Self::Vector(Box::new(Self::try_from(*v)?)),
242
243            T::Struct(s) => Self::Datatype {
244                package: s.address.to_canonical_string(/* with_prefix */ true),
245                module: s.module.to_string(),
246                type_: s.name.to_string(),
247                type_parameters: s
248                    .type_params
249                    .into_iter()
250                    .map(Self::try_from)
251                    .collect::<Result<Vec<_>, _>>()?,
252            },
253        })
254    }
255}
256
257impl TryFrom<A::MoveTypeLayout> for MoveTypeLayout {
258    type Error = Error;
259
260    fn try_from(layout: A::MoveTypeLayout) -> Result<Self, Error> {
261        use A::MoveTypeLayout as TL;
262
263        Ok(match layout {
264            TL::Signer => return Err(unexpected_signer_error()),
265
266            TL::U8 => Self::U8,
267            TL::U16 => Self::U16,
268            TL::U32 => Self::U32,
269            TL::U64 => Self::U64,
270            TL::U128 => Self::U128,
271            TL::U256 => Self::U256,
272
273            TL::Bool => Self::Bool,
274            TL::Address => Self::Address,
275
276            TL::Vector(v) => Self::Vector(Box::new(Self::try_from(*v)?)),
277            TL::Struct(s) => Self::Struct((*s).try_into()?),
278            TL::Enum(e) => Self::Enum((*e).try_into()?),
279        })
280    }
281}
282
283impl TryFrom<A::MoveEnumLayout> for MoveEnumLayout {
284    type Error = Error;
285
286    fn try_from(layout: A::MoveEnumLayout) -> Result<Self, Error> {
287        let A::MoveEnumLayout { variants, .. } = layout;
288        let mut variant_layouts = Vec::new();
289        for ((name, _), variant_fields) in variants {
290            let mut field_layouts = Vec::new();
291            for field in variant_fields {
292                field_layouts.push(MoveFieldLayout::try_from(field)?);
293            }
294            variant_layouts.push(MoveVariantLayout {
295                name: name.to_string(),
296                layout: field_layouts,
297            });
298        }
299
300        Ok(MoveEnumLayout {
301            variants: variant_layouts,
302        })
303    }
304}
305
306impl TryFrom<A::MoveStructLayout> for MoveStructLayout {
307    type Error = Error;
308
309    fn try_from(layout: A::MoveStructLayout) -> Result<Self, Error> {
310        Ok(Self {
311            type_: layout.type_.to_canonical_string(/* with_prefix */ true),
312            fields: layout
313                .fields
314                .into_iter()
315                .map(MoveFieldLayout::try_from)
316                .collect::<Result<_, _>>()?,
317        })
318    }
319}
320
321impl TryFrom<A::MoveFieldLayout> for MoveFieldLayout {
322    type Error = Error;
323
324    fn try_from(layout: A::MoveFieldLayout) -> Result<Self, Error> {
325        Ok(Self {
326            name: layout.name.to_string(),
327            layout: layout.layout.try_into()?,
328        })
329    }
330}
331
332/// Error from seeing a `signer` value or type, which shouldn't be possible in
333/// IOTA Move.
334pub(crate) fn unexpected_signer_error() -> Error {
335    Error::Internal("Unexpected value of type: signer.".to_string())
336}
337
338#[cfg(test)]
339mod tests {
340    use std::str::FromStr;
341
342    use expect_test::expect;
343
344    use super::*;
345
346    fn signature(repr: impl Into<String>) -> Result<MoveTypeSignature, Error> {
347        let tag = TypeTag::from_str(repr.into().as_str()).unwrap();
348        MoveType::new(tag).signature_impl()
349    }
350
351    #[test]
352    fn complex_type() {
353        let sig = signature("vector<0x42::foo::Bar<address, u32, bool, u256>>").unwrap();
354        let expect = expect![[r#"
355            Vector(
356                Datatype {
357                    package: "0x0000000000000000000000000000000000000000000000000000000000000042",
358                    module: "foo",
359                    type_: "Bar",
360                    type_parameters: [
361                        Address,
362                        U32,
363                        Bool,
364                        U256,
365                    ],
366                },
367            )"#]];
368        expect.assert_eq(&format!("{sig:#?}"));
369    }
370
371    #[test]
372    fn signer_type() {
373        let err = signature("signer").unwrap_err();
374        let expect = expect![[r#"Internal("Unexpected value of type: signer.")"#]];
375        expect.assert_eq(&format!("{err:?}"));
376    }
377
378    #[test]
379    fn nested_signer_type() {
380        let err = signature("0x42::baz::Qux<u32, vector<signer>>").unwrap_err();
381        let expect = expect![[r#"Internal("Unexpected value of type: signer.")"#]];
382        expect.assert_eq(&format!("{err:?}"));
383    }
384}