Skip to content

Introspection

In Dojo, every model automatically implements the Introspect trait. This trait outlines the data structure of the model, which is utilized by both the world database engine and Torii for automatic data indexing.

The dojo-core library already implements the Introspect trait for Cairo built-in types.

Custom Types

For user defined types, it's crucial to implement the Introspect trait if you plan to use those types inside a model.

Two possible cases:

  1. If the user-defined type contains only Cairo built-in types and is defined within your project, simply derive Introspect and the implementation for the type will be handled automatically by Cairo.
#[derive(Drop, Serde, Introspect)]
struct Stats {
    atk: u8,
    def: u8,
}
  1. If the user-defined type includes a type that is either defined outside of your project or is an unsupported type, you will need to manually implement the Introspect trait.

Implement the trait

The trait has this signature:

trait Introspect<T> {
    fn size() -> Option<usize>;
    fn layout() -> Layout;
    fn ty() -> Ty;
}

The size function should return None if your model, or any type within it, includes at least one dynamic type such as ByteArray or Array.

Here are the definition of Ty and Layout.

As an example of implementation, consider the following:

// My project.
#[derive(Drop, Serde)]
#[dojo::model]
struct Player {
    #[key]
    token_id: u256,
    stats: Stats,
}
 
// From an other project imported as a dependency.
struct Stats {
    atk: u8,
    def: u8,
}
impl StatsIntrospect of dojo::database::introspect::Introspect<Stats> {
    #[inline(always)]
    fn size() -> Option<usize> {
        Option::Some(2)
    }
 
    fn layout() -> dojo::database::introspect::Layout {
        dojo::database::introspect::Layout::Struct(
            array![
                dojo::database::introspect::FieldLayout {
                    selector: selector!("atk"),
                    layout: dojo::database::introspect::Introspect::<u8>::layout()
                },
                dojo::database::introspect::FieldLayout {
                    selector: selector!("def"),
                    layout: dojo::database::introspect::Introspect::<u8>::layout()
                },
            ]
                .span()
        )
    }
 
    #[inline(always)]
    fn ty() -> dojo::database::introspect::Ty {
        dojo::database::introspect::Ty::Struct(
            dojo::database::introspect::Struct {
                name: 'Stats',
                attrs: array![].span(),
                children: array![
                    dojo::database::introspect::Member {
                        name: 'atk',
                        attrs: array![].span(),
                        ty: dojo::database::introspect::Introspect::<u8>::ty()
                    },
                    dojo::database::introspect::Member {
                        name: 'def',
                        attrs: array![].span(),
                        ty: dojo::database::introspect::Introspect::<u8>::ty()
                    },
                ]
                    .span()
            }
        )
    }
}

IntrospectPacked trait

In some situations, you might need to store a model in a packed way. This is useful when you know the size of the model and want to save some storage space.

For this, you can derive the IntrospectPacked trait, which will force the use of the Fixed layout.

#[derive(Drop, Serde, IntrospectPacked)]
struct Stats {
    atk: u8,
    def: u8,
}

Dynamic types such as ByteArray and Array are prohibited in a packed model. However, you can include other Cairo structs within a packed model, provided these structs also implement the IntrospectPacked trait.

Storage and layout

The Layout enum describes the storage layout of the model in the Dojo database engine.

When the Fixed layout is used, all the fields of the model are stored in a single storage location, in the order of the fields in the struct. This has the advantage of saving storage space, but it also means that the fields are stored contiguously in memory, which can be a disadvantage if you need to upgrade your model ending up with a new storage layout.

For other layouts, each field is stored independently in a different storage location, computed from the field's selector (like Starknet does with regular contract's storage). This has the advantage of allowing for more flexible storage layouts, but it also means more gas to compute the storage location of a field as hash computation is involved.