1use async_graphql::{
6 connection::{Connection, CursorType, Edge},
7 *,
8};
9use iota_package_resolver::Module as ParsedMoveModule;
10use move_disassembler::disassembler::Disassembler;
11use move_ir_types::location::Loc;
12
13use crate::{
14 consistency::{ConsistentIndexCursor, ConsistentNamedCursor},
15 error::Error,
16 types::{
17 base64::Base64,
18 cursor::{JsonCursor, Page},
19 datatype::MoveDatatype,
20 iota_address::IotaAddress,
21 move_enum::MoveEnum,
22 move_function::MoveFunction,
23 move_package::MovePackage,
24 move_struct::MoveStruct,
25 },
26};
27
28#[derive(Clone)]
29pub(crate) struct MoveModule {
30 pub storage_id: IotaAddress,
31 pub native: Vec<u8>,
32 pub parsed: ParsedMoveModule,
33 pub checkpoint_viewed_at: u64,
35}
36
37pub(crate) type CFriend = JsonCursor<ConsistentIndexCursor>;
38pub(crate) type CStruct = JsonCursor<ConsistentNamedCursor>;
39pub(crate) type CFunction = JsonCursor<ConsistentNamedCursor>;
40
41#[Object]
44impl MoveModule {
45 async fn package(&self, ctx: &Context<'_>) -> Result<MovePackage> {
47 MovePackage::query(
48 ctx,
49 self.storage_id,
50 MovePackage::by_id_at(self.checkpoint_viewed_at),
51 )
52 .await
53 .extend()?
54 .ok_or_else(|| {
55 Error::Internal(format!(
56 "Cannot load package for module {}::{}",
57 self.storage_id,
58 self.parsed.name(),
59 ))
60 })
61 .extend()
62 }
63
64 async fn name(&self) -> &str {
66 self.parsed.name()
67 }
68
69 async fn file_format_version(&self) -> u32 {
71 self.parsed.bytecode().version
72 }
73
74 async fn friends(
77 &self,
78 ctx: &Context<'_>,
79 first: Option<u64>,
80 after: Option<CFriend>,
81 last: Option<u64>,
82 before: Option<CFriend>,
83 ) -> Result<Connection<String, MoveModule>> {
84 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
85 let bytecode = self.parsed.bytecode();
86
87 let mut connection = Connection::new(false, false);
88 let Some((prev, next, checkpoint_viewed_at, cs)) = page
89 .paginate_consistent_indices(bytecode.friend_decls.len(), self.checkpoint_viewed_at)?
90 else {
91 return Ok(connection);
92 };
93
94 connection.has_previous_page = prev;
95 connection.has_next_page = next;
96
97 let runtime_id = *bytecode.self_id().address();
98 let Some(package) = MovePackage::query(
99 ctx,
100 self.storage_id,
101 MovePackage::by_id_at(checkpoint_viewed_at),
102 )
103 .await
104 .extend()?
105 else {
106 return Err(Error::Internal(format!(
107 "Failed to load package for module: {}",
108 self.storage_id,
109 ))
110 .extend());
111 };
112
113 for c in cs {
116 let decl = &bytecode.friend_decls[c.ix];
117 let friend_pkg = bytecode.address_identifier_at(decl.address);
118 let friend_mod = bytecode.identifier_at(decl.name);
119
120 if friend_pkg != &runtime_id {
121 return Err(Error::Internal(format!(
122 "Friend module of {} from a different package: {}::{}",
123 runtime_id.to_canonical_display(true),
124 friend_pkg.to_canonical_display(true),
125 friend_mod,
126 ))
127 .extend());
128 }
129
130 let Some(friend) = package.module_impl(friend_mod.as_str()).extend()? else {
131 return Err(Error::Internal(format!(
132 "Failed to load friend module of {}::{}: {}",
133 self.storage_id,
134 self.parsed.name(),
135 friend_mod,
136 ))
137 .extend());
138 };
139
140 connection.edges.push(Edge::new(c.encode_cursor(), friend));
141 }
142
143 Ok(connection)
144 }
145
146 #[graphql(name = "struct")]
148 async fn struct_(&self, name: String) -> Result<Option<MoveStruct>> {
149 self.struct_impl(name).extend()
150 }
151
152 async fn structs(
154 &self,
155 ctx: &Context<'_>,
156 first: Option<u64>,
157 after: Option<CStruct>,
158 last: Option<u64>,
159 before: Option<CStruct>,
160 ) -> Result<Option<Connection<String, MoveStruct>>> {
161 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
162 let after = page.after().map(|a| a.name.as_str());
163 let before = page.before().map(|b| b.name.as_str());
164 let struct_range = self.parsed.structs(after, before);
165
166 let cursor_viewed_at = page.validate_cursor_consistency()?;
167 let checkpoint_viewed_at = cursor_viewed_at.unwrap_or(self.checkpoint_viewed_at);
168
169 let mut connection = Connection::new(false, false);
170 let struct_names = if page.is_from_front() {
171 struct_range.take(page.limit()).collect()
172 } else {
173 let mut names: Vec<_> = struct_range.rev().take(page.limit()).collect();
174 names.reverse();
175 names
176 };
177
178 connection.has_previous_page = struct_names
179 .first()
180 .is_some_and(|fst| self.parsed.structs(None, Some(fst)).next().is_some());
181
182 connection.has_next_page = struct_names
183 .last()
184 .is_some_and(|lst| self.parsed.structs(Some(lst), None).next().is_some());
185
186 for name in struct_names {
187 let Some(struct_) = self.struct_impl(name.to_string()).extend()? else {
188 return Err(Error::Internal(format!(
189 "Cannot deserialize struct {name} in module {}::{}",
190 self.storage_id,
191 self.parsed.name(),
192 )))
193 .extend();
194 };
195
196 let cursor = JsonCursor::new(ConsistentNamedCursor {
197 name: name.to_string(),
198 c: checkpoint_viewed_at,
199 })
200 .encode_cursor();
201 connection.edges.push(Edge::new(cursor, struct_));
202 }
203
204 if connection.edges.is_empty() {
205 Ok(None)
206 } else {
207 Ok(Some(connection))
208 }
209 }
210
211 #[graphql(name = "enum")]
213 async fn enum_(&self, name: String) -> Result<Option<MoveEnum>> {
214 self.enum_impl(name).extend()
215 }
216
217 async fn enums(
219 &self,
220 ctx: &Context<'_>,
221 first: Option<u64>,
222 after: Option<CStruct>,
223 last: Option<u64>,
224 before: Option<CStruct>,
225 ) -> Result<Option<Connection<String, MoveEnum>>> {
226 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
227 let after = page.after().map(|a| a.name.as_str());
228 let before = page.before().map(|b| b.name.as_str());
229 let enum_range = self.parsed.enums(after, before);
230
231 let cursor_viewed_at = page.validate_cursor_consistency()?;
232 let checkpoint_viewed_at = cursor_viewed_at.unwrap_or(self.checkpoint_viewed_at);
233
234 let mut connection = Connection::new(false, false);
235 let enum_names = if page.is_from_front() {
236 enum_range.take(page.limit()).collect()
237 } else {
238 let mut names: Vec<_> = enum_range.rev().take(page.limit()).collect();
239 names.reverse();
240 names
241 };
242
243 connection.has_previous_page = enum_names
244 .first()
245 .is_some_and(|fst| self.parsed.enums(None, Some(fst)).next().is_some());
246
247 connection.has_next_page = enum_names
248 .last()
249 .is_some_and(|lst| self.parsed.enums(Some(lst), None).next().is_some());
250
251 for name in enum_names {
252 let Some(enum_) = self.enum_impl(name.to_string()).extend()? else {
253 return Err(Error::Internal(format!(
254 "Cannot deserialize enum {name} in module {}::{}",
255 self.storage_id,
256 self.parsed.name(),
257 )))
258 .extend();
259 };
260
261 let cursor = JsonCursor::new(ConsistentNamedCursor {
262 name: name.to_string(),
263 c: checkpoint_viewed_at,
264 })
265 .encode_cursor();
266 connection.edges.push(Edge::new(cursor, enum_));
267 }
268
269 if connection.edges.is_empty() {
270 Ok(None)
271 } else {
272 Ok(Some(connection))
273 }
274 }
275
276 async fn datatype(&self, name: String) -> Result<Option<MoveDatatype>> {
279 match self.struct_impl(name.clone()) {
280 Ok(Some(s)) => Ok(Some(MoveDatatype::Struct(s))),
281 Ok(None) => self
282 .enum_impl(name)
283 .map(|x| x.map(MoveDatatype::Enum))
284 .extend(),
285 Err(e) => Err(e.into()),
286 }
287 }
288
289 async fn datatypes(
292 &self,
293 ctx: &Context<'_>,
294 first: Option<u64>,
295 after: Option<CStruct>,
296 last: Option<u64>,
297 before: Option<CStruct>,
298 ) -> Result<Option<Connection<String, MoveDatatype>>> {
299 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
300 let after = page.after().map(|a| a.name.as_str());
301 let before = page.before().map(|b| b.name.as_str());
302 let datatype_range = self.parsed.datatypes(after, before);
303
304 let cursor_viewed_at = page.validate_cursor_consistency()?;
305 let checkpoint_viewed_at = cursor_viewed_at.unwrap_or(self.checkpoint_viewed_at);
306
307 let mut connection = Connection::new(false, false);
308 let datatype_names = if page.is_from_front() {
309 datatype_range.take(page.limit()).collect()
310 } else {
311 let mut names: Vec<_> = datatype_range.rev().take(page.limit()).collect();
312 names.reverse();
313 names
314 };
315
316 connection.has_previous_page = datatype_names
317 .first()
318 .is_some_and(|fst| self.parsed.datatypes(None, Some(fst)).next().is_some());
319
320 connection.has_next_page = datatype_names
321 .last()
322 .is_some_and(|lst| self.parsed.datatypes(Some(lst), None).next().is_some());
323
324 for name in datatype_names {
325 let datatype = match self.struct_impl(name.to_string()) {
326 Ok(None) => self
327 .enum_impl(name.to_string())
328 .map(|x| x.map(MoveDatatype::Enum))
329 .extend()?,
330 Ok(Some(s)) => Some(MoveDatatype::Struct(s)),
331 Err(e) => return Err(e.into()),
332 }
333 .ok_or_else(|| {
334 Error::Internal(format!(
335 "Cannot deserialize datatype {name} in module {}::{}",
336 self.storage_id,
337 self.parsed.name(),
338 ))
339 })?;
340
341 let cursor = JsonCursor::new(ConsistentNamedCursor {
342 name: name.to_string(),
343 c: checkpoint_viewed_at,
344 })
345 .encode_cursor();
346 connection.edges.push(Edge::new(cursor, datatype));
347 }
348
349 if connection.edges.is_empty() {
350 Ok(None)
351 } else {
352 Ok(Some(connection))
353 }
354 }
355
356 async fn function(&self, name: String) -> Result<Option<MoveFunction>> {
358 self.function_impl(name).extend()
359 }
360
361 async fn functions(
363 &self,
364 ctx: &Context<'_>,
365 first: Option<u64>,
366 after: Option<CFunction>,
367 last: Option<u64>,
368 before: Option<CFunction>,
369 ) -> Result<Option<Connection<String, MoveFunction>>> {
370 let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?;
371 let after = page.after().map(|a| a.name.as_str());
372 let before = page.before().map(|b| b.name.as_str());
373 let function_range = self.parsed.functions(after, before);
374
375 let cursor_viewed_at = page.validate_cursor_consistency()?;
376 let checkpoint_viewed_at = cursor_viewed_at.unwrap_or(self.checkpoint_viewed_at);
377
378 let mut connection = Connection::new(false, false);
379 let function_names = if page.is_from_front() {
380 function_range.take(page.limit()).collect()
381 } else {
382 let mut names: Vec<_> = function_range.rev().take(page.limit()).collect();
383 names.reverse();
384 names
385 };
386
387 connection.has_previous_page = function_names
388 .first()
389 .is_some_and(|fst| self.parsed.functions(None, Some(fst)).next().is_some());
390
391 connection.has_next_page = function_names
392 .last()
393 .is_some_and(|lst| self.parsed.functions(Some(lst), None).next().is_some());
394
395 for name in function_names {
396 let Some(function) = self.function_impl(name.to_string()).extend()? else {
397 return Err(Error::Internal(format!(
398 "Cannot deserialize function {name} in module {}::{}",
399 self.storage_id,
400 self.parsed.name(),
401 )))
402 .extend();
403 };
404
405 let cursor = JsonCursor::new(ConsistentNamedCursor {
406 name: name.to_string(),
407 c: checkpoint_viewed_at,
408 })
409 .encode_cursor();
410 connection.edges.push(Edge::new(cursor, function));
411 }
412
413 if connection.edges.is_empty() {
414 Ok(None)
415 } else {
416 Ok(Some(connection))
417 }
418 }
419
420 async fn bytes(&self) -> Option<Base64> {
422 Some(Base64::from(self.native.clone()))
423 }
424
425 async fn disassembly(&self) -> Result<Option<String>> {
427 Ok(Some(
428 Disassembler::from_module(self.parsed.bytecode(), Loc::invalid())
429 .map_err(|e| Error::Internal(format!("Error creating disassembler: {e}")))
430 .extend()?
431 .disassemble()
432 .map_err(|e| Error::Internal(format!("Error creating disassembly: {e}")))
433 .extend()?,
434 ))
435 }
436}
437
438impl MoveModule {
439 fn struct_impl(&self, name: String) -> Result<Option<MoveStruct>, Error> {
440 let def = match self.parsed.struct_def(&name) {
441 Ok(Some(def)) => def,
442 Ok(None) => return Ok(None),
443 Err(e) => return Err(Error::Internal(e.to_string())),
444 };
445
446 MoveStruct::new(
447 self.parsed.name().to_string(),
448 name,
449 def,
450 self.checkpoint_viewed_at,
451 )
452 .map(Option::Some)
453 }
454
455 fn enum_impl(&self, name: String) -> Result<Option<MoveEnum>, Error> {
456 let def = match self.parsed.enum_def(&name) {
457 Ok(Some(def)) => def,
458 Ok(None) => return Ok(None),
459 Err(e) => return Err(Error::Internal(e.to_string())),
460 };
461
462 MoveEnum::new(
463 self.parsed.name().to_string(),
464 name,
465 def,
466 self.checkpoint_viewed_at,
467 )
468 .map(Option::Some)
469 }
470
471 pub(crate) fn function_impl(&self, name: String) -> Result<Option<MoveFunction>, Error> {
472 let def = match self.parsed.function_def(&name) {
473 Ok(Some(def)) => def,
474 Ok(None) => return Ok(None),
475 Err(e) => return Err(Error::Internal(e.to_string())),
476 };
477
478 Ok(Some(MoveFunction::new(
479 self.storage_id,
480 self.parsed.name().to_string(),
481 name,
482 def,
483 self.checkpoint_viewed_at,
484 )))
485 }
486
487 pub(crate) async fn query(
488 ctx: &Context<'_>,
489 address: IotaAddress,
490 name: &str,
491 checkpoint_viewed_at: u64,
492 ) -> Result<Option<Self>, Error> {
493 let Some(package) =
494 MovePackage::query(ctx, address, MovePackage::by_id_at(checkpoint_viewed_at)).await?
495 else {
496 return Ok(None);
497 };
498
499 package.module_impl(name)
500 }
501}