🇵🇹
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
  • Storage Organization​
  • Packed vs Non-Packed layout​
  • Eager Loading vs. Lazy Loading​
  • ustustManual vs. Automatic Key Generation​
  • Considerations​

Was this helpful?

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

Storage Layout

Last updated 1 year ago

Was this helpful?

Smart contract authors are given some flexibility in regards on how they want to organize the storage layout of their contracts. Let's dive deeper into the concepts behind ink! storage to get a better understanding of some of its implications and limitations.

Storage Organization

The following schema depicts the storage which is exposed to ink! by the contracts pallet:

For example, if we have a somewhat small contract storage struct consisting of only a few tiny fields, pulling everything from the storage inside every message is not problematic. It may even be advantageous - especially if we expect most messages to interact with most of the storage fields.

On the other hand, this can get problematic if we're storing a large ink::prelude::vec::Vec in the contract storage but provide messages that do not need to read and write from this Vec. In that scenario, each and every contract message bears runtime overhead by dealing with that Vec, regardless whether they access it or not. This results in extra gas costs. To solve this problem we need to turn our storage into a non-packed layout somehow.

CAUTION

If any type exhibiting Packed layout gets large enough (an ever-growing Vec might be a prime candidate for this), it will break your contract. This is because for encoding and decoding storage items, there is a buffer with only limited capacity (around 16KB in the default configuration) available. This means any contract trying to decode more than that will trap! If you are unsure about the potential size a data structure might get, consider using an ink! Mapping, which can store an arbitrary number of elements, instead.

The following example demonstrates how we can solve the problem introduced in the above section. You'll notice that for the lazily loaded storage field, we now work with getters and setters to access and modify the underlying storage value:

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

#[ink::contract]
mod mycontract {
    use ink::prelude::vec::Vec;
    use ink::storage::Lazy;

    #[derive(Default)]
    #[ink(storage)]
    pub struct MyContract {
        tiny_value: Balance,
        /// This vector might get large and expensive to work with.
        /// We want to enforce a non-`Packed` storage layout.
        large_vec: Lazy<Vec<Balance>>,
    }

    impl MyContract {
        #[ink(constructor)]
        pub fn new() -> Self {
            Self::default()
        }

        /// Because `large_vec` is loaded lazily, this message is always cheap.
        #[ink(message)]
        pub fn get_balance(&self) -> Balance {
            self.tiny_value
        }

        /// Lazy fields like `large_vec` provide `get()` and `set()` storage operators.
        #[ink(message)]
        pub fn add_balance(&mut self, value: Balance) {
            let mut balances = self.large_vec.get_or_default();
            balances.push(value);
            self.large_vec.set(&balances);
        }
    }
}

CAUTION

ink::prelude::vec::Vec's are always loaded in their entirety. This is because all elements of the ink::prelude::vec::Vec live under a single storage key. Wrapping the ink::prelude::vec::Vec inside Lazy, like the provided example above does, has no influence on its inner layout. If you are dealing with large or sparse arrays on contract storage, consider using a Mapping instead.

#[ink(storage)]
pub struct MyContract {
    /// The storage key for this field is always `0x0000007f`
    inner: Lazy<bool, ManualKey<127>>,
}

This may be advantageous: Your storage key will always stay the same, regardless of the version of your contract or ink! itself (note that the key calculation algorithm may change with future ink! versions).

TIP

Using ManualKey instead of AutoKey might be especially desirable for upgradable contracts, as using AutoKey might result in a different storage key for the same field in a newer version of the contract. This may break your contract after an upgrade 😱!

The storage key of the contracts root storage struct defaults to 0x00000000. However, contract developers can set the key to an arbitrary 4 bytes value by providing it a ManualKey like so:

/// Manually set the root storage key of `MyContract` to be `0xcafebabe`.
#[ink(storage)]
pub struct MyContract<KEY: StorageKey = ManualKey<0xcafebabe>> {
    value: bool,
}

It might be worthwhile to think about the desired storage layout of your contract. While using a Packed layout will keep your contracts overall code size smaller, it can cause unnecessarily high gas costs. Thus, we consider it a good practice to break up large or complex storage layouts into reasonably sized distinct storage cells.

NOTE

ink! Mappings are always non-Packed and loaded lazily, one key-value pair at the time.

Storage data is always encoded with the codec. The storage API operates by storing and loading entries into and from a single storage cells, where each storage cell is accessed under its own dedicated storage key. To some extent, the storage API works similar to a traditional key-value database.

Packed vs Non-Packed layout

Types that can be stored entirely under a single storage cell are considered . By default, ink! tries to store all storage struct fields under a single storage cell. Consequentially, with a Packed storage layout, any message interacting with the contract storage will always need to operate on the entire contract storage struct.

Eager Loading vs. Lazy Loading

ink! provides means of breaking the storage up into smaller pieces, which can be loaded on demand, with the primitive. Wrapping any storage field inside a Lazy struct makes the storage struct in which that field appears also non-Packed, preventing it from being eagerly loaded during arbitrary storage operations:

Note that in above illustration, the key of 0x12345678 just serves as an example; we'll learn more about storage key calculation .

ustustManual vs. Automatic Key Generation

By default, keys are calculated automatically for you, thanks to the primitive. They'll be generated at compile time and ruled out for conflicts. However, for non-Packed types like Lazy or the Mapping, the primitive allows manual control over the storage key of a field like so:

Considerations

🖥️
📑
💽
SCALE
​
Packed
​
Lazy
later in this chapter
​
AutoKey
ManualKey
​
​