Recentemente, tenho revisitado o Solidity para consolidar alguns detalhes e estou escrevendo uma "Introdução Simples ao Solidity" para ajudar os iniciantes (os especialistas em programação podem procurar outros tutoriais). A atualização será feita semanalmente com 1-3 aulas.
Twitter: @0xAA_Science
Comunidade: Discord | Grupo do WeChat | Website wtf.academy
Todo o código e tutoriais estão disponíveis no GitHub: github.com/AmazingAng/WTF-Solidity
Os tipos de referência (Reference Type) no Solidity incluem arrays (matrizes) e structs (estruturas). Como esses tipos de variáveis são mais complexos e ocupam mais espaço de armazenamento, é necessário especificar a localização de armazenamento dos dados ao usá-los.
No Solidity, existem três tipos de localização de dados: storage, memory e calldata. Cada tipo de armazenamento possui um custo de gas diferente. Os dados do tipo storage são armazenados on-chain, semelhante a um disco rígido em um computador, e consomem mais gas. Os dados do tipo memory e calldata são temporariamente armazenados na memória, com um consumo de gas menor. Em linhas gerais:
-
storage: As variáveis de estado do contrato são por padrão do tipostoragee são armazenadas na cadeia. -
memory: Parâmetros de função e variáveis temporárias dentro de funções normalmente utilizam o tipomemorye são armazenadas na memória, fora da cadeia. Especialmente quando o tipo de retorno da função é variável (de comprimento variável), é necessário utilizar o modificadormemory, por exemplo: string, bytes, array e estruturas personalizadas. -
calldata: Semelhante aomemory, os dados são armazenados na memória e fora da cadeia. A diferença em relação aomemoryé que as variáveis emcalldatasão somente leitura (imutáveis) e são normalmente utilizadas para parâmetros de função. Exemplo:
function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){
// O parâmetro é um array calldata, não pode ser modificado
// _x[0] = 0 // Esta modificação causará um erro
return(_x);
}Exemplo:
Ao atribuir entre diferentes tipos de armazenamento, às vezes é criada uma cópia independente (modificar a nova variável não afeta a variável original), e outras vezes é criada uma referência (modificar a nova variável afeta a original). As regras são as seguintes:
-
Atribuir essencialmente cria uma referência ao corpo, portanto, a alteração do corpo ou da referência pode ser sincronizada:
-
Atribuir de
storage(variáveis de estado do contrato) parastoragelocal (dentro de funções) cria uma referência, e alterar a nova variável afetará a original. Exemplo:uint[] x = [1,2,3]; // Variável de estado: array x function fStorage() public{ // Declaração de uma variável storage xStorage, que aponta para x. Alterar xStorage afetará x uint[] storage xStorage = x; xStorage[0] = 100; }
Exemplo:
-
memoryatribuído amemorycria uma referência, e alterar a nova variável afetará a original.
-
-
Em outras situações, a atribuição cria uma cópia do corpo, ou seja, a modificação em um não será refletida no outro.
As variáveis no Solidity são divididas em três tipos de escopo: variáveis de estado (state variable), variáveis locais (local variable) e variáveis globais (global variable).
As variáveis de estado são aquelas cujos dados são armazenados na cadeia e podem ser acessadas por todas as funções dentro do contrato. Essas variáveis são declaradas dentro do contrato, fora das funções:
contract Variaveis {
uint public x = 1;
uint public y;
string public z;
}Podemos alterar o valor das variáveis de estado dentro das funções:
function foo() external{
// Podemos alterar o valor das variáveis de estado dentro da função
x = 5;
y = 2;
z = "0xAA";
}As variáveis locais são aquelas válidas apenas durante a execução de uma função e se tornam inválidas após a saída da função. Os dados das variáveis locais são armazenados na memória, fora da cadeia, e consomem menos gas. As variáveis locais são declaradas dentro de funções:
function bar() external pure returns(uint){
uint xx = 1;
uint yy = 3;
uint zz = xx + yy;
return(zz);
}As variáveis globais possuem um escopo global de funcionamento e são palavras-chave reservadas do Solidity. Elas podem ser utilizadas diretamente dentro das funções sem precisar serem declaradas:
function global() external view returns(address, uint, bytes memory){
address remetente = msg.sender;
uint numeroBloco = block.number;
bytes memory dados = msg.data;
return(remetente, numeroBloco, dados);
}No exemplo acima, utilizamos três variáveis globais comuns: msg.sender, block.number e msg.data, que representam o endereço do remetente da mensagem, a altura do bloco atual e os dados da mensagem, respectivamente. Abaixo seguem algumas variáveis globais comuns. Para uma lista completa, consulte este link:
blockhash(uint blockNumber): (bytes32) Hash de um bloco específico - apenas válido para aproximadamente os últimos 256 blocos, excluindo o bloco atual.block.coinbase: (address payable) Endereço do minerador do bloco atual.block.gaslimit: (uint) Limite de gas do bloco atual.block.number: (uint) Número do bloco atual.block.timestamp: (uint) Timestamp do bloco atual, em segundos desde a "unix epoch".gasleft(): (uint256) Gas restante.msg.data: (bytes calldata) Dados completos da chamada.msg.sender: (address payable) Remetente da mensagem (caller atual).msg.sig: (bytes4) Primeiros quatro bytes dos dados da chamada (identificador da função).msg.value: (uint) Valor em wei enviado na transação atual.
Exemplo:
Não existem pontos flutuantes no Solidity, e o ponto é representado como 0, para garantir a precisão das transações e evitar perdas de precisão. O uso das unidades de Ether ajuda a evitar erros de cálculo e facilita o manuseio de transações de criptomoedas no contrato.
wei: 1gwei: 1e9 = 1000000000ether: 1e18 = 1000000000000000000
function weiUnit() external pure returns(uint) {
assert(1 wei == 1e0);
assert(1 wei == 1);
return 1 wei;
}
function gweiUnit() external pure returns(uint) {
assert(1 gwei == 1e9);
assert(1 gwei == 1000000000);
return 1 gwei;
}
function etherUnit() external pure returns(uint) {
assert(1 ether == 1e18);
assert(1 ether == 1000000000000000000);
return 1 ether;
}Exemplo:
É comum definir uma operação que deve ser concluída em uma semana dentro de um contrato, ou que um evento ocorrerá um mês após. Isso garante a precisão da execução do contrato, independentemente de possíveis erros técnicos. Portanto, as unidades de tempo são um conceito importante no Solidity, melhorando a legibilidade e manutenção do contrato.
seconds: 1minutes: 60 segundos = 60hours: 60 minutos = 3600days: 24 horas = 86400weeks: 7 dias = 604800
function secondsUnit() external pure returns(uint) {
assert(1 seconds == 1);
return 1 seconds;
}
function minutesUnit() external pure returns(uint) {
assert(1 minutes == 60);
assert(1 minutes == 60 seconds);
return 1 minutes;
}
function hoursUnit() external pure returns(uint) {
assert(1 hours == 3600);
assert(1 hours == 60 minutes);
return 1 hours;
}
function daysUnit() external pure returns(uint) {
assert(1 days == 86400);
assert(1 days == 24 hours);
return 1 days;
}
function weeksUnit() external pure returns(uint) {
assert(1 weeks == 604800);
assert(1 weeks == 7 days);
return 1 weeks;
}Exemplo:
Nesta aula, apresentamos os tipos de referência, a localização dos dados e o escopo das variáveis no Solidity. O foco principal foi nos termos storage, memory e calldata e suas aplicações. Esses termos foram introduzidos para economizar espaço de armazenamento limitado on-chain e reduzir o consumo de gas. Na próxima aula, abordaremos arrays nos tipos de referência.




