1use async_graphql::{
6 connection::{Connection, CursorType, Edge},
7 *,
8};
9use iota_indexer::{models::objects::StoredHistoryObject, types::OwnerType};
10use iota_types::{
11 TypeTag,
12 dynamic_field::{
13 DynamicFieldInfo, DynamicFieldType, derive_dynamic_field_id,
14 visitor::{Field, FieldVisitor},
15 },
16};
17
18use crate::{
19 consistency::{View, build_objects_query},
20 data::{Db, QueryExecutor, package_resolver::PackageResolver},
21 error::Error,
22 filter,
23 raw_query::RawQuery,
24 types::{
25 available_range::AvailableRange,
26 base64::Base64,
27 cursor::{Page, Target},
28 iota_address::IotaAddress,
29 move_object::MoveObject,
30 move_value::MoveValue,
31 object::{self, Object, ObjectKind},
32 type_filter::ExactTypeFilter,
33 },
34};
35
36pub(crate) struct DynamicField {
37 pub super_: MoveObject,
38}
39
40#[derive(Union)]
41pub(crate) enum DynamicFieldValue {
42 MoveObject(MoveObject), MoveValue(MoveValue), }
45
46#[derive(InputObject)] pub(crate) struct DynamicFieldName {
48 pub type_: ExactTypeFilter,
52 pub bcs: Base64,
54}
55
56#[Object]
68impl DynamicField {
69 async fn name(&self, ctx: &Context<'_>) -> Result<Option<MoveValue>> {
73 let resolver: &PackageResolver = ctx.data_unchecked();
74
75 let type_ = TypeTag::from(self.super_.native.type_().clone());
76 let layout = resolver.type_layout(type_.clone()).await.map_err(|e| {
77 Error::Internal(format!(
78 "Error fetching layout for type {}: {e}",
79 type_.to_canonical_display(true)
80 ))
81 })?;
82
83 let Field {
84 name_layout,
85 name_bytes,
86 ..
87 } = FieldVisitor::deserialize(self.super_.native.contents(), &layout)
88 .map_err(|e| Error::Internal(e.to_string()))
89 .extend()?;
90
91 Ok(Some(MoveValue::new(
92 name_layout.into(),
93 Base64::from(name_bytes.to_owned()),
94 )))
95 }
96
97 async fn value(&self, ctx: &Context<'_>) -> Result<Option<DynamicFieldValue>> {
102 let resolver: &PackageResolver = ctx.data_unchecked();
103
104 let type_ = TypeTag::from(self.super_.native.type_().clone());
105 let layout = resolver.type_layout(type_.clone()).await.map_err(|e| {
106 Error::Internal(format!(
107 "Error fetching layout for type {}: {e}",
108 type_.to_canonical_display(true)
109 ))
110 })?;
111
112 let Field {
113 kind,
114 value_layout,
115 value_bytes,
116 ..
117 } = FieldVisitor::deserialize(self.super_.native.contents(), &layout)
118 .map_err(|e| Error::Internal(e.to_string()))
119 .extend()?;
120
121 if kind == DynamicFieldType::DynamicObject {
122 let df_object_id: IotaAddress = bcs::from_bytes(value_bytes)
123 .map_err(|e| Error::Internal(format!("Failed to deserialize object ID: {e}")))
124 .extend()?;
125
126 let obj = MoveObject::query(
127 ctx,
128 df_object_id,
129 Object::under_parent(self.root_version(), self.super_.super_.checkpoint_viewed_at),
130 )
131 .await
132 .extend()?;
133
134 Ok(obj.map(DynamicFieldValue::MoveObject))
135 } else {
136 Ok(Some(DynamicFieldValue::MoveValue(MoveValue::new(
137 value_layout.into(),
138 Base64::from(value_bytes.to_owned()),
139 ))))
140 }
141 }
142}
143
144impl DynamicField {
145 pub(crate) async fn query(
153 ctx: &Context<'_>,
154 parent: IotaAddress,
155 parent_version: Option<u64>,
156 name: DynamicFieldName,
157 kind: DynamicFieldType,
158 checkpoint_viewed_at: u64,
159 ) -> Result<Option<DynamicField>, Error> {
160 let type_ = match kind {
161 DynamicFieldType::DynamicField => name.type_.0,
162 DynamicFieldType::DynamicObject => {
163 DynamicFieldInfo::dynamic_object_field_wrapper(name.type_.0).into()
164 }
165 };
166
167 let field_id = derive_dynamic_field_id(parent, &type_, &name.bcs.0)
168 .map_err(|e| Error::Internal(format!("Failed to derive dynamic field id: {e}")))?;
169
170 let super_ = MoveObject::query(
171 ctx,
172 IotaAddress::from(field_id),
173 if let Some(parent_version) = parent_version {
174 Object::under_parent(parent_version, checkpoint_viewed_at)
175 } else {
176 Object::latest_at(checkpoint_viewed_at)
177 },
178 )
179 .await?;
180
181 super_.map(Self::try_from).transpose()
182 }
183
184 pub(crate) async fn paginate(
191 db: &Db,
192 page: Page<object::Cursor>,
193 parent: IotaAddress,
194 parent_version: Option<u64>,
195 checkpoint_viewed_at: u64,
196 ) -> Result<Connection<String, DynamicField>, Error> {
197 let cursor_viewed_at = page.validate_cursor_consistency()?;
202 let checkpoint_viewed_at = cursor_viewed_at.unwrap_or(checkpoint_viewed_at);
203
204 let Some((prev, next, results)) = db
205 .execute_repeatable(move |conn| {
206 let Some(range) = AvailableRange::result(conn, checkpoint_viewed_at)? else {
207 return Ok::<_, diesel::result::Error>(None);
208 };
209
210 Ok(Some(page.paginate_raw_query::<StoredHistoryObject>(
211 conn,
212 checkpoint_viewed_at,
213 dynamic_fields_query(parent, parent_version, range, &page),
214 )?))
215 })
216 .await?
217 else {
218 return Err(Error::Client(
219 "Requested data is outside the available range".to_string(),
220 ));
221 };
222
223 let mut conn: Connection<String, DynamicField> = Connection::new(prev, next);
224
225 for stored in results {
226 let cursor = stored.cursor(checkpoint_viewed_at).encode_cursor();
229
230 let object = Object::try_from_stored_history_object(
231 stored,
232 checkpoint_viewed_at,
233 parent_version,
234 )?;
235
236 let move_ = MoveObject::try_from(&object).map_err(|_| {
237 Error::Internal(format!(
238 "Failed to deserialize as Move object: {}",
239 object.address
240 ))
241 })?;
242
243 let dynamic_field = DynamicField::try_from(move_)?;
244 conn.edges.push(Edge::new(cursor, dynamic_field));
245 }
246
247 Ok(conn)
248 }
249
250 pub(crate) fn root_version(&self) -> u64 {
251 self.super_.root_version()
252 }
253}
254
255impl TryFrom<MoveObject> for DynamicField {
256 type Error = Error;
257
258 fn try_from(stored: MoveObject) -> Result<Self, Error> {
259 let super_ = &stored.super_;
260
261 let native = match &super_.kind {
262 ObjectKind::NotIndexed(native) | ObjectKind::Indexed(native, _) => native,
263
264 ObjectKind::WrappedOrDeleted(_) => {
265 return Err(Error::Internal(
266 "DynamicField is wrapped or deleted.".to_string(),
267 ));
268 }
269 };
270
271 let Some(object) = native.data.try_as_move() else {
272 return Err(Error::Internal("DynamicField is not an object".to_string()));
273 };
274
275 let Some(tag) = object.type_().other() else {
276 return Err(Error::Internal("DynamicField is not a struct".to_string()));
277 };
278
279 if !DynamicFieldInfo::is_dynamic_field(tag) {
280 return Err(Error::Internal("Wrong type for DynamicField".to_string()));
281 }
282
283 Ok(DynamicField { super_: stored })
284 }
285}
286
287fn dynamic_fields_query(
299 parent: IotaAddress,
300 parent_version: Option<u64>,
301 range: AvailableRange,
302 page: &Page<object::Cursor>,
303) -> RawQuery {
304 build_objects_query(
305 View::Consistent,
306 range,
307 page,
308 move |query| apply_filter(query, parent, parent_version),
309 move |newer| {
310 if let Some(parent_version) = parent_version {
311 filter!(newer, format!("object_version <= {}", parent_version))
312 } else {
313 newer
314 }
315 },
316 )
317}
318
319fn apply_filter(query: RawQuery, parent: IotaAddress, parent_version: Option<u64>) -> RawQuery {
320 let query = filter!(
321 query,
322 format!(
323 "owner_id = '\\x{}'::bytea AND owner_type = {} AND df_kind IS NOT NULL",
324 hex::encode(parent.into_vec()),
325 OwnerType::Object as i16
326 )
327 );
328
329 if let Some(version) = parent_version {
330 filter!(query, format!("object_version <= {}", version))
331 } else {
332 query
333 }
334}