Skip to main content

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