1use std::collections::HashMap;
5use std::sync::Arc;
6
7use diesel::prelude::*;
8use serde::de::DeserializeOwned;
9
10use move_core_types::annotated_value::MoveTypeLayout;
11use sui_json_rpc::coin_api::parse_to_struct_tag;
12use sui_json_rpc_types::{Balance, Coin as SuiCoin};
13use sui_package_resolver::{PackageStore, Resolver};
14use sui_types::base_types::{ObjectID, ObjectRef};
15use sui_types::digests::ObjectDigest;
16use sui_types::dynamic_field::{DynamicFieldType, Field};
17use sui_types::object::{Object, ObjectRead};
18
19use crate::errors::IndexerError;
20use crate::schema::{full_objects_history, objects, objects_history, objects_snapshot};
21use crate::types::{IndexedDeletedObject, IndexedObject, ObjectStatus, owner_to_owner_info};
22
23#[derive(Queryable)]
24pub struct DynamicFieldColumn {
25    pub object_id: Vec<u8>,
26    pub object_version: i64,
27    pub object_digest: Vec<u8>,
28    pub df_kind: Option<i16>,
29    pub df_name: Option<Vec<u8>>,
30    pub df_object_type: Option<String>,
31    pub df_object_id: Option<Vec<u8>>,
32}
33
34#[derive(Queryable)]
35pub struct ObjectRefColumn {
36    pub object_id: Vec<u8>,
37    pub object_version: i64,
38    pub object_digest: Vec<u8>,
39}
40
41#[derive(Queryable, Insertable, Debug, Identifiable, Clone, QueryableByName)]
45#[diesel(table_name = objects, primary_key(object_id))]
46pub struct StoredObject {
47    pub object_id: Vec<u8>,
48    pub object_version: i64,
49    pub object_digest: Vec<u8>,
50    pub owner_type: i16,
51    pub owner_id: Option<Vec<u8>>,
52    pub object_type: Option<String>,
55    pub object_type_package: Option<Vec<u8>>,
56    pub object_type_module: Option<String>,
57    pub object_type_name: Option<String>,
59    pub serialized_object: Vec<u8>,
60    pub coin_type: Option<String>,
61    pub coin_balance: Option<i64>,
63    pub df_kind: Option<i16>,
64}
65
66impl From<IndexedObject> for StoredObject {
67    fn from(o: IndexedObject) -> Self {
68        let IndexedObject {
69            checkpoint_sequence_number: _,
70            object,
71            df_kind,
72        } = o;
73        let (owner_type, owner_id) = owner_to_owner_info(&object.owner);
74        let coin_type = object
75            .coin_type_maybe()
76            .map(|t| t.to_canonical_string(true));
77        let coin_balance = if coin_type.is_some() {
78            Some(object.get_coin_value_unsafe())
79        } else {
80            None
81        };
82        Self {
83            object_id: object.id().to_vec(),
84            object_version: object.version().value() as i64,
85            object_digest: object.digest().into_inner().to_vec(),
86            owner_type: owner_type as i16,
87            owner_id: owner_id.map(|id| id.to_vec()),
88            object_type: object
89                .type_()
90                .map(|t| t.to_canonical_string(true)),
91            object_type_package: object.type_().map(|t| t.address().to_vec()),
92            object_type_module: object.type_().map(|t| t.module().to_string()),
93            object_type_name: object.type_().map(|t| t.name().to_string()),
94            serialized_object: bcs::to_bytes(&object).unwrap(),
95            coin_type,
96            coin_balance: coin_balance.map(|b| b as i64),
97            df_kind: df_kind.map(|k| match k {
98                DynamicFieldType::DynamicField => 0,
99                DynamicFieldType::DynamicObject => 1,
100            }),
101        }
102    }
103}
104
105#[derive(Queryable, Insertable, Debug, Identifiable, Clone, QueryableByName)]
106#[diesel(table_name = objects, primary_key(object_id))]
107pub struct StoredDeletedObject {
108    pub object_id: Vec<u8>,
109    pub object_version: i64,
110}
111
112impl From<IndexedDeletedObject> for StoredDeletedObject {
113    fn from(o: IndexedDeletedObject) -> Self {
114        Self {
115            object_id: o.object_id.to_vec(),
116            object_version: o.object_version as i64,
117        }
118    }
119}
120
121#[derive(Queryable, Insertable, Selectable, Debug, Identifiable, Clone, QueryableByName)]
122#[diesel(table_name = objects_snapshot, primary_key(object_id))]
123pub struct StoredObjectSnapshot {
124    pub object_id: Vec<u8>,
125    pub object_version: i64,
126    pub object_status: i16,
127    pub object_digest: Option<Vec<u8>>,
128    pub checkpoint_sequence_number: i64,
129    pub owner_type: Option<i16>,
130    pub owner_id: Option<Vec<u8>>,
131    pub object_type: Option<String>,
132    pub object_type_package: Option<Vec<u8>>,
133    pub object_type_module: Option<String>,
134    pub object_type_name: Option<String>,
135    pub serialized_object: Option<Vec<u8>>,
136    pub coin_type: Option<String>,
137    pub coin_balance: Option<i64>,
138    pub df_kind: Option<i16>,
139}
140
141impl From<IndexedObject> for StoredObjectSnapshot {
142    fn from(o: IndexedObject) -> Self {
143        let IndexedObject {
144            checkpoint_sequence_number,
145            object,
146            df_kind,
147        } = o;
148        let (owner_type, owner_id) = owner_to_owner_info(&object.owner);
149        let coin_type = object
150            .coin_type_maybe()
151            .map(|t| t.to_canonical_string(true));
152        let coin_balance = if coin_type.is_some() {
153            Some(object.get_coin_value_unsafe())
154        } else {
155            None
156        };
157
158        Self {
159            object_id: object.id().to_vec(),
160            object_version: object.version().value() as i64,
161            object_status: ObjectStatus::Active as i16,
162            object_digest: Some(object.digest().into_inner().to_vec()),
163            checkpoint_sequence_number: checkpoint_sequence_number as i64,
164            owner_type: Some(owner_type as i16),
165            owner_id: owner_id.map(|id| id.to_vec()),
166            object_type: object
167                .type_()
168                .map(|t| t.to_canonical_string(true)),
169            object_type_package: object.type_().map(|t| t.address().to_vec()),
170            object_type_module: object.type_().map(|t| t.module().to_string()),
171            object_type_name: object.type_().map(|t| t.name().to_string()),
172            serialized_object: Some(bcs::to_bytes(&object).unwrap()),
173            coin_type,
174            coin_balance: coin_balance.map(|b| b as i64),
175            df_kind: df_kind.map(|k| match k {
176                DynamicFieldType::DynamicField => 0,
177                DynamicFieldType::DynamicObject => 1,
178            }),
179        }
180    }
181}
182
183impl From<IndexedDeletedObject> for StoredObjectSnapshot {
184    fn from(o: IndexedDeletedObject) -> Self {
185        Self {
186            object_id: o.object_id.to_vec(),
187            object_version: o.object_version as i64,
188            object_status: ObjectStatus::WrappedOrDeleted as i16,
189            object_digest: None,
190            checkpoint_sequence_number: o.checkpoint_sequence_number as i64,
191            owner_type: None,
192            owner_id: None,
193            object_type: None,
194            object_type_package: None,
195            object_type_module: None,
196            object_type_name: None,
197            serialized_object: None,
198            coin_type: None,
199            coin_balance: None,
200            df_kind: None,
201        }
202    }
203}
204
205#[derive(Queryable, Insertable, Selectable, Debug, Identifiable, Clone, QueryableByName)]
206#[diesel(table_name = objects_history, primary_key(object_id, object_version, checkpoint_sequence_number))]
207pub struct StoredHistoryObject {
208    pub object_id: Vec<u8>,
209    pub object_version: i64,
210    pub object_status: i16,
211    pub object_digest: Option<Vec<u8>>,
212    pub checkpoint_sequence_number: i64,
213    pub owner_type: Option<i16>,
214    pub owner_id: Option<Vec<u8>>,
215    pub object_type: Option<String>,
216    pub object_type_package: Option<Vec<u8>>,
217    pub object_type_module: Option<String>,
218    pub object_type_name: Option<String>,
219    pub serialized_object: Option<Vec<u8>>,
220    pub coin_type: Option<String>,
221    pub coin_balance: Option<i64>,
222    pub df_kind: Option<i16>,
223}
224
225impl From<IndexedObject> for StoredHistoryObject {
226    fn from(o: IndexedObject) -> Self {
227        let IndexedObject {
228            checkpoint_sequence_number,
229            object,
230            df_kind,
231        } = o;
232        let (owner_type, owner_id) = owner_to_owner_info(&object.owner);
233        let coin_type = object
234            .coin_type_maybe()
235            .map(|t| t.to_canonical_string(true));
236        let coin_balance = if coin_type.is_some() {
237            Some(object.get_coin_value_unsafe())
238        } else {
239            None
240        };
241
242        Self {
243            object_id: object.id().to_vec(),
244            object_version: object.version().value() as i64,
245            object_status: ObjectStatus::Active as i16,
246            object_digest: Some(object.digest().into_inner().to_vec()),
247            checkpoint_sequence_number: checkpoint_sequence_number as i64,
248            owner_type: Some(owner_type as i16),
249            owner_id: owner_id.map(|id| id.to_vec()),
250            object_type: object
251                .type_()
252                .map(|t| t.to_canonical_string(true)),
253            object_type_package: object.type_().map(|t| t.address().to_vec()),
254            object_type_module: object.type_().map(|t| t.module().to_string()),
255            object_type_name: object.type_().map(|t| t.name().to_string()),
256            serialized_object: Some(bcs::to_bytes(&object).unwrap()),
257            coin_type,
258            coin_balance: coin_balance.map(|b| b as i64),
259            df_kind: df_kind.map(|k| match k {
260                DynamicFieldType::DynamicField => 0,
261                DynamicFieldType::DynamicObject => 1,
262            }),
263        }
264    }
265}
266
267impl From<IndexedDeletedObject> for StoredHistoryObject {
268    fn from(o: IndexedDeletedObject) -> Self {
269        Self {
270            object_id: o.object_id.to_vec(),
271            object_version: o.object_version as i64,
272            object_status: ObjectStatus::WrappedOrDeleted as i16,
273            object_digest: None,
274            checkpoint_sequence_number: o.checkpoint_sequence_number as i64,
275            owner_type: None,
276            owner_id: None,
277            object_type: None,
278            object_type_package: None,
279            object_type_module: None,
280            object_type_name: None,
281            serialized_object: None,
282            coin_type: None,
283            coin_balance: None,
284            df_kind: None,
285        }
286    }
287}
288
289impl TryFrom<StoredObject> for Object {
290    type Error = IndexerError;
291
292    fn try_from(o: StoredObject) -> Result<Self, Self::Error> {
293        bcs::from_bytes(&o.serialized_object).map_err(|e| {
294            IndexerError::SerdeError(format!(
295                "Failed to deserialize object: {:?}, error: {}",
296                o.object_id, e
297            ))
298        })
299    }
300}
301
302impl StoredObject {
303    pub async fn try_into_object_read(
304        self,
305        package_resolver: Arc<Resolver<impl PackageStore>>,
306    ) -> Result<ObjectRead, IndexerError> {
307        let oref = self.get_object_ref()?;
308        let object: sui_types::object::Object = self.try_into()?;
309        let Some(move_object) = object.data.try_as_move().cloned() else {
310            return Ok(ObjectRead::Exists(oref, object, None));
311        };
312
313        let move_type_layout = package_resolver
314            .type_layout(move_object.type_().clone().into())
315            .await
316            .map_err(|e| {
317                IndexerError::ResolveMoveStructError(format!(
318                    "Failed to convert into object read for obj {}:{}, type: {}. Error: {e}",
319                    object.id(),
320                    object.version(),
321                    move_object.type_(),
322                ))
323            })?;
324        let move_struct_layout = match move_type_layout {
325            MoveTypeLayout::Struct(s) => Ok(s),
326            _ => Err(IndexerError::ResolveMoveStructError(
327                "MoveTypeLayout is not Struct".to_string(),
328            )),
329        }?;
330
331        Ok(ObjectRead::Exists(oref, object, Some(*move_struct_layout)))
332    }
333
334    pub fn get_object_ref(&self) -> Result<ObjectRef, IndexerError> {
335        let object_id = ObjectID::from_bytes(self.object_id.clone()).map_err(|_| {
336            IndexerError::SerdeError(format!("Can't convert {:?} to object_id", self.object_id))
337        })?;
338        let object_digest =
339            ObjectDigest::try_from(self.object_digest.as_slice()).map_err(|_| {
340                IndexerError::SerdeError(format!(
341                    "Can't convert {:?} to object_digest",
342                    self.object_digest
343                ))
344            })?;
345        Ok((
346            object_id,
347            (self.object_version as u64).into(),
348            object_digest,
349        ))
350    }
351
352    pub fn to_dynamic_field<K, V>(&self) -> Option<Field<K, V>>
353    where
354        K: DeserializeOwned,
355        V: DeserializeOwned,
356    {
357        let object: Object = bcs::from_bytes(&self.serialized_object).ok()?;
358
359        let object = object.data.try_as_move()?;
360        let ty = object.type_();
361
362        if !ty.is_dynamic_field() {
363            return None;
364        }
365
366        bcs::from_bytes(object.contents()).ok()
367    }
368}
369
370impl TryFrom<StoredObject> for SuiCoin {
371    type Error = IndexerError;
372
373    fn try_from(o: StoredObject) -> Result<Self, Self::Error> {
374        let object: Object = o.clone().try_into()?;
375        let (coin_object_id, version, digest) = o.get_object_ref()?;
376        let coin_type_canonical =
377            o.coin_type
378                .ok_or(IndexerError::PersistentStorageDataCorruptionError(format!(
379                    "Object {} is supposed to be a coin but has an empty coin_type column",
380                    coin_object_id,
381                )))?;
382        let coin_type = parse_to_struct_tag(coin_type_canonical.as_str())
383            .map_err(|_| {
384                IndexerError::PersistentStorageDataCorruptionError(format!(
385                    "The type of object {} cannot be parsed as a struct tag",
386                    coin_object_id,
387                ))
388            })?
389            .to_string();
390        let balance = o
391            .coin_balance
392            .ok_or(IndexerError::PersistentStorageDataCorruptionError(format!(
393                "Object {} is supposed to be a coin but has an empty coin_balance column",
394                coin_object_id,
395            )))?;
396        Ok(SuiCoin {
397            coin_type,
398            coin_object_id,
399            version,
400            digest,
401            balance: balance as u64,
402            previous_transaction: object.previous_transaction,
403        })
404    }
405}
406
407#[derive(QueryableByName)]
408pub struct CoinBalance {
409    #[diesel(sql_type = diesel::sql_types::Text)]
410    pub coin_type: String,
411    #[diesel(sql_type = diesel::sql_types::BigInt)]
412    pub coin_num: i64,
413    #[diesel(sql_type = diesel::sql_types::BigInt)]
414    pub coin_balance: i64,
415}
416
417impl TryFrom<CoinBalance> for Balance {
418    type Error = IndexerError;
419
420    fn try_from(c: CoinBalance) -> Result<Self, Self::Error> {
421        let coin_type = parse_to_struct_tag(c.coin_type.as_str())
422            .map_err(|_| {
423                IndexerError::PersistentStorageDataCorruptionError(
424                    "The type of coin balance cannot be parsed as a struct tag".to_string(),
425                )
426            })?
427            .to_string();
428        Ok(Self {
429            coin_type,
430            coin_object_count: c.coin_num as usize,
431            total_balance: c.coin_balance as u128,
433            locked_balance: HashMap::default(),
434        })
435    }
436}
437
438#[derive(Queryable, Insertable, Debug, Identifiable, Clone, QueryableByName, Selectable)]
439#[diesel(table_name = full_objects_history, primary_key(object_id, object_version))]
440pub struct StoredFullHistoryObject {
441    pub object_id: Vec<u8>,
442    pub object_version: i64,
443    pub serialized_object: Option<Vec<u8>>,
444}
445
446impl From<IndexedObject> for StoredFullHistoryObject {
447    fn from(o: IndexedObject) -> Self {
448        let object = o.object;
449        Self {
450            object_id: object.id().to_vec(),
451            object_version: object.version().value() as i64,
452            serialized_object: Some(bcs::to_bytes(&object).unwrap()),
453        }
454    }
455}
456
457impl From<IndexedDeletedObject> for StoredFullHistoryObject {
458    fn from(o: IndexedDeletedObject) -> Self {
459        Self {
460            object_id: o.object_id.to_vec(),
461            object_version: o.object_version as i64,
462            serialized_object: None,
463        }
464    }
465}
466
467impl TryFrom<StoredHistoryObject> for StoredObject {
468    type Error = IndexerError;
469
470    fn try_from(o: StoredHistoryObject) -> Result<Self, Self::Error> {
471        if o.object_digest.is_none() || o.owner_type.is_none() || o.serialized_object.is_none() {
473            return Err(IndexerError::PostgresReadError(
474                "Missing required fields in StoredHistoryObject".to_string(),
475            ));
476        }
477
478        Ok(Self {
479            object_id: o.object_id,
480            object_version: o.object_version,
481            object_digest: o.object_digest.unwrap(),
482            owner_type: o.owner_type.unwrap(),
483            owner_id: o.owner_id,
484            object_type: o.object_type,
485            object_type_package: o.object_type_package,
486            object_type_module: o.object_type_module,
487            object_type_name: o.object_type_name,
488            serialized_object: o.serialized_object.unwrap(),
489            coin_type: o.coin_type,
490            coin_balance: o.coin_balance,
491            df_kind: o.df_kind,
492        })
493    }
494}
495
496impl TryFrom<StoredObjectSnapshot> for StoredObject {
497    type Error = IndexerError;
498
499    fn try_from(o: StoredObjectSnapshot) -> Result<Self, Self::Error> {
500        if o.object_digest.is_none() || o.owner_type.is_none() || o.serialized_object.is_none() {
502            return Err(IndexerError::PostgresReadError(
503                "Missing required fields in StoredObjectSnapshot".to_string(),
504            ));
505        }
506
507        Ok(Self {
508            object_id: o.object_id,
509            object_version: o.object_version,
510            object_digest: o.object_digest.unwrap(),
511            owner_type: o.owner_type.unwrap(),
512            owner_id: o.owner_id,
513            object_type: o.object_type,
514            object_type_package: o.object_type_package,
515            object_type_module: o.object_type_module,
516            object_type_name: o.object_type_name,
517            serialized_object: o.serialized_object.unwrap(),
518            coin_type: o.coin_type,
519            coin_balance: o.coin_balance,
520            df_kind: o.df_kind,
521        })
522    }
523}
524
525#[cfg(test)]
526mod tests {
527    use move_core_types::{account_address::AccountAddress, language_storage::StructTag};
528    use sui_types::{
529        Identifier, TypeTag,
530        coin::Coin,
531        digests::TransactionDigest,
532        gas_coin::{GAS, GasCoin},
533        object::{Data, MoveObject, ObjectInner, Owner},
534    };
535
536    use super::*;
537
538    #[test]
539    fn test_canonical_string_of_object_type_for_coin() {
540        let test_obj = Object::new_gas_for_testing();
541        let indexed_obj = IndexedObject::from_object(1, test_obj, None);
542
543        let stored_obj = StoredObject::from(indexed_obj);
544
545        match stored_obj.object_type {
546            Some(t) => {
547                assert_eq!(
548                    t,
549                    "0x0000000000000000000000000000000000000000000000000000000000000002::coin::Coin<0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI>"
550                );
551            }
552            None => {
553                panic!("object_type should not be none");
554            }
555        }
556    }
557
558    #[test]
559    fn test_convert_stored_obj_to_sui_coin() {
560        let test_obj = Object::new_gas_for_testing();
561        let indexed_obj = IndexedObject::from_object(1, test_obj, None);
562
563        let stored_obj = StoredObject::from(indexed_obj);
564
565        let sui_coin = SuiCoin::try_from(stored_obj).unwrap();
566        assert_eq!(sui_coin.coin_type, "0x2::sui::SUI");
567    }
568
569    #[test]
570    fn test_output_format_coin_balance() {
571        let test_obj = Object::new_gas_for_testing();
572        let indexed_obj = IndexedObject::from_object(1, test_obj, None);
573
574        let stored_obj = StoredObject::from(indexed_obj);
575        let test_balance = CoinBalance {
576            coin_type: stored_obj.coin_type.unwrap(),
577            coin_num: 1,
578            coin_balance: 100,
579        };
580        let balance = Balance::try_from(test_balance).unwrap();
581        assert_eq!(balance.coin_type, "0x2::sui::SUI");
582    }
583
584    #[test]
585    fn test_vec_of_coin_sui_conversion() {
586        let vec_coins_type = TypeTag::Vector(Box::new(
588            Coin::type_(TypeTag::Struct(Box::new(GAS::type_()))).into(),
589        ));
590        let object_type = StructTag {
591            address: AccountAddress::from_hex_literal("0xe7").unwrap(),
592            module: Identifier::new("vec_coin").unwrap(),
593            name: Identifier::new("VecCoin").unwrap(),
594            type_params: vec![vec_coins_type],
595        };
596
597        let id = ObjectID::ZERO;
598        let gas = 10;
599
600        let contents = bcs::to_bytes(&vec![GasCoin::new(id, gas)]).unwrap();
601        let data = Data::Move(
602            unsafe {
603                MoveObject::new_from_execution_with_limit(
604                    object_type.into(),
605                    true,
606                    1.into(),
607                    contents,
608                    256,
609                )
610            }
611            .unwrap(),
612        );
613
614        let owner = AccountAddress::from_hex_literal("0x1").unwrap();
615
616        let object = ObjectInner {
617            owner: Owner::AddressOwner(owner.into()),
618            data,
619            previous_transaction: TransactionDigest::genesis_marker(),
620            storage_rebate: 0,
621        }
622        .into();
623
624        let indexed_obj = IndexedObject::from_object(1, object, None);
625
626        let stored_obj = StoredObject::from(indexed_obj);
627
628        match stored_obj.object_type {
629            Some(t) => {
630                assert_eq!(
631                    t,
632                    "0x00000000000000000000000000000000000000000000000000000000000000e7::vec_coin::VecCoin<vector<0x0000000000000000000000000000000000000000000000000000000000000002::coin::Coin<0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI>>>"
633                );
634            }
635            None => {
636                panic!("object_type should not be none");
637            }
638        }
639    }
640}