🇵🇹
DAO Lunes Labs - PT
  • 👋Seja Bem-vindo a DAO Lunes Labs
  • Overview
    • 🚪Introdução
    • 🚀Manifesto DAO Lunes Labs
    • 🍕Tokenomics
    • 👑Proposta de Valor
    • 🧑‍🚀Comunidade e Participação
    • 🗺️Roadmap
    • 💼Compromisso com Ética e Responsabilidade
    • 👾Programa de Bug Bounty: Lunes Security Initiative (LSI)
  • Developers
    • 💽Para Nodes
      • 🥾Instalar Node
        • 🏗️Rust toolchain
        • 🐧Linux
    • 🖥️Para Desenvolvedores
      • 📑Smart Contract Ink! 4.x
        • ✨Configuração
          • 📐Criando um projeto com ink!
          • 🖥️Compile Seu Contrato
          • ⛓️Executar um nó Lunes
          • ⬆️Implante seu contrato
          • 🔛Chame Seu Contrato
          • 🛠️Solução de problemas
        • 🪄Fundamentos
          • 👾Modelo de Contrato
          • 👨‍🚀Armazenando Valores
          • 🔭Lendo Valores do Armazenamento
          • 🦸‍♂️Alterando os Valores de Armazenamento
          • 🎡Eventos
          • 👨‍🔧Seletores
          • 🪶Definições de Trait
          • 🗣️Chamadas entre Contratos (Cross-Contract Calls)
          • 🦸Contratos Atualizáveis
          • 🤺Funções do Ambiente
          • 🏟️Tipos de Ambiente de Cadeia
          • 💠Metadados
          • 🧪Testes de Contrato
          • 🕵️‍♂️Depuração de Contratos
          • 🔬Verificação de Contrato
        • 🎙️Macros e Atributos
          • 📇#[ink::contract]
          • 👽#[ink(anonymous)]
          • 👷#[ink(constructor)]
          • 📏#[ink(default)]
          • 🎢#[ink(event)]
          • 🛩️#[ink(impl)]
          • 📧#[ink(message)]
          • 👨‍💼#[ink(namespace = "…")]
          • 💸#[ink(payable)]
          • ⚡#[ink(selector = S:u32)]
          • 💽#[ink(storage)]
          • 💣#[ink(topic)]
          • ⛓️#[ink::chain_extension]
        • 💽Storege & Data Structires
          • Working with Mapping
          • Storage Layout
          • Custom Data Structures
          • Metadata Format
        • 👾Frontend Development
          • Getting Started
          • Connect Wallet
          • Hooks
            • All Hooks
            • Contracts
              • useCall
              • useCallSubscription
              • useContract
              • useDryRun
              • useEventSubscription
              • useEvents
              • useTx
              • useTxPaymentInfo
            • Wallets
              • useWallet
              • useAllWallets
              • useInstalledWallets
              • useUninstalledWallets
            • API
              • useApi
              • useBalance
              • useBlockHeader
          • Configuration
          • useink / core
            • Contracts
              • Call
              • decodeCallResult
              • decodeError
              • getRegistryError
              • toAbiMessage
          • useink / chains
            • Getting Started
            • Chain Configurations
            • ChainId
          • useink / notifications
            • Getting Started
            • Configuration
            • useNotifications
            • toNotificationLevel
          • useink / utils
            • Getting Started
            • Pick Helpers
            • tx Helpers
            • Types
        • 💡Examples
          • 📔Smart Contracts
          • 📱Dapps
        • 🛠️Tools
          • 🖌️OpenBrush
      • 📒Smart Contract - EVM
        • Create ERC-20 Ink Token!
      • 💰Desenvolvendo uma Wallet Lunes
        • 👾Transações de Tokens PSP22
    • 🎨Para Designers
      • 🖌️Brand Lunes
Powered by GitBook
On this page
  • Using custom types on storage​
  • Generic storage fields​

Was this helpful?

  1. Developers
  2. Para Desenvolvedores
  3. Smart Contract Ink! 4.x
  4. Storege & Data Structires

Custom Data Structures

Last updated 1 year ago

Was this helpful?

The ink_storage crate provides useful utilities and data structures to organize and manipulate the contract's storage. However, contract authors should know that they can also create their own custom data structures.

Using custom types on storage

Any custom type wanting to be compatible with ink! storage must implement the trait, so it can be SCALE and . Additionally, the traits and are required as well. But don't worry, usually these traits can just be derived:

/// A custom type that we can use in our contract storage
#[derive(scale::Decode, scale::Encode)]
#[cfg_attr(
    feature = "std",
    derive(scale_info::TypeInfo, ink::storage::traits::StorageLayout)
)]
pub struct Inner {
    value: bool,
}

#[ink(storage)]
pub struct ContractStorage {
    inner: Inner,
}

Even better: there is a macro , which derives all necessary traits for you. If there is no need to implement any special behavior, the above code example can be simplified further as follows:

/// A custom type that we can use in our contract storage
#[ink::storage_item]
pub struct Inner {
    value: bool,
}

#[ink(storage)]
pub struct ContractStorage {
    inner: Inner,
}

Naturally, you can as well implement any required trait manually. Please directly refer to the relevant trait documentations for more information.

NOTE

Types with custom implementations of the ink! storage traits can still use this macro only for key calculation by disabling the derives: #[ink::storage_item(derive = false)].

Let's say you want a mapping where accessing a non-existent key should just return it's default value, akin to how mappings work in Solidity. Additionally, you want to know how many values there are in the mapping (its length). This could be implemented as a thin wrapper around the ink! Mapping as follows:

/// Values for this map need to implement the `Default` trait.
/// Naturally, they also must be compatible with contract storage.
/// Note that the underlying `Mapping` type only supports `Packed` values.
#[ink::storage_item]
pub struct DefaultMap<K, V: Packed + Default> {
    values: Mapping<K, V>,
    length: u32,
}

impl<K: Encode, V: Packed + Default> DefaultMap<K, V> {
    /// Accessing non-existent keys will return the default value.
    pub fn get(&self, key: &K) -> V {
        self.values.get(key).unwrap_or_default()
    }

    /// Inserting into the map increases its length by one.
    pub fn set<I, U>(&mut self, key: I, value: &U)
    where
        I: scale::EncodeLike<K>,
        E: scale::EncodeLike<V> + Storable,
    {
        if self.values.insert(key, value).is_none() {
            self.length += 1
        }
    }

    /// Removing a value from the map decreases its length by one.
    pub fn remove(&mut self, key: &K) {
        if self.values.take(key).is_some() {
            self.length -= 1
        }
    }

    /// Return how many values the mapping contains
    pub fn len(&self) -> u32 {
        self.length
    }
}

/// `DefaultMap` is compatible with contract storage.
#[ink(storage)]
pub struct MyContract {
    my_map: DefaultMap<BlockNumber, Balance>,
}

CAUTION

Generic data types may substantially increase your contracts overall code size, making it more costly to store on-chain.

The #[ink::storage_item] macro is responsible for storage key calculation of non- types. Without it, the key for non-Packed fields will be zero. Using this macro is necessary if you don't plan to use a on a non-Packed type.

Generic storage fields

It is possible to use generic data types in your storage, as long as any generic type satisfies the required storage trait bounds. In fact, we already witnessed this in the previous sections about the .

The reason for this is .

🖥️
📑
💽
​
Storable
encoded
decoded
StorageLayout
TypeInfo
#[ink::storage_item]
Packed
ManualKey
​
Mapping
Rust's monomorphization