Prepare-se para mergulhar no emocionante mundo dos contratos inteligentes Solidity! Este artigo é seu guia definitivo para dominar as variáveis imutáveis e constantes. Da implementação aos custos de execução, você aprenderá tudo o que há para saber sobre essas poderosas ferramentas. Vamos até mesmo explicar por que as variáveis constantes são uma opção mais econômica do que as variáveis imutáveis, usando os opcodes EVM como referência. Portanto, aperte o cinto de segurança e prepare-se para uma viagem educativa enquanto exploramos o fascinante reino da programação em Solidity!
CONSTANTES
No contexto do Solidity, uma variável constante deve ter um valor conhecido no momento da compilação e deve ser atribuída quando a variável é declarada.
Qualquer expressão que exija acesso às informações da blockchain (por exemplo, block.timestamp, address(this).balance ou block.number) ou aos dados de execução (msg.value ou gasleft()) ou que faça chamadas para contratos externos ou que mude durante a execução não é permitida em uma declaração de variável constante. Isso será explicado em mais detalhes neste artigo.
Como declarar uma variável constante
Você declara uma variável constante usando a palavra-chave "constant", e os nomes das variáveis geralmente são hardcoded (prática de deixar dados fixados no código) da seguinte forma;
uint32 constant public FACTOR = 4178934374;
IMUTÁVEL
No Solidity, as variáveis declaradas como "immutable" são um pouco menos restritas do que as declaradas como "constant". Um valor pode ser atribuído a uma variável imutável no construtor do contrato ou no momento de sua declaração e só pode ser atribuído uma vez. Diferentemente das variáveis constantes, as imutáveis podem ser lidas durante o tempo de construção e seu valor é substituído no código de tempo de execução do contrato pelo compilador.
Entretanto, as imutáveis declaradas com um valor no momento da declaração só podem ser consideradas inicializadas quando o construtor do contrato estiver sendo executado e não podem ser inicializadas com um valor que dependa de outra variável imutável. Essa restrição existe para garantir que haja uma ordem clara e consistente de inicialização da variável de estado e execução do construtor, especialmente quando há herança envolvida.
Como declarar uma variável imutável
Você declara uma variável imutável usando a palavra-chave "immutable" da seguinte forma;
contract Immutable{
//Uma variável imutável pode ser atribuída no momento de sua declaração
address immutable user = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
address immutable owner;
//A variável imutável do proprietário recebeu um valor no construtor de seu contrato
constructor(address _owner){
owner = _owner;
}
}
Alguns pontos importantes sobre variáveis constantes e imutáveis
Uma variável constante pode ser lida durante o tempo de implantação no construtor e também pode ser lida depois que o contrato tiver sido implantado. Isso significa que o valor da constante é definido no momento da compilação e não pode ser alterado, além de ser acessível a outras partes do contrato ou a contratos externos que interagem com ele, da seguinte forma;
contract ConstantGetter{
uint8 constant public AGE = 100;
address constant public USER = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
constructor(){
address getAddress = USER;
}
function getAge() public pure returns(uint8){
return AGE;
}
}
Ao contrário das constantes, quando um valor é atribuído a uma variável "imutável", ele não pode ser lido até que o construtor do contrato termine de ser executado.
Isso significa que, mesmo que um valor seja atribuído a uma variável "imutável" dentro do construtor e depois lido imediatamente, o valor não estará disponível até que o construtor termine de ser executado. Veja o exemplo de código abaixo;
contract ImmutableGetter{
uint8 immutable public age = 100;
address immutable public USER = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
constructor(){
//isso gerará um erro
//address getAddress = USER;
}
function getAge() public pure returns(uint8){
return age;
}
}
As variáveis declaradas como constantes e imutáveis são armazenadas no bytecode do contrato, não no armazenamento do contrato. Explicarei melhor à medida que avançarmos.
Observe que, na função getAge() no trecho de código acima, declarei a mutabilidade do estado como 'pure' e não como 'view', porque não estamos lendo do armazenamento do contrato. Somente as funções que leem do armazenamento do contrato fazem uso da mutabilidade do estado de 'view'.
Visibilidade do Estado para uma Variável Constante
Abaixo estão as visibilidades de estado que podem ser usadas para declarar uma variável constante com um exemplo de código;
- Public (Pública)
- Private (Privada)
- Internal (Interna)
contract ConstantVisibility{
//Mensagem declarada usando visibilidade privada
string constant MESSAGE = "I Love Solidity";
//Conta de administrador declarada usando uma visibilidade pública
address constant public ADMIN = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
//Semente declarada usando visibilidade interna
bytes32 constant internal SEED = 0x819ab889b8b799ba02e2621bfa1641c428191e88101c4c6c0809536e6965bb4b;
//Isso gerará um erro, pois as variáveis constantes não podem ser definidas como visibilidade externa
//uint8 constant external AGE = 60;
}
Visibilidade de estado para uma variável immutable
contract ImmutableVisibility{
//Mensagem declarada usando visibilidade privada
bytes32 immutable message = "I Love Solidity";
//Conta de administrador declarada usando uma visibilidade pública
address immutable public admin;
//Semente declarada usando visibilidade interna
bytes32 immutable internal seed;
//Isso gerará um erro, pois as variáveis imutáveis não podem ser definidas como visibilidade externa
//uint8 immutable external age = 60;
constructor(address _admin, bytes32 _seed) {
admin = _admin;
seed = _seed;
}
}
Entendendo as Restrições de Variáveis Constantes e Imutáveis no Solidity
As variáveis constantes e imutáveis possuem restrições diferentes em relação aos tipos de valores que podem ser atribuídos a elas.
- As constantes podem ter qualquer tipo de valor, inclusive inteiros, números de ponto fixo, booleanos, endereços, tipos de contrato, arrays de bytes e enums. Além disso, as constantes também podem ter valores de string e bytes. No entanto, ainda não há suporte para structs como um tipo de constante.
- As imutáveis, por outro lado, só podem ter tipos de valor. Tipos sem valor, como string, bytes e structs, não são permitidos como imutáveis.
Constante Definida em nível de Arquivo
As constantes no Solidity podem ainda ser definidas no nível de arquivo, fora de qualquer bloco de contrato. Essas constantes podem então ser usadas em outros arquivos importando o arquivo .sol inteiro que contém as constantes
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
bytes32 constant public ROLE = 0xc10c77be35aff266144ed64c26a1fa104bae2f284ae99ac4a34203454704a185;
contract ConstFile{
address owner;
}
ou importando constantes específicas usando a sintaxe de importação específica.
Importância da definição constant no nível de arquivo / importação de arquivo de variável constante
A definição de variáveis constantes no nível de arquivo pode ser útil nos casos em que se deseja compartilhar valores entre vários contratos ou se deseja manter os valores em um arquivo separado para melhor organização. Ao importar as constantes em outros arquivos, você pode evitar a duplicação dos valores e tornar o código mais fácil de manter.
Armazenamento de Variáveis Constantes e Imutáveis
O compilador do Solidity não reserva um slot de armazenamento para variáveis constantes ou imutáveis e, em vez disso, substitui cada ocorrência dessas variáveis pelo valor atribuído no bytecode do contrato.
Isso significa que, no nível do bytecode, as constantes e as variáveis imutáveis são simplesmente alinhadas ao código onde quer que sejam usadas. Essa otimização ajuda a reduzir o tamanho do armazenamento do contrato e a aumentar sua eficiência. É importante observar que esse alinhamento ocorre somente durante o processo de compilação e a declaração da variável original permanece no código-fonte.
Como as variáveis constantes e imutáveis são armazenadas no bytecode do contrato e custo do gas
Usando o contrato abaixo, vamos compilar e obter o bytecode do contrato
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Constants{
uint32 immutable public FACTOR = 0x41789343;
bytes32 constant public ROLE = 0xc10c77be35aff266144ed64c26a1fa104bae2f284ae99ac4a34203454704a185;
bytes6 constant public RANDOM = 0xc10c77be35af;
}
Análise do Custo de Gas
As variáveis constantes e imutáveis têm custos de gas significativamente menores. Ao atribuir um valor a uma variável constante, ele é duplicado e reavaliado toda vez que é acessado, permitindo otimizações locais. As variáveis imutáveis, por outro lado, são avaliadas apenas uma vez após a criação e seu valor é duplicado em todas as partes do código em que são referenciadas.
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ConstImmute{
//Custo de gas = 313
uint32 immutable public FACTOR = 0x41789343;
//Custo de gas = 335
bytes32 constant public ROLE = 0xc10c77be35aff266144ed64c26a1fa104bae2f284ae99ac4a34203454704a185;
//Custo de gas = 363
bytes6 constant public RANDOM = 0xc10c77be35af;
}
Veja o custo de gas para cada comentário no trecho de código acima. Agora a pergunta é: Por que as variáveis declaradas como constantes são mais baratas do que as variáveis imutáveis?
Por que as variáveis declaradas como constantes são mais baratas do que as variáveis imutáveis?
As constantes nunca são preenchidas no bytecode do contrato, ao passo que às imutáveis é reservada uma palavra completa de 32 bytes no bytecode do contrato, mesmo que seu valor exija menos bytes (por exemplo, uma imutável definida como um tipo uint32) ainda será preenchida com 32 bytes).
Bytecode do Contract
608060405234801561001057600080fd5b50600436106100415760003560e01c806335815b95146
100465780639d53fe2b14610064578063f964d10914610082575b600080fd5b61004e6100a0565b
60405161005b9190610117565b60405180910390f35b61006c6100c4565b6040516100799190610
14b565b60405180910390f35b61008a6100eb565b60405161009791906101a1565b604051809103
90f35b7f["0000000000000000000000000000000000000000000000000000000041789343"]815
65b7f["c10c77be35aff266144ed64c26a1fa104bae2f284ae99ac4a34203454704a185"]60001b
81565b65{"c10c77be35af"]60d01b81565b600063ffffffff82169050919050565b61011181610
0f8565b82525050565b600060208201905061012c6000830184610108565b92915050565b600081
9050919050565b61014581610132565b82525050565b60006020820190506101606000830184610
13c565b92915050565b60007fffffffffffff000000000000000000000000000000000000000000
000000000082169050919050565b61019b81610166565b82525050565b60006020820190506101b
66000830184610192565b9291505056fea2646970667358221220eaa2c5878953ff1c6e01271ad2
19cbfd6ddcb5270eaf8c47ee96ccc6bb48115964736f6c63430008110033
Se você observar o bytecode acima entre colchetes [""]), a variável imutável FACTOR = 0x41789343 é preenchida com zeros à esquerda:
["0000000000000000000000000000000000000000000000000000000041789343"]
enquanto a variável constante RANDOM = 0xc10c77be35af não é.
{"c10c77be35af"]
- Isso ocorre porque 32 bytes são reservados para variáveis imutáveis, mesmo que elas exijam menos bytes. Por esse motivo, os valores constantes podem, às vezes, ser mais econômicos do que os valores imutáveis.
- Outro motivo pelo qual as constantes e as imutáveis são eficientes em termos de gas é que as operações de SLOAD para ler o armazenamento do contrato, que custam cerca de 100 de gas na EVM, não são executadas nelas porque seus valores não estão guardados no armazenamento do contrato. Você pode dar uma olhada nos opcodes abaixo.
000 PUSH1 a0
002 PUSH1 40
004 MSTORE
005 PUSH4 41789343
010 PUSH4 ffffffff
015 AND
016 PUSH1 80
018 SWAP1
019 PUSH4 ffffffff
024 AND
025 DUP2
026 MSTORE
027 POP
028 CALLVALUE
029 DUP1
030 ISZERO
031 PUSH2 0027
034 JUMPI
035 PUSH1 00
037 DUP1
038 REVERT
039 JUMPDEST
040 POP
041 PUSH1 80
043 MLOAD
044 PUSH2 01f2
047 PUSH2 0042
050 PUSH1 00
052 CODECOPY
053 PUSH1 00
055 PUSH1 a2
057 ADD
058 MSTORE
059 PUSH2 01f2
062 PUSH1 00
064 RETURN
065 INVALID
066 PUSH1 80
068 PUSH1 40
070 MSTORE
071 CALLVALUE
072 DUP1
073 ISZERO
074 PUSH2 0010
077 JUMPI
078 PUSH1 00
080 DUP1
081 REVERT
082 JUMPDEST
083 POP
084 PUSH1 04
086 CALLDATASIZE
087 LT
088 PUSH2 0041
091 JUMPI
092 PUSH1 00
094 CALLDATALOAD
095 PUSH1 e0
097 SHR
098 DUP1
099 PUSH4 35815b95 //bytes4(keccak256('FACTOR()')) = 0x35815b95
104 EQ
105 PUSH2 0046
108 JUMPI
109 DUP1
110 PUSH4 9d53fe2b //bytes4(keccak256('ROLE()')) = 0x9d53fe2b
115 EQ
116 PUSH2 0064
119 JUMPI
120 DUP1
121 PUSH4 f964d109 //bytes4(keccak256('RANDOM()')) = 0xf964d109
126 EQ
127 PUSH2 0082
130 JUMPI
131 JUMPDEST
132 PUSH1 00
134 DUP1
135 REVERT
136 JUMPDEST
137 PUSH2 004e
140 PUSH2 00a0
143 JUMP
144 JUMPDEST
145 PUSH1 40
147 MLOAD
148 PUSH2 005b
151 SWAP2
152 SWAP1
153 PUSH2 0117
156 JUMP
157 JUMPDEST
158 PUSH1 40
160 MLOAD
161 DUP1
162 SWAP2
163 SUB
164 SWAP1
165 RETURN
166 JUMPDEST
167 PUSH2 006c
Se você observar com atenção, nenhuma operação SLOAD foi executada nos códigos de operação acima.
Conclusão
Para criar contratos inteligentes Solidity eficientes e seguros, é fundamental compreender as variações e limitações das constantes e imutáveis. Ao priorizar esses aspectos, você pode criar contratos inteligentes confiáveis e econômicos na blockchain Ethereum.
Espero que você tenha achado este artigo informativo. Em caso afirmativo, dê um sinal de positivo, deixe um comentário e compartilhe-o com outras pessoas. Fique atento ao meu próximo artigo!
Esse artigo foi escrito por Oluwatosin Serah e traduzido por Fátima Lima. O original pode ser lido aqui.
Oldest comments (0)