Solidity - Guia Rápido
Solidity é uma linguagem de programação de alto nível orientada a contratos para a implementação de contratos inteligentes. A solidez é altamente influenciada por C ++, Python e JavaScript e foi projetada para atingir a Máquina Virtual Ethereum (EVM).
Solidity é estaticamente tipado, suporta herança, bibliotecas e linguagem de programação de tipos complexos definidos pelo usuário.
Você pode usar o Solidity para criar contratos para usos como votação, crowdfunding, leilões cegos e carteiras com várias assinaturas.
O que é Ethereum?
Ethereum é um ie descentralizado. plataforma blockchain que executa contratos inteligentes, ou seja, aplicativos que são executados exatamente como programados, sem qualquer possibilidade de inatividade, censura, fraude ou interferência de terceiros.
A Máquina Virtual Ethereum (EVM)
A Máquina Virtual Ethereum, também conhecida como EVM, é o ambiente de execução para contratos inteligentes no Ethereum. A Máquina Virtual Ethereum se concentra em fornecer segurança e executar código não confiável por computadores em todo o mundo.
O EVM é especializado na prevenção de ataques de negação de serviço e garante que os programas não tenham acesso ao estado uns dos outros, garantindo que a comunicação possa ser estabelecida sem qualquer interferência potencial.
A Máquina Virtual Ethereum foi projetada para servir como um ambiente de execução para contratos inteligentes baseados no Ethereum.
O que é o contrato inteligente?
Um contrato inteligente é um protocolo de computador destinado a facilitar, verificar ou fazer cumprir digitalmente a negociação ou execução de um contrato. Contratos inteligentes permitem a realização de transações confiáveis sem terceiros. Essas transações são rastreáveis e irreversíveis.
O conceito de contratos inteligentes foi proposto pela primeira vez por Nick Szabo em 1994. Szabo é um jurista e criptógrafo conhecido por lançar as bases para a moeda digital.
Tudo bem se você não entender o Smart Contract agora, entraremos em mais detalhes mais tarde.
Este capítulo explica como podemos configurar o compilador Solidity na máquina CentOS. Se você não tem uma máquina Linux, pode usar nosso Compilador Online para pequenos contratos e para aprender rapidamente o Solidity.
Método 1 - npm / Node.js
Esta é a maneira mais rápida de instalar o compilador Solidity em sua máquina CentoS. Seguimos os passos para instalar o Solidity Compiler -
Instale Node.js
Primeiro, certifique-se de ter node.js disponível em sua máquina CentOS. Se não estiver disponível, instale-o usando os seguintes comandos -
# First install epel-release
$sudo yum install epel-release
# Now install nodejs
$sudo yum install nodejs
# Next install npm (Nodejs Package Manager )
$sudo yum install npm
# Finally verify installation
$npm --version
Se tudo tiver sido instalado, você verá uma saída parecida com esta -
3.10.10
Instalar solc
Depois de instalar o gerenciador de pacotes Node.js, você pode prosseguir com a instalação do compilador Solidity conforme abaixo -
$sudonpm install -g solc
O comando acima instalará o programa solcjs e o tornará disponível globalmente em todo o sistema. Agora você pode testar seu compilador Solidity emitindo o seguinte comando -
$solcjs-version
Se tudo correr bem, isso imprimirá algo como a seguir -
0.5.2+commit.1df8f40c.Emscripten.clang
Agora você está pronto para usar solcjs, que possui menos recursos do que o compilador Solidity padrão, mas ele lhe dará um bom ponto de partida.
Método 2 - imagem do Docker
Você pode puxar uma imagem do Docker e começar a usá-la para começar com a programação do Solidity. A seguir estão as etapas simples. A seguir está o comando para puxar uma imagem do Solidity Docker.
$docker pull ethereum/solc:stable
Depois que uma imagem do docker é baixada, podemos verificá-la usando o seguinte comando.
$docker run ethereum/solc:stable-version
Isso imprimirá algo como a seguir -
$ docker run ethereum/solc:stable -version
solc, the solidity compiler commandlineinterfaceVersion: 0.5.2+commit.1df8f40c.Linux.g++
Método 3: Instalação de Pacotes Binários
Se você deseja instalar o compilador completo em sua máquina Linux, verifique o site oficial Instalando o compilador Solidity.
Os arquivos de origem do Solidity podem conter qualquer número de definições de contrato, diretivas de importação e diretivas pragma.
Vamos começar com um arquivo fonte simples de Solidity. A seguir está um exemplo de um arquivo Solidity -
pragma solidity >=0.4.0 <0.6.0;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}
Pragma
A primeira linha é uma diretiva pragma que informa que o código-fonte foi escrito para a versão 0.4.0 do Solidity ou qualquer coisa mais recente que não interrompa a funcionalidade até, mas não incluindo, a versão 0.6.0.
Uma diretiva pragma é sempre local para um arquivo de origem e se você importar outro arquivo, o pragma desse arquivo não se aplicará automaticamente ao arquivo de importação.
Portanto, um pragma para um arquivo que não será compilado antes da versão 0.4.0 e também não funcionará em um compilador a partir da versão 0.5.0 será escrito da seguinte maneira -
pragma solidity ^0.4.0;
Aqui, a segunda condição é adicionada usando ^.
Contrato
Um contrato Solidity é uma coleção de código (suas funções) e dados (seu estado) que reside em um endereço específico no Ethereumblockchain.
A linha uintstoredData declara uma variável de estado chamada storedData do tipo uint e as funções set e get podem ser usadas para modificar ou recuperar o valor da variável.
Importando arquivos
Embora o exemplo acima não tenha uma instrução de importação, o Solidity oferece suporte a instruções de importação que são muito semelhantes às disponíveis em JavaScript.
A instrução a seguir importa todos os símbolos globais de "nome do arquivo".
import "filename";
O exemplo a seguir cria um novo símbolo global symbolName cujos membros são todos os símbolos globais de "filename".
import * as symbolName from "filename";
Para importar um arquivo x do mesmo diretório que o arquivo atual, use import "./x" as x ;. Se você usar importar "x" como x; em vez disso, um arquivo diferente pode ser referenciado em um "diretório de inclusão" global.
Palavras-chave reservadas
A seguir estão as palavras-chave reservadas no Solidity -
abstrato | depois de | apelido | Aplique |
auto | caso | pegar | cópia de |
padrão | definir | final | imutável |
implementos | dentro | na linha | deixei |
macro | partida | mutável | nulo |
do | sobrepor | parcial | promessa |
referência | relocável | selado | tamanho de |
estático | apoia | interruptor | experimentar |
typedef | tipo de | não verificado |
Estamos usando Remix IDE para compilar e executar nossa base de código do Solidity.
Step 1 - Copie o código fornecido na seção Remix IDE Code.
Exemplo
pragma solidity ^0.5.0;
contract SolidityTest {
constructor() public{
}
function getResult() public view returns(uint){
uint a = 1;
uint b = 2;
uint result = a + b;
return result;
}
}
Step 2 - Na guia Compilar, clique em Start to Compile botão.
Step 3 - Na guia Executar, clique em Deploy botão.
Step 4 - Na guia Executar, selecione SolidityTest at 0x... no menu suspenso.
Step 5 - Clique getResult Botão para exibir o resultado.
Resultado
0: uint256: 3
O Solidity suporta comentários no estilo C e C ++, assim -
Qualquer texto entre // e o final de uma linha é tratado como um comentário e é ignorado pelo Solidity Compiler.
Qualquer texto entre os caracteres / * e * / é tratado como um comentário. Isso pode abranger várias linhas.
Exemplo
O exemplo a seguir mostra como usar comentários no Solidity.
function getResult() public view returns(uint){
// This is a comment. It is similar to comments in C++
/*
* This is a multi-line comment in solidity
* It is very similar to comments in C Programming
*/
uint a = 1;
uint b = 2;
uint result = a + b;
return result;
}
Ao escrever um programa em qualquer idioma, você precisa usar várias variáveis para armazenar várias informações. As variáveis nada mais são do que locais de memória reservados para armazenar valores. Isso significa que, ao criar uma variável, você reserva algum espaço na memória.
Você pode gostar de armazenar informações de vários tipos de dados como caractere, caractere largo, inteiro, ponto flutuante, ponto flutuante duplo, booleano etc. Com base no tipo de dados de uma variável, o sistema operacional aloca memória e decide o que pode ser armazenado no memória reservada.
Tipos de valor
Solidity oferece ao programador uma rica variedade de tipos de dados integrados e definidos pelo usuário. A tabela a seguir lista sete tipos de dados básicos C ++ -
Tipo | Palavra-chave | Valores |
---|---|---|
boleano | bool | verdadeiro falso |
Inteiro | int / uint | Inteiros assinados e não assinados de tamanhos variados. |
Inteiro | int8 a int256 | Int assinado de 8 bits a 256 bits. int256 é o mesmo que int. |
Inteiro | uint8 a uint256 | Int sem sinal de 8 bits a 256 bits. uint256 é o mesmo que uint. |
Números de pontos fixos | corrigido / não corrigido | Números de pontos fixos assinados e não assinados de vários tamanhos. |
Números de pontos fixos | corrigido / não corrigido | Números de pontos fixos assinados e não assinados de vários tamanhos. |
Números de pontos fixos | fixedMxN | Número de ponto fixo com sinal, em que M representa o número de bits obtidos por tipo e N representa os pontos decimais. M deve ser divisível por 8 e vai de 8 a 256. N pode ser de 0 a 80. fixo é igual a fixo 128x18. |
Números de pontos fixos | ufixedMxN | Número de ponto fixo sem sinal, onde M representa o número de bits obtidos por tipo e N representa as casas decimais. M deve ser divisível por 8 e vai de 8 a 256. N pode ser de 0 a 80. ufixed é o mesmo que ufixed128x18. |
endereço
address contém o valor de 20 bytes que representa o tamanho de um endereço Ethereum. Um endereço pode ser usado para obter o saldo usando o método .balance e pode ser usado para transferir o saldo para outro endereço usando o método .transfer.
address x = 0x212;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
A solidez oferece suporte a três tipos de variáveis.
State Variables - Variáveis cujos valores são armazenados permanentemente em um armazenamento de contrato.
Local Variables - Variáveis cujos valores estão presentes até a função estar em execução.
Global Variables - Existem variáveis especiais no namespace global usado para obter informações sobre o blockchain.
Solidity é uma linguagem digitada estaticamente, o que significa que o tipo de variável estadual ou local precisa ser especificado durante a declaração. Cada variável declarada sempre tem um valor padrão baseado em seu tipo. Não existe o conceito de "indefinido" ou "nulo".
Estado variável
Variáveis cujos valores são armazenados permanentemente em um armazenamento de contrato.
pragma solidity ^0.5.0;
contract SolidityTest {
uint storedData; // State variable
constructor() public {
storedData = 10; // Using State variable
}
}
Variável Local
Variáveis cujos valores estão disponíveis apenas dentro de uma função em que foram definidas. Os parâmetros da função são sempre locais para essa função.
pragma solidity ^0.5.0;
contract SolidityTest {
uint storedData; // State variable
constructor() public {
storedData = 10;
}
function getResult() public view returns(uint){
uint a = 1; // local variable
uint b = 2;
uint result = a + b;
return result; //access the local variable
}
}
Exemplo
pragma solidity ^0.5.0;
contract SolidityTest {
uint storedData; // State variable
constructor() public {
storedData = 10;
}
function getResult() public view returns(uint){
uint a = 1; // local variable
uint b = 2;
uint result = a + b;
return storedData; //access the state variable
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Resultado
0: uint256: 10
Variáveis globais
Essas são variáveis especiais que existem no espaço de trabalho global e fornecem informações sobre o blockchain e as propriedades da transação.
Nome | Devoluções |
---|---|
blockhash (uint blockNumber) retorna (bytes32) | Hash do bloco fornecido - funciona apenas para 256 blocos mais recentes, excluindo os atuais |
block.coinbase (endereço a pagar) | Endereço atual do mineiro |
block.difficulty (uint) | Dificuldade do bloco atual |
block.gaslimit (uint) | Limite de gás do bloco atual |
block.number (uint) | Número do bloco atual |
block.timestamp (uint) | Timestamp de bloco atual em segundos desde a época unix |
gasleft () retorna (uint256) | Gás restante |
msg.data (bytes calldata) | Calldata completa |
msg.sender (endereço a pagar) | Remetente da mensagem (chamador atual) |
msg.sig (bytes4) | Primeiros quatro bytes do calldata (identificador de função) |
msg.value (uint) | Número de wei enviado com a mensagem |
agora (uint) | Carimbo de data e hora do bloco atual |
tx.gasprice (uint) | Preço do gás da transação |
tx.origin (endereço a pagar) | Remetente da transação |
Nomes de variáveis de solidez
Ao nomear suas variáveis no Solidity, mantenha as seguintes regras em mente.
Você não deve usar nenhuma das palavras-chave reservadas do Solidity como um nome de variável. Essas palavras-chave são mencionadas na próxima seção. Por exemplo, os nomes das variáveis break ou boolean não são válidos.
Os nomes das variáveis de solidez não devem começar com um numeral (0-9). Eles devem começar com uma letra ou um caractere de sublinhado. Por exemplo, 123test é um nome de variável inválido, mas _123test é válido.
Os nomes das variáveis do Solidity diferenciam maiúsculas de minúsculas. Por exemplo, Nome e nome são duas variáveis diferentes.
O escopo das variáveis locais é limitado à função em que são definidas, mas as variáveis de estado podem ter três tipos de escopos.
Public- Variáveis de estado públicas podem ser acessadas internamente, bem como por meio de mensagens. Para uma variável de estado pública, uma função getter automática é gerada.
Internal - Variáveis de estado interno podem ser acessadas apenas internamente a partir do contrato atual ou contrato derivado dele sem usar isso.
Private - Variáveis de estado privadas podem ser acessadas apenas internamente a partir do contrato atual, elas não são definidas no contrato derivado dele.
Exemplo
pragma solidity ^0.5.0;
contract C {
uint public data = 30;
uint internal iData= 10;
function x() public returns (uint) {
data = 3; // internal access
return data;
}
}
contract Caller {
C c = new C();
function f() public view returns (uint) {
return c.data(); //external access
}
}
contract D is C {
function y() public returns (uint) {
iData = 3; // internal access
return iData;
}
function getResult() public view returns(uint){
uint a = 1; // local variable
uint b = 2;
uint result = a + b;
return storedData; //access the state variable
}
}
O que é um operador?
Vamos dar uma expressão simples 4 + 5 is equal to 9. Aqui 4 e 5 são chamadosoperands e '+' é chamado de operator. Solidity oferece suporte aos seguintes tipos de operadores.
- Operadores aritméticos
- Operadores de comparação
- Operadores lógicos (ou relacionais)
- Operadores de atribuição
- Operadores condicionais (ou ternários)
Vamos dar uma olhada em todos os operadores, um por um.
Operadores aritméticos
O Solidity suporta os seguintes operadores aritméticos -
Suponha que a variável A tenha 10 e a variável B tenha 20, então -
Mostrar exemplo
Sr. Não. | Operador e descrição |
---|---|
1 | + (Addition) Adiciona dois operandos Ex: A + B dará 30 |
2 | - (Subtraction) Subtrai o segundo operando do primeiro Ex: A - B dará -10 |
3 | * (Multiplication) Multiplique os dois operandos Ex: A * B dará 200 |
4 | / (Division) Divida o numerador pelo denominador Ex: B / A dará 2 |
5 | % (Modulus) Produz o resto de uma divisão inteira Ex: B% A dará 0 |
6 | ++ (Increment) Aumenta um valor inteiro em um Ex: A ++ dará 11 |
7 | -- (Decrement) Diminui um valor inteiro em um Ex: A-- dará 9 |
Operadores de comparação
O Solidity oferece suporte aos seguintes operadores de comparação -
Suponha que a variável A tenha 10 e a variável B tenha 20, então -
Mostrar exemplo
Sr. Não. | Operador e descrição |
---|---|
1 | = = (Equal) Verifica se o valor de dois operandos é igual ou não, caso positivo a condição torna-se verdadeira. Ex: (A == B) não é verdade. |
2 | != (Not Equal) Verifica se os valores de dois operandos são iguais ou não, se os valores não são iguais, a condição se torna verdadeira. Ex: (A! = B) é verdade. |
3 | > (Greater than) Verifica se o valor do operando esquerdo é maior que o valor do operando direito, se sim, a condição torna-se verdadeira. Ex: (A> B) não é verdade. |
4 | < (Less than) Verifica se o valor do operando esquerdo é menor que o valor do operando direito, se sim, a condição torna-se verdadeira. Ex: (A <B) é verdade. |
5 | >= (Greater than or Equal to) Verifica se o valor do operando esquerdo é maior ou igual ao valor do operando direito, se sim, a condição se torna verdadeira. Ex: (A> = B) não é verdade. |
6 | <= (Less than or Equal to) Verifica se o valor do operando esquerdo é menor ou igual ao valor do operando direito, se sim, a condição se torna verdadeira. Ex: (A <= B) é verdadeiro. |
Operadores lógicos
Solidity suporta os seguintes operadores lógicos -
Suponha que a variável A tenha 10 e a variável B tenha 20, então -
Mostrar exemplo
Sr. Não. | Operador e descrição |
---|---|
1 | && (Logical AND) Se ambos os operandos forem diferentes de zero, a condição se torna verdadeira. Ex: (A && B) é verdade. |
2 | || (Logical OR) Se qualquer um dos dois operandos for diferente de zero, a condição se torna verdadeira. Ex: (A || B) é verdade. |
3 | ! (Logical NOT) Reverte o estado lógico de seu operando. Se uma condição for verdadeira, o operador lógico NOT a tornará falsa. Ex:! (A && B) é falso. |
Operadores bit a bit
O Solidity oferece suporte aos seguintes operadores bit a bit -
Suponha que a variável A tenha 2 e a variável B tenha 3, então -
Mostrar exemplo
Sr. Não. | Operador e descrição |
---|---|
1 | & (Bitwise AND) Ele executa uma operação booleana AND em cada bit de seus argumentos inteiros. Ex: (A e B) é 2. |
2 | | (BitWise OR) Ele executa uma operação booleana OR em cada bit de seus argumentos inteiros. Ex: (A | B) é 3. |
3 | ^ (Bitwise XOR) Ele executa uma operação booleana OU exclusiva em cada bit de seus argumentos inteiros. OU exclusivo significa que o operando um é verdadeiro ou o operando dois é verdadeiro, mas não ambos. Ex: (A ^ B) é 1. |
4 | ~ (Bitwise Not) É um operador unário e opera invertendo todos os bits do operando. Ex: (~ B) é -4. |
5 | << (Left Shift) Ele move todos os bits em seu primeiro operando para a esquerda pelo número de casas especificado no segundo operando. Novos bits são preenchidos com zeros. Deslocar um valor para a esquerda em uma posição é equivalente a multiplicá-lo por 2, deslocar duas posições é equivalente a multiplicar por 4 e assim por diante. Ex: (A << 1) é 4. |
6 | >> (Right Shift) Operador binário de deslocamento à direita. O valor do operando esquerdo é movido para a direita pelo número de bits especificado pelo operando direito. Ex: (A >> 1) é 1. |
7 | >>> (Right shift with Zero) Esse operador é como o operador >>, exceto que os bits deslocados à esquerda são sempre zero. Ex: (A >>> 1) é 1. |
Operadores de atribuição
O Solidity oferece suporte aos seguintes operadores de atribuição -
Mostrar exemplo
Sr. Não. | Operador e descrição |
---|---|
1 | = (Simple Assignment ) Atribui valores do operando do lado direito para o operando do lado esquerdo Ex: C = A + B irá atribuir o valor de A + B em C |
2 | += (Add and Assignment) Ele adiciona o operando direito ao operando esquerdo e atribui o resultado ao operando esquerdo. Ex: C + = A é equivalente a C = C + A |
3 | −= (Subtract and Assignment) Ele subtrai o operando direito do operando esquerdo e atribui o resultado ao operando esquerdo. Ex: C - = A é equivalente a C = C - A |
4 | *= (Multiply and Assignment) Ele multiplica o operando direito pelo operando esquerdo e atribui o resultado ao operando esquerdo. Ex: C * = A é equivalente a C = C * A |
5 | /= (Divide and Assignment) Ele divide o operando esquerdo com o operando direito e atribui o resultado ao operando esquerdo. Ex: C / = A é equivalente a C = C / A |
6 | %= (Modules and Assignment) Leva o módulo usando dois operandos e atribui o resultado ao operando esquerdo. Ex: C% = A é equivalente a C = C% A |
Note - A mesma lógica se aplica aos operadores bit a bit, então eles se tornarão como << =, >> =, >> =, & =, | = e ^ =.
Operador condicional (? :)
O operador condicional primeiro avalia uma expressão para um valor verdadeiro ou falso e, em seguida, executa uma das duas instruções fornecidas, dependendo do resultado da avaliação.
Mostrar exemplo
Sr. Não. | Operador e descrição |
---|---|
1 | ? : (Conditional ) Se a condição for verdadeira? Então valor X: Caso contrário, valor Y |
Ao escrever um contrato, você pode se deparar com uma situação em que precisará executar uma ação repetidamente. Em tais situações, você precisaria escrever instruções de loop para reduzir o número de linhas.
A solidez oferece suporte a todos os loops necessários para aliviar a pressão da programação.
Sr. Não | Loops e descrição |
---|---|
1 | While Loop O loop mais básico no Solidity é o loop while que será discutido neste capítulo. |
2 | fazer ... enquanto Loop O loop do ... while é semelhante ao loop while, exceto que a verificação da condição ocorre no final do loop. |
3 | For Loop O loop for é a forma mais compacta de looping. Inclui as três partes importantes a seguir. |
4 | Controle de Loop Solidity fornece controle total para lidar com loops e instruções de switch. |
Ao escrever um programa, pode haver uma situação em que você precise adotar um de um determinado conjunto de caminhos. Nesses casos, você precisa usar instruções condicionais que permitem que seu programa tome decisões corretas e execute as ações corretas.
Solidity oferece suporte a declarações condicionais que são usadas para realizar ações diferentes com base em condições diferentes. Aqui vamos explicar oif..else declaração.
Fluxograma de if-else
O fluxograma a seguir mostra como a instrução if-else funciona.
A solidez suporta as seguintes formas de if..else declaração -
Sr. Não | Declarações e descrição |
---|---|
1 | declaração if A instrução if é a instrução de controle fundamental que permite ao Solidity tomar decisões e executar instruções condicionalmente. |
2 | declaração if ... else A instrução 'if ... else' é a próxima forma de instrução de controle que permite ao Solidity executar instruções de uma forma mais controlada. |
3 | if ... else if ... declaração. A instrução if ... else if ... é uma forma avançada de if ... else que permite ao Solidity tomar uma decisão correta a partir de várias condições. |
Solidity oferece suporte ao literal String usando aspas duplas (") e simples ('). Ele fornece string como um tipo de dados para declarar uma variável do tipo String.
pragma solidity ^0.5.0;
contract SolidityTest {
string data = "test";
}
No exemplo acima, "teste" é um literal de string e data é uma variável de string. A maneira mais preferida é usar tipos de byte em vez de String, pois a operação de string requer mais gás em comparação à operação de byte. Solidity fornece conversão embutida entre bytes em string e vice-versa. No Solidity, podemos atribuir o literal String a uma variável do tipo byte32 facilmente. O Solidity o considera um literal byte32.
pragma solidity ^0.5.0;
contract SolidityTest {
bytes32 data = "test";
}
Personagens de Fuga
Sr. Não. | Descrição do personagem |
---|---|
1 | \n Inicia uma nova linha. |
2 | \\ Barra invertida |
3 | \' Citação única |
4 | \" Citação dupla |
5 | \b Backspace |
6 | \f Feed de formulário |
7 | \r Retorno de carruagem |
8 | \t Aba |
9 | \v Guia Vertical |
10 | \xNN Representa o valor Hex e insere os bytes apropriados. |
11 | \uNNNN Representa o valor Unicode e insere a sequência UTF-8. |
Conversão de bytes para string
Bytes podem ser convertidos em String usando o construtor string ().
bytes memory bstr = new bytes(10);
string message = string(bstr);
Exemplo
Experimente o código a seguir para entender como a string funciona no Solidity.
pragma solidity ^0.5.0;
contract SolidityTest {
constructor() public{
}
function getResult() public view returns(string memory){
uint a = 1;
uint b = 2;
uint result = a + b;
return integerToString(result);
}
function integerToString(uint _i) internal pure
returns (string memory) {
if (_i == 0) {
return "0";
}
uint j = _i;
uint len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint k = len - 1;
while (_i != 0) {
bstr[k--] = byte(uint8(48 + _i % 10));
_i /= 10;
}
return string(bstr);
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Resultado
0: string: 3
Array é uma estrutura de dados que armazena uma coleção sequencial de tamanho fixo de elementos do mesmo tipo. Uma matriz é usada para armazenar uma coleção de dados, mas geralmente é mais útil pensar em uma matriz como uma coleção de variáveis do mesmo tipo.
Em vez de declarar variáveis individuais, como número0, número1, ... e número99, você declara uma variável de matriz, como números e usa números [0], números [1] e ..., números [99] para representar variáveis individuais. Um elemento específico em uma matriz é acessado por um índice.
No Solidity, um array pode ser de tamanho fixo em tempo de compilação ou de tamanho dinâmico. Para array de armazenamento, ele também pode ter diferentes tipos de elementos. No caso de array de memória, o tipo de elemento não pode ser mapeado e no caso de ser usado como parâmetro de função, o tipo de elemento deve ser um tipo ABI.
Todas as matrizes consistem em locais de memória contíguos. O endereço mais baixo corresponde ao primeiro elemento e o endereço mais alto ao último elemento.
Declaração de matrizes
Para declarar uma matriz de tamanho fixo em Solidity, o programador especifica o tipo dos elementos e o número de elementos exigidos por uma matriz da seguinte maneira -
type arrayName [ arraySize ];
Isso é chamado de matriz de dimensão única. oarraySize deve ser uma constante inteira maior que zero e typepode ser qualquer tipo de dados Solidity válido. Por exemplo, para declarar uma matriz de 10 elementos chamada balance do tipo uint, use esta instrução -
uint balance[10];
Para declarar uma matriz de tamanho dinâmico no Solidity, o programador especifica o tipo dos elementos da seguinte forma -
type[] arrayName;
Inicializando matrizes
Você pode inicializar os elementos do array Solidity um por um ou usando uma única instrução da seguinte maneira -
uint balance[3] = [1, 2, 3];
O número de valores entre colchetes [] não pode ser maior do que o número de elementos que declaramos para a matriz entre colchetes []. A seguir está um exemplo para atribuir um único elemento da matriz -
Se você omitir o tamanho do array, um array grande o suficiente para conter a inicialização é criado. Portanto, se você escrever -
uint balance[] = [1, 2, 3];
Você criará exatamente o mesmo array que fez no exemplo anterior.
balance[2] = 5;
O acima cessionários instrução número elemento 3 rd na matriz um valor de 5.
Criação de matrizes de memória dinâmica
Matrizes de memória dinâmica são criadas usando nova palavra-chave.
uint size = 3;
uint balance[] = new uint[](size);
Acessando Elementos de Matriz
Um elemento é acessado indexando o nome da matriz. Isso é feito colocando o índice do elemento entre colchetes após o nome da matriz. Por exemplo -
uint salary = balance[2];
A instrução acima pegará o terceiro elemento da matriz e atribuirá o valor à variável salary. A seguir está um exemplo, que usará todos os três conceitos acima mencionados viz. declaração, atribuição e acesso a matrizes -
Membros
length- length retorna o tamanho da matriz. comprimento pode ser usado para alterar o tamanho da matriz dinâmica, configurando-a.
push- push permite anexar um elemento a um array de armazenamento dinâmico no final. Ele retorna o novo comprimento da matriz.
Exemplo
Tente o código a seguir para entender como os arrays funcionam no Solidity.
pragma solidity ^0.5.0;
contract test {
function testArray() public pure{
uint len = 7;
//dynamic array
uint[] memory a = new uint[](7);
//bytes is same as byte[]
bytes memory b = new bytes(len);
assert(a.length == 7);
assert(b.length == len);
//access array variable
a[6] = 8;
//test array variable
assert(a[6] == 8);
//static array
uint[3] memory c = [uint(1) , 2, 3];
assert(c.length == 3);
}
}
Enums restringem uma variável para ter apenas alguns valores predefinidos. Os valores nesta lista enumerada são chamados de enums.
Com o uso de enums, é possível reduzir o número de bugs em seu código.
Por exemplo, se considerarmos um aplicativo para uma loja de sucos naturais, seria possível restringir o tamanho do vidro para pequeno, médio e grande. Isso garantiria que ninguém pudesse fazer pedidos de qualquer tamanho que não fosse pequeno, médio ou grande.
Exemplo
Tente o código a seguir para entender como o enum funciona no Solidity.
pragma solidity ^0.5.0;
contract test {
enum FreshJuiceSize{ SMALL, MEDIUM, LARGE }
FreshJuiceSize choice;
FreshJuiceSize constant defaultChoice = FreshJuiceSize.MEDIUM;
function setLarge() public {
choice = FreshJuiceSize.LARGE;
}
function getChoice() public view returns (FreshJuiceSize) {
return choice;
}
function getDefaultChoice() public pure returns (uint) {
return uint(defaultChoice);
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Primeiro clique setLarge Botão para definir o valor como GRANDE e clique em getChoice para obter a escolha selecionada.
Resultado
uint8: 2
Clique getDefaultChoice Botão para obter a escolha padrão.
Resultado
uint256: 1
Os tipos de estrutura são usados para representar um registro. Suponha que você queira manter o controle de seus livros em uma biblioteca. Você pode querer rastrear os seguintes atributos sobre cada livro -
- Title
- Author
- Subject
- ID do livro
Definindo uma Estrutura
Para definir um Struct, você deve usar o structpalavra-chave. A palavra-chave struct define um novo tipo de dados, com mais de um membro. O formato da instrução de estrutura é o seguinte -
struct struct_name {
type1 type_name_1;
type2 type_name_2;
type3 type_name_3;
}
Exemplo
struct Book {
string title;
string author;
uint book_id;
}
Acessando um Struct e sua variável
Para acessar qualquer membro de uma estrutura, usamos o operador de acesso de membro (.). O operador de acesso de membro é codificado como um período entre o nome da variável de estrutura e o membro da estrutura que desejamos acessar. Você usaria a estrutura para definir variáveis do tipo de estrutura. O exemplo a seguir mostra como usar uma estrutura em um programa.
Exemplo
Experimente o código a seguir para entender como as estruturas funcionam no Solidity.
pragma solidity ^0.5.0;
contract test {
struct Book {
string title;
string author;
uint book_id;
}
Book book;
function setBook() public {
book = Book('Learn Java', 'TP', 1);
}
function getBookId() public view returns (uint) {
return book.book_id;
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Primeiro clique setBook Botão para definir o valor como GRANDE e clique em getBookId para obter o ID do livro selecionado.
Resultado
uint256: 1
Mapeamento é um tipo de referência como matrizes e estruturas. A seguir está a sintaxe para declarar um tipo de mapeamento.
mapping(_KeyType => _ValueType)
Onde
_KeyType- pode ser qualquer tipo integrado mais bytes e string. Nenhum tipo de referência ou objetos complexos são permitidos.
_ValueType - pode ser de qualquer tipo.
Considerações
O mapeamento só pode ter o tipo de storage e geralmente são usados para variáveis de estado.
O mapeamento pode ser marcado como público. Solidity cria automaticamente getter para ele.
Exemplo
Tente o código a seguir para entender como o tipo de mapeamento funciona no Solidity.
pragma solidity ^0.5.0;
contract LedgerBalance {
mapping(address => uint) public balances;
function updateBalance(uint newBalance) public {
balances[msg.sender] = newBalance;
}
}
contract Updater {
function updateBalance() public returns (uint) {
LedgerBalance ledgerBalance = new LedgerBalance();
ledgerBalance.updateBalance(10);
return ledgerBalance.balances(address(this));
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Primeiro clique updateBalance Botão para definir o valor como 10, em seguida, olhe para os registros que mostrarão a saída decodificada como -
Resultado
{
"0": "uint256: 10"
}
A solidez permite a conversão implícita e explícita. O compilador Solidity permite a conversão implícita entre dois tipos de dados, desde que nenhuma conversão implícita seja possível e não haja perda de informações. Por exemplo, uint8 é conversível em uint16, mas int8 é conversível em uint256, pois int8 pode conter valores negativos não permitidos em uint256.
Conversão Explícita
Podemos converter explicitamente um tipo de dados em outro usando a sintaxe do construtor.
int8 y = -3;
uint x = uint(y);
//Now x = 0xfffff..fd == two complement representation of -3 in 256 bit format.
A conversão para um tipo menor custa bits de pedido maiores.
uint32 a = 0x12345678;
uint16 b = uint16(a); // b = 0x5678
A conversão para um tipo superior adiciona bits de preenchimento à esquerda.
uint16 a = 0x1234;
uint32 b = uint32(a); // b = 0x00001234
A conversão para um byte menor custa mais dados do pedido.
bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b = 0x12
A conversão para byte maior adiciona bits de preenchimento à direita.
bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b = 0x12340000
A conversão entre bytes de tamanho fixo e int só é possível quando ambos são do mesmo tamanho.
bytes2 a = 0x1234;
uint32 b = uint16(a); // b = 0x00001234
uint32 c = uint32(bytes4(a)); // c = 0x12340000
uint8 d = uint8(uint16(a)); // d = 0x34
uint8 e = uint8(bytes1(a)); // e = 0x12
Os números hexadecimais podem ser atribuídos a qualquer tipo de número inteiro se nenhum truncamento for necessário.
uint8 a = 12; // no error
uint32 b = 1234; // no error
uint16 c = 0x123456; // error, as truncation required to 0x3456
Em termos de solidez, podemos usar wei, finney, szabo ou éter como sufixo para um literal a ser usado para converter várias denominações baseadas em éter. A unidade mais baixa é wei e 1e12 representa 1 x 10 12 .
assert(1 wei == 1);
assert(1 szabo == 1e12);
assert(1 finney == 1e15);
assert(1 ether == 1e18);
assert(2 ether == 2000 fenny);
Unidades de tempo
Semelhante à moeda, Solidity tem unidades de tempo em que a unidade mais baixa é o segundo e podemos usar segundos, minutos, horas, dias e semanas como sufixo para denotar o tempo.
assert(1 seconds == 1);
assert(1 minutes == 60 seconds);
assert(1 hours == 60 minutes);
assert(1 day == 24 hours);
assert(1 week == 7 days);
Variáveis especiais são variáveis globalmente disponíveis e fornecem informações sobre o blockchain. A seguir está a lista de variáveis especiais -
Sr. Não. | Variável Especial e Descrição |
---|---|
1 | blockhash(uint blockNumber) returns (bytes32) Hash do bloco fornecido - funciona apenas para os 256 blocos mais recentes, excluindo os atuais. |
2 | block.coinbase (address payable) Endereço do mineiro do bloco atual. |
3 | block.difficulty (uint) dificuldade do bloco atual. |
4 | block.gaslimit (uint) Limite de gás do bloco atual. |
5 | block.number (uint) Número do bloco atual. |
6 | block.timestamp Carimbo de data / hora do bloco atual em segundos desde a época unix |
7 | gasleft() returns (uint256) Gás restante. |
8 | msg.data (bytes calldata) Calldata completa. |
9 | msg.sender (address payable) Remetente da mensagem (chamada atual). |
10 | msg.sig (bytes4) Os primeiros quatro bytes do calldata (ou seja, identificador de função) |
11 | msg.value (uint) Número de wei enviados com a mensagem. |
12 | now (uint) Carimbo de data e hora do bloco atual (alias para block.timestamp). |
13 | tx.gasprice (uint) Preço do gás da transação. |
14 | tx.origin (address payable) Remetente da transação (cadeia de chamadas completa). |
Exemplo
Tente o seguinte código para ver o uso de msg, uma variável especial para obter o endereço do remetente no Solidity.
pragma solidity ^0.5.0;
contract LedgerBalance {
mapping(address => uint) public balances;
function updateBalance(uint newBalance) public {
balances[msg.sender] = newBalance;
}
}
contract Updater {
function updateBalance() public returns (uint) {
LedgerBalance ledgerBalance = new LedgerBalance();
ledgerBalance.updateBalance(10);
return ledgerBalance.balances(address(this));
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Primeiro clique updateBalance Botão para definir o valor como 10, em seguida, olhe para os registros que mostrarão a saída decodificada como -
Resultado
{
"0": "uint256: 10"
}
O Guia de estilo ajuda a manter o layout do código consistente e torna o código mais legível. A seguir estão as melhores práticas a seguir ao escrever contratos com a Solidity.
Layout de código
Indentation- Use 4 espaços em vez de tabulação para manter o nível de recuo. Evite misturar espaços com guias.
Two Blank Lines Rule - Use 2 linhas em branco entre duas definições de contrato.
pragma solidity ^0.5.0;
contract LedgerBalance {
//...
}
contract Updater {
//...
}
One Blank Line Rule- Use 1 linha em branco entre duas funções. Em caso de declaração única, não é necessário deixar linhas em branco.
pragma solidity ^0.5.0;
contract A {
function balance() public pure;
function account() public pure;
}
contract B is A {
function balance() public pure {
// ...
}
function account() public pure {
// ...
}
}
Maximum Line Length - Uma única linha não deve cruzar 79 caracteres para que os leitores possam analisar facilmente o código.
Wrapping rules- O primeiro argumento está na nova linha sem abrir parênteses. Use recuo único por argumento. Elemento de terminação); deve ser o último.
function_with_a_long_name(
longArgument1,
longArgument2,
longArgument3
);
variable = function_with_a_long_name(
longArgument1,
longArgument2,
longArgument3
);
event multipleArguments(
address sender,
address recipient,
uint256 publicKey,
uint256 amount,
bytes32[] options
);
MultipleArguments(
sender,
recipient,
publicKey,
amount,
options
);
Source Code Encoding - A codificação UTF-8 ou ASCII deve ser usada preferencialmente.
Imports - As instruções de importação devem ser colocadas no início do arquivo, logo após a declaração do pragma.
Order of Functions - As funções devem ser agrupadas de acordo com sua visibilidade.
pragma solidity ^0.5.0;
contract A {
constructor() public {
// ...
}
function() external {
// ...
}
// External functions
// ...
// External view functions
// ...
// External pure functions
// ...
// Public functions
// ...
// Internal functions
// ...
// Private functions
// ...
}
Avoid extra whitespaces - Evite espaços em branco imediatamente entre parênteses, colchetes ou colchetes.
Control structures- Os colchetes devem abrir na mesma linha da declaração. Fechar na própria linha mantendo o mesmo recuo. Use um espaço com chave de abertura.
pragma solidity ^0.5.0;
contract Coin {
struct Bank {
address owner;
uint balance;
}
}
if (x < 3) {
x += 1;
} else if (x > 7) {
x -= 1;
} else {
x = 5;
}
if (x < 3)
x += 1;
else
x -= 1;
Function Declaration- Use a regra acima para chaves. Sempre adicione um rótulo de visibilidade. O rótulo de visibilidade deve vir primeiro, antes de qualquer modificador personalizado.
function kill() public onlyowner {
selfdestruct(owner);
}
Mappings - Evite espaços em branco ao declarar variáveis de mapeamento.
mapping(uint => uint) map;
mapping(address => bool) registeredAddresses;
mapping(uint => mapping(bool => Data[])) public data;
mapping(uint => mapping(uint => s)) data;
Variable declaration - Evite espaços em branco ao declarar variáveis de matriz.
uint[] x; // not unit [] x;
String declaration - Use aspas duplas para declarar uma string em vez de aspas simples.
str = "foo";
str = "Hamlet says, 'To be or not to be...'";
Ordem de Layout
Os elementos devem ser dispostos na seguinte ordem.
Declarações de pragma
Declarações de importação
Interfaces
Libraries
Contracts
Em interfaces, bibliotecas ou contratos, o pedido deve ser como -
Declarações de tipo
Variáveis de Estado
Events
Functions
Convenções de nomenclatura
O contrato e a biblioteca devem ser nomeados usando o estilo CapWords. Por exemplo, SmartContract, Owner etc.
O nome do contrato e da biblioteca deve corresponder aos nomes dos arquivos.
No caso de vários contratos / bibliotecas em um arquivo, use o nome do contrato / biblioteca principal.
Owned.sol
pragma solidity ^0.5.0;
// Owned.sol
contract Owned {
address public owner;
constructor() public {
owner = msg.sender;
}
modifier onlyOwner {
//....
}
function transferOwnership(address newOwner) public onlyOwner {
//...
}
}
Congress.sol
pragma solidity ^0.5.0;
// Congress.sol
import "./Owned.sol";
contract Congress is Owned, TokenRecipient {
//...
}
Nomes de Struct
- Use o estilo CapWords como SmartCoin.Nomes de Eventos
- Use o estilo CapWords como Depósito, Após a transferência.Nomes de Função
- Use o MixedCase Style como o InitteSupply.Variáveis locais e estaduais
- Use o MixedCase Style como creatorAddress, supply.Constantes
- Use todas as letras maiúsculas com sublinhado para separar palavras como MAX_BLOCKS.Nomes de modificadores
- Use o estilo mixCase como onlyAfter.Nomes Enum
- Use o estilo CapWords como TokenGroup.
Uma função é um grupo de código reutilizável que pode ser chamado em qualquer lugar em seu programa. Isso elimina a necessidade de escrever o mesmo código repetidamente. Ajuda os programadores a escrever códigos modulares. As funções permitem que um programador divida um grande programa em várias funções pequenas e gerenciáveis.
Como qualquer outra linguagem de programação avançada, Solidity também oferece suporte a todos os recursos necessários para escrever código modular usando funções. Esta seção explica como escrever suas próprias funções no Solidity.
Definição de Função
Antes de usarmos uma função, precisamos defini-la. A maneira mais comum de definir uma função no Solidity é usando ofunction palavra-chave, seguida por um nome de função exclusivo, uma lista de parâmetros (que pode estar vazia) e um bloco de instrução cercado por chaves.
Sintaxe
A sintaxe básica é mostrada aqui.
function function-name(parameter-list) scope returns() {
//statements
}
Exemplo
Experimente o seguinte exemplo. Ele define uma função chamada getResult que não aceita parâmetros -
pragma solidity ^0.5.0;
contract Test {
function getResult() public view returns(uint){
uint a = 1; // local variable
uint b = 2;
uint result = a + b;
return result;
}
}
Chamando uma função
Para invocar uma função em algum lugar posteriormente no Contrato, você simplesmente precisa escrever o nome dessa função conforme mostrado no código a seguir.
Experimente o código a seguir para entender como a string funciona no Solidity.
pragma solidity ^0.5.0;
contract SolidityTest {
constructor() public{
}
function getResult() public view returns(string memory){
uint a = 1;
uint b = 2;
uint result = a + b;
return integerToString(result);
}
function integerToString(uint _i) internal pure
returns (string memory) {
if (_i == 0) {
return "0";
}
uint j = _i;
uint len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint k = len - 1;
while (_i != 0) {
bstr[k--] = byte(uint8(48 + _i % 10));
_i /= 10;
}
return string(bstr);//access local variable
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Resultado
0: string: 3
Parâmetros de Função
Até agora, vimos funções sem parâmetros. Mas existe uma facilidade para passar parâmetros diferentes ao chamar uma função. Esses parâmetros passados podem ser capturados dentro da função e qualquer manipulação pode ser feita sobre esses parâmetros. Uma função pode ter vários parâmetros separados por vírgula.
Exemplo
Experimente o seguinte exemplo. Nós usamos umuint2strfunção aqui. Leva um parâmetro.
pragma solidity ^0.5.0;
contract SolidityTest {
constructor() public{
}
function getResult() public view returns(string memory){
uint a = 1;
uint b = 2;
uint result = a + b;
return integerToString(result);
}
function integerToString(uint _i) internal pure
returns (string memory) {
if (_i == 0) {
return "0";
}
uint j = _i;
uint len;
while (j != 0) {
len++;
j /= 10;
}
bytes memory bstr = new bytes(len);
uint k = len - 1;
while (_i != 0) {
bstr[k--] = byte(uint8(48 + _i % 10));
_i /= 10;
}
return string(bstr);//access local variable
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Resultado
0: string: 3
A declaração de retorno
A função Solidity pode ter um opcional returndeclaração. Isso é necessário se você deseja retornar um valor de uma função. Esta declaração deve ser a última declaração em uma função.
Como no exemplo acima, estamos usando a função uint2str para retornar uma string.
No Solidity, uma função pode retornar vários valores também. Veja o exemplo abaixo -
pragma solidity ^0.5.0;
contract Test {
function getResult() public view returns(uint product, uint sum){
uint a = 1; // local variable
uint b = 2;
product = a * b;
sum = a + b;
//alternative return statement to return
//multiple values
//return(a*b, a+b);
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Resultado
0: uint256: product 2
1: uint256: sum 3
Modificadores de função são usados para modificar o comportamento de uma função. Por exemplo, para adicionar um pré-requisito a uma função.
Primeiro, criamos um modificador com ou sem parâmetro.
contract Owner {
modifier onlyOwner {
require(msg.sender == owner);
_;
}
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
}
O corpo da função é inserido onde o símbolo especial "_;" aparece na definição de um modificador. Portanto, se a condição do modificador for satisfeita ao chamar esta função, a função é executada e, caso contrário, uma exceção é lançada.
Veja o exemplo abaixo -
pragma solidity ^0.5.0;
contract Owner {
address owner;
constructor() public {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
}
contract Register is Owner {
mapping (address => bool) registeredAddresses;
uint price;
constructor(uint initialPrice) public { price = initialPrice; }
function register() public payable costs(price) {
registeredAddresses[msg.sender] = true;
}
function changePrice(uint _price) public onlyOwner {
price = _price;
}
}
As funções de visualização garantem que não modificarão o estado. Uma função pode ser declarada comoview. As instruções a seguir, se presentes na função, são consideradas modificando o estado e o compilador emitirá um aviso nesses casos.
Modificando variáveis de estado.
Eventos emissores.
Criação de outros contratos.
Usando a autodestruição.
Enviando Ether via chamadas.
Chamar qualquer função que não esteja marcada como view ou pure.
Usando chamadas de baixo nível.
Usando assembly embutido contendo certos opcodes.
O método Getter são funções de visualização padrão.
Veja o exemplo abaixo usando uma função de visualização.
Exemplo
pragma solidity ^0.5.0;
contract Test {
function getResult() public view returns(uint product, uint sum){
uint a = 1; // local variable
uint b = 2;
product = a * b;
sum = a + b;
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Resultado
0: uint256: product 2
1: uint256: sum 3
Funções puras garantem que não leiam ou modifiquem o estado. Uma função pode ser declarada comopure. As instruções a seguir, se presentes na função, são consideradas como leitura do estado e o compilador emitirá um aviso nesses casos.
Lendo variáveis de estado.
Acessando endereço (this) .balance ou <address> .balance.
Acessando qualquer uma das variáveis especiais de block, tx, msg (msg.sig e msg.data podem ser lidos).
Chamar qualquer função não marcada como pura.
Usando assembly embutido que contém certos opcodes.
Funções puras podem usar as funções revert () e require () para reverter possíveis alterações de estado se ocorrer um erro.
Veja o exemplo abaixo usando uma função de visualização.
Exemplo
pragma solidity ^0.5.0;
contract Test {
function getResult() public pure returns(uint product, uint sum){
uint a = 1;
uint b = 2;
product = a * b;
sum = a + b;
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Resultado
0: uint256: product 2
1: uint256: sum 3
A função de fallback é uma função especial disponível para um contrato. Possui os seguintes recursos -
É chamado quando uma função inexistente é chamada no contrato.
É necessário ser marcado como externo.
Não tem nome.
Não tem argumentos
Não pode devolver nada.
Pode ser definido um por contrato.
Se não estiver marcado como pagável, ele lançará uma exceção se o contrato receber o plain ether sem dados.
O exemplo a seguir mostra o conceito de uma função de fallback por contrato.
Exemplo
pragma solidity ^0.5.0;
contract Test {
uint public x ;
function() external { x = 1; }
}
contract Sink {
function() external payable { }
}
contract Caller {
function callTest(Test test) public returns (bool) {
(bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
// test.x is now 1
address payable testPayable = address(uint160(address(test)));
// Sending ether to Test contract,
// the transfer will fail, i.e. this returns false here.
return (testPayable.send(2 ether));
}
function callSink(Sink sink) public returns (bool) {
address payable sinkPayable = address(sink);
return (sinkPayable.send(2 ether));
}
}
Você pode ter várias definições para o mesmo nome de função no mesmo escopo. A definição da função deve diferir uma da outra pelos tipos e / ou número de argumentos na lista de argumentos. Você não pode sobrecarregar as declarações de função que diferem apenas pelo tipo de retorno.
O exemplo a seguir mostra o conceito de uma sobrecarga de função no Solidity.
Exemplo
pragma solidity ^0.5.0;
contract Test {
function getSum(uint a, uint b) public pure returns(uint){
return a + b;
}
function getSum(uint a, uint b, uint c) public pure returns(uint){
return a + b + c;
}
function callSumWithTwoArguments() public pure returns(uint){
return getSum(1,2);
}
function callSumWithThreeArguments() public pure returns(uint){
return getSum(1,2,3);
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Clique no botão callSumWithTwoArguments primeiro e depois no botão callSumWithThreeArguments para ver o resultado.
Resultado
0: uint256: 3
0: uint256: 6
Solidity também oferece funções matemáticas embutidas. A seguir estão os métodos muito usados -
addmod(uint x, uint y, uint k) returns (uint)- calcula (x + y)% k onde a adição é realizada com precisão arbitrária e não envolve em 2 256 .
mulmod(uint x, uint y, uint k) returns (uint)- calcula (x * y)% k onde a adição é realizada com precisão arbitrária e não envolve em 2 256 .
O exemplo a seguir mostra o uso de funções matemáticas no Solidity.
Exemplo
pragma solidity ^0.5.0;
contract Test {
function callAddMod() public pure returns(uint){
return addmod(4, 5, 3);
}
function callMulMod() public pure returns(uint){
return mulmod(4, 5, 3);
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Clique no botão callAddMod primeiro e depois no botão callMulMod para ver o resultado.
Resultado
0: uint256: 0
0: uint256: 2
O Solidity também oferece funções criptográficas integradas. A seguir estão métodos importantes -
keccak256(bytes memory) returns (bytes32) - calcula o hash Keccak-256 da entrada.
sha256(bytes memory) returns (bytes32) - calcula o hash SHA-256 da entrada.
ripemd160(bytes memory) returns (bytes20) - calcula o hash RIPEMD-160 da entrada.
sha256(bytes memory) returns (bytes32) - calcula o hash SHA-256 da entrada.
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)- recupera o endereço associado à chave pública da assinatura da curva elíptica ou retorna zero em caso de erro. Os parâmetros da função correspondem aos valores ECDSA da assinatura: r - primeiros 32 bytes da assinatura; s: segundos 32 bytes de assinatura; v: 1 byte final de assinatura. Este método retorna um endereço.
O exemplo a seguir mostra o uso da função criptográfica no Solidity.
Exemplo
pragma solidity ^0.5.0;
contract Test {
function callKeccak256() public pure returns(bytes32 result){
return keccak256("ABC");
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Resultado
0: bytes32: result 0xe1629b9dda060bb30c7908346f6af189c16773fa148d3366701fbaa35d54f3c8
O padrão de retirada garante que a chamada de transferência direta não seja feita, o que representa uma ameaça à segurança. O contrato a seguir está mostrando o uso inseguro de transferência de chamadas para enviar éter.
pragma solidity ^0.5.0;
contract Test {
address payable public richest;
uint public mostSent;
constructor() public payable {
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {
// Insecure practice
richest.transfer(msg.value);
richest = msg.sender;
mostSent = msg.value;
return true;
} else {
return false;
}
}
}
O contrato acima pode ser processado em estado inutilizável, fazendo com que o mais rico seja um contrato de função de fallback em falha. Quando a função fallback falha, a função BecomeRichest () também falha e o contrato fica preso para sempre. Para mitigar esse problema, podemos usar o padrão de retirada.
No padrão de retirada, redefiniremos o valor pendente antes de cada transferência. Isso garantirá que apenas o contrato do chamador falhe.
pragma solidity ^0.5.0;
contract Test {
address public richest;
uint public mostSent;
mapping (address => uint) pendingWithdrawals;
constructor() public payable {
richest = msg.sender;
mostSent = msg.value;
}
function becomeRichest() public payable returns (bool) {
if (msg.value > mostSent) {
pendingWithdrawals[richest] += msg.value;
richest = msg.sender;
mostSent = msg.value;
return true;
} else {
return false;
}
}
function withdraw() public {
uint amount = pendingWithdrawals[msg.sender];
pendingWithdrawals[msg.sender] = 0;
msg.sender.transfer(amount);
}
}
O acesso restrito a um contrato é uma prática comum. Por padrão, um estado de contrato é somente leitura, a menos que seja especificado como público.
Podemos restringir quem pode modificar o estado do contrato ou chamar as funções de um contrato usando modificadores. Vamos criar e usar vários modificadores conforme explicado abaixo -
onlyBy - uma vez usado em uma função, apenas o chamador mencionado pode chamar essa função.
onlyAfter - uma vez usada em uma função, essa função pode ser chamada após um certo período de tempo.
costs - uma vez usado em uma função, o chamador pode chamar essa função apenas se determinado valor for fornecido.
Exemplo
pragma solidity ^0.5.0;
contract Test {
address public owner = msg.sender;
uint public creationTime = now;
modifier onlyBy(address _account) {
require(
msg.sender == _account,
"Sender not authorized."
);
_;
}
function changeOwner(address _newOwner) public onlyBy(owner) {
owner = _newOwner;
}
modifier onlyAfter(uint _time) {
require(
now >= _time,
"Function called too early."
);
_;
}
function disown() public onlyBy(owner) onlyAfter(creationTime + 6 weeks) {
delete owner;
}
modifier costs(uint _amount) {
require(
msg.value >= _amount,
"Not enough Ether provided."
);
_;
if (msg.value > _amount)
msg.sender.transfer(msg.value - _amount);
}
function forceOwnerChange(address _newOwner) public payable costs(200 ether) {
owner = _newOwner;
if (uint(owner) & 0 == 1) return;
}
}
Contrato em Solidity é semelhante a uma classe em C ++. Um contrato tem as seguintes propriedades.
Constructor - Uma função especial declarada com a palavra-chave construtor que será executada uma vez por contrato e é invocada quando um contrato é criado.
State Variables - Variáveis por contrato para armazenar o estado do contrato.
Functions - Funções por contrato que podem modificar as variáveis de estado para alterar o estado de um contrato.
Quantificadores de Visibilidade
A seguir estão vários quantificadores de visibilidade para funções / variáveis de estado de um contrato.
external- As funções externas devem ser chamadas por outros contratos. Eles não podem ser usados para chamadas internas. Para chamar uma função externa dentro do contrato, a chamada de this.function_name () é necessária. Variáveis de estado não podem ser marcadas como externas.
public- Funções / variáveis públicas podem ser usadas tanto externa quanto internamente. Para a variável de estado público, o Solidity cria automaticamente uma função getter.
internal - As funções / variáveis internas só podem ser usadas internamente ou por contratos derivados.
private - As funções / variáveis privadas só podem ser utilizadas internamente e nem mesmo por contratos derivados.
Exemplo
pragma solidity ^0.5.0;
contract C {
//private state variable
uint private data;
//public state variable
uint public info;
//constructor
constructor() public {
info = 10;
}
//private function
function increment(uint a) private pure returns(uint) { return a + 1; }
//public function
function updateData(uint a) public { data = a; }
function getData() public view returns(uint) { return data; }
function compute(uint a, uint b) internal pure returns (uint) { return a + b; }
}
//External Contract
contract D {
function readData() public returns(uint) {
C c = new C();
c.updateData(7);
return c.getData();
}
}
//Derived Contract
contract E is C {
uint private result;
C private c;
constructor() public {
c = new C();
}
function getComputedResult() public {
result = compute(3, 5);
}
function getResult() public view returns(uint) { return result; }
function getData() public view returns(uint) { return c.info(); }
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First . Execute vários métodos de contratos. Para E.getComputedResult () seguido por E.getResult () mostra -
Resultado
0: uint256: 8
Herança é uma forma de estender a funcionalidade de um contrato. Solidity oferece suporte tanto para herança única quanto para herança múltipla. A seguir estão os principais destaques.
Um contrato derivado pode acessar todos os membros não privados, incluindo métodos internos e variáveis de estado. Mas usar isso não é permitido.
A substituição da função é permitida desde que a assinatura da função permaneça a mesma. Em caso de diferença nos parâmetros de saída, a compilação falhará.
Podemos chamar a função de um supercontrato usando a palavra-chave super ou o nome do supercontrato.
No caso de herança múltipla, a chamada de função usando super dá preferência à maioria dos contratos derivados.
Exemplo
pragma solidity ^0.5.0;
contract C {
//private state variable
uint private data;
//public state variable
uint public info;
//constructor
constructor() public {
info = 10;
}
//private function
function increment(uint a) private pure returns(uint) { return a + 1; }
//public function
function updateData(uint a) public { data = a; }
function getData() public view returns(uint) { return data; }
function compute(uint a, uint b) internal pure returns (uint) { return a + b; }
}
//Derived Contract
contract E is C {
uint private result;
C private c;
constructor() public {
c = new C();
}
function getComputedResult() public {
result = compute(3, 5);
}
function getResult() public view returns(uint) { return result; }
function getData() public view returns(uint) { return c.info(); }
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First . Execute vários métodos de contratos. Para E.getComputedResult () seguido por E.getResult () mostra -
Resultado
0: uint256: 8
Construtor é uma função especial declarada usando constructorpalavra-chave. É uma função opcional e é usada para inicializar variáveis de estado de um contrato. A seguir estão as principais características de um construtor.
Um contrato pode ter apenas um construtor.
Um código de construtor é executado uma vez quando um contrato é criado e é usado para inicializar o estado do contrato.
Após a execução de um código de construtor, o código final é implantado em blockchain. Este código inclui funções públicas e código acessível por meio de funções públicas. O código do construtor ou qualquer método interno usado apenas pelo construtor não está incluído no código final.
Um construtor pode ser público ou interno.
Um construtor interno marca o contrato como abstrato.
No caso, nenhum construtor é definido, um construtor padrão está presente no contrato.
pragma solidity ^0.5.0;
contract Test {
constructor() public {}
}
Caso o contrato base tenha construtor com argumentos, cada contrato derivado deve passá-los.
O construtor de base pode ser inicializado diretamente usando a seguinte maneira -
pragma solidity ^0.5.0;
contract Base {
uint data;
constructor(uint _data) public {
data = _data;
}
}
contract Derived is Base (5) {
constructor() public {}
}
O construtor de base pode ser inicializado indiretamente usando a seguinte maneira -
pragma solidity ^0.5.0;
contract Base {
uint data;
constructor(uint _data) public {
data = _data;
}
}
contract Derived is Base {
constructor(uint _info) Base(_info * _info) public {}
}
Maneiras diretas e indiretas de inicializar o construtor do contrato base não são permitidas.
Se o contrato derivado não estiver passando argumento (s) para o construtor do contrato base, então o contrato derivado se tornará abstrato.
Contrato abstrato é aquele que contém pelo menos uma função sem qualquer implementação. Esse contrato é usado como um contrato base. Geralmente, um contrato abstrato contém funções tanto implementadas quanto abstratas. O contrato derivado implementará a função abstrata e usará as funções existentes como e quando necessário.
Caso um contrato derivado não esteja implementando a função abstrata, esse contrato derivado será marcado como abstrato.
Exemplo
Experimente o código a seguir para entender como os contratos abstratos funcionam no Solidity.
pragma solidity ^0.5.0;
contract Calculator {
function getResult() public view returns(uint);
}
contract Test is Calculator {
function getResult() public view returns(uint) {
uint a = 1;
uint b = 2;
uint result = a + b;
return result;
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Resultado
0: uint256: 3
As interfaces são semelhantes a contratos abstratos e são criadas usando interfacepalavra-chave. A seguir estão as principais características de uma interface.
A interface não pode ter nenhuma função com implementação.
As funções de uma interface podem ser apenas do tipo externo.
A interface não pode ter construtor.
A interface não pode ter variáveis de estado.
A interface pode ter enum, structs que podem ser acessados usando a notação de ponto do nome da interface.
Exemplo
Experimente o código a seguir para entender como a interface funciona no Solidity.
pragma solidity ^0.5.0;
interface Calculator {
function getResult() external view returns(uint);
}
contract Test is Calculator {
constructor() public {}
function getResult() external view returns(uint){
uint a = 1;
uint b = 2;
uint result = a + b;
return result;
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Note - Selecione Teste no menu suspenso antes de clicar no botão implantar.
Resultado
0: uint256: 3
As bibliotecas são semelhantes aos contratos, mas destinam-se principalmente à reutilização. Uma Biblioteca contém funções que outros contratos podem chamar. O Solidity tem certas restrições ao uso de uma Biblioteca. A seguir estão as principais características de uma Biblioteca Solidity.
As funções da biblioteca podem ser chamadas diretamente se não modificarem o estado. Isso significa que funções puras ou de visualização só podem ser chamadas de fora da biblioteca.
A biblioteca não pode ser destruída, pois é considerada sem estado.
Uma biblioteca não pode ter variáveis de estado.
Uma biblioteca não pode herdar nenhum elemento.
Uma biblioteca não pode ser herdada.
Exemplo
Experimente o código a seguir para entender como uma biblioteca funciona no Solidity.
pragma solidity ^0.5.0;
library Search {
function indexOf(uint[] storage self, uint value) public view returns (uint) {
for (uint i = 0; i < self.length; i++) if (self[i] == value) return i;
return uint(-1);
}
}
contract Test {
uint[] data;
constructor() public {
data.push(1);
data.push(2);
data.push(3);
data.push(4);
data.push(5);
}
function isValuePresent() external view returns(uint){
uint value = 4;
//search if value is present in the array using Library function
uint index = Search.indexOf(data, value);
return index;
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Note - Selecione Teste no menu suspenso antes de clicar no botão implantar.
Resultado
0: uint256: 3
Usando Para
A diretiva using A for B; pode ser usado para anexar funções de biblioteca da biblioteca A a um determinado tipo B. Essas funções usarão o tipo de chamador como seu primeiro parâmetro (identificado usando self).
Exemplo
Experimente o código a seguir para entender como uma biblioteca funciona no Solidity.
pragma solidity ^0.5.0;
library Search {
function indexOf(uint[] storage self, uint value) public view returns (uint) {
for (uint i = 0; i < self.length; i++)if (self[i] == value) return i;
return uint(-1);
}
}
contract Test {
using Search for uint[];
uint[] data;
constructor() public {
data.push(1);
data.push(2);
data.push(3);
data.push(4);
data.push(5);
}
function isValuePresent() external view returns(uint){
uint value = 4;
//Now data is representing the Library
uint index = data.indexOf(value);
return index;
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Note - Selecione Teste no menu suspenso antes de clicar no botão implantar.
Resultado
0: uint256: 3
O Solidity oferece a opção de usar a linguagem assembly para escrever montagens embutidas no código-fonte do Solidity. Também podemos escrever um código de montagem autônomo que então será convertido em bytecode. O Standalone Assembly é uma linguagem intermediária para um compilador Solidity e converte o código do Solidity em um Standalone Assembly e então em código de bytes. Podemos usar a mesma linguagem usada no Inline Assembly para escrever código em um assembly autônomo.
Montagem Inline
O código de montagem embutido pode ser intercalado na base de código do Solidity para ter um controle mais refinado sobre EVM e é usado especialmente ao escrever as funções da biblioteca.
Um código assembly é escrito em assembly { ... } quadra.
Exemplo
Experimente o código a seguir para entender como uma biblioteca funciona no Solidity.
pragma solidity ^0.5.0;
library Sum {
function sumUsingInlineAssembly(uint[] memory _data) public pure returns (uint o_sum) {
for (uint i = 0; i < _data.length; ++i) {
assembly {
o_sum := add(o_sum, mload(add(add(_data, 0x20), mul(i, 0x20))))
}
}
}
}
contract Test {
uint[] data;
constructor() public {
data.push(1);
data.push(2);
data.push(3);
data.push(4);
data.push(5);
}
function sum() external view returns(uint){
return Sum.sumUsingInlineAssembly(data);
}
}
Execute o programa acima usando as etapas fornecidas no capítulo Aplicativo Solidity First .
Note - Selecione Teste no menu suspenso antes de clicar no botão implantar.
Resultado
0: uint256: 15
O evento é um membro herdável de um contrato. Um evento é emitido, ele armazena os argumentos passados nos logs de transações. Esses logs são armazenados no blockchain e podem ser acessados usando o endereço do contrato até que o contrato esteja presente no blockchain. Um evento gerado não é acessível de dentro dos contratos, nem mesmo daquele que os criou e emitiu.
Um evento pode ser declarado usando a palavra-chave do evento.
//Declare an Event
event Deposit(address indexed _from, bytes32 indexed _id, uint _value);
//Emit an event
emit Deposit(msg.sender, _id, msg.value);
Exemplo
Experimente o código a seguir para entender como um evento funciona no Solidity.
Primeiro, crie um contrato e emita um evento.
pragma solidity ^0.5.0;
contract Test {
event Deposit(address indexed _from, bytes32 indexed _id, uint _value);
function deposit(bytes32 _id) public payable {
emit Deposit(msg.sender, _id, msg.value);
}
}
Em seguida, acesse o evento do contrato em código JavaScript.
var abi = /* abi as generated using compiler */;
var ClientReceipt = web3.eth.contract(abi);
var clientReceiptContract = ClientReceipt.at("0x1234...ab67" /* address */);
var event = clientReceiptContract.Deposit(function(error, result) {
if (!error)console.log(result);
});
Deve imprimir detalhes semelhantes aos seguintes -
Resultado
{
"returnValues": {
"_from": "0x1111...FFFFCCCC",
"_id": "0x50...sd5adb20",
"_value": "0x420042"
},
"raw": {
"data": "0x7f...91385",
"topics": ["0xfd4...b4ead7", "0x7f...1a91385"]
}
}
O Solidity oferece várias funções para tratamento de erros. Geralmente, quando ocorre um erro, o estado é revertido para seu estado original. Outras verificações são para impedir o acesso não autorizado ao código. A seguir estão alguns dos métodos importantes usados no tratamento de erros -
assert(bool condition)- Caso a condição não seja atendida, esta chamada de método causa um opcode inválido e todas as alterações feitas no estado são revertidas. Este método deve ser usado para erros internos.
require(bool condition)- Caso a condição não seja atendida, esta chamada de método retorna ao estado original. - Este método deve ser usado para erros em entradas ou componentes externos.
require(bool condition, string memory message)- Caso a condição não seja atendida, esta chamada de método retorna ao estado original. - Este método deve ser usado para erros em entradas ou componentes externos. Ele fornece uma opção para fornecer uma mensagem personalizada.
revert() - Este método aborta a execução e reverte todas as alterações feitas no estado.
revert(string memory reason)- Este método aborta a execução e reverte todas as alterações feitas no estado. Ele fornece uma opção para fornecer uma mensagem personalizada.
Exemplo
Tente o código a seguir para entender como funciona o tratamento de erros no Solidity.
pragma solidity ^0.5.0;
contract Vendor {
address public seller;
modifier onlySeller() {
require(
msg.sender == seller,
"Only seller can call this."
);
_;
}
function sell(uint amount) public payable onlySeller {
if (amount > msg.value / 2 ether)
revert("Not enough Ether provided.");
// Perform the sell operation.
}
}
Quando reverter for chamado, ele retornará os dados hexadecimais conforme a seguir.
Resultado
0x08c379a0 // Function selector for Error(string)
0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset
0x000000000000000000000000000000000000000000000000000000000000001a // String length
0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data