1use async_graphql::{
6 connection::{Connection, CursorType, Edge},
7 *,
8};
9use iota_indexer::types::OwnerType;
10use iota_types::{
11 dynamic_field::{
12 DynamicFieldInfo, DynamicFieldType, derive_dynamic_field_id,
13 visitor::{Field, FieldVisitor},
14 },
15 iota_sdk_types_conversions::type_tag_core_to_sdk,
16};
17
18use crate::{
19 backward_view::{consistent, dynamic_fields},
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, ActiveObject, Object, StoredBackwardObject},
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(Box<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_ = self.super_.native.type_tag();
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_string(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 type_tag_core_to_sdk(&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_ = self.super_.native.type_tag();
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_string(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(|obj| DynamicFieldValue::MoveObject(Box::new(obj))))
135 } else {
136 Ok(Some(DynamicFieldValue::MoveValue(MoveValue::new(
137 type_tag_core_to_sdk(&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 max_available_range = db.max_available_range;
205
206 let Some((prev, next, results)) = db
207 .execute_repeatable(move |conn| {
208 if !AvailableRange::is_checkpoint_in_backward_history_range(
209 conn,
210 checkpoint_viewed_at,
211 max_available_range,
212 )? {
213 return Ok::<_, diesel::result::Error>(None);
214 };
215
216 let query = match parent_version {
217 Some(pv) => dynamic_fields::query(parent, pv, &page),
218 None => {
219 consistent::query(checkpoint_viewed_at, &page, |q| apply_filter(q, parent))
220 }
221 };
222
223 Ok(Some(page.paginate_raw_query::<StoredBackwardObject>(
224 conn,
225 checkpoint_viewed_at,
226 query,
227 )?))
228 })
229 .await?
230 else {
231 return Err(Error::Client(
232 "Requested data is outside the available range".to_string(),
233 ));
234 };
235
236 let mut conn: Connection<String, DynamicField> = Connection::new(prev, next);
237
238 for stored in results {
239 let cursor = stored.cursor(checkpoint_viewed_at).encode_cursor();
242 let stored_history = stored.into_stored_history(checkpoint_viewed_at);
243 let active_object = ActiveObject::try_from(stored_history)?;
244 let object =
245 Object::from_active_object(active_object, checkpoint_viewed_at, parent_version);
246
247 let move_ = MoveObject::try_from(&object).map_err(|_| {
248 Error::Internal(format!(
249 "Failed to deserialize as Move object: {}",
250 object.address
251 ))
252 })?;
253
254 let dynamic_field = DynamicField::try_from(move_)?;
255 conn.edges.push(Edge::new(cursor, dynamic_field));
256 }
257
258 Ok(conn)
259 }
260
261 pub(crate) fn root_version(&self) -> u64 {
262 self.super_.root_version()
263 }
264}
265
266impl TryFrom<MoveObject> for DynamicField {
267 type Error = Error;
268
269 fn try_from(stored: MoveObject) -> Result<Self, Error> {
270 let super_ = &stored.super_;
271
272 let native = super_.native_impl();
273
274 let Some(object) = native.data.as_opt_struct() else {
275 return Err(Error::Internal("DynamicField is not an object".to_string()));
276 };
277
278 if !DynamicFieldInfo::is_dynamic_field(object.struct_tag()) {
279 return Err(Error::Internal("Wrong type for DynamicField".to_string()));
280 }
281
282 Ok(DynamicField { super_: stored })
283 }
284}
285
286fn apply_filter(query: RawQuery, parent: IotaAddress) -> RawQuery {
287 filter!(
288 query,
289 format!(
290 "owner_id = '\\x{}'::bytea AND owner_type = {} AND df_kind IS NOT NULL",
291 hex::encode(parent.into_vec()),
292 OwnerType::Object as i16
293 )
294 )
295}