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