1use 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#[Object]
133impl MoveType {
134 async fn repr(&self) -> String {
136 self.native.to_canonical_string(true)
137 }
138
139 async fn signature(&self) -> Result<MoveTypeSignature> {
141 self.signature_impl().extend()
143 }
144
145 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 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(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(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(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(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
342pub(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}