0

I want to create custom data structure. I'm a newbie with Rust and Ink! and can't get how to implement my data structure to use it in Mapping. I have such a structure:

pub struct TokenInfo {
    data: [u8; 284],
    in_collection: bool,
    init_price: Balance,
    init_owner: AccountId,
    for_sale: bool,
    discount_duration: u64,
    birth_date: Timestamp,
}
  1. Should I implement it as a structure and add a Mapping field of that structure to my storage or implement it as separated values and add a Mapping field to each of it to my storage?
  2. I found something close to my problem on this site https://ink.substrate.io/datastructures/custom-datastructure but there's only example with one bool variable.
  • How to implement SpreadLayout for my structure with a bunch of different variables?
  • How to calculate FOOTPRINT? Guess I should add 1 for every i32. But what's about u8? Divide by 4? What's about bool? In example above there's added 1 only for one bool variable. I understand that you can't lock half of a storage cell but don't understand how should I calculate the FOOTPRINT. I'm really confused about it. I got 82 for FOOTPRINT, does my calculations right?
  • Also I don't understand how to implement functions: pull_spread(), push_spread() and clear_spread(). How to handle a lot of variables in these functions?
  1. How to use structure implemented as a SpreadLayout? I have an error of unsatisfied trait bounds when trying to get value of the structure with get() function of Mapping or when trying to set value with insert() function. Should I use new functions: pull_spread(), push_spread() and clear_spread() instead?
5
  • re question 1: consider that doing a Mapping with the entire struct means that you'll r/w every field whenever you want to w/r just a single field, which means wasteful storage io and expensive fees for simple contract calls. Commented Jul 19, 2022 at 20:40
  • Hey, are you deriving the SpreadAllocate trait on your root storage struct and using initialize_contract? Both of these are necessary when working with Mappings. As for the SpreadLayout trait, you shouldn't need to implement it manually since you're working with primitive types - so if you do need to implement such traits (which I don't think you do in this case) you can simply derive them.
    – HCastano
    Commented Jul 19, 2022 at 23:39
  • @BernardoRodrigues Thanks for your answer. It is really helpful because I will write every field only on initialize an instance but after will read/write 1-3 fields. I misunderstood of that concept a little because my previous blockchain was Tezos where you create a lot of structures and add them to Mapping without any problem.
    – lewlya
    Commented Jul 20, 2022 at 6:26
  • @HCastano No, I am trying to derive SpreadAllocate for secondary storage and want to have a Mapping to TokenInfo structure in my root storage.
    – lewlya
    Commented Jul 20, 2022 at 6:32
  • Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer.
    – Community Bot
    Commented Jul 20, 2022 at 9:05

1 Answer 1

3

So I think we need to clarify some things about ink! storage.

It is really helpful because I will write every field only on initialize an instance but after will read/write 1-3 fields

So in ink! all reads storage are eager.

What this means is that whenever this is read from storage, the entire struct will be read, even if just a single field is accessed.

For example:

#[ink(storage)]
pub struct ContractStorage {
    foo: u32,
    bar: AccountId,
}

#[ink(message)]
fn read_from_storage(&mut self) {
    // This reads the _entire_ `ContractStorage` struct from storage, which includes the
    // `bar` field.
    let foo = self.foo;
    todo!()
}

No, I am trying to derive SpreadAllocate for secondary storage and want to have a Mapping to TokenInfo structure in my root storage.

So there is not such thing as secondary storage. At the end of the day you only have fields in the root storage struct (the one tagged with #[ink(storage)].

Now, for the custom struct in a Mapping part. The Mapping API basically requires that your type implements scale::Encode, scale::Decode, SpreadLayout, and PackedLayout (see the requirements for Mapping::insert()).

Since your TokenInfo struct is made up of primitive types, which themselves implement all these traits, then you can simply derive those traits for TokenInfo. No need to manually implement them.

#![cfg_attr(not(feature = "std"), no_std)]

use ink_lang as ink;

#[ink::contract]
mod flipper {
    use ink_storage::traits::{SpreadAllocate, PackedLayout, SpreadLayout};

    #[derive(PackedLayout, SpreadLayout, scale::Encode, scale::Decode)]
    #[cfg_attr(feature = "std", derive(::scale_info::TypeInfo))]
    pub struct TokenInfo {
        data: [u8; 284],
        in_collection: bool,
        init_price: Balance,
        init_owner: AccountId,
        for_sale: bool,
        discount_duration: u64,
        birth_date: Timestamp,
    }

    #[ink(storage)]
    #[derive(SpreadAllocate)]
    pub struct Flipper {
        map: ink_storage::Mapping<u32, TokenInfo>,
    }

    impl Flipper {
        #[ink(constructor)]
        pub fn default() -> Self {
            ink_lang::utils::initialize_contract(|_| {})
        }

        #[ink(message)]
        pub fn flip(&mut self) {
            let s = TokenInfo {
                data: [0; 284],
                in_collection: Default::default(),
                init_price: Default::default(),
                init_owner: Default::default(),
                for_sale: Default::default(),
                discount_duration: Default::default(),
                birth_date: Default::default(),
            };

            self.map.insert(&0, &s);
        }
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.