17 de setembro, 2021
Leitura de 12min
Hardhat
Estava planejando lançar um tutorial sobre como implantar seu primeiro contrato inteligente NFT, mas enquanto o escrevia, compreendi que mais da metade do conteúdo era sobre como configurar Hardhat para ter o ambiente de desenvolvimento perfeito pronto para seu próximo projeto.
Então, vamos começar por aqui e quando eu lançar o tutorial sobre NFT você pode aproveitar o trabalho que fizemos hoje para estar pronto instantaneamente para começar a desenvolver seu próximo contrato inteligente. Vamos prosseguir?
O que você vai aprender hoje?
- Configurar o Hardhat para compilar, implantar, testar e depurar seu código Solidity
- Escrever um contrato inteligente
- Testar seu contrato inteligente
- Implantar seu contrato inteligente na Rinkeby
- Ter seu contrato inteligente verificado pela Etherscan
Hardhat
Hardhat é a ferramenta principal que usaremos hoje e indispensável se você precisar desenvolver e depurar seu contrato inteligente localmente antes de implantá-lo na rede de teste e depois na rede principal.
O que é Hardhat?
Hardhat é um ambiente de desenvolvimento para compilar, implantar, testar e depurar seu software da Ethereum. Ajuda desenvolvedores a gerenciar e automatizar as tarefas recorrentes inerentes ao processo de criação de contratos inteligentes e dApps, além de introduzir facilmente mais funcionalidades nesse fluxo de trabalho. Isso significa compilar, executar e testar contratos inteligentes em seu núcleo.
Por que usar hardhat?
- Você pode executar a Solidity localmente para implantar, executar, testar e depurar seus contratos sem lidar com ambientes ativos.
- Você obtém rastreamentos de pilha da Solidity, console.log e mensagens de erro explícitas quando as transações falharem. Sim, você ouviu direito, console.log no seu contrato Solidity!
- Toneladas de plugins para adicionar funcionalidades legais e permitir que você integre suas ferramentas existentes. Usaremos alguns deles para sentir essa magia!
- Suporte nativo completo para TypeScript.
Links úteis para saber mais
Crie seu primeiro projeto Hardhat
Crie seu primeiro projeto Hardhat
É hora de abrir seu Terminal, seu IDE de código do Visual Studio, e começar a construir!
Que projeto vamos construir?
Neste tutorial, vamos construir um contrato inteligente “fácil”. Será bem estúpido, mas o nosso principal objetivo aqui é configurar o hardhat e implantar o contrato, não o efeito do contrato.
O que o contrato vai fazer?
- Acompanhar o “propósito” do contrato em uma string que será visível publicamente
- Você pode alterar o propósito somente se pagar mais que o antigo proprietário
- Você não pode alterar o propósito se já for o proprietário
- Você pode retirar seu ETH somente se não for o proprietário do propósito atual
- Emitir um evento de Mudança de Propósito (PurposeChange) quando o propósito for alterado
Crie o projeto, inicie-o e configure o Hardhat
Abra seu terminal e vamos lá. Vamos chamar nosso projeto de propósito mundial!
mkdir world-purpose
cd world-purpose
npm init -y
yarn add --dev hardhat
npx hardhat
Com npx hardhat
o assistente hardhat dará o pontapé inicial para ajudá-lo a configurar seu projeto.
- Escolha
Create a basic sample project
- Escolha o padrão
Hardhat project root
porque neste tutorial estamos configurando apenas o hardhat. - Confirme para adicionar o
.gitignore
- Confirme para instalar o
sample project's dependecies with yarn
, isso vai instalar algumas dependências necessárias para o seu projeto que usaremos ao longo do caminho
Agora sua estrutura de projeto deve ter esses arquivos/pasta
-
contracts
onde todos os seus contratos serão armazenados -
scripts
onde todos seus scripts/tarefas serão armazenados -
test
onde seus testes de contrato inteligente serão armazenados -
hardhat.config.js
onde você configurará seu projeto de hardhat
Comandos e conceitos importantes de hardhat que você deve dominar
- Arquivo de Configuração Hardhat
- Tarefas Hardhat
-
npx hardhat node
- Execute a tarefa do nó para iniciar um servidor JSON-RPC no topo da Rede Hardhat (sua blockchain ethereum local)
-
npx hardhat test
- Para executar testes armazenados na pasta de teste
-
npx hardhat compile
- Para compilar o projeto completo, construindo seu contrato inteligente
-
npx hardhat clean
- Para limpar o cache e excluir contratos inteligentes compilados
-
npx hardhat run —-network <network> script/path
- Para executar um script específico em uma network
específica
Adicionar Suporte TypeScript
npx hardhat node
- Execute a tarefa do nó para iniciar um servidor JSON-RPC no topo da Rede Hardhat (sua blockchain ethereum local)npx hardhat test
- Para executar testes armazenados na pasta de testenpx hardhat compile
- Para compilar o projeto completo, construindo seu contrato inteligentenpx hardhat clean
- Para limpar o cache e excluir contratos inteligentes compilados npx hardhat run —-network <network> script/path
- Para executar um script específico em uma network
específicaComo dissemos, o Hardhat suporta nativamente o typescript e nós vamos utilizá-lo. Usar javascript ou typescript não muda muito, mas essa é minha preferência pessoal porque penso que te permite cometer menos erros e entender melhor como usar bibliotecas externas.
Vamos instalar algumas dependências necessárias
yarn add --dev ts-node typescript
yarn add --dev chai @types/node @types/mocha @types/chai
Altere a extensão do arquivo de configuração do hardhat para .ts
e atualize o conteúdo com isso
import { task } from 'hardhat/config';
import '@nomiclabs/hardhat-waffle';
// Essa é uma tarefa de amostra do Hardhat. Para aprender como criar sua a sua própria acesse
// https://hardhat.org/guides/create-task.html
task('accounts', 'Prints the list of accounts', async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
for (const account of accounts) {
console.log(account.address);
}
});
// Você precisa exportar um objeto para estabelecer sua configuração
// Acesse https://hardhat.org/config/ para saber mais
export default {
solidity: '0.8.4',
};
Agora crie um default tsconfig.json
na raiz do seu projeto com esse conteúdo
{
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"outDir": "dist"
},
"include": ["./scripts", "./test"],
"files": ["./hardhat.config.ts"]
}
Agora atualize também o arquivo de teste e o arquivo de script para o tipo de arquivo typescript .ts
e altere seu código.
import { expect } from 'chai';
import { ethers } from 'hardhat';
describe('Greeter', function () {
it("Should return the new greeting once it's changed", async function () {
const Greeter = await ethers.getContractFactory('Greeter');
const greeter = await Greeter.deploy('Hello, world!');
await greeter.deployed();
expect(await greeter.greet()).to.equal('Hello, world!');
const setGreetingTx = await greeter.setGreeting('Hola, mundo!');
// espera até que a transação seja minada
await setGreetingTx.wait();
expect(await greeter.greet()).to.equal('Hola, mundo!');
});
});
// Exigimos o Ambiente de Tempo de Execução Hardhat Runtime Environment explicitamente aqui. Isso é opcional
// porém útil para executar o script de forma independente através do `node <script>`.
//
// Ao executar o script com `npx hardhat run <script>` você encontrará o Hardhat
// Membros do Ambiente de Tempo de Execução disponíveis no escopo global
import { ethers } from 'hardhat';
async function main() {
// O Hardhat sempre executa a tarefa de compilação ao executar os scripts com sua interface
// linha de comando
//
// Se esse script for executado diretamente usando `node` você pode querer chamar de compilar
// manualmente para garantir que tudo está compilado
// await hre.run('compile');
// Recebemos o contrato para implantar
const Greeter = await ethers.getContractFactory('Greeter');
const greeter = await Greeter.deploy('Hello, Hardhat!');
await greeter.deployed();
console.log('Greeter deployed to:', greeter.address);
}
// Recomendamos este padrão para poder usar async/await em todos os lugares
// e lidar adequadamente com os erros.
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Vamos testar se tudo está funcionando corretamente
npx hardhat test
npx hardhat node
npx hardhat run --network localhost scripts/sample-script.ts
Vamos adicionar alguns plugins do Hardhat e alguns utilitários de código
adicionar solhint, solhint-plugin-prettier e hardhat-solhint plugin
Agora queremos adicionar suporte para solhint
, um utilitário de linting para o código Solidity que irá nos ajudar a seguir regras rígidas enquanto desenvolvemos nosso contrato inteligente. Essas regras são úteis tanto para seguir a melhor prática padrão de estilo de código quanto para aderir às melhores abordagens de segurança.
solhint+solhint prettier
yarn add --dev solhint solhint-plugin-prettier prettier prettier-plugin-solidity
Adicione um arquivo de configuração .solhint.json
em sua pasta raiz
{
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error"
}
}
Adicione um arquivo de configuração .prettierrc
e adicione os estilos que você preferir. Esta é minha escolha pessoal
{
"arrowParens": "always",
"singleQuote": true,
"bracketSpacing": false,
"trailingComma": "all",
"printWidth": 120,
"overrides": [
{
"files": "*.sol",
"options": {
"printWidth": 120,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false,
"explicitTypes": "always"
}
}
]
}
Se você quiser saber mais sobre solhint e seu plugin prettier, confira seu site de documentação
hardhat-solhint
Se você quer saber mais, siga a documentação plugin solhint do Hardhat.
yarn add --dev @nomiclabs/hardhat-solhint
e atualize a configuração hardhat adicionando essa linha à importação
import “@nomiclabs/hardhat-solhint”;
typechain para suporte de contrato digitado
Esta parte é totalmente opcional. Como eu disse, amo typescript e gosto de usá-la em todos os lugares sempre que possível. Adicionar suporte para contratos digitados me permite saber perfeitamente quais funções estão disponíveis, quais parâmetros são necessários de qual tipo e o que estão retornando.
Você poderia seguir totalmente sem ele, mas eu sugiro fortemente que você siga este passo.
Por que typechain?
-
Uso de Configuração Zero - Execute a tarefa de compilação normalmente, e os artefatos do Typechain serão automaticamente gerados em um diretório raiz chamado
typechain
. - Geração incremental - somente arquivos recompilados terão seus types regenerados
-
Sem atrito - o type de retorno de
ethers.getContractFactory
será digitado corretamente - não há necessidade de lançamentos
Se você quiser se aprofundar neste projeto e saber todas as opções de configurações possíveis, você pode seguir estes links:
Vamos fazer isso
yarn add --dev typechain @typechain/hardhat @typechain/ethers-v5
Adicione essas importações ao seu arquivo de configuração Hardhat
import '@typechain/hardhat'
import '@nomiclabs/hardhat-ethers'
import '@nomiclabs/hardhat-waffle'
Adicione "resolveJsonModule": true
ao seu tsconfig.json
Agora, quando você compilar o typechain do seu contrato, gerará o type correspondente dentro da pasta typechain
e você poderá usá-la nos seus testes e aplicativos da web!
Atualize os arquivos de testes para usar os types gerados pelo Typechain
import {ethers, waffle} from 'hardhat';
import chai from 'chai';
import GreeterArtifact from '../artifacts/contracts/Greeter.sol/Greeter.json';
import {Greeter} from '../typechain/Greeter';
const {deployContract} = waffle;
const {expect} = chai;
describe('Greeter', function () {
let greeter: Greeter;
it("Should return the new greeting once it's changed", async function () {
const signers = await ethers.getSigners();
greeter = (await deployContract(signers[0], GreeterArtifact, ['Hello, world!'])) as Greeter;
expect(await greeter.greet()).to.equal('Hello, world!');
const setGreetingTx = await greeter.setGreeting('Hola, mundo!');
// aguarda até que a transação seja minada
await setGreetingTx.wait();
expect(await greeter.greet()).to.equal('Hola, mundo!');
});
});
Atualizar scripts em package.json
Esta parte é totalmente opcional, mas lhe permite executar comandos mais rapidamente e pré-configurá-los. Abra seu package.json
e substitua a seção scripts
com esses comandos:
"clean": "npx hardhat clean",
"chain": "npx hardhat node",
"deploy": "npx hardhat run --network localhost scripts/deploy.ts",
"deploy:rinkeby": "npx hardhat run --network rinkeby scripts/deploy.ts",
"test": "npx hardhat test"
Agora, se você quiser simplesmente executar o nó Hardhat, você pode escrever no seu console yarn chain
e bum! Estamos prontos para ir!
Se você tiver pulado a parte TypeScript e quiser usar apenas Javascript, basta mudar aqueles
.ts
de volta para.js
e tudo funcionará conforme esperado.
Desenvolvendo o contrato inteligente
Certo, agora estamos realmente prontos para ir. Renomeie Greeter.sol
na sua pasta contracts
para WorldPurpose.sol
e vamos começar a construir daqui.
Nosso contrato precisa fazer estas coisas:
- Acompanhar o “propósito” do contrato em uma estrutura
- Você pode alterar o propósito apenas se pagar mais que o proprietário anterior
- Você não pode alterar o propósito se você já é o proprietário
- Você pode retirar seu ETH somente se você não é o proprietário do propósito atual
- Emitir um evento de Mudança de Propósito quando o propósito for alterado
Conceitos que você deve dominar
Código de Contrato Inteligente
Vamos começar criando uma Estrutura para armazenar as informações do Propósito
/// @notice estrutura do Propósito
struct Purpose {
address owner;
string purpose;
uint256 investment;
}
Vamos agora definir algumas variáveis de estado para acompanhar tanto o Propósito atual quanto o investimento do usuário
/// @notice Acompanhar investimento do usuário
mapping(address => uint256) private balances;
/// @notice Acompanhar o propósito mundial atual
Purpose private purpose;
Por último, mas não menos importante, defina um Evento que vai ser emitido quando um novo Propósito Mundial for definido
/// @notice Evento para acompanhar o novo Propósito
event PurposeChange(address indexed owner, string purpose, uint256 investment);
Vamos criar algumas funções utilitárias para obter o purpose
atual e o saldo do usuário que ainda está no contrato. Precisamos fazer isto porque as variáveis balances
e purpose
são private
para que não possam ser acessadas diretamente dos aplicativos externos de contrato/web3. Precisamos expô-los através dessas funções
/**
@notice Recolhimento para o propósito atual
@return currentPurpose O propósito mundial atual ativo
*/
function getCurrentPurpose() public view returns (Purpose memory currentPurpose) {
return purpose;
}
/**
@notice Obtenha o valor total do investimento que você fez. Retorna ambos os investimentos bloqueados e desbloqueados.
@return balance O saldo que você ainda tem no contrato
*/
function getBalance() public view returns (uint256 balance) {
return balances[msg.sender];
}
Agora, vamos criar a função setPurpose
. Ela recebe um parâmetro como entrada: _purpose
. A função deve ser payable
porque queremos aceitar alguns Ethers para definir o propósito (aqueles ethers serão retornáveis pelo proprietário que tiver seu propósito substituído por outra pessoa).
A transação vai revert
se algumas condições não forem atendidas:
- o parâmetro de entrada
_purpose
está vazio - o
msg.value
(quantidade de ether enviado com a transação) está vazio (0 ethers) - o
msg.value
é menor que a do propósito atual - o proprietário do propósito mundial atual tentar substituir seu propósito (
msg.sender
deve ser diferente do proprietário atual)
/**
@notice Modifier para verificar se o proprietário anterior do propósito não é o mesmo que o novo proponente
*/
modifier onlyNewUser() {
// Verifica se o novo dono não é o anterior
require(purpose.owner != msg.sender, "You cannot override your own purpose");
_;
}
/**
@notice Define o novo propósito mundial
@param _purpose O conteúdo do novo propósito
@return newPurpose O novo propósito mundial ativo
*/
function setPurpose(string memory _purpose) public payable onlyNewUser returns (Purpose memory newPurpose) {
// Verifica se o novo proprietário nos enviou fundos suficientes para substituir o propósito anterior
require(msg.value > purpose.investment, "You need to invest more than the previous purpose owner");
// Verifica se o novo propósito está vazio
bytes memory purposeBytes = bytes(_purpose);
require(purposeBytes.length > 0, "You need to set a purpose message");
// Atualiza o propósito com o novo
purpose = Purpose(msg.sender, _purpose, msg.value);
// Atualiza o valor do remetente
balances[msg.sender] += msg.value;
// Emite o evento de MudançaDePropósito
emit PurposeChange(msg.sender, _purpose, msg.value);
//Retorna o novo propósito
return purpose;
}
Se tudo funcionar, definimos o novo propósito, atualizamos o saldo e emitimos o evento.
Agora queremos permitir que os usuários retirem seus investimentos de propósitos anteriores. Note, por favor, que apenas o investimento do propósito atual está “bloqueado”. Ele será desbloqueado somente quando uma nova pessoa definir o novo propósito.
/**
@notice Retira os fundos do propósito antigo. Se você tem um propósito ativo, esses fundos são “bloqueados”
*/
function withdraw() public {
// Obtém o saldo do usuário
uint256 withdrawable = balances[msg.sender];
// Agora precisamos verificar quanto o usuário pode retirar
address currentOwner = purpose.owner;
if (currentOwner == msg.sender) {
withdrawable -= purpose.investment;
}
// Verifica se o usuário tem saldo suficiente para retirar
require(withdrawable > 0, "You don't have enough withdrawable balance");
// Atualiza o saldo
balances[msg.sender] -= withdrawable;
// Transfere o saldo
(bool sent, ) = msg.sender.call{value: withdrawable}("");
require(sent, "Failed to send user balance back to the user");
}
Implante-o localmente apenas para testar que tudo funciona conforme esperado. Você deve ver algo assim:
Implementação do contrato na blockchain local bem-sucedida
Adicionar Teste local
Agora eu não vou entrar em detalhes sobre o código dentro do arquivo de teste, mas vou explicar o conceito por trás dele. Você deve sempre criar casos de teste para seu contrato. É um jeito rápido de entender se a lógica do contrato está funcionando conforme o esperado e permite evitar a implantação de algo que não está funcionando.
Meu fluxo de trabalho atual é assim: pego todas as funções e testo que elas estão fazendo o que eu espero que façam, tanto em casos de sucesso quanto em casos de reversão. Nada mais, nada menos.
Ao escrever testes para contratos Solidity, você usará a Waffle. Vá até o site deles para ter uma visão geral da biblioteca, é muito bem feita e oferece muitos utilitários.
Agora, exclua o arquivo que você tem da sua pasta test
, crie um novo chamado worldpurpose.ts
e substitua o conteúdo com isso
import {ethers, waffle} from 'hardhat';
import chai from 'chai';
import WorldPurposeArtifact from '../artifacts/contracts/WorldPurpose.sol/WorldPurpose.json';
import {WorldPurpose} from '../typechain/WorldPurpose';
import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers';
const {deployContract} = waffle;
const {expect} = chai;
describe('WorldPurpose Contract', () => {
let owner: SignerWithAddress;
let addr1: SignerWithAddress;
let addr2: SignerWithAddress;
let addrs: SignerWithAddress[];
let worldPurpose: WorldPurpose;
beforeEach(async () => {
// eslint-disable-next-line no-unused-vars
[owner, addr1, addr2, ...addrs] = await ethers.getSigners();
worldPurpose = (await deployContract(owner, WorldPurposeArtifact)) as WorldPurpose;
});
describe('Test setPurpose', () => {
it("set purpose success when there's no purpose", async () => {
const purposeTitle = 'Reduce the ETH fee cost in the next 3 months';
const purposeInvestment = ethers.utils.parseEther('0.1');
await worldPurpose.connect(addr1).setPurpose(purposeTitle, {
value: purposeInvestment,
});
// Verifica que o propósito foi definido
const currentPurpose = await worldPurpose.getCurrentPurpose();
expect(currentPurpose.purpose).to.equal(purposeTitle);
expect(currentPurpose.owner).to.equal(addr1.address);
expect(currentPurpose.investment).to.equal(purposeInvestment);
// Verifica que o saldo foi atualizado
const balance = await worldPurpose.connect(addr1).getBalance();
expect(balance).to.equal(purposeInvestment);
});
it('override the prev purpose', async () => {
await worldPurpose.connect(addr2).setPurpose("I'm the old world purpose", {
value: ethers.utils.parseEther('0.1'),
});
const purposeTitle = "I'm the new world purpose!";
const purposeInvestment = ethers.utils.parseEther('0.11');
await worldPurpose.connect(addr1).setPurpose(purposeTitle, {
value: purposeInvestment,
});
// Verifica se o propósito foi definido
const currentPurpose = await worldPurpose.getCurrentPurpose();
expect(currentPurpose.purpose).to.equal(purposeTitle);
expect(currentPurpose.owner).to.equal(addr1.address);
expect(currentPurpose.investment).to.equal(purposeInvestment);
// Verifica se o saldo foi atualizado
const balance = await worldPurpose.connect(addr1).getBalance();
expect(balance).to.equal(purposeInvestment);
});
it('Check PurposeChange event is emitted ', async () => {
const purposeTitle = "I'm the new world purpose!";
const purposeInvestment = ethers.utils.parseEther('0.11');
const tx = await worldPurpose.connect(addr1).setPurpose(purposeTitle, {
value: purposeInvestment,
});
await expect(tx).to.emit(worldPurpose, 'PurposeChange').withArgs(addr1.address, purposeTitle, purposeInvestment);
});
it("You can't override your own purpose", async () => {
await worldPurpose.connect(addr1).setPurpose("I'm the new world purpose!", {
value: ethers.utils.parseEther('0.10'),
});
const tx = worldPurpose.connect(addr1).setPurpose('I want to override the my own purpose!', {
value: ethers.utils.parseEther('0.11'),
});
await expect(tx).to.be.revertedWith('You cannot override your own purpose');
});
it('Investment needs to be greater than 0', async () => {
const tx = worldPurpose.connect(addr1).setPurpose('I would like to pay nothing to set a purpose, can I?', {
value: ethers.utils.parseEther('0'),
});
await expect(tx).to.be.revertedWith('You need to invest more than the previous purpose owner');
});
it('Purpose message must be not empty', async () => {
const tx = worldPurpose.connect(addr1).setPurpose('', {
value: ethers.utils.parseEther('0.1'),
});
await expect(tx).to.be.revertedWith('You need to set a purpose message');
});
it('New purpose investment needs to be greater than the previous one', async () => {
await worldPurpose.connect(addr1).setPurpose("I'm the old purpose!", {
value: ethers.utils.parseEther('0.10'),
});
const tx = worldPurpose
.connect(addr2)
.setPurpose('I want to pay less than the previous owner of the purpose, can I?', {
value: ethers.utils.parseEther('0.01'),
});
await expect(tx).to.be.revertedWith('You need to invest more than the previous purpose owner');
});
});
describe('Test withdraw', () => {
it('Withdraw your previous investment', async () => {
const firstInvestment = ethers.utils.parseEther('0.10');
await worldPurpose.connect(addr1).setPurpose('First purpose', {
value: ethers.utils.parseEther('0.10'),
});
await worldPurpose.connect(addr2).setPurpose('Second purpose', {
value: ethers.utils.parseEther('0.11'),
});
const tx = await worldPurpose.connect(addr1).withdraw();
// Verifica que o meu saldo atual no contrato é 0
const balance = await worldPurpose.connect(addr1).getBalance();
expect(balance).to.equal(0);
// Verifica que eu coloquei de volta na minha carteira toda a importação
await expect(tx).to.changeEtherBalance(addr1, firstInvestment);
});
it('Withdraw only the unlocked investment', async () => {
const firstInvestment = ethers.utils.parseEther('0.10');
await worldPurpose.connect(addr1).setPurpose('First purpose', {
value: ethers.utils.parseEther('0.10'),
});
await worldPurpose.connect(addr2).setPurpose('Second purpose', {
value: ethers.utils.parseEther('0.11'),
});
const secondInvestment = ethers.utils.parseEther('0.2');
await worldPurpose.connect(addr1).setPurpose('Third purpose from the first addr1', {
value: secondInvestment,
});
const tx = await worldPurpose.connect(addr1).withdraw();
// Neste caso, o usuário pode retirar apenas o primeiro investimento
// O segundo ainda está “bloqueado” porque ele é o dono do propósito atual
// Verifica que meu saldo atual no contrato é 0
const balance = await worldPurpose.connect(addr1).getBalance();
expect(balance).to.equal(secondInvestment);
// Verifica que eu coloquei de volta na minha carteira toda a importação
await expect(tx).to.changeEtherBalance(addr1, firstInvestment);
});
it('You cant withdraw when your balance is empty', async () => {
const tx = worldPurpose.connect(addr1).withdraw();
await expect(tx).to.be.revertedWith("You don't have enough withdrawable balance");
});
it('Withdraw only the unlocked investment', async () => {
await worldPurpose.connect(addr1).setPurpose('First purpose', {
value: ethers.utils.parseEther('0.10'),
});
const tx = worldPurpose.connect(addr1).withdraw();
// Seus fundos ainda estão “bloqueados” porque você é o proprietário do propósito atual
await expect(tx).to.be.revertedWith("You don't have enough withdrawable balance");
});
});
});
Execute seus testes com yarn test
e veja se tudo passou como esperado
Execução dos testes
Implante seu contrato na rede de teste
===== INÍCIO DA ISENÇÃO DE RESPONSABILIDADE =====
NÃO USE SUA CARTEIRA PRINCIPAL PRIVADA PARA FINS DE TESTES
USE UMA CARTEIRA DE QUEIMA OU CRIE UMA CARTEIRA NOVA
===== FIM DA ISENÇÃO DE RESPONSABILIDADE =====
===== INÍCIO DA ISENÇÃO DE RESPONSABILIDADE =====
NÃO USE SUA CARTEIRA PRINCIPAL PRIVADA PARA FINS DE TESTES
USE UMA CARTEIRA DE QUEIMA OU CRIE UMA CARTEIRA NOVA
===== FIM DA ISENÇÃO DE RESPONSABILIDADE =====
Nós já vimos como implantar o contrato em nossa chain de hardhat local, mas agora queremos fazê-lo para a rede de teste Rinkeby (o procedimento é o mesmo para a rede principal, mas não é o alcance do tutorial).
Para implantar na chain local, só precisamos digitar no console este comando
npx hardhat run --network localhost scripts/deploy.ts
Para implantar seus contratos em outra rede, precisamos apenas alterar o valor do parâmetro de --network
, porém, antes de fazê-lo, precisamos fazer algumas etapas preparatórias.
Obtenha um pouco de ETH de uma rede de teste torneira (Faucet)
Implantar um contrato custa gas
, então precisamos de um pouco de uma torneira (Faucet). Escolha uma das redes de teste e vá até a torneira para obter fundos.
No nosso caso, decidimos usar a Rinkeby, então vá e obtenha alguns fundos por lá.
Escolha um Provedor de Ethereum
Para implantar nosso contrato, precisamos de um jeito de interagir diretamente com a rede da Ethereum. Hoje em dia, temos duas opções possíveis:
No nosso caso, vamos usar a Alchemy, então vá lá, crie uma conta, crie um novo aplicativo e pegue a Chave API da Rinkeby.
Atualize o arquivo de configuração Hardhat
Instale as dependências dotenv
, essa biblioteca de NodeJs nos permite criar um arquivo de ambiente .env
para armazenar nossas variáveis sem expô-las ao código fonte.
yarn add --dev dotenv
Agora crie, na raiz do seu projeto, um arquivo .env
e cole toda a informação que você coletou
RENKEBY_URL=https://eth-rinkeby.alchemyapi.io/v2/<YOUR_ALCHEMY_APP_ID>
PRIVATE_KEY=<YOUR_BURNER_WALLET_PRIVATE_KEY>
Nunca é suficiente enfatizar. Não use o caso da sua carteira privada principal para fins de teste. Crie uma carteira separada ou use uma carteira de queima para fazer esse tipo de teste!
A última etapa é atualizar o arquivo hardhat.config.ts
para adicionar dotenv
e atualizar as informações da rinkeby.
No topo do arquivo adicione require("dotenv").config();
; e agora atualize a configuração assim
const config: HardhatUserConfig = {
solidity: '0.8.4',
networks: {
rinkeby: {
url: process.env.RENKEBY_URL || '',
accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
},
},
};
Estamos preparados? Vamos implantá-lo!
npx hardhat run --network rinkeby scripts/deploy.ts
Se tudo correr bem, você deverá ver algo assim no seu console
Se você copiar aquele endereço de contrato e colar no Etherscan da Rinkeby, poderá vê-lo ao vivo! O meu está localizado aqui:
0xAf688A3405531e0FC274F6453cD8d3f3dB07D706
Tenha seu contrato verificado pela Etherscan
A verificação do código-fonte fornece transparência para os usuários interagirem com contratos inteligentes. Ao fazer o upload do código-fonte, a Etherscan combinará o código compilado com o da blockchain. Assim como os contratos, um “contrato inteligente” deve fornecer aos usuários finais mais informações sobre o que eles estão “assinando digitalmente” e dar aos usuários uma oportunidade de auditar o código para verificar independentemente que ele realmente faz o que deveria fazer.
A verificação do código-fonte fornece transparência para os usuários interagirem com contratos inteligentes. Ao fazer o upload do código-fonte, a Etherscan combinará o código compilado com o da blockchain. Assim como os contratos, um “contrato inteligente” deve fornecer aos usuários finais mais informações sobre o que eles estão “assinando digitalmente” e dar aos usuários uma oportunidade de auditar o código para verificar independentemente que ele realmente faz o que deveria fazer.
Então, vamos fazê-lo. É realmente um passo fácil porque o Hardhat fornece um Plugin específico para fazer isso: hardhat-etherscan.
Vamos instalar yarn add --dev @nomiclabs/hardhat-etherscan
e incluí-lo no seu hardhat.config.ts
adicionando import "@nomiclabs/hardhat-etherscan"
; ao final das suas importações.
O próximo passo é registrar uma conta na Etherscan e gerar uma Chave API.
Uma vez que você a tiver, precisamos atualizar nosso arquivo .env
adicionando essa linha de código
ETHERSCAN_API_KEY=<PAST_YOU_API_KEY>
e atualizar o hardhat.config.ts
adicionando a configuração da Etherscan necessária pelo plugin.
A última coisa a fazer é executar a tarefa verify
que foi adicionada pelo plugin:
npx hardhat verify --network rinkeby <YOUR_CONTRACT_ADDRESS>
O plugin tem muitas configurações e opções diferentes, então confira a documentação se você quiser saber mais.
Se tudo sair bem, você deverá ver algo assim no seu console
Contrato verificado pela Etherscan
Se você passar pela página de contrato da Etherscan novamente, você deverá ver uma marca de seleção verde no topo da seção de Contrato. Muito bom!
Conclusão e os próximos passos
Se você quiser usar este solidity-template
para dar o pontapé no seu próximo projeto de contrato inteligente, basta acessar o repositório GitHub https://github.com/StErMi/solidity-template e ir no botão verde “Use este modelo” e bum, você está pronto para ir!
Esse é apenas o começo, estou planejando adicionar mais recursos ao longo do tempo como por exemplo:
- Suporte ao ESLint para a biblioteca de teste do typescript
- Suporte ao implementar-hardhat e Plugin de Hardhat para gerenciar melhor a implementação
- Adicionar suporte ao plugin de cobertura-solidity
- Adicionar suporte ao plugin de hardhat-gas-reporter
- TBD
Você tem algum feedback ou quer contribuir com o modelo da Solidity? Basta criar uma solicitação Pull Request na página do GitHub e vamos discutir isso!
Gostou desse conteúdo? Me siga para mais!
- GitHub:https://github.com/StErMi
- Twitter: https://twitter.com/StErMi
- Medium: https://medium.com/@stermi
- Dev.to: https://dev.to/stermi
Esse artigo foi escrito por StErMi e traduzido por Isabela Curado Nehme. Seu original pode ser lido aqui.
Oldest comments (0)