Skip to main content

iota_indexer/models/
display.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use diesel::prelude::*;
6use iota_sdk_move_types::iota_framework::vec_map::VecMap;
7use iota_sdk_types::{Address, Event, Identifier, ObjectId, StructTag, TypeTag};
8use iota_types::{
9    display::{DisplayObject, DisplayVersionUpdatedEvent},
10    id::ID,
11    object::Object,
12};
13
14use crate::{errors::IndexerError, schema::display};
15
16const DISPLAY_STRUCT_NAME: Identifier = Identifier::from_static("Display");
17
18/// Identifies what type is encoded in [`StoredDisplay`]'s `bcs` column.
19#[derive(Debug, Copy, Clone)]
20pub enum DisplayBcsKind {
21    /// Full [`DisplayVersionUpdatedEvent`] BCS, written from on-chain events.
22    Event = 0,
23    /// Only the [`DisplayVersionUpdatedEvent::fields`].
24    Fields = 1,
25}
26
27impl TryFrom<i16> for DisplayBcsKind {
28    type Error = IndexerError;
29
30    fn try_from(value: i16) -> Result<Self, Self::Error> {
31        Ok(match value {
32            0 => Self::Event,
33            1 => Self::Fields,
34            value => {
35                return Err(IndexerError::PersistentStorageDataCorruption(format!(
36                    "{value} as DisplayBcsKind"
37                )));
38            }
39        })
40    }
41}
42
43#[derive(Queryable, Insertable, Selectable, Debug, Clone)]
44#[diesel(table_name = display)]
45pub struct StoredDisplay {
46    pub object_type: String,
47    pub id: Vec<u8>,
48    pub version: i16,
49    pub bcs: Vec<u8>,
50    pub bcs_kind: i16,
51}
52
53impl StoredDisplay {
54    pub fn try_from_event(event: &Event) -> Option<Self> {
55        if !event.struct_tag.is_display_version_updated() {
56            return None;
57        }
58        let ty = match event.struct_tag.type_params() {
59            [TypeTag::Struct(struct_type)] => struct_type,
60            _ => return None,
61        };
62        let display_event: DisplayVersionUpdatedEvent = bcs::from_bytes(&event.contents).ok()?;
63
64        Some(Self {
65            object_type: ty.to_canonical_string(/* with_prefix */ true),
66            id: display_event.id.bytes.as_bytes().to_vec(),
67            version: display_event.version as i16,
68            bcs: bcs::to_bytes(&display_event.fields)
69                .expect("serializing a deserialized value should succeed"),
70            bcs_kind: DisplayBcsKind::Fields as i16,
71        })
72    }
73
74    /// Builds a display row from a live `0x2::display::Display<T>` object.
75    ///
76    /// Returns `None` when the object is not a `Display<T>`.
77    pub fn try_from_object(object: &Object) -> Option<Self> {
78        let move_object = object.data.as_opt_struct()?;
79        let struct_tag = move_object.struct_tag();
80        if !is_display(struct_tag) {
81            return None;
82        }
83        let [TypeTag::Struct(object_type)] = struct_tag.type_params() else {
84            return None;
85        };
86        let display: DisplayObject = bcs::from_bytes(move_object.contents()).ok()?;
87
88        Some(Self {
89            object_type: object_type.to_canonical_string(/* with_prefix */ true),
90            id: display.id.object_id().as_bytes().to_vec(),
91            version: display.version as i16,
92            bcs: bcs::to_bytes(&display.fields)
93                .expect("serializing a deserialized value should succeed"),
94            bcs_kind: DisplayBcsKind::Fields as i16,
95        })
96    }
97
98    pub fn to_display_fields(&self) -> Result<VecMap<String, String>, IndexerError> {
99        match DisplayBcsKind::try_from(self.bcs_kind)? {
100            DisplayBcsKind::Event => {
101                bcs::from_bytes::<DisplayVersionUpdatedEvent>(&self.bcs).map(|event| event.fields)
102            }
103            DisplayBcsKind::Fields => bcs::from_bytes(&self.bcs),
104        }
105        .map_err(|e| {
106            IndexerError::PersistentStorageDataCorruption(format!(
107                "failed to decode stored display: {e}"
108            ))
109        })
110    }
111}
112
113/// Extracts the canonical name of `T` from the type of a
114/// `0x2::display::DisplayCreated<T>` event, without touching the event
115/// contents.
116///
117/// Returns `None` if the provided event is of a different type.
118pub fn displayed_type_from_created_event(event: &Event) -> Option<String> {
119    if !event.struct_tag.is_display_created() {
120        return None;
121    }
122    let [TypeTag::Struct(displayed_type)] = event.struct_tag.type_params() else {
123        return None;
124    };
125    Some(displayed_type.to_canonical_string(/* with_prefix */ true))
126}
127
128/// Extracts the `ObjectId` of the created `Display<T>` object from the
129/// contents of a `0x2::display::DisplayCreated<T>` event.
130///
131/// Returns `None` if the provided event is of a different type.
132pub fn display_id_from_created_event(event: &Event) -> Option<ObjectId> {
133    if !event.struct_tag.is_display_created() {
134        return None;
135    }
136    let created_event: ID = bcs::from_bytes(&event.contents).ok()?;
137    Some(created_event.bytes)
138}
139
140/// Returns whether `struct_tag` is of `0x2::display::Display` type.
141fn is_display(struct_tag: &StructTag) -> bool {
142    struct_tag.address() == Address::FRAMEWORK
143        && struct_tag.module() == &Identifier::DISPLAY_MODULE
144        && struct_tag.name() == &DISPLAY_STRUCT_NAME
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    fn display_event(struct_tag: StructTag, contents: Vec<u8>) -> Event {
152        Event {
153            package_id: ObjectId::random(),
154            module: Identifier::DISPLAY_MODULE,
155            sender: Address::random(),
156            struct_tag,
157            contents,
158        }
159    }
160
161    /// The type `T` a `Display<T>` refers to.
162    fn displayed_type() -> StructTag {
163        StructTag::new(
164            Address::random(),
165            Identifier::from_static("test"),
166            Identifier::from_static("Test"),
167            vec![],
168        )
169    }
170
171    #[test]
172    fn display_created_event_helpers_extract_type_and_id() {
173        let displayed_type = displayed_type();
174        let display_id = ObjectId::random();
175        let event = display_event(
176            StructTag::new_display_created(displayed_type.clone()),
177            bcs::to_bytes(&ID::new(display_id)).unwrap(),
178        );
179
180        assert_eq!(
181            displayed_type_from_created_event(&event),
182            Some(displayed_type.to_canonical_string(true))
183        );
184        assert_eq!(display_id_from_created_event(&event), Some(display_id));
185    }
186
187    #[test]
188    fn display_created_event_helpers_ignore_other_events() {
189        let event = display_event(
190            StructTag::new_display_version_updated(displayed_type()),
191            vec![],
192        );
193        assert_eq!(displayed_type_from_created_event(&event), None);
194        assert_eq!(display_id_from_created_event(&event), None);
195    }
196}