Solidity: linguagem de programação para contratos inteligentes
Solidity é utilizado para programar contratos inteligentes sofisticados para operar na blockchain da Ethereum. É uma linguagem de programação que oferece uma abordagem interessante que a diferencia das outras linguagens.
O que é Solidity?
Solidity é uma linguagem de programação de alto nível para criar contratos inteligentes na blockchain Ethereum. Os «contratos inteligentes» são contratos autoexecutáveis que automatizam a troca de ativos ou fundos entre duas ou mais partes. Este tipo de contrato não necessita de um intermediário para garantir o seu cumprimento, razão pela qual são especiais.
O código-fonte do Solidity é compilado em bytecode e inserido na blockchain do Ethereum como um contrato inteligente. O contrato inteligente pode ser executado por qualquer nó da rede e o seu estado é armazenado na blockchain. Aqui está um exemplo de um contrato simples que simula uma máquina de venda de NFT:
pragma Solidity 0.8.7;
contract NFTVendingMachine {
// Declare state variables
address public owner;
mapping (address => uint) public nftBalance;
// Run on deployment
constructor() {
owner = msg.sender;
nftBalance[address(this)] = 100;
}
// Allow the owner to restock the NFT balance
function restock(uint amount) public {
require(msg.sender == owner, "Only the owner can restock.");
nftBalance[address(this)] += amount;
}
// Allow anyone to purchase NFTs
function purchase(uint amount) public payable {
require(msg.value >= amount * 1 ether, "You must pay at least 1 ETH per NFT");
require(nftBalance[address(this)] >= amount, "Not enough NFTs in stock to complete this purchase");
nftBalance[address(this)] -= amount;
nftBalance[msg.sender] += amount;
}
}solidityEm que casos se utiliza Solidity?
O Solidity foi desenvolvido especificamente para criar aplicações descentralizadas, DApps (Distributed Applications), que são executadas na Ethereum Virtual Machine (EVM). Os contratos inteligentes são ideais para gerir ativos digitais, criar bolsas de valores descentralizadas e implementar sistemas de votação, entre outros aspetos.
Finanças Descentralizadas (DeFi)
Solidity é usado para desenvolver aplicações DeFi, como mercados de valores descentralizados, plataformas de empréstimos e financiamentos, mercados de previsão e criptomoedas. DeFi é um dos casos de uso mais populares da tecnologia blockchain. Solidity tornou-se uma ferramenta indispensável para criar aplicações DeFi na rede Ethereum.
Tokens não fungíveis
Os tokens não fungíveis (NFT, non-fungible tokens) gozam de grande popularidade desde 2020. Os NFT são determinados ativos digitais armazenados na blockchain. Podem ser obras de arte digitais, recordações desportivas ou artefactos do setor dos videojogos. O Solidity é utilizado para criar os contratos inteligentes que suportam os NFT.
Gestão da cadeia de abastecimento
É possível utilizar Solidity para criar contratos inteligentes com o objetivo de supervisionar e gerir as cadeias de abastecimento. Os contratos são utilizados para automatizar diferentes processos da cadeia de abastecimento. Entre eles, encontram-se o acompanhamento dos movimentos de mercadorias, a verificação da autenticidade dos produtos e o processamento dos pagamentos entre as diferentes partes.
Sistemas de votação
O Solidity pode ser usado para criar contratos inteligentes que implementam sistemas de votação seguros e transparentes na blockchain. Os contratos podem ser usados para garantir que os votos sejam contados corretamente e que o processo de votação seja justo e transparente.
Quais são as vantagens e desvantagens do Solidity?
Em geral, Solidity é uma linguagem de programação poderosa, desenvolvida para criar contratos inteligentes na blockchain Ethereum. No entanto, como toda tecnologia, Solidity oferece vantagens e desvantagens específicas que os programadores devem ter em conta ao criar contratos inteligentes. De qualquer forma, criar contratos inteligentes confiáveis requer um certo nível de habilidade e atenção.
A título de exemplo, vamos mostrar um contrato inteligente que funciona como um buraco negro: todo o Ether enviado para o contrato é irreversivelmente absorvido, uma vez que não é possível reembolsar o Ether.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >= 0.9.0;
// This contract swallows all Ether sent to it
contract Blackhole {
event Received(address, uint);
receive() external payable {
emit Received(msg.sender, msg.value);
}
}solidityVantagens do Solidity
- Flexibilidade: Solidity é uma linguagem de programação muito versátil. Pode ser usada para criar diferentes contratos inteligentes com uma grande variedade de casos de uso.
- Segurança: Solidity foi desenvolvida precisamente a pensar na segurança. A linguagem inclui características como controlos de acesso, gestão de exceções e mecanismos de falha que ajudam os programadores a escrever contratos seguros.
- Compatibilidade com Ethereum: Solidity é atualmente a linguagem de programação por excelência para criar contratos inteligentes na blockchain Ethereum.
- Comunidade sólida: Solidity conta com uma grande comunidade de programadores de blockchain. Ou seja, dispõe de inúmeros recursos para aprender e resolver problemas.
Desvantagens do Solidity
- Curva de aprendizagem: Solidity tem uma curva de aprendizagem relativamente alta para programadores que estão a lidar com blockchain e contratos inteligentes pela primeira vez.
- Imutabilidade: uma vez que um contrato inteligente é introduzido na blockchain, ele não pode ser modificado, portanto, os programadores devem ser extremamente cuidadosos ao redigi-lo e testá-lo.
- Ausência de revisão formal: Solidity não possui ferramentas integradas que revisam o código formalmente, portanto, os desenvolvedores precisam recorrer a ferramentas externas para garantir que seus contratos estejam bem escritos.
- Ferramentas limitadas: o ecossistema de ferramentas do Solidity ainda é relativamente pouco desenvolvido. Portanto, pode causar problemas com IDE, estruturas de testes e outras ferramentas de desenvolvimento.
Como é a sintaxe básica do Solidity?
Solidity é uma linguagem orientada a objetos, especializada em contratos inteligentes e influenciada por JavaScript, Python e C++. A sintaxe do Solidity é semelhante à do JavaScript, embora apresente algumas peculiaridades interessantes.
Variáveis em Solidity
À primeira vista, as variáveis do Solidity parecem funcionar como em outras linguagens semelhantes. No entanto, a maior diferença está no facto de que o ambiente de execução é a Ethereum Virtual Machine (EVM). Todas as operações na EVM, bem como o armazenamento de dados, custam uma certa quantidade de «gas». Portanto, ao programar, pode ser necessário ponderar como implementar uma operação da forma mais eficiente possível.
Além das variáveis «normais», o Solidity reconhece constantes que devem ser definidas na compilação. As constantes requerem menos gás para serem armazenadas:
// Regular variable can be declared without defining
int a;
// Constant needs to be defined at declaration
int constant b = 51;solidityA situação das variáveis inmutables é semelhante. Estas consomem menos gás e não permitem ser modificadas após terem sido atribuídas um valor. Ao contrário das variáveis constantes, é possível atribuir-lhes um valor durante a execução.
Estruturas de controlo em Solidity
Solidity é uma linguagem de programação imperativa, portanto, suporta as instruções de controlo habituais, por exemplo, condicionais e loops. A seguir, mostramos o código para escolher o maior de dois números, a ou b:
int largerNumber = 0;
// If-else statement
if (a > b) {
largerNumber = a;
} else {
largerNumber = b;
}solidityO loop for no Solidity é semelhante à sintaxe em JavaScript ou C++:
// Loop 10 times
for (int i = 0; i < 10; i++) {
// …
}solidityO loop while também segue a mesma estrutura. Combinamos uma condição de interrupção com uma variável numérica para o contador:
bool continueLoop = true;
int counter = 0;
// Loop at most 10 times
while (continueLoop && counter < 10) {
// …
counter++;
}solidityTipos simples em Solidity
Solidity é uma linguagem de tipagem estática e suporta os tipos de variáveis comuns às linguagens de programação. Os tipos de dados mais simples que representam valores individuais são os booleanos, numéricos e strings (cadeias de caracteres), entre outros.
Os booleanos em Solidity podem assumir o valor true ou false. Podem ser combinados entre si com os operadores booleanos conhecidos e utilizados em sentenças if:
bool paymentReceived = true;
bool itemsStocked = true;
bool continueTransaction = paymentReceived && itemsStocked;
if (continueTransaction) {
// ...
}solidityO Solidity suporta uma ampla gama de tipos de números. No caso dos números inteiros, distingue-se entre números com sinal (int) e sem sinal (uint) (estes últimos são sempre positivos). Além disso, o valor de um número pode ser representado em incrementos de 8 bits, de int8 e int16 a int265:
uint8 smallNumber = 120;
int8 negativeNumber = -125;
int8 result = smallNumber + negativeNumber;
assert(result == -5)solidityAs cadeias de caracteres em Solidity são usadas principalmente para criar mensagens de estado. A linguagem de programação suporta aspas simples e duplas, bem como o padrão de caracteres Unicode:
string message = 'Hello World';
string success = unicode"Transfer sent";SolidityFunções em Solidity
Tal como na maioria das linguagens de programação, as funções são uma parte essencial do Solidity. As funções são definidas de forma semelhante ao JavaScript, pelo que é necessário especificar cada tipo de argumento. Além disso, utiliza-se a palavra-chave returns para indicar o tipo de valor obtido como resposta:
// Define a function
function addNumbers(int a, int b) returns (int) {
return a + b;
}solidityAs chamadas para uma função são feitas como de costume:
// Call the function
int result = addNumbers(2, 3);solidityCuriosamente, de forma análoga aos argumentos com nome, os valores de retorno também podem ter nome. Neste caso, basta atribuir as variáveis correspondentes no corpo da função, não sendo necessário introduzir um retorno através de return:
function divideNumbers(int dividend, int divisor) returns (int quotient) {
quotient = dividend / divisor;
// No `return` necessary
}soliditySemelhante às variáveis constant ou immutable, as funções no Solidity podem ser marcadas para não alterarem o estado. Para isso, são utilizadas as palavras-chave view e pure. Uma função view não altera o estado, enquanto uma função pure garante também não ler variáveis de estado.
Contratos inteligentes em Solidity
Além dos tipos habituais, o Solidity possui vários tipos especializados em contratos inteligentes. O tipo mais comum é address, que atribui endereços Ethereum, onde os endereços payable podem receber transferências em Ether. Para isso, os endereços payable utilizam os métodos balance() e transfer().
// Get address of this contract
address mine = address(this);
// Get payable external address
address payable other = payable(0x123);
// Transfer if balances fulfill conditions
if (other.balance < 10 && mine.balance >= 100) {
other.transfer(10);
}solidityA partir do tipo address, surge o tipo contract como estrutura essencial da linguagem. Os contratos equivalem às classes nas linguagens de programação orientadas a objetos. Eles são responsáveis por agrupar uma coleção de dados de estado e funções e protegê-los do mundo exterior. Os contratos contam com herança múltipla, como em Python ou C++. Os contratos normalmente começam com uma linha pragma que indica a versão permitida do Solidity, seguida da definição do contrato propriamente dito:
// Make sure Solidity version matches
pragma Solidity >=0.7.1 <0.9.0;
// Contract definition
contract Purchase {
// Public state variables
address seller;
address buyer;
// View-function
function getSeller() external view returns (address) {
return seller;
}
}solidityOs contratos inteligentes podem definir dados de estado e funções. Tal como em C++ e Java, é possível definir um dos três níveis de acesso em cada caso:
public: é possível aceder à variável a partir do contrato com permissões de leitura e escrita. Além disso, é criada automaticamente uma funçãoviewcomo getter (capturador) para poder ler a variável a partir do exterior.internal: a variável está protegida contra qualquer acesso externo. É possível acessá-la com direitos de leitura e escrita, tanto a partir do contrato quanto dos contratos herdeiros.private: é semelhante ainternal, mas não é possível aceder à variável a partir dos contratos herdeiros.
As funções também podem ser classificadas como external. Uma função external funciona como parte da interface do contrato e é utilizada para acesso externo. A função receive é uma forma bem conhecida de receber Ether:
// Define without `function` keyword
receive() external payable {
// Handle Ether
}solidityModificadores em Solidity
Os modificadores no Solidity permitem-lhe utilizar uma sintaxe mais precisa. Os modificadores funcionam de forma semelhante aos decoradores do Python: tal como os decoradores, os modificadores são utilizados para modificar a chamada a uma função. São frequentemente utilizados para verificar se uma condição é cumprida antes de executar uma função:
contract Sale {
uint price;
address payable owner;
modifier onlyOwner {
// Will throw error if called by anyone other than the owner
require(
msg.sender == owner,
"Only owner can call this function."
);
// The wrapped function's body is inserted here
_;
}
// `onlyOwner` wraps `changePrice`
function changePrice(uint newPrice) public onlyOwner {
// We'll only get here if the owner called this function
price = newPrice;
}
}solidityGestão de transações com Solidity
O Solidity possui uma gestão de transações integrada, permitindo verificar se uma transferência de Ether foi processada integralmente ou não foi processada de todo. A linguagem utiliza a palavra-chave revert, que provoca o «roll-back» de uma transação. A palavra-chave error permite definir os seus próprios códigos de erro:
// Custom error definition
error InsufficientPayment(uint256 paid, uint256 required);
// Contract representing a sale
contract Sale {
uint price;
// Purchase if enough ether transferred
function purchase() public payable {
if (msg.value < price) {
revert InsufficientPayment(msg.value, price);
}
// Complete purchase
}
}solidityOutro padrão frequente é o uso da função require(), que pode ser utilizada de forma semelhante à revert:
// Using `require()` function
if (!condition) revert("Error message");
// Equivalent to
require(condition, "Error message");solidity