WEB3DEV

Cover image for Compreendendo o Layout de Armazenamento do Solidity e Como Acessar Variáveis de Estado
Isabela Curado Nehme
Isabela Curado Nehme

Posted on

Compreendendo o Layout de Armazenamento do Solidity e Como Acessar Variáveis de Estado

https://miro.medium.com/v2/resize:fit:720/format:webp/1*vRazbr-BAMjY_mEWtZhcgQ.jpeg

Foto de Raymond Rasmusson no Unsplash.

Introdução

Quando comecei a aprender o básico do Solidity, tive dificuldade em entender a estrutura de armazenamento. Uma boa maneira de interpretá-la é como uma estrutura de dados de valor-chave que armazena variáveis ​​de estado de contratos inteligentes.

Pensar no armazenamento como um array nos ajudará a entendê-lo melhor. O Solidity fornece 2²⁵⁶ slots (indexados de 0 a 2²⁵⁶- 1), cada um com comprimento fixo de 32 bytes.

slot[0] = data
slot[1] = data
slot[2] = data
.
.
.
slot[n] = data
Enter fullscreen mode Exit fullscreen mode

Variáveis ​​de estado são armazenadas de acordo com sua declaração de posição, comprimento e se são um valor ou um tipo dinâmico.

Este artigo tem como objetivo apresentar uma exploração concisa e não muito técnica do layout de armazenamento do Solidity, na esperança de que seja acessível e compreensível até mesmo para desenvolvedores iniciantes de contratos inteligentes.

Uma série de exemplos curtos e breves são apresentados para estudar diferentes cenários sobre como as variáveis ​​de estado são armazenadas no armazenamento.

Se você quiser fazer os exemplos sozinho (o que eu recomendo totalmente), você pode usar o Remix IDE para implantar e usar o console para interagir com os contratos.

Layout de armazenamento para tipos de valor

Vamos estudar a série de exemplos a seguir:

Observação: todos os exemplos de contratos neste artigo são implantados na rede de teste Sepolia e os endereços são fornecidos para cada exemplo.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

// endereço do contrato: 0x511aC56388Bea7c42ECC3F80631f297B22045A93

contract StorageLayout {
    uint256 num = 1; // vai para o slot 0
}
Enter fullscreen mode Exit fullscreen mode

StorageLayout declara uma única variável de estado num do tipo uint256. Esse tipo de dados contém 32 bytes, que é o comprimento máximo de um slot, portanto, esta variável ocupa o slot 0.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

// endereço do contrato: 0xB36DdD815E90E0699BFaD90Ad1658F0f59715201

contract StorageLayout {
    uint256 x = 1; // slot 0
    uint256 y = 2; // slot 1
    uint256 z = 3; // slot 2
}
Enter fullscreen mode Exit fullscreen mode

Neste exemplo, são declaradas três variáveis ​​do tipo uint256 , x será armazenada no slot 0 porque é a primeira variável de estado declarada, y irá para o slot 1 e z para o slot 2.

Até aí tudo fácil, certo?

Mas o que acontece quando as variáveis ​​de estado são menores que 32 bytes?

O Solidity pode agrupar dados em um único slot, se eles couberem. Vejamos o próximo exemplo:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

// endereço do contrato: 0xeBa088B4182EC4261FA4fd2526F58995Dc1Ec117

contract StorageLayout {
    uint16 x = 1;
    uint16 y = 2;
    uint16 z = 3;
}
Enter fullscreen mode Exit fullscreen mode

StorageLayout declara três variáveis ​​de estado do tipo uint16, este tipo pode ser representado com 2 bytes. Vamos usar o método web3.eth.getStorageAt(contractAddress, slotPosition) da biblioteca web3.js para ler o conteúdo do slot 0. Para utilizar esse método, precisamos passar como parâmetros o endereço do contrato que queremos consultar e a posição do slot que queremos acessar. O método retornará o conteúdo do slot em uma representação de 32 bytes.

Vamos resolver este exemplo com o Remix:

  • Crie um novo arquivo com o contrato e compile.
  • Conecte a Metamask à rede de teste Sepolia:

https://miro.medium.com/v2/resize:fit:586/format:webp/1*Bc20kYm0u8AnhKImspONoQ.png

Imagem 1. Remix IDE.
  • Agora implante o contrato com o botão “Implantar” (Deploy). Após a implantação do contrato, use o terminal para interagir com o contrato:

https://miro.medium.com/v2/resize:fit:640/format:webp/1*YblY3uYf5bp0Zu0Wfo4w2Q.png

Imagem 2. Remix IDE.

Executar o comando no terminal retornará:

slot[0] = 0x0000000000000000000000000000000000000000000000000000**000300020001**
Enter fullscreen mode Exit fullscreen mode

Como você pode ver, as três variáveis ​​de estado foram compactadas juntas da direita para a esquerda em um único slot, o que aconteceu porque variáveis ​​do tipo uint16 não podem preencher o slot inteiro. Para preencher o slot, o Solidity deixou os dados preenchidos para 32 bytes com zeros.

Todas as variáveis ​​​​de estado no Solidity são codificadas em ABI e, ao recuperar seus valores, são decodificadas automaticamente.

Podemos decodificar cada saída usando o método web3.eth.abi.decodeParameter(TYPE, DATA), por exemplo:

web3.eth.abi.decodeParameter("uint16", "0x0000000000000000000000000000000000000000000000000000000000000001")
Enter fullscreen mode Exit fullscreen mode

Este comando retornará 1.

Vejamos outro exemplo:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

// endereço do contrato: 0xa5418D2de1c56fdde47aCdBDeCE927a241B45d8F

contract StorageLayout {
    bool status = true;
    address addr = 0xCc8188e984b4C392091043CAa73D227Ef5e0d0a7;
}
Enter fullscreen mode Exit fullscreen mode

Se implantarmos isso na rede de teste Sepolia e executarmos os mesmos comandos do exemplo anterior para ler o armazenamento, obteremos:

slot[0] = 0x0000000000000000000000cc8188e984b4c392091043caa73d227ef5e0d0a7**01**
Enter fullscreen mode Exit fullscreen mode

address é um tipo integrado exclusivo no Solidity, que tem um comprimento de 20 bytes, os booleanos podem ser representados com um byte - 0x00 para false e 0x01 para true.

Da direita para a esquerda, o primeiro byte (em negrito) representa o boolean status (estado booleano) com valor 0x01. Os próximos 20 bytes representam o endereço e o restante dos dados são preenchidos com zeros para completar os 32 bytes.

Vamos dar uma olhada neste exemplo final para tipos de valor:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

// endereço do contrato: 0x92e8d7602b86Fa66F579F2aDE30Fd0493791C4f9

contract StorageLayout {
    uint16 x = 1;
    uint256 y = 2;
    uint16 z = 3;
}
Enter fullscreen mode Exit fullscreen mode

O layout de armazenamento para esse exemplo é:

slot[0] = 0x00000000000000000000000000000000000000000000000000000000000000**01**
slot[1] = 0x00000000000000000000000000000000000000000000000000000000000000**02**
slot[2] = 0x00000000000000000000000000000000000000000000000000000000000000**03**
Enter fullscreen mode Exit fullscreen mode

Neste caso, o Solidity não pode empacotar as variáveis ​​​​em um único slot como antes porque y é do tipo uint256, que está entre x e z e preenche sozinho todo o slot 1. Por isso, cada variável será armazenada em slots individuais.

Layout de armazenamento para tipos dinâmicos

Os tipos dinâmicos podem mudar dinamicamente de tamanho, aumentando ou diminuindo a quantidade de dados que contêm. Por esse motivo, os elementos desses tipos não podem ser armazenados sequencialmente entre outras variáveis ​​​​de estado da mesma maneira que o Solidity armazena tipos de valor.

Arrays

Vamos estudar o seguinte contrato:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

// endereço do contrato: 0xf9AaF13CefB378d22672578719E0fa5114Fc1484

contract StorageLayout {
    bool private status = true; // slot 0
    uint256[] private numArray = [1, 2, 3, 4, 5]; // o slot 1 contém o comprimento do array
    address private z = 0xCc8188e984b4C392091043CAa73D227Ef5e0d0a7; // slot 2

    function getSlotForArrayElement(uint256 _elementIndex) public pure returns (bytes32) {
        bytes32 startingSlotForArrayElements = keccak256(abi.encode(1));
        return bytes32(uint256(startingSlotForArrayElements) + _elementIndex);
    }
}   
Enter fullscreen mode Exit fullscreen mode

Se verificarmos os dados armazenados nos slots 0 e 2 após a implantação deste contrato, saberemos que encontraremos status e z, respectivamente, de acordo com as regras de armazenamento para tipos de valor.

Se acessarmos o conteúdo do slot 1 com o seguinte comando:

web3.eth.getStorageAt("0xf9AaF13CefB378d22672578719E0fa5114Fc1484", "1")
Enter fullscreen mode Exit fullscreen mode

Obteremos:

0x0000000000000000000000000000000000000000000000000000000000000005
Enter fullscreen mode Exit fullscreen mode

O array contém 5 elementos, mas a única coisa que obtivemos foi um único byte (0x05), que representa o comprimento do array.

Para arrays, o Solidity armazena o comprimento do array no slot em que o array foi declarado, os dados que o array contém são armazenados em outros slots. Para determinar o slot no qual os dados do array estão armazenados, precisamos obter o keccak256 do índice da declaração do slot do array. Vamos simplificar isso com a seguinte fórmula:

keccak256(abi.encode(ARRAY_SLOT_DECLARATION))
Enter fullscreen mode Exit fullscreen mode

numArray foi declarado no slot 1 e apenas o comprimento do array é armazenado lá. Agora, vamos aplicar a fórmula acima para calcular o slot a partir do qual os dados do array serão armazenados, executando a função getArraySlotForElement e passando 0 como parâmetro, isso retornará:

// representação do índice do slot em hexadecimal
0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6
Enter fullscreen mode Exit fullscreen mode

Podemos converter esse valor hexadecimal em decimal para obter o seguinte grande número:

// índice de slot a partir do qual os dados de `numArray` serão armazenados
80084422859880547211683076133703299733277748156566366325829078699459944778998
Enter fullscreen mode Exit fullscreen mode

Agora, se lermos o conteúdo desse slot, obteremos:

0x0000000000000000000000000000000000000000000000000000000000000001
Enter fullscreen mode Exit fullscreen mode

Como você pode ver, acessamos o primeiro elemento do array, que é 1.

Se você executar getArraySlotElement e passar o índice do restante dos elementos do array, você notará que os elementos são armazenados sequencialmente. Uma vez que o Solidity determina o slot inicial, os slots para cada um dos elementos do array são:

// para o elemento`1` no índice 0 do array, o slot é:
8008442285988054721168307613370329973327774815656636632582907869945994477899**8**

// para o elemento`2` no índice 1 do array , o slot é:
8008442285988054721168307613370329973327774815656636632582907869945994477899**9**

// para o elemento`3` no índice 2 do array, o slot é:
8008442285988054721168307613370329973327774815656636632582907869945994477900**0**

// para o elemento`4` no índice 3 do array, o slot é:
8008442285988054721168307613370329973327774815656636632582907869945994477900**1**

// para o elemento`5` no índice 4 do array, o slot é:
8008442285988054721168307613370329973327774815656636632582907869945994477900**2**
Enter fullscreen mode Exit fullscreen mode

Mapeamentos

Assim como nos arrays, os elementos dos mapeamentos não são armazenados sequencialmente, o slot no qual mapping é declarado não contém nenhuma informação, pois os mapeamentos não têm comprimento. A fórmula para determinar o slot no qual cada elemento de um mapping será armazenado é a seguinte:

keccak256(abi.encode(KEY, SLOT_INDEX_DECLARATION))
Enter fullscreen mode Exit fullscreen mode

Essa fórmula calcula o hash da concatenação da chave com o slot no qual o mapping foi declarado. O slot do mapeamento atua como salt para evitar que os elementos do mapeamento substituam os slots de outros mapeamentos.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

// endereço do contrato: 0x2eD8B9aAAba437253fC3B2D9fDf04fac99dDc208

contract StorageLayout {
    mapping(address => uint256) private addressToBalance;
    uint8 private constant mappingSlotIndex = 0;

    constructor() {
        addressToBalance[0x5B38Da6a701c568545dCfcB03FcB875f56beddC4] = 123;
        addressToBalance[0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2] = 456;
        addressToBalance[0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db] = 789;
    }

    function getMappingElementSlotIndex(address _key) public pure returns (bytes32) {
        return keccak256(abi.encode(_key, mappingSlotIndex));
    }
}
Enter fullscreen mode Exit fullscreen mode

O construtor deste contrato inicializa addressToBalance com três endereços e algum saldo. getMappingElementSlotIndex pega uma chave do mapeamento e calcula o slot no qual o valor mapeado para essa chave está armazenado.

Se tivermos interesse em saber o slot para o endereço 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, podemos chamar getMappingElementSlotIndex e com o endereço e ele retornará:

0x58f8e73c330daffe64653449eb9a999c1162911d5129dd8193c7233d46ade2d5
Enter fullscreen mode Exit fullscreen mode

Se lermos os dados nesse slot:

web3.eth.getStorageAt("0x2eD8B9aAAba437253fC3B2D9fDf04fac99dDc208","0x58f8e73c330daffe64653449eb9a999c1162911d5129dd8193c7233d46ade2d5")
Enter fullscreen mode Exit fullscreen mode

Nós obteremos:

0x000000000000000000000000000000000000000000000000000000000000007b
Enter fullscreen mode Exit fullscreen mode

Se transformarmos essa string hexadecimal em decimal, obteremos 123.

Os slots para os outros dois endereços são:

// para 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 o slot é:
0x1a1017a437881fd8fee8ab135586d886995df9286bd91e5d3c250f79b2327f02

// para 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db o slot é:
0xbc67542bfa83c3e43faa1ce49daa83c7bb0610df1c8f6899b8fbb170f5c183ee
Enter fullscreen mode Exit fullscreen mode

Se compararmos os três slots, podemos verificar que os dados não são armazenados sequencialmente, na verdade, eles estão muito distantes um do outro.

Vamos calcular para ver quantos slots separam 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 de 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db:

// subtrai os valores em hexadecimal:
bc67542bfa83c3e43faa1ce49daa83c7bb0610df1c8f6899b8fbb170f5c183ee
- 1a1017a437881fd8fee8ab135586d886995df9286bd91e5d3c250f79b2327f02

// resultado da subtração em hexadecimal:
A2573C87C2FBA000000000000000000000000000000000000000000000000000
// para decimal:
73428814930032578957074960535759132369224003477876394282500466786575899951104 // slots
Enter fullscreen mode Exit fullscreen mode

Ufa! Eles estão longe um do outro!

Agora podemos concluir que, diferentemente dos arrays, os dados nos mapeamentos não são armazenados sequencialmente.

Agora vamos estudar o caso de ter 2 mapeamentos declarados com o mesmo tipo de chave.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// endereço do contrato: 0x94850369761233deD50Ab12E1F1fcDC0AfbddF7D

contract StorageLayout {
    mapping(address => uint256) private addressToBalance1;
    mapping(address => uint256) private addressToBalance2;
    uint8 private constant mappingSlotIndex1 = 0;
    uint8 private constant mappingSlotIndex2 = 1;

    constructor() {
        addressToBalance1[0x5B38Da6a701c568545dCfcB03FcB875f56beddC4] = 123;
        addressToBalance1[0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2] = 456;
        addressToBalance1[0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db] = 789;

        addressToBalance2[0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB] = 789;
        addressToBalance2[0x617F2E2fD72FD9D5503197092aC168c91465E7f2] = 456;
        addressToBalance2[0x17F6AD8Ef982297579C203069C1DbfFE4348c372] = 123;
    }

    function getMappingElementSlotIndex1(address _key) public pure returns (bytes32) {
        return keccak256(abi.encode(_key, mappingSlotIndex1));
    }

    function getMappingElementSlotIndex2(address _key) public pure returns (bytes32) {
        return keccak256(abi.encode(_key, mappingSlotIndex2));
    }
}
Enter fullscreen mode Exit fullscreen mode

Neste caso, podemos perceber a importância de calcular o slot com o hash da concatenação da chave e o slot onde o mapeamento foi declarado. As funções hash são determinísticas - isso significa que, para a mesma entrada, a função hash gerará sempre a mesma saída, sem que a declaração do slot de mapeamento atue, pois os mapeamentos salt que compartilham a mesma chave substituirão os dados de outros mapeamentos.

Chamar getMappingElementSlotIndex1 para 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 retorna:

0x58f8e73c330daffe64653449eb9a999c1162911d5129dd8193c7233d46ade2d5
Enter fullscreen mode Exit fullscreen mode

Mas chamar getMappingElementSlotIndex2 para a mesma chave retorna:

0x36306db541fd1551fd93a60031e8a8c89d69ddef41d6249f5fdc265dbc8fffa2
Enter fullscreen mode Exit fullscreen mode

Os slots são completamente diferentes, o salt adicionado garante que os dados não serão sobrescritos por mapeamentos com as mesmas chaves.

Caso os valores dos mapeamentos sejam outros mapeamentos ou arrays de arrays, as regras explicadas antes se aplicam recursivamente.

Strings e Bytes

Strings e bytes codificados e armazenados no armazenamento acontecem exatamente da mesma maneira. Portanto, abordarei nesta seção apenas string.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// endereço do contrato: 0xb2e0a6814389eF64257FFD01b9Da263d585D7790

contract StorageLayout {
    string public name = "Pacelli";
}
Enter fullscreen mode Exit fullscreen mode

Implantamos este contrato e acessamos o slot 0:

web3.eth.getStorageAt("0xb2e0a6814389eF64257FFD01b9Da263d585D7790", "0")
Enter fullscreen mode Exit fullscreen mode

E obtemos:

0x506163656c6c690000000000000000000000000000000000000000000000000e
Enter fullscreen mode Exit fullscreen mode

O resultado é interessante, vemos dois conjuntos de bytes que representam dados separados por zeros.

No lado direito, temos 0x0e, que representa o comprimento da string. Se convertermos esse valor hexadecimal para decimal obtemos 14 bytes.

No lado esquerdo, vemos os dados reais, 0x506163656c6c69, e, se contarmos os bytes, a soma totaliza 14. Converta esse valor hexadecimal em texto e obteremos Pacelli.

Podemos concluir que se a string e o length(comprimento) da string couberem no mesmo slot, o Solidity os empacotará juntos. Mas o que acontece se a string e o comprimento não couberem no mesmo slot? Nesse caso, as regras para arrays se aplicam a strings, da mesma forma que o comprimento da string é armazenado na declaração do slot e os dados são divididos em pedaços de 32 bytes armazenados sequencialmente.

Vejamos o seguinte contrato:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// endereço do contrato: 0x05b34B6c48129e5Df2Fc36A1162B2dedB7D751a3

contract StorageLayout {
    string public text = "No mês passado esqueci de pagar meu serviço de telefonia móvel e o provedor quase cortou meu serviço :(, lixo lixo lixo lixo teste teste teste, hoje é sábado, está chovendo muito e esqueci meu guarda-chuva.";
    bytes32 public startingSlotString = keccak256(abi.encode(0));

    function getStartingSlotForString(uint256 _index) public view returns (bytes32) {
        return bytes32(uint256(startingSlotString) + _index);
    }
}
Enter fullscreen mode Exit fullscreen mode

No slot 0 obtemos:

web3.eth.getStorageAt("0x2180EA94D776CF9F9Cc1526d86A9c4036b0fC25b", "0")

// retorna:
0x0000000000000000000000000000000000000000000000000000000000000199
Enter fullscreen mode Exit fullscreen mode

0x0199 é o comprimento da string, em decimal, o valor é 409 bytes.

Agora vamos ler nos slots:

// no slot 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56**3** obtemos:
0x4c617374206d6f6e7468204920666f72676f7420746f20706179206d79206d6f

// em texto:
Mês passado eu esqueci de pagar meu ser

// no slot 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56**4** obtemos:
0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564

// em texto:
viço de telefonia móvel e o prove

... e assim por diante
Enter fullscreen mode Exit fullscreen mode

Para continuar lendo o resto do texto, basta continuar adicionando um.

Structs

Para variáveis ​​de estado struct, o índice do slot onde a struct é declarada é reservado para o primeiro elemento, o próximo slot para o segundo e assim por diante.

O próximo contrato declara uma struct Car, a declaração de tipo não ocupa um slot no armazenamento. Em seguida, um novo Car é criado: Car(Toyota, 2012, ABCDEF, sedan, 10000).

// SPDX-License-Idenfitier: MIT
pragma solidity ^0.8.0;

// endereço do contrato: 0xe3A9d8B1d576D50B0B4f3B7Ae76A6b729881eEF9

contract StorageLayout {
    struct Car {
        string brand;
        uint256 year;
        uint256 price;
        bool isSold;
    }

    Car public car = Car({brand: "Toyota", year: 2012, price: 10000, isSold: true});
}
Enter fullscreen mode Exit fullscreen mode

Se acessarmos os slots:

web3.eth.getStorageAt("0xe3A9d8B1d576D50B0B4f3B7Ae76A6b729881eEF9", "0")
web3.eth.getStorageAt("0xe3A9d8B1d576D50B0B4f3B7Ae76A6b729881eEF9", "1")
web3.eth.getStorageAt("0xe3A9d8B1d576D50B0B4f3B7Ae76A6b729881eEF9", "2")
web3.eth.getStorageAt("0xe3A9d8B1d576D50B0B4f3B7Ae76A6b729881eEF9", "3")
Enter fullscreen mode Exit fullscreen mode

Obteremos:

slot[0] = 0x546f796f7461000000000000000000000000000000000000000000000000000c
slot[1] = 0x00000000000000000000000000000000000000000000000000000000000007dc
slot[2] = 0x0000000000000000000000000000000000000000000000000000000000002710
slot[3] = 0x0000000000000000000000000000000000000000000000000000000000000001
Enter fullscreen mode Exit fullscreen mode

Como esperado, no slot 0 obtemos a marca brand, no slot 1 obtemos o ano year, no slot 2 obtemos o preço e, finalmente, no slot 3 obtemos se está vendido isSold.

O empacotamento também ocorre em estruturas se as variáveis ​​couberem no mesmo slot.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// endereço do contrato: 0xf2137C5c3Ed626B1462F3Ac3E8145a38b86E3049

contract StorageLayout {
    struct Values {
        uint24 value1;
        uint24 value2;
        uint24 value3;
        uint24 value4;
    }

    Values public values = Values(10, 20, 30, 40);
}
Enter fullscreen mode Exit fullscreen mode

Se lermos o slot 0, obteremos:

slot[0] = 0x000000000000000000000000000000000000000000002800001e00001400000a
Enter fullscreen mode Exit fullscreen mode

Da direita para a esquerda: 0x0a é 10, 0x14 é 20, 0x1e é 30 e 0x28 é 40.

Se os elementos de uma struct forem do tipo dinâmico, as regras discutidas anteriormente se aplicam da mesma maneira.

Conclusão

Compreender o layout de armazenamento no Solidity é importante porque ajuda na eficiência do espaço e pode reduzir os custos de gás de implantação e execução.

Dependendo dos valores que nossas variáveis ​​de estado irão conter, precisamos selecionar corretamente os tipos uintN e bytesN e declará-las na ordem correta para que sejam empacotadas juntas. Desta forma, podemos acessar múltiplas variáveis ​​em uma única chamada.

Outra coisa a dizer é que, na maioria dos exemplos, as variáveis ​​de estado foram declaradas com visibilidade private (privada). Há um equívoco comum de que variáveis ​​private não são acessíveis ou são secretas, mas vimos que, usando uma biblioteca como web3.js com seu método web3.eth.getStorageAt, podemos ler qualquer slot de armazenamento para acessar dados independentemente da visibilidade. Todos os dados armazenados em uma blockchain pública como a Ethereum são acessíveis a qualquer pessoa, portanto, nunca armazene informações confidenciais, a menos que tenham sido criptografadas.

Leitura adicional

Este artigo foi escrito por Eugenio Pacelli Flores Voitier e traduzido por Isabela Curado Nehme. Seu original pode ser lido aqui.

Top comments (0)