Smart contracts #2: Escrevendo seu primeiro contrato
Passo-a-passo: Criando um smart contract na rede Ethereum
Este é o segundo artigo de uma série titulada “Smart Contracts”, onde irei tratar de smart contracts com foco na plataforma Ethereum. Com esta série, meu objetivo é trazer conteúdo completo e de maneira compreensiva sobre o tema, que tratarei de forma teórica e prática. Um novo artigo será publicado toda terça-feira. Neste artigo, vamos utilizar o Remix para criar contratos funcionais.
Caso você tenha interesse em aprender mais e tenha um conhecimento razoável da língua inglesa, toda quarta-feira publico também um artigo tratando de uma definição importante do mundo da tecnologia blockchain, em uma série titulada “Blockchain definition of the week”.
Artigos anteriores:
No artigo de hoje, vamos construir smart contracts fáceis, porém funcionais, e iremos testá-los.
Conceitos
Antes de começar, é importante definir alguns conceitos importantes. Segue abaixo uma lista com definições necessárias para entendimento completo do artigo de hoje:
Solidity
Linguagem utilizada pelo Ethereum para o desenvolvimento de smart contracts. Solidity é uma linguagem de alto nível, fortemente tipada, com bibliotecas, herança e orientada a objetos. Anteriormente, eram usadas Mutan, LLL e Serpent, mas estas linguagens já estão quase extintas. Solidity tem semelhanças com JavaScript, e sua mais nova alternativa é chamada Vyper, que se parece com Python. Porém, Solidity é a mais utilizada pela rede.
Remix
IDE (Integrated Development Environment) que funciona no navegador do usuário e permite que o mesmo escreva, publique e interaja com smart contracts. Remix é a IDE mais utilizada, e permite interação com a blockchain do Ethereum por meio de um cliente como o MetaMask ou o teste de contratos internamente fazendo uso de uma virtual machine baseada em JavaScript.
Gas
Unidade de medida do poder computacional necessário para realizar uma operação na rede Ethereum. O gas é usado para determinar quanto deve ser pago para executar uma transação na blockchain do Ethereum. Por exemplo, uma transação de ETH do usuário A para o usuário B necessita de menos poder computacional do que publicar um smart contract, consequentemente usando menos gas. Quando o total de gas necessário para uma transação é estipulado, o usuário pode então selecionar quanto quer pagar por cada unidade de gas (chamado gas price). Para ter sua transação publicada mais rapidamente, um usuário optará por pagar um preço mais alto pelo gas. O preço do gas é pago em Ether, e normalmente calculado em Gwei (0.000000001 ETH), de maneira que, uma transação que requer 21000 unidades de gas para ser processada, com um preço de 10 Gwei por unidade de gas, custará 0.00021ETH.
*Gas é um conceito importante e complexo. Mais informações aqui.
Mainnet
Rede principal do Ethereum, onde transações são executadas de maneira imutável e com valores reais. Rede mais utilizada.
Testnets
Além da mainnet, o Ethereum permite a criação de testnets, que utilizam Ethers “fictícios” para realizar operações, e em que desenvolvedores podem experimentar diferentes funcionalidades antes de publicar contratos ou lançar aplicações na rede principal. Redes testnet podem ser criadas com especificações diferentes da mainnet, e podem ser privadas ou públicas. As quatro maiores testnets públicas são Ropsten, Rinkeby, Morden e Kovan, que derivam seus nomes de estações de metrô pelo mundo.
Primeiro contrato
Para começar, vamos dissecar o contrato “HelloWorld” do artigo anterior.
pragma solidity ^0.4.24; //Versão da linguagemcontract Hello { //Nome do contrato
//Função HelloWorld retorna a mensagem "Hello World"
function HelloWorld () public pure returns (string) {
return "Hello World";
}
}
Sintaxe
- pragma solidity ^0.4.24;
Especifica a linguagem e versão da linguagem utilizada. Equivalente à declaração <!DOCTYPE html>
. Por ser uma linguagem nova, Solidity conta com updates frequentes, que buscam melhorar a usabilidade. Atualmente, a versão mais avançada é a 0.4.24.
Esta declaração com o número referente à versão da linguagem é obrigatória em todos os contratos.
- contract Hello {}
Indica a criação de um contrato com nome “Hello” e funções incluídas dentro das chaves.
- function HelloWorld () {}
Indica a criação de uma função com nome “HelloWorld”, com parâmetros especificados dentro dos parênteses e funcionalidades dentro das chaves. Caso não hajam parâmetros, basta deixar os parênteses vazios.
- public pure returns (string) {
public
denota que a função é pública, pode ser visualizada e chamada por usuários ou por um evento interno do contrato.
pure
denota que a função promete não modificar nem ler ou estado da blockchain, ou seja, transações de valor ou busca de informações do bloco não podem ser utilizados, por exemplo. Como regra básica, este tipo de função opera apenas com informações já contidas no contrato, consequentemente sem interagir com o estado da blockchain.
returns (string)
indica que a função retorna uma cadeia de caracteres.
- return “Hello World”;
A função, quando chamada com sucesso, retorna a mensagem (cadeia de caracteres) Hello World.
- //
Denotam comentários que não afetam o código, e são usados apenas para efeito explicativo.
Testando o código
Vamos agora testar o código acima para ver se ele faz o que deve fazer. Veja abaixo como fazê-lo.
- Acesse o Remix.
- Copie e cole o código do contrato. (Ou escreva o contrato da memória, se conseguir). Você pode deletar os comentários se quiser.
- Clique em “Start to compile” no canto superior direito. Você deveria ver algo assim:
Se tudo estiver certo com o código, o compilador exibirá uma aba verde na direita com o nome do contrato. Para hoje, não se preocupe com a aba roxa mencionando “Static Analysis”.
4. Clique em “Run” ao lado de “Compile” no canto superior direito.
5. Selecione o “Environment”: “JavaScript VM”. Isto permitirá que testemos o contrato sem ter que publicá-lo na rede.
6. Clique em “Deploy”. Isto “publicará” seu contrato no simulador que é a JavaScript VM.
7. Clique em cima do contrato “Hello at 0x….” na seção “Deployed Contracts”. Voce deverá ver algo assim:
Agora estamos prontos para testar! O que fizemos até agora foi:
Escrever o código → Compilar o contrato → Publicar o contrato
Os passos são os mesmos para a mainnet ou para as testnets, nós simplesmente estamos usando um simulador por questão de simplicidade.
Importante: JavaScript VM não é uma testnet!
Ok, vamos lá. Para testar a função “HelloWorld”, clique em cima do botão azul com o mesmo nome.
Pronto! O contrato fez o que deveria fazer. Com o clique da função, foi retornada a mensagem “Hello World”.
Segundo contrato
Vamos agora mudar o código do contrato um pouco:
pragma solidity ^0.4.24;contract owned {
address owner;
constructor() public {
owner = msg.sender;
}
}contract Hello is owned { function HelloWorld () public view returns (string) {
require (msg.sender == owner);
return "Hello World";
}
}
Sintaxe
Desta vez, não adicionei comentários. Veja por si mesmo o que mudou e o que você acha que estas mudanças fazem. Abaixo seguem descrições das novas funcionalidades:
- contract owned {}
Criamos um novo contrato que também faz parte do nosso contrato “Hello”. Solidity permite herança, ou seja, um contrato pode herdar as funcionalidades de outro contrato. Para fazer isso, precisamos de um outro contrato e utilizamos ‘is’ (‘contract Hello is owned’). O contrato “owned” vai ser usado para definir um dono do contrato, que terá acesso à funcionalidades que não são acessíveis aos demais.
- address owner;
Declaramos a variável owner, que é um address, ou endereço da rede Ethereum. O porquê disso ficará mais claro abaixo.
- constructor() public {}
constructor
é uma função que executa uma vez automaticamente com a criação do contrato. É utilizado para definir funcionalidades essenciais do contrato que são necessárias para o funcionamento das demais funções, que precisam ser chamadas.
Em português, é chamado de construtor.
- owner = msg.sender;
Determina que o endereço “dono” do contrato é o que criar o contrato.
msg
em Solidity se refere à transação enviada que causou a execução de algo no contrato. Pode ser usado como:
msg.sender
para determinar o endereço que enviou a transação,
msg.value
para determinar o valor em Ether da transação enviada,
ou também msg.data
, msg.sig
e msg.gas
, que não vamos tratar nesse artigo.
No caso acima, como o usuário que criou o contrato consequentemente “executou” o construtor, seu endereço é, para o construtor, o “msg.sender”. Com a declaração acima, o contrato então guarda que a variável “owner” é igual ao endereço Ethereum do usuário que criou o contrato.
- public view returns (string)
No contrato anterior, utilizamos pure
, porém, neste contrato, usamos msg.sender
na função, que requer leitura do estado da blockchain (para saber o endereço). Com isso, não podemos usar pure
, e então usamos view
, que permite ler, mas não modificar o estado da blockchain.
Mas por quê não podemos permitir que todas as funções possam fazer tudo?
A razão de utilizar tipos diferentes de funções, cada qual com permissões diferentes, é para limitar o uso excessivo de gas, que congestiona a rede, e leva à transações mais caras e lentas. Otimização do uso de gas é muito importante no desenvolvimento de smart contracts. Se uma função não precisa fazer mais do que certas coisas, vale a pena limitá-la.
Mais sobre isso na seção “Smart contracts and gas” neste link.
- require (msg.sender == owner);
require
define requerimentos necessários para executar a função. Neste caso, definimos que só o dono do contrato pode chamar a função “HelloWorld”.
Vamos testar essas mudanças.
Testando o contrato
Clique na lata de lixo para remover o contrato anterior.
Copie e cole o novo código no compilador, e siga os mesmos passos indicados para o primeiro contrato.
Após clicar em “Deploy”, não clique ainda na função. Clique em “Account” e selecione um dos endereços com “100 ether”. Mas lembre-se de qual era o endereço original! No meu caso, o endereço original era ‘0x583…’.
Tente agora chamar a função. Nada vai acontecer. O compilador vai registrar o seguinte:
O que aconteceu é que quem tentou chamar a função foi um usuário que não é o dono do contrato, e portanto não pôde fazê-lo. Tente novamente com outra conta com “100 ether”. Novamente, nada deve acontecer.
Agora, selecione o endereço original e tente novamente. Agora a função vai funcionar, e retornar “Hello World”. A razão é que este endereço publicou o contrato, virando então seu dono, que é o único que pode chamar a função.
Utilizando o ambiente JavaScript VM, estamos apenas simulando como funcionaria o contrato, e com isso, temos acesso à vários endereços com um depósito grande de Ether. Porém, imaginando um smart contract mais complexo, e publicado na rede, podemos entender onde se aplicam as funcionalidades do nosso contrato. require (msg.sender) == owner);
, por exemplo, permite uma certa centralização, e pode ser usada por exemplo para permitir que o dono destrua o contrato, ou seja o único que possa retirar fundos do contrato, no caso de uma ICO. Com isso, nenhum outro endereço, nem mesmo outro endereço da mesma pessoa conseguiria executar tais funções.
Conclusões
Você acaba de construir dois smart contracts funcionais na rede do Ethereum! Minha sugestão é brincar com o código, para ver o que cada modificação faz. Mude o nome do contrato, ou da mensagem retornada pela função para criar um contrato personalizado com seu nome por exemplo. Veja o que a linguagem permite e não permite. Os contratos acima são bem simples, mas a lógica é a mesma para contratos mais complexos.
Up next…
No próximo artigo, vamos criar um contrato mais complexo. Vamos tratar de mais funcionalidades e aprofundar um pouco mais na sintaxe da linguagem Solidity. O contrato do próximo artigo vai ser utilizado no resto da série para construir um dApp.
Como sempre, deixe abaixo seus comentários e sugestões.