sui_analytics_indexer/handlers/tables/
mod.rs1use std::collections::BTreeMap;
10use std::collections::BTreeSet;
11
12use anyhow::Result;
13use anyhow::anyhow;
14use move_core_types::annotated_value::MoveStruct;
15use move_core_types::annotated_value::MoveTypeLayout;
16use move_core_types::annotated_value::MoveValue;
17use move_core_types::language_storage::StructTag;
18use move_core_types::language_storage::TypeTag;
19use sui_package_resolver::PackageStore;
20use sui_package_resolver::Resolver;
21use sui_types::base_types::ObjectID;
22use sui_types::effects::TransactionEffects;
23use sui_types::effects::TransactionEffectsAPI;
24use sui_types::object::Object;
25use sui_types::object::Owner;
26use sui_types::object::bounded_visitor::BoundedVisitor;
27use sui_types::transaction::TransactionData;
28use sui_types::transaction::TransactionDataAPI;
29
30use crate::tables::InputObjectKind;
31use crate::tables::ObjectStatus;
32use crate::tables::OwnerType;
33
34pub mod checkpoint;
35pub mod df;
36pub mod event;
37pub mod move_call;
38pub mod object;
39pub mod package;
40pub mod package_bcs;
41pub mod transaction;
42pub mod transaction_bcs;
43pub mod transaction_objects;
44pub mod wrapped_object;
45
46pub use checkpoint::CheckpointProcessor;
47pub use df::DynamicFieldProcessor;
48pub use event::EventProcessor;
49pub use move_call::MoveCallProcessor;
50pub use object::ObjectProcessor;
51pub use package::PackageProcessor;
52pub use package_bcs::PackageBCSProcessor;
53pub use transaction::TransactionProcessor;
54pub use transaction_bcs::TransactionBCSProcessor;
55pub use transaction_objects::TransactionObjectsProcessor;
56pub use wrapped_object::WrappedObjectProcessor;
57
58const WRAPPED_INDEXING_DISALLOW_LIST: [&str; 4] = [
59 "0x1::string::String",
60 "0x1::ascii::String",
61 "0x2::url::Url",
62 "0x2::object::ID",
63];
64
65#[derive(Debug, Default)]
66pub struct WrappedStruct {
67 pub object_id: Option<ObjectID>,
68 pub struct_tag: Option<StructTag>,
69}
70
71pub struct InputObjectTracker {
72 shared: BTreeSet<ObjectID>,
73 coins: BTreeSet<ObjectID>,
74 input: BTreeSet<ObjectID>,
75}
76
77pub struct ObjectStatusTracker {
78 created: BTreeSet<ObjectID>,
79 mutated: BTreeSet<ObjectID>,
80 deleted: BTreeSet<ObjectID>,
81}
82
83impl InputObjectTracker {
84 pub fn new(txn_data: &TransactionData) -> Self {
85 let shared: BTreeSet<ObjectID> = txn_data
86 .shared_input_objects()
87 .iter()
88 .map(|shared_io| shared_io.id())
89 .collect();
90 let coins: BTreeSet<ObjectID> = txn_data.gas().iter().map(|obj_ref| obj_ref.0).collect();
91 let input: BTreeSet<ObjectID> = txn_data
92 .input_objects()
93 .expect("Input objects must be valid")
94 .iter()
95 .map(|io_kind| io_kind.object_id())
96 .collect();
97 Self {
98 shared,
99 coins,
100 input,
101 }
102 }
103
104 pub fn get_input_object_kind(&self, object_id: &ObjectID) -> Option<InputObjectKind> {
105 if self.coins.contains(object_id) {
106 Some(InputObjectKind::GasCoin)
107 } else if self.shared.contains(object_id) {
108 Some(InputObjectKind::SharedInput)
109 } else if self.input.contains(object_id) {
110 Some(InputObjectKind::Input)
111 } else {
112 None
113 }
114 }
115}
116
117impl ObjectStatusTracker {
118 pub fn new(effects: &TransactionEffects) -> Self {
119 let created: BTreeSet<ObjectID> = effects
120 .created()
121 .iter()
122 .map(|(obj_ref, _)| obj_ref.0)
123 .collect();
124 let mutated: BTreeSet<ObjectID> = effects
125 .mutated()
126 .iter()
127 .chain(effects.unwrapped().iter())
128 .map(|(obj_ref, _)| obj_ref.0)
129 .collect();
130 let deleted: BTreeSet<ObjectID> = effects
131 .all_tombstones()
132 .into_iter()
133 .map(|(id, _)| id)
134 .collect();
135 Self {
136 created,
137 mutated,
138 deleted,
139 }
140 }
141
142 pub fn get_object_status(&self, object_id: &ObjectID) -> Option<ObjectStatus> {
143 if self.mutated.contains(object_id) {
144 Some(ObjectStatus::Mutated)
145 } else if self.deleted.contains(object_id) {
146 Some(ObjectStatus::Deleted)
147 } else if self.created.contains(object_id) {
148 Some(ObjectStatus::Created)
149 } else {
150 None
151 }
152 }
153}
154
155pub fn initial_shared_version(object: &Object) -> Option<u64> {
156 match object.owner {
157 Owner::Shared {
158 initial_shared_version,
159 } => Some(initial_shared_version.value()),
160 _ => None,
161 }
162}
163
164pub fn get_owner_type(object: &Object) -> OwnerType {
165 match object.owner {
166 Owner::AddressOwner(_) => OwnerType::AddressOwner,
167 Owner::ObjectOwner(_) => OwnerType::ObjectOwner,
168 Owner::Shared { .. } => OwnerType::Shared,
169 Owner::Immutable => OwnerType::Immutable,
170 Owner::ConsensusAddressOwner { .. } => OwnerType::AddressOwner,
171 }
172}
173
174pub fn get_owner_address(object: &Object) -> Option<String> {
175 match object.owner {
176 Owner::AddressOwner(address) => Some(address.to_string()),
177 Owner::ObjectOwner(address) => Some(address.to_string()),
178 Owner::Shared { .. } => None,
179 Owner::Immutable => None,
180 Owner::ConsensusAddressOwner { owner, .. } => Some(owner.to_string()),
181 }
182}
183
184pub fn get_is_consensus(object: &Object) -> bool {
185 match object.owner {
186 Owner::AddressOwner(_) => false,
187 Owner::ObjectOwner(_) => false,
188 Owner::Shared { .. } => true,
189 Owner::Immutable => false,
190 Owner::ConsensusAddressOwner { .. } => true,
191 }
192}
193
194pub async fn get_move_struct<T: PackageStore>(
195 struct_tag: &StructTag,
196 contents: &[u8],
197 resolver: &Resolver<T>,
198) -> Result<MoveStruct> {
199 let move_struct = match resolver
200 .type_layout(TypeTag::Struct(Box::new(struct_tag.clone())))
201 .await?
202 {
203 MoveTypeLayout::Struct(move_struct_layout) => {
204 BoundedVisitor::deserialize_struct(contents, &move_struct_layout)
205 }
206 _ => Err(anyhow!("Object is not a move struct")),
207 }?;
208 Ok(move_struct)
209}
210
211pub fn parse_struct(
212 path: &str,
213 move_struct: MoveStruct,
214 all_structs: &mut BTreeMap<String, WrappedStruct>,
215) {
216 let mut wrapped_struct = WrappedStruct {
217 struct_tag: Some(move_struct.type_),
218 ..Default::default()
219 };
220 for (k, v) in move_struct.fields {
221 parse_struct_field(
222 &format!("{}.{}", path, &k),
223 v,
224 &mut wrapped_struct,
225 all_structs,
226 );
227 }
228 all_structs.insert(path.to_string(), wrapped_struct);
229}
230
231fn parse_struct_field(
232 path: &str,
233 move_value: MoveValue,
234 curr_struct: &mut WrappedStruct,
235 all_structs: &mut BTreeMap<String, WrappedStruct>,
236) {
237 match move_value {
238 MoveValue::Struct(move_struct) => {
239 let values = move_struct
240 .fields
241 .iter()
242 .map(|(id, value)| (id.to_string(), value))
243 .collect::<BTreeMap<_, _>>();
244 let struct_name = format!(
245 "0x{}::{}::{}",
246 move_struct.type_.address.short_str_lossless(),
247 move_struct.type_.module,
248 move_struct.type_.name
249 );
250 if "0x2::object::UID" == struct_name {
251 if let Some(MoveValue::Struct(id_struct)) = values.get("id").cloned() {
252 let id_values = id_struct
253 .fields
254 .iter()
255 .map(|(id, value)| (id.to_string(), value))
256 .collect::<BTreeMap<_, _>>();
257 if let Some(MoveValue::Address(address) | MoveValue::Signer(address)) =
258 id_values.get("bytes").cloned()
259 {
260 curr_struct.object_id = Some(ObjectID::from_address(*address))
261 }
262 }
263 } else if "0x1::option::Option" == struct_name {
264 if let Some(MoveValue::Vector(vec_values)) = values.get("vec").cloned()
266 && let Some(first_value) = vec_values.first()
267 {
268 parse_struct_field(
269 &format!("{}[0]", path),
270 first_value.clone(),
271 curr_struct,
272 all_structs,
273 );
274 }
275 } else if !WRAPPED_INDEXING_DISALLOW_LIST.contains(&&*struct_name) {
276 parse_struct(path, move_struct, all_structs)
278 }
279 }
280 MoveValue::Variant(v) => {
281 for (k, field) in v.fields.iter() {
282 parse_struct_field(
283 &format!("{}.{}", path, k),
284 field.clone(),
285 curr_struct,
286 all_structs,
287 );
288 }
289 }
290 MoveValue::Vector(fields) => {
291 for (index, field) in fields.iter().enumerate() {
292 parse_struct_field(
293 &format!("{}[{}]", path, &index),
294 field.clone(),
295 curr_struct,
296 all_structs,
297 );
298 }
299 }
300 _ => {}
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::parse_struct;
307 use move_core_types::account_address::AccountAddress;
308 use move_core_types::annotated_value::MoveStruct;
309 use move_core_types::annotated_value::MoveValue;
310 use move_core_types::annotated_value::MoveVariant;
311 use move_core_types::identifier::Identifier;
312 use move_core_types::language_storage::StructTag;
313 use std::collections::BTreeMap;
314 use std::str::FromStr;
315 use sui_types::base_types::ObjectID;
316
317 #[tokio::test]
318 async fn test_wrapped_object_parsing() -> anyhow::Result<()> {
319 let uid_field = MoveValue::Struct(MoveStruct {
320 type_: StructTag::from_str("0x2::object::UID")?,
321 fields: vec![(
322 Identifier::from_str("id")?,
323 MoveValue::Struct(MoveStruct {
324 type_: StructTag::from_str("0x2::object::ID")?,
325 fields: vec![(
326 Identifier::from_str("bytes")?,
327 MoveValue::Signer(AccountAddress::from_hex_literal("0x300")?),
328 )],
329 }),
330 )],
331 });
332 let balance_field = MoveValue::Struct(MoveStruct {
333 type_: StructTag::from_str("0x2::balance::Balance")?,
334 fields: vec![(Identifier::from_str("value")?, MoveValue::U32(10))],
335 });
336 let move_struct = MoveStruct {
337 type_: StructTag::from_str("0x2::test::Test")?,
338 fields: vec![
339 (Identifier::from_str("id")?, uid_field),
340 (Identifier::from_str("principal")?, balance_field),
341 ],
342 };
343 let mut all_structs = BTreeMap::new();
344 parse_struct("$", move_struct, &mut all_structs);
345 assert_eq!(
346 all_structs.get("$").unwrap().object_id,
347 Some(ObjectID::from_hex_literal("0x300")?)
348 );
349 assert_eq!(
350 all_structs.get("$.principal").unwrap().struct_tag,
351 Some(StructTag::from_str("0x2::balance::Balance")?)
352 );
353 Ok(())
354 }
355
356 #[tokio::test]
357 async fn test_wrapped_object_parsing_within_enum() -> anyhow::Result<()> {
358 let uid_field = MoveValue::Struct(MoveStruct {
359 type_: StructTag::from_str("0x2::object::UID")?,
360 fields: vec![(
361 Identifier::from_str("id")?,
362 MoveValue::Struct(MoveStruct {
363 type_: StructTag::from_str("0x2::object::ID")?,
364 fields: vec![(
365 Identifier::from_str("bytes")?,
366 MoveValue::Signer(AccountAddress::from_hex_literal("0x300")?),
367 )],
368 }),
369 )],
370 });
371 let balance_field = MoveValue::Struct(MoveStruct {
372 type_: StructTag::from_str("0x2::balance::Balance")?,
373 fields: vec![(Identifier::from_str("value")?, MoveValue::U32(10))],
374 });
375 let move_enum = MoveVariant {
376 type_: StructTag::from_str("0x2::test::TestEnum")?,
377 variant_name: Identifier::from_str("TestVariant")?,
378 tag: 0,
379 fields: vec![
380 (Identifier::from_str("field0")?, MoveValue::U64(10)),
381 (Identifier::from_str("principal")?, balance_field),
382 ],
383 };
384 let move_struct = MoveStruct {
385 type_: StructTag::from_str("0x2::test::Test")?,
386 fields: vec![
387 (Identifier::from_str("id")?, uid_field),
388 (
389 Identifier::from_str("enum_field")?,
390 MoveValue::Variant(move_enum),
391 ),
392 ],
393 };
394 let mut all_structs = BTreeMap::new();
395 parse_struct("$", move_struct, &mut all_structs);
396 assert_eq!(
397 all_structs.get("$").unwrap().object_id,
398 Some(ObjectID::from_hex_literal("0x300")?)
399 );
400 assert_eq!(
401 all_structs
402 .get("$.enum_field.principal")
403 .unwrap()
404 .struct_tag,
405 Some(StructTag::from_str("0x2::balance::Balance")?)
406 );
407 Ok(())
408 }
409}