Foto de Steve Johnson, no Unsplash
Sempre que estiver escrevendo contratos inteligentes no Solidity, você deve estar ciente de como suas variáveis e dados são tratados pela Máquina Virtual Ethereum (EVM). As escolhas que você fizer influenciarão, entre outras coisas, os custos de gás – para chamar suas funções ou implantar seu contrato – bem como o layout de armazenamento.
Dado que cada pedaço de espaço de bloco na Ethereum é altamente valorizado (daí o custo do ETH, embora tenha caído recentemente), isso influencia fortemente a eficiência do seu código e o contrato resultante, que esperamos que seja invocado com frequência. Cada chamada de função usa gás; cada pequena economia ajuda na soma do tempo de vida potencial do total de invocações de função.
Sumário
1 . Armazenamento
2 . Memória
3 . Dados de Chamada
4 . Comparações
4 . Referências
Armazenamento
Storage
é o mais fácil de entender - é onde todas as variáveis de estado são armazenadas. Como o estado pode ser alterado em um contrato (por exemplo, dentro de uma função), as variáveis de armazenamento
devem ser mutáveis. No entanto, sua localização é persistente e elas são armazenadas na blockchain.
As variáveis de estado em storage
são organizadas de forma compacta — se possível, vários valores ocuparão o mesmo slot em storage
. Além dos casos especiais de matrizes e structs dinamicamente dimensionados, outras variáveis são empacotadas em blocos de 32 bytes.
Se essas variáveis tiverem menos de 32 bytes, elas serão combinadas para ocupar o mesmo slot. Caso contrário, eles serão empurrados para o próximo slot de armazenamento
. Os dados são armazenados de forma contígua (ou seja, um após o outro), a partir do slot 0
(até os slots 1
, 2
, 3
, etc), por ordem de sua declaração no contrato.
Matrizes e structs dinâmicos sempre ocupam um novo slot, e quaisquer variáveis que os seguem também serão inicializadas para iniciar um novo slot de armazenamento
.
Como o tamanho das matrizes e structs dinâmicos são desconhecidos a priori (ou seja, até que você os atribua posteriormente em seu contrato), eles não podem ser armazenados com seus dados entre outras variáveis de estado. Em vez disso, assume-se que eles ocupam 32 bytes e os elementos dentro deles são armazenados começando em um slot de armazenamento
separado, que é calculado usando um hash Keccak-256.
No entanto, as variáveis de estado constante não são salvas em um slot de armazenamento
. Em vez disso, elas são injetadas diretamente no bytecode do contrato — sempre que essas variáveis são lidas, o contrato as troca automaticamente pelo valor constante atribuído.
Memória
Memory
é reservada para variáveis definidas no escopo de uma função. Elas só persistem enquanto uma função é chamada e, portanto, são variáveis temporárias que não podem ser acessadas fora desse escopo (ou seja, em qualquer outro lugar do seu contrato além dessa função). No entanto, elas são mutáveis dentro dessa função.
O Solidity reserva quatro slots de 32 bytes para a memória
, com intervalos de bytes específicos, consistindo em: 1) espaço de rascunho de 64 bytes para métodos de hash; 2) 32 bytes para o tamanho da memória
atualmente alocada, que é o ponteiro de memória
livre onde o Solidity sempre coloca novos objetos; e 3) um slot zero de 32 bytes — que é usado como valor inicial para matrizes de memória
dinâmica e nunca deve ser gravado.
Devido a essas diferenças de layout, há situações para matrizes e structs em que eles ocupam diferentes quantidades de espaço dependendo se estão em storage
ou em memory
.
Exemplo:
uint8[4] arr;
struct Str {
uint v1;
uint v2;
uint8 v3;
uint8 v4;
}
Em ambos os casos, o array arr
e o struct Str
ocupam 128 bytes na memória
(ou seja, 4 itens, 32 bytes cada). No entanto, como local de armazenamento
, arr
ocupa apenas 32 bytes (1 slot) enquanto Str
ocupa 96 bytes (3 slots, 32 bytes cada).
Dados de Chamada
Calldata
é um local imutável e temporário onde os argumentos da função são armazenados, se comportando principalmente como memory
.
Recomenda-se tentar usar calldata
porque evita cópias desnecessárias e garante que os dados fiquem inalterados. Matrizes e structs com localização de dados em calldata
também podem ser retornados de funções.
Este tipo de dado é assumido em um formato definido pela especificação ABI, ou seja, preenchido em múltiplos de 32 bytes (o que difere das chamadas de funções internas). Os argumentos para construtores são um pouco diferentes, pois são anexados diretamente ao final do código do contrato (também na codificação ABI).
Comparações
Sempre que você definir uma variável de tipo de referência (matriz ou struct), também precisará definir sua localização de dados — a menos que seja uma variável de estado, caso em que é interpretada automaticamente como storage
. Desde o Solidity v0.6.9, memory
e calldata
são permitidos em todas as funções independentemente do seu tipo de visibilidade (ou seja, externa, pública, etc).
As atribuições resultarão na criação de cópias ou meras referências ao mesmo dado - semelhante a objetos ou matrizes em Javascript:
- Atribuições entre
storage
ememory
(ou decalldata
) sempre criam uma cópia separada. - Atribuições de
memory
paramemory
apenas criam referências. Portanto, alterar uma variável de memória altera todas as outras variáveis de memória que se referem aos mesmos dados. - As atribuições de
storage
para uma variável de armazenamento local também atribuem apenas uma referência. - Todas as outras atribuições à
storage
sempre são copiadas.
Para parâmetros de matrizes em funções, é recomendável usar calldata
em vez de memory
, pois isso proporciona uma economia significativa de gás. Por exemplo, uma função de soma que faz um loop em uma matriz de entrada pode economizar aproximadamente 1829 gás (~3,5%) usando calldata
.
// Gás usado: 50992
function func1 (uint[] memory nums) external {
for (uint i = 0; i < nums.length; ++i) {
...
}
}// Gás usado: 49163
function func2 (uint[] calldata nums) external {
for (uint i = 0; i < nums.length; ++i) {
...
}
}
E é isso! Nesse artigo tivemos algumas informações sobre locais de dados no Solidity. Obrigado por ler!
Referências
- https://docs.soliditylang.org/en/v0.8.13/types.html#data-location-and-assignment-behaviour
- https://docs.soliditylang.org/en/v0.8.13/types.html#reference-types
- https://docs.soliditylang.org/en/v0.8.13/internals/layout_in_storage.html
- https://twitter.com/Web3Oscar/status/1514509414501343234
- https://twitter.com/PatrickAlphaC/status/1514257121302429696
Artigo original escrito por YBM. Traduzido por Paulinho Giovannini.
Latest comments (0)