Se você não possui experiência alguma com Node.js/JavaScript, recomendo começar por este outro tutorial, que usa ferramentas menos profissionais e mais simples.
Se o Bitcoin foi o grande catalisador para o surgimento das criptomoedas modernas como conhecemos, a Ethereum foi a grande catalisadora para o surgimento de milhares de outras criptomoedas, NFTs e a própria Web 3.0 que vem se falando cada vez mais. Vitalik Buterin, criador da rede Ethereum e seu time, vislumbraram na tecnologia blockchain não apenas a possibilidade de criar um dinheiro virtual P2P, ou seja descentralizado e seguro, como a oportunidade de permitir a criação de “contratos inteligentes” (smart contracts). Assim, qualquer programador ao redor do mundo poderia publicar seus códigos para serem executados na rede Ethereum e com isso iniciar o surgimento de todo um ecossistema ainda maior e mais vibrante (embora ainda de menor capitalização) do que o Bitcoin original.
Em 2014, foi Gavin Wood, um dos fundadores da Ethereum, que teve a ideia de criar a linguagem Solidity, especificamente para a codificação de Smart Contracts na rede Ethereum e demais redes compatíveis (forks, como a BNB Smart Chain que surgiu anos mais tarde). Os smart contracts escritos em Solidity são publicados na rede e toda vez que acionados por clientes, rodam em um interpretador chamado EVM, a Ethereum Virtual Machine, semelhante ao que acontece com a VM do Java, caso você conheça.
A linguagem Solidity é hoje a principal tecnologia relacionada a smart contracts que você deve estudar se deseja aprender a criar aplicações que rodam na blockchain, principalmente Dex, Defi, DAOs e é claro, dapps Web3. Uma linguagem jovem, semelhante ao padrão ECMA para quem está acostumado com programação JS, mas com tipagem estática. Aliás, os contratos em si lembram muito as classes existentes em outras linguagens Orientadas à Objetos.
A minha ideia com este tutorial é lhe dar uma introdução nesta linguagem e tecnologia, lhe despertando então interesse (e capacidade) por conhecer mais sobre este universo que não pára de crescer. Acredito estar desta forma colaborando com o desenvolvimento de novos profissionais neste meio, principalmente aqueles sem grandes conhecimentos do idioma Inglês, onde você encontra muito material acerca de Solidity.
Neste tutorial usaremos o HardHat. Se preferir, pode assistir ao vídeo abaixo ao invés de ler, o conteúdo é o mesmo.
Então vamos lá!
#1 – Ambiente
Primeiro é importante entender que não tem uma única forma de criar os seus smart contracts com Solidity. Em outro tutorial aqui do blog eu mostrei como fazer com a ferramenta Remix, por exemplo. Eu vou mostrar aqui da forma como eu aprendi e que considero mais profissional. Mais tarde, sinta-se livre para abandonar o que ensinarei e seguir o seu próprio caminho.
Você vai precisar primeiramente do Node.js, pois ele é a base para outras ferramentas que vamos usar. Então vá até o site oficial e baixe a versão LTS para sua máquina, instalando-a até o final (marcando tudo que ela perguntar durante o processo). Também vou usar aqui o editor de código Visual Studio Code, que é gratuito e pode ser baixado no site oficial.
As instalações são bem diretas, mas se precisar de ajuda, o vídeo abaixo pode te ajudar.
Um dos motivos de eu recomendar o Visual Studio Code ao invés de outros editores é que além dele ser gratuito, leve e multiplataforma, ele tem a possibilidade de instalarmos extensões para diferentes linguagens, como no caso da extensão Solidity da empresa Nomic Foundation, que eu recomendo que instale na aba de extensões assim que estiver com o VS Code aberto a primeira vez.
Agora que você preparou o seu editor de código e o Node já está devidamente instalado também, é hora de instalarmos o HardHat. O HardHat é um toolkit, uma suíte de ferramentas para ajudar na criação de smart contracts e que vai acelerar bastante a nossa produtividade nesse tipo de desenvolvimento. Ele é desenvolvido em Node.js e por isso que precisamos ter o Node instalado antes dele.
Para instalar o HardHat vamos usar o NPM, o gerenciador de pacotes do Node. Primeiro crie uma pasta para o seu projeto, vou chamar de hello-solidity-hardhat e depois inicialize ela com o NPM e instale o HardHat, como abaixo.
1 2 3 4 |
npm init -y npm install -D hardhat |
Este comando vai baixar e instalar todos os arquivos do HardHat no seu projeto, o que pode demorar um bocado, então não se assuste.
Quando terminar, você poderá criar o seu primeiro projeto HardHat. Para isso, rode o comando abaixo para inicializar o configurador de projeto HardHat.
1 2 3 |
npx hardhat --init |
Isso irá iniciar um assistente de configuração que irá te fazer algumas perguntas, se ele perguntar sobre HardHat 3 ou 2, vá de 3 que é a versão mais nova e recomendada. O HardHat possui suporte nativo ao TypeScript então é essa linguagem que vamos utilizar, mas quando ele perguntar sobre tipo de projeto, escolha Mocha + EthersJS e vá confirmando as demais perguntas até o final. Como resultado você terá a estrutura do seu projeto criada automaticamente, como abaixo.
Essa estrutura inicial é de um projeto chamado Counter e você encontrará os contratos dele na pasta contracts, sendo os arquivos com extensão “.sol” (de Solidity). Nada importante pra gente, pode ignorar.
#2 – Criando o Hello World
Como vamos criar o nosso contrato do zero e ele vai ser bem simples, recomendo excluir todo o conteúdo da pasta contracts, deixando a mesma vazia, bem como o conteúdo da pasta test, escreveremos nossos próprios testes mais à frente.
Agora, na pasta de contracts, vamos criar o nosso arquivo HelloWorld.sol, que vai ser o nosso arquivo de contrato.
Assim que tiver criado este arquivo em branco, tem duas instruções iniciais que devemos colocar. Uma delas é a licença do seu código-fonte, que você pode incluir usando o atalho spdx e apertando Enter, que vai adicionar a licença MIT. Tendo interesse em mudar a licença para outra à sua escolhe, fique à vontade.
A segunda instrução é a versão do compilador de Solidity que será usada para compilar este contrato. Isso porque assim como funciona com a linguagem Java, C# e outras, nós escrevemos o programa (contrato neste caso) em uma linguagem e ela precisa ser compilada para uma linguagem de máquina que será interpretada pela EVM (Ethereum Virtual Machine) mais tarde. Ao longo do tempo novas versões da linguagem são lançadas com novas funcionalidades e portanto é importante especificar qual a versão que você precisa para o seu projeto.
Para iso, use o atalho pragma apertando Enter, que vai adicionar uma instrução de versão para você, que deve ser preenchida com a versão de Solidity que foi instalada junto do HardHat na sua máquina.
Ao término destas duas instruções, o seu arquivo deve estar assim (minha versão é a 0.8.24. Se você não souber a sua versão, basta digitar qualquer uma que a mensagem de erro vai te informar a versão que você possui ao passar o mouse por cima do erro.
1 2 3 4 |
// SPDX-License-Identifier: MIT pragma solidity 0.8.28; |
Abaixo destas informações todas é que vamos começar a codificar nosso primeiro contrato. Se você já conhece alguma linguagem orientada à objetos antes, vai se sentir em casa pois escrever um contrato é muito semelhante a escrever classes. Para começar, você define o seu contrato usando a palavra reservada contract e definindo o seu escopo entre chaves.
1 2 3 4 5 |
contract HelloWorld { } |
Dentro do contrato você pode colocar suas propriedades e funções, assim como faria com classes e começaremos declarando uma propriedade chamada message, apenas para guardar o conteúdo de uma mensagem.
1 2 3 4 5 |
contract HelloWorld { string public message = "Hello World!"; } |
O tipo da variável definimos como string (texto), o modificador de acesso colocamos como public (o que indica que pode ser acessado de fora do contrato), o nome da variável colocamos message (mensagem em Inglês) e atribuímos o valor entre aspas Hello World para ela, da mesma forma que faríamos em JavaScript e tantas outras linguagens de programação.
Outros tipos válidos para variáveis, dentre muitos:
- bool: true ou false;
- int: números inteiros (com variações em bits como int8, int256, etc);
- uint: inteiros positivos (sem sinal, unsigned, com variações em bits como uint8, uint256, etc);
- address: endereços na rede;
Da mesma forma, o processo de criação de uma função também é bem simples e abaixo segue um exemplo de função helloWorld.
1 2 3 4 5 6 7 8 9 |
contract HelloWorld { string public message = "Hello World!"; function helloWorld() public view returns (string memory) { return message; } } |
Neste exemplo usamos a palavra reservada function para dizer que vamos escrever uma função, damos o nome à mesma (camel case) e abrimos parênteses para definir os parâmetros, que optei aqui por não ter nenhum. Na sequência definimos o modificador de acesso (public, indicando que ela pode ser chamada fora deste contrato), o tipo de função (usei view pois vamos precisar visualizar uma informação do contrato) e a palavra reservada returns para informar o que essa informação retorna, neste caso uma única variável string memory (que explicarei mais tarde o porque do memory).
Talvez o ponto mais diferentão aqui seja a palavra reservada view, que indica que esta função vai ler o “estado” do contrato, ou seja, as suas propriedades/variáveis internas. Outra opção seria a palavra reservada pure, que indica que esta é uma função pura, ou seja, sem estado, sem usar variáveis externas. É importante ressaltar desde já que o uso de variáveis de classe ou de função impactam diretamente no custo das chamadas à este contrato, o que exploraremos futuramente aqui no blog. E por último também temos as funções payable, que permitem que alguém envie fundos (pague) para usar a função.
Além disso, outro ponto que é bacana reforçar é quanto ao modificador de acesso, public, que se não for informado vai ser private por padrão, ou seja, sem acesso externo.
O conteúdo da função em si dispensa explicações e agora que temos nosso HelloWorld codificado, é hora de testarmos ele.
#3 – Testando o Hello World
Testes são uma parte crucial do desenvolvimento de qualquer software mas Solidity coloca isso em outro patamar. Isso porque como seu contrato ficará visível na blockchain e na maioria das vezes ele envolverá dinheiro, é crucial que ele esteja em pleno funcionamento e que seja seguro. Não apenas isso, as transações na blockchain são imutáveis, então não temos margem para corrigir pequenas coisinhas depois, como é comum no mundo web.
Dito isso, vá até a sua pasta de test e crie um arquivo HelloWorld.test.ts nela, embora este nome seja apenas uma recomendação, pode ser o nome que quiser.
Dentro deste arquivo vamos escrever os testes unitários do nosso contrato, para isso o HardHat vai nos fornecer o toolkit Mocja que vai deixar a experiência muito próxima da que teríamos usando frameworks de testes JS como Tape e Jest. Caso esteja familiarizado com alguma destas bibliotecas ou outras concorrentes, se sentirá em casa.
Vamos começar nossa suíte de testes importando o que será necessário.
1 2 3 4 5 6 |
import { expect } from "chai"; import { network } from "hardhat"; const { ethers } = await network.connect(); |
A função expect será usada nos testes, enquanto que a network nos traz propriedades e funções da HardHat Network, a VM de testes local do HardHat que simula um nó de blockchain. Com o objeto network, podemos pegar inclusive uma instância do EthersJS, que é uma lib de comunicação com a blockchain, já falei dela aqui em outros tutoriais do blog (como este).
Agora para criar a estrutura padrão dos testes chame a função describe, defina um nome para a suíte de testes e na função de callback que é o segundo parâmetro, iremos colocar nossos futuros testes.
1 2 3 4 5 |
describe("HelloWorld", () => { }); |
Agora para escrever os testes em si, você pode adicionar chamadas à função it quantas vezes quiser, dentro do describe, como abaixo.
1 2 3 4 5 6 |
it("Should Hello the world", async () => { const helloWorld = await ethers.deployContract("HelloWorld"); expect(await helloWorld.helloWorld()).equal("Hello World!"); }); |
Neste teste de exemplo, o único que vamos precisar, nós damos um nome ao teste, seguido da função de teste que nada mais está fazendo do que chamando nossa função de deploy, pegando o contrato instalado por ela, chamando a função do nosso contrato (helloWorld, lembra?) e analisando o retorno dela através da função expect.
A função expect é quem vai dizer se nosso teste passou ou não, baseado no booleano resultante da expressão lógica que passamos à ela, no caso a comparação literal com a frase que deve estar em nosso contrato, na variável message.
Para rodar esta bateria de testes é bem simples, basta abrir o terminal e uma vez dentro da pasta do seu projeto rodar o comando abaixo do HardHat, certificando-se de estar rodando o terminal como administrador.
1 2 3 |
npx hardhat test |
Isso vai fazer com que o HardHat rode os testes, lhe apresentando os resultados como abaixo.
#4 – Testando em Solidity
A partir da versão 3 do HardHat também é possível escrever unit tests em Solidity, a mesma linguagem do smart contract, graças a uma integração com o Forge, mesmo recurso de testes do toolkit concorrente Foundry. Alguns cenários de teste podem ser interessantes testar via Solidity, principalmente aqueles em que o contrato seria chamado por outro contrato.
Para escrever um teste em Solidity, crie um novo arquivo chamado HelloWorld.t.sol na pasta contracts e comece importando o contrato principal e a lib de testes.
1 2 3 4 5 6 7 |
// SPDX-License-Identifier: MIT pragma solidity ^0.8.28; import {HelloWorld} from "./HelloWorld.sol"; import {Test} from "forge-std/Test.sol"; |
Depois, crie um contrato HelloWorldTest que herda da lib Test e possui uma propriedade do tipo do contrato principal.
1 2 3 4 5 |
contract HelloWorldTest is Test { HelloWorld helloWorld; } |
Para o setup dos testes, crie uma função que instancia o contrato.
1 2 3 4 5 |
function setUp() public { helloWorld = new HelloWorld(); } |
E por fim, crie uma função de teste, como abaixo.
1 2 3 4 5 6 7 |
function test_HelloWorld() public view { bytes32 expected = keccak256(bytes("Hello World!")); bytes32 actual = keccak256(bytes(helloWorld.helloWorld())); require(actual == expected, "Should be Hello World!"); } |
Note que essa função faz exatamente o mesmo teste que tínhamos escrito em TypeScript antes, mas como Solidity não lida bem com strings como tipo nativo, acabamos tendo de escrever bem mais instruções para chegar no mesmo resultado.
Para rodar esse teste, use novamente o comando npx hardhat test e todas as suítes executarão, tanto Solidity quanto TypeScript, como abaixo.
E com isso finalizamos o nosso primeiro tutorial de Solidity com HardHat aqui do blog!
Querendo um segundo, um pouco mais avançado? Recomendo esse aqui!
Quer aprender a fazer deploy deste smart contract na blockchain? Leia esse aqui.
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.