WEB3DEV

Cover image for Série Raio X Solidity: Dominando o Armazenamento — Parte 1
Paulo Gio
Paulo Gio

Posted on • Atualizado em

Série Raio X Solidity: Dominando o Armazenamento — Parte 1

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*BflxegbOFHKfBwZqgE0v3Q.jpeg

https://www.dreamstime.com/royalty-free-stock-images-large-modern-warehouse-image28362589

O que exatamente se entende por "armazenamento de contrato"?

É o local de armazenamento em um contrato inteligente onde são armazenadas todas as suas Variáveis de Estado em slots de armazenamento de maneira permanente.

Vamos detalhar isso.

Variáveis de Estado, que são definidas no nível do contrato em si e residem apenas no Armazenamento do Contrato.

Slots de Armazenamento, "lugares" ou "compartimentos" em um contrato para armazenar as Variáveis de Estado.

E, é Permanente, pois o valor permanece lá até que a próxima transação que altera o estado o modifique e, em seguida, o novo valor atualizado também permanece lá até que a próxima alteração aconteça e assim por diante.

Está sempre disponível para ser lido por uma EOA (Externally Owned Account, ou conta de propriedade externa) e/ou outro contrato inteligente, mesmo após uma operação ter terminado.

Ei... "outro contrato inteligente" ...?

Sim, um contrato inteligente pode APENAS ler de / escrever em seu próprio armazenamento de contrato por padrão.

Quando quiser ler de / escrever em outro armazenamento de contrato (alvo), isso é POSSÍVEL APENAS quando o contrato alvo possui funções de leitura / escrita disponíveis em seu próprio código como públicas / externas.

Isso faz com que o ABI do contrato alvo exponha funções que entidades externas têm acesso.

Se você quiser saber mais sobre ABI, confira aqui, por favor.

Vale a pena mencionar aqui que, quando uma EOA lê do armazenamento, é gratuito.

Mas, custa gás quando um contrato lê do armazenamento como parte do código (não lido diretamente por uma EOA).

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

contract Storage {

   uint256 a;

   function readStorage() public view returns (uint256) {
       return a;
   }
}
Enter fullscreen mode Exit fullscreen mode

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*aqmamXrwEry55-FfZGfw3A.png

Para mais detalhes sobre leitura do armazenamento (opcode: SLOAD), consulte isto.

Agora, vem a Escrita (opcode: SSTORE)

Escrever no slot de Armazenamento do Contrato (ou seja, mudar o estado do contrato) é a operação mais cara em termos de gás na EVM.

Estimando o custo em gás para escrever em um slot de armazenamento:

Em resumo, é complicado.

O custo ao escrever em um slot de armazenamento depende de vários fatores, como:

(1) se o novo valor armazenado é um zero

Ou um valor diferente de zero?

(2) se é a primeira vez que o valor do slot é alterado

Ou se já foi alterado antes?

(3) se o slot já foi acessado anteriormente à operação de escrita em questão

Ou se está sendo acessado pela primeira vez para escrever um novo valor?

Todos os prós e contras são resumidos em construções if-else aqui.

Eu recomendo fortemente que você experimente todos os diferentes cenários que puder imaginar em um código de exemplo no Remix IDE e veja/compare os custos do gás para um entendimento completo.

Pessoalmente, eu recomendaria escrever no armazenamento apenas quando ABSOLUTAMENTE necessário e, mesmo assim, usar ferramentas/plug-ins como eth-gas-reporter, hardhat-gas-reporter para ver os custos de gás de uma transação que aceita alterações de estado em seu fluxo de trabalho.

Vamos agora entender como o Armazenamento é estruturado.

Isso ajuda a criar um modelo mental justo do armazenamento do contrato.

Os 2 termos chave são:

chave e valor

Armazenamento do Contrato = mapeamento chave-valor (e não um array).

Uma chave é sempre um número de 256 bits (32 bytes) começando com '0'.

Conforme o valor máximo que um número não-negativo de 256 bits pode conter, teoricamente, o valor máximo de uma chave pode ser (2**256) - 1.

Esse valor é tão grande que eu não consigo compreendê-lo cognitivamente.

E você, querido(a)?

Um valor é também uma palavra de 32 bytes que representa o valor subjacente armazenado em um slot específico no armazenamento.

"Palavra"... o que é isso? Consulte isto.

Isso significa que um contrato pode conter (2**256) - 1 variáveis de estado em seu armazenamento.

Ufa... Isso é um monte de variáveis de estado em um único contrato inteligente.

Mais ainda, se 2 ou mais variáveis de estado estiverem agrupadas em 1 slot (discutido adiante), o total pode ser muuuito maior.

Este é um modelo mental que criei para o meu próprio entendimento:

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*dcxvDG04HBq7dELucu_lZg.jpeg

Considere isso equivalente a uma estante que armazena materiais em prateleiras em diferentes níveis em um armazém de fábrica, como a seguir:

Eu não adicionei a imagem do título à toa, ela tem um propósito.

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*B9CPFvc3LgexkKEHNgEAmA.jpeg

Considere a estante vertical de cor azul (destacada em vermelho) como a área de armazenamento de um contrato.

Dentro dessa estante, temos bases/prateleiras laranjas (destacadas em amarelo), que são os slots.

Os slots são referenciados por números-chave, as mesmas chaves explicadas acima.

As caixas/paletes marrons (destacadas em rosa claro) são variáveis de estado/armazenamento.

Vamos agora entender como as Variáveis de Estado são organizadas no armazenamento.

Todas as variáveis de estado de tamanho fixo são armazenadas de forma contígua no armazenamento, na ordem de sua declaração no contrato inteligente.

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

contract Storage {

   uint256 a = 10;
   uint256 b;
   uint128 c = 30;
   uint128 d = 40;
   uint256 e = 50;
   string storageName;

   function readStorageSlot() public view
   returns(
   bytes32 valueAtSlotZero,
   bytes32 valueAtSlotOne,
   bytes32 valueAtSlotTwo,
   bytes32 valueAtSlotThree,
   bytes32 valueAtSlotFour) {

       assembly {
           valueAtSlotZero := sload(0)
           valueAtSlotOne := sload(1)
           valueAtSlotTwo := sload(2)
           valueAtSlotThree := sload(3)
           valueAtSlotFour := sload(4)
       }
   }

   function storeString(string memory memoryName) public {
       storageName = memoryName;
   }
}
Enter fullscreen mode Exit fullscreen mode

https://miro.medium.com/v2/resize:fit:750/format:webp/1*7bPHEURvF6oeAFnPw2idrg.png

No código de exemplo acima, o valor uint256 (= 10) da variável de estado a é armazenado no slot # 0, ocupando todo o espaço de palavra de 32 bytes desse slot (uint256 = 256 bits = 32 bytes).

Em seguida, o valor uint256 de b (= 0) no slot # 1.

E, então, o slot # 2 agrupa duas variáveis de valor (= 30, 40) uint128 (128 bits = 16 bytes) juntas em um total de espaço de palavra de 32 bytes.

Verifique a saída do Remix IDE:

d = 40 em decimal (28 em hex) é armazenado no primeiro byte da palavra, seguido por c = 30 em decimal (1e em hex).

Observe que o primeiro item armazenado em um slot de armazenamento agrupado é alinhado em ordem inferior.

Uma pequena tarefa de casa:

Verifique a saída no Remix IDE quando você rearranjar a ordem no código acima para a seguinte:

   uint256 a = 10;
   uint256 b;
   uint128 d = 40;
   uint128 c = 30;
   uint256 e = 50;
Enter fullscreen mode Exit fullscreen mode

Chegando ao ponto em que dei um argumento de string "Manu" como entrada na função storeString:

Ele pega esse argumento na memória (memoryName) e o armazena/escreve no armazenamento (storageName) e é assim que você obtém o valor "Manu" no slot # 4.

Observe todas as saídas acima de perto e você notará que a EVM trata todos os dados como palavras bytes32 internamente.

Com isso, concluo a Parte-1.

A seguir, explicarei o conceito e uso de algo chamado Ponteiros de Armazenamento / Referência.

E, como sempre, ficarei grato se você puder deixar um feedback/reação honesto e/ou crítica construtiva sobre este texto para que eu possa melhorá-lo.

Até a próxima vez, boa programação (em Solidity)! :)

Artigo original publicado por Manu Kapoor. Traduzido por Paulinho Giovannini.

Top comments (0)