O que estamos construindo
Se você sempre quis criar seu próprio tipo de moeda ou token que seria usado como meio de troca de valor ou negociação, você está no lugar certo hoje. Estaremos construindo e implantando um token ERC-20 na rede de testes Kovan, da blockchain Optimism.
Vamos orientá-lo em todo o processo de codificação, teste, configuração de sua rede, adição de tokens por meio de uma torneira (faucet), implantação do contrato na rede de teste da blockchain Optimism e interação com o contrato.
Espere, o que é Optimism?
Optimism é uma Blockchain EVM de camada 2 rápida e transacionalmente barata que permite aos desenvolvedores construir contratos no topo de sua cadeia com Solidity. Se você quiser ler mais sobre o que é a blockchain Optimism, há um ótimo artigo no CoinMarketCap explicando suas vantagens e diferenças.
Requisitos
Antes de começarmos, aqui está uma lista de coisas que você precisará em seu computador:
- NVM ou Node v16.15.1+
- Yarn
- VSCode
- Carteira Metamask
Configuração do Projeto
Para começar, vamos aproveitar o Hardhat para desenvolver, compilar, testar e implantar nosso contrato localmente. Se você não estiver familiarizado, este será um bom guia para começar.
Para começar, vamos desenvolver um novo projeto com TypeScript.
mkdir optimism-erc20;
cd optimism-erc20;
npx hardhat;
# PROMPT 1 - Choose base
888 888 888 888 888
888 888 888 888 888
888 888 888 888 888
8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
888 888 "88b 888P" d88" 888 888 "88b "88b 888
888 888 .d888888 888 888 888 888 888 .d888888 888
888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
👷 Welcome to Hardhat v2.9.9 👷
? What do you want to do? ...
Create a basic sample project
Create an advanced sample project
❯ Create an advanced sample project that uses TypeScript
Create an empty hardhat.config.js
Quit
# PROMPT 2 - Confirm project root
✔ What do you want to do? · Create an advanced sample project that uses TypeScript
? Hardhat project root: > /path/to/optimism-erc20
# PROMPT 3 - Git Ignore
✔ What do you want to do? · Create an advanced sample project that uses TypeScript
✔ Hardhat project root: · /path/to/optimism-erc20
? Do you want to add a .gitignore? (Y/n) > y
# PROMPT 4 - Install Dependencies
✔ What do you want to do? · Create an advanced sample project that uses TypeScript
✔ Hardhat project root: · /Users/manny/Documents/github/optimism-erc20
✔ Do you want to add a .gitignore? (Y/n) · y
? Do you want to install this sample project's dependencies with npm (hardhat @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers @nomiclabs/hardhat-etherscan dotenv eslint eslint-config-prettier eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-prettier eslint-plugin-promise hardhat-gas-reporter prettier prettier-plugin-solidity solhint solidity-coverage @typechain/ethers-v5 @typechain/hardhat @typescript-eslint/eslint-plugin @typescript-eslint/parser @types/chai @types/node @types/mocha ts-node typechain typescript)? (Y/n) > y
Se abrirmos nosso projeto no VSCode, veremos uma grande lista de arquivos gerados para nós, mas não se preocupe, não precisaremos usar todos eles.
Criando Nosso Contrato
Agora que temos todos os nossos arquivos, vamos começar modificando nosso contrato Solidity atual (Greeter.sol
) na pasta contracts
, renomeando-o para Buidl.sol
e fazendo as seguintes modificações.
Arquivo: ./contracts/Buidl.sol
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract BuidlToken {
}
Com essa base instalada, vamos aproveitar algum código existente para criar facilmente nosso próprio token ERC-20 com base em um padrão amplamente adotado. Para fazer isso, usaremos o pacote npm de contratos do OpenZeppelin.
# /optimism-erc20
yarn add @openzeppelin/contracts;
Com este pacote npm recém-instalado, podemos adicioná-lo ao nosso contrato, estender o contrato e passar os atributos-padrão necessários para definir o nome e seu símbolo.
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract BuidlToken is ERC20 {
constructor(uint256 initialSupply) ERC20("BuidlToken", "BDL") {
_mint(msg.sender, initialSupply);
}
}
É isso! Temos nosso token ERC-20. A razão pela qual nosso código é tão curto é porque estamos usando todas as funções definidas pelo arquivo ERC20.sol do OpenZeppelin. O arquivo fornecido pelo OpenZeppelin é um conjunto herdado de funções do contrato ERC-20, sobre o qual você pode obter mais detalhes aqui. No VSCode, você pode realmente ver as funções usando Command (CTRL no PC) + Clique na parte ERC20.sol da importação.
Isso deve abrir o arquivo localizado na pasta node_modules e mostrar o conteúdo de todo o arquivo que estamos estendendo.
Há uma coisa que precisamos levar em consideração com esse token ERC-20 padrão: o fornecimento só pode ser definido uma vez, no momento da implantação do contrato. Vamos fazer uma pequena modificação em nosso contrato para que apenas o dono (a carteira que implantou o contrato) possa modificar o fornecimento após a implantação do contrato.
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract BuidlToken is ERC20, Ownable {
constructor(uint256 initialSupply) ERC20("BuidlToken", "BDL") {
_mint(msg.sender, initialSupply);
}
/**
* Proprietário do contrato - Aumente o fornecimento total e adicione-o à carteira
*/
function mint(uint256 amount) external onlyOwner {
_mint(msg.sender, amount);
}
/**
* Usuário - Diminua seu fornecimento total
*/
function burn(uint256 amount) public {
_burn(msg.sender, amount);
}
}
Implantando nosso contrato localmente
A próxima etapa é implantar nosso contrato em nosso ambiente de desenvolvimento local para garantir que as coisas sejam compiladas corretamente. Para fazer isso, precisaremos modificar o deploy.ts.
Arquivo: ./scripts/deploy.ts
// Requisitamos explicitamente o Hardhat Runtime Environment aqui. Isso é opcional, mas útil
// para executar o script de maneira autônoma por meio do `node <script>`.
//
// Ao executar o script com `npx hardhat run <script>`, você encontrará os membros do
// Hardhat Runtime Environment disponíveis no escopo global.
import { ethers } from "hardhat";
async function main() {
// O Hardhat sempre executa a tarefa de compilação ao executar scripts com sua interface
// de linha de comando.
//
// Se este script for executado diretamente usando o `node`, você pode querer chamar a
// compilação manualmente para garantir que tudo seja compilado
// aguarde hre.run('compile');
// Recebemos o contrato para implantar
const Contract = await ethers.getContractFactory("BuidlToken");
const contract = await Contract.deploy(1000); // Crie 1000 tokens iniciais
await contract.deployed();
console.log("Contrato implantado para:", contract.address);
}
// Recomendamos esse padrão para poder usar async/await em todos os lugares e lidar
// adequadamente com os erros.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Depois de modificar o arquivo, precisaremos executar dois terminais para implantar as coisas. A primeira é executar a máquina virtual Ethereum de desenvolvimento local e a segunda é implantá-la nesse ambiente.
Terminal 1:
# /optimism-erc20
./node_modules/.bin/hardhat node;
# Saída esperada
# Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
#
# Accounts
# ========
#
# WARNING: These accounts, and their private keys, are publicly known.
# Any funds sent to them on Mainnet or any other live network WILL BE LOST.
#
# Account #0: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (10000 ETH)
# Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
#
# Account #1: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 (10000 ETH)
# Private Key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
#
# ...
#
# WARNING: These accounts, and their private keys, are publicly known.
# Any funds sent to them on Mainnet or any other live network WILL BE LOST.
Terminal 2:
# /optimism-erc20
./node_modules/.bin/hardhat run scripts/deploy.ts --network localhost;
# Saída esperada
# Generating typings for: 5 artifacts in dir: typechain for target: ethers-v5
# Successfully generated 11 typings!
# Compiled 5 Solidity files successfully
# Contract deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Testando nosso contrato
Quando você tiver seu código de contrato, é um bom hábito criar uma série de testes para garantir que as coisas estejam funcionando conforme o esperado. Em uma breve visão geral de como estruturo os testes, a diretriz que tento buscar é a seguinte:
1 - Imports
2 - Configurations
3 - Helpers (Optional)
4 - Tests
A - Function One Expected Failure(s)
B - Function One Expected Success(es)
C - Function Two ...
D - Scenario(s)
Levando este guia em consideração, as principais coisas que vamos testar são:
Cunhagem - Aumentando o fornecimento
Queima - Diminuindo o fornecimento
Aprovação - Dando permissão a outro usuário para gastar tokens em nosso nome
Transferência - Movendo tokens de um usuário para outro
Cenário Tokens insuficientes - Um usuário tem permissão para gastar tokens de outro usuário, mas os tokens já foram gastos ou queimados
OBSERVAÇÃO: Abaixo temos um monte de código para ler, mas ele fornece algumas informações sobre como os testes são escritos. Se você quiser pular esta parte, basta rolar por ela, mas se quiser testar seu código, recomendo apenas copiá-la por enquanto.
Arquivo: ./test/index.ts
// Importações
// ========================================================
import { expect } from "chai";
import { ethers } from "hardhat";
import ContractABI from "../artifacts/contracts/Buidl.sol/BuidlToken.json";
// Configurações
// ========================================================
/**
* Nome do contrato
*/
const CONTRACT_NAME = "BuidlToken";
/**
* @dev Account #0: Primeiro endereço de carteira fornecido quando executamos o nó do Hardhat
*/
const OWNER_ADDRESS = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
/**
* @dev Account #1: Segundo endereço de carteira fornecido quando executamos o nó do Hardhat
*/
const RANDOM_ADDRESS = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
/**
* @dev Account #2: Terceiro endereço de carteira fornecido quando executamos o nó do Hardhat
*/
const ANOTHER_ADDRESS = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC";
// Testes
// ========================================================
describe(`${CONTRACT_NAME} - Contract Tests`, async () => {
/**
* cunhagem
*/
it("mint - deve FALHAR ao cunhar -1", async () => {
// Configuração
const initialSupply = 1000;
const amountToMint = -1;
// - Implantar contrato
const Contract = await ethers.getContractFactory(`$CONTRACT_NAME}`);
const deployedContract = await Contract.deploy(initialSupply);
await deployedContract.deployed();
// - Carteira de configuração que irá interagir com o contrato como signatário
const provider = new ethers.providers.JsonRpcProvider();
const signer = provider.getSigner(OWNER_ADDRESS);
// - Obtenha o contrato vinculado ao signatário
const contract = new ethers.Contract(
(await deployedContract.deployed()).address,
ContractABI.abi,
signer
);
try {
// Init
await contract.mint(amountToMint);
} catch (error: any) {
// Expectativas
expect(error?.reason).to.be.eq("value out-of-bounds");
}
});
/**
* cunhagem
*/
it("mint - deve PASSAR ao cunhar 10", async () => {
// Configuração
const initialSupply = 1000;
const amountToMint = 10;
const walletOwner = OWNER_ADDRESS;
// - Implantar contrato
const Contract = await ethers.getContractFactory(`${CONTRACT_NAME}`);
const deployedContract = await Contract.deploy(initialSupply);
await deployedContract.deployed();
// - Carteira de configuração que irá interagir com o contrato como signatário
const provider = new ethers.providers.JsonRpcProvider();
const signer = provider.getSigner(walletOwner);
// - Obtenha o contrato vinculado ao signatário
const contract = new ethers.Contract(
(await deployedContract.deployed()).address,
ContractABI.abi,
signer
);
// Init
await contract.mint(amountToMint);
const result = await contract.totalSupply();
// Expectativas
expect(result).to.be.eq(initialSupply + amountToMint);
});
/**
* queima
*/
it("burn - deve FALHAR ao queimar -1", async () => {
// Configuração
const initialSupply = 1000;
const amountToBurn = -1;
const walletOwner = OWNER_ADDRESS;
// - Implantar contrato
const Contract = await ethers.getContractFactory(`${CONTRACT_NAME}`);
const deployedContract = await Contract.deploy(initialSupply);
await deployedContract.deployed();
// - Carteira de configuração que irá interagir com o contrato como signatário
const provider = new ethers.providers.JsonRpcProvider();
const signer = provider.getSigner(walletOwner);
// - Obtenha o contrato vinculado ao signatário
const contract = new ethers.Contract(
(await deployedContract.deployed()).address,
ContractABI.abi,
signer
);
try {
// Init
await contract.burn(amountToBurn);
} catch (error: any) {
// Expectativas
expect(error?.reason).to.be.eq("valor fora dos limites");
}
});
/**
* queima
*/
it("burn - deve FALHAR ao queimar tokens que não são de propriedade", async () => {
// Configuração
const initialSupply = 1000;
const amountToBurn = 100;
const walletOwner = RANDOM_ADDRESS;
// - Implantar contrato
const Contract = await ethers.getContractFactory(`${CONTRACT_NAME}`);
const deployedContract = await Contract.deploy(initialSupply);
await deployedContract.deployed();
// - Carteira de configuração que irá interagir com o contrato como signatário
const provider = new ethers.providers.JsonRpcProvider();
const signer = provider.getSigner(walletOwner);
// - Obtenha o contrato vinculado ao signatário
const contract = new ethers.Contract(
(await deployedContract.deployed()).address,
ContractABI.abi,
signer
);
try {
// Init
await contract.burn(amountToBurn);
} catch (error: any) {
// Expectativas
expect(error?.reason).to.be.eq(
"Erro: Exceção da VM durante o processamento da transação: revertido com string de razão 'ERC20: o valor da queima excede o saldo'"
);
}
});
/**
* queima
*/
it("burn - deve PASSAR ao queimar tokens que existem/possuem", async () => {
// Configuração
const initialSupply = 1000;
const amountToBurn = 100;
const walletOwner = OWNER_ADDRESS;
// - Implantar contrato
const Contract = await ethers.getContractFactory(`${CONTRACT_NAME}`);
const deployedContract = await Contract.deploy(initialSupply);
await deployedContract.deployed();
// - Carteira de configuração que irá interagir com o contrato como signatário
const provider = new ethers.providers.JsonRpcProvider();
const signer = provider.getSigner(walletOwner);
// - Obtenha o contrato vinculado ao signatário
const contract = new ethers.Contract(
(await deployedContract.deployed()).address,
ContractABI.abi,
signer
);
// Init
await contract.burn(amountToBurn);
const result = await contract.totalSupply();
// Expectativas
expect(result).to.be.eq(initialSupply - amountToBurn);
});
/**
* aprovação
*/
it("approve - deve FALHAR ao aprovar -1", async () => {
// Setup
const initialSupply = 1000;
const amountToApprove = -1;
const walletOwner = OWNER_ADDRESS;
const walletApproved = RANDOM_ADDRESS;
// - Implantar contrato
const Contract = await ethers.getContractFactory(`${CONTRACT_NAME}`);
const deployedContract = await Contract.deploy(initialSupply);
await deployedContract.deployed();
// - Carteira de configuração que irá interagir com o contrato como signatário
const provider = new ethers.providers.JsonRpcProvider();
const signer = provider.getSigner(walletOwner);
// - Obtenha o contrato vinculado ao signatário
const contract = new ethers.Contract(
(await deployedContract.deployed()).address,
ContractABI.abi,
signer
);
try {
// Init
await contract.approve(walletApproved, amountToApprove);
} catch (error: any) {
// Expectativas
expect(error?.reason).to.be.eq("valor fora dos limites");
}
});
/**
* aprovação
*/
it("approve - deve PASSAR ao aprovar 10", async () => {
// Configuração
const initialSupply = 1000;
const amountToApprove = 10;
const walletOwner = OWNER_ADDRESS;
const walletApproved = RANDOM_ADDRESS;
// - Implantar contrato
const Contract = await ethers.getContractFactory(`${CONTRACT_NAME}`);
const deployedContract = await Contract.deploy(initialSupply);
await deployedContract.deployed();
// - Carteira de configuração que irá interagir com o contrato como signatário
const provider = new ethers.providers.JsonRpcProvider();
const signer = provider.getSigner(walletOwner);
// - Obtenha o contrato vinculado ao signatário
const contract = new ethers.Contract(
(await deployedContract.deployed()).address,
ContractABI.abi,
signer
);
// Init
await contract.approve(walletApproved, amountToApprove);
const result = await contract.allowance(walletOwner, walletApproved);
// Expectativas
expect(result.toNumber()).to.be.eq(amountToApprove);
});
/**
* transferência
*/
it("transfer - deve FALHAR ao transferir -1", async () => {
// Configuração
const initialSupply = 1000;
const amountToTransfer = -1;
const walletOwner = OWNER_ADDRESS;
const walletReceiving = RANDOM_ADDRESS;
// - Implantar contrato
const Contract = await ethers.getContractFactory(`${CONTRACT_NAME}`);
const deployedContract = await Contract.deploy(initialSupply);
await deployedContract.deployed();
// - Carteira de configuração que irá interagir com o contrato como signatário
const provider = new ethers.providers.JsonRpcProvider();
const signer = provider.getSigner(walletOwner);
// - Obtenha o contrato vinculado ao signatário
const contract = new ethers.Contract(
(await deployedContract.deployed()).address,
ContractABI.abi,
signer
);
try {
// Init
await contract.transfer(walletReceiving, amountToTransfer);
} catch (error: any) {
// Expectativas
expect(error?.reason).to.be.eq("valor fora dos limites");
}
});
/**
* transferência
*/
it("transfer - deve FALHAR ao transferir mais do que possui", async () => {
// Configuração
const initialSupply = 1000;
const amountToTransfer = 1001;
const walletOwner = OWNER_ADDRESS;
const walletReceiving = RANDOM_ADDRESS;
// - Implantar contrato
const Contract = await ethers.getContractFactory(`${CONTRACT_NAME}`);
const deployedContract = await Contract.deploy(initialSupply);
await deployedContract.deployed();
// - Carteira de configuração que irá interagir com o contrato como signatário
const provider = new ethers.providers.JsonRpcProvider();
const signer = provider.getSigner(walletOwner);
// - Obtenha o contrato vinculado ao signatário
const contract = new ethers.Contract(
(await deployedContract.deployed()).address,
ContractABI.abi,
signer
);
try {
// Init
await contract.transfer(walletReceiving, amountToTransfer);
} catch (error: any) {
// Expectativas
expect(error?.reason).to.be.eq(
"Erro: Exceção da VM ao processar a transação: revertido com string de razão 'ERC20: o valor da transferência excede o saldo'"
);
}
});
/**
* transferência
*/
it("transfer - deve PASSAR ao transferir 10", async () => {
// Configuração
const initialSupply = 1000;
const amountToTransfer = 10;
const walletOwner = OWNER_ADDRESS;
const walletReceiving = RANDOM_ADDRESS;
// - Implantar contrato
const Contract = await ethers.getContractFactory(`${CONTRACT_NAME}`);
const deployedContract = await Contract.deploy(initialSupply);
await deployedContract.deployed();
// - Carteira de configuração que irá interagir com o contrato como signatário
const provider = new ethers.providers.JsonRpcProvider();
const signer = provider.getSigner(walletOwner);
// - Obtenha o contrato vinculado ao signatário
const contract = new ethers.Contract(
(await deployedContract.deployed()).address,
ContractABI.abi,
signer
);
// Init
await contract.transfer(walletReceiving, amountToTransfer);
const result = await contract.balanceOf(walletReceiving);
// Expectativas
expect(result.toNumber()).to.be.eq(amountToTransfer);
});
/**
* cenário transferFrom
*/
it("cenário transferFrom - deve FALHAR quando o gastador aprovado gasta mais do que o proprietário tem", async () => {
// Configuração
const initialSupply = 1000;
const amountToApprove = 10;
const walletOwner = OWNER_ADDRESS;
const walletApproved = RANDOM_ADDRESS;
const walletReceiving = ANOTHER_ADDRESS;
// - Implantar contrato
const Contract = await ethers.getContractFactory(`${CONTRACT_NAME}`);
const deployedContract = await Contract.deploy(initialSupply);
await deployedContract.deployed();
// - Carteira de configuração que irá interagir com o contrato como signatário
const provider = new ethers.providers.JsonRpcProvider();
const signerOwner = provider.getSigner(walletOwner);
const signerSpender = provider.getSigner(walletApproved);
// - Obtenha o contrato vinculado ao signatário
const contractOwner = new ethers.Contract(
(await deployedContract.deployed()).address,
ContractABI.abi,
signerOwner
);
// - Obtenha o contrato vinculado a outro signatário
const contractSpender = new ethers.Contract(
(await deployedContract.deployed()).address,
ContractABI.abi,
signerSpender
);
// Init
await contractOwner.approve(walletApproved, amountToApprove);
const resultAllowance = await contractOwner.allowance(
walletOwner,
walletApproved
);
await contractOwner.burn(initialSupply);
try {
await contractSpender.transferFrom(
walletOwner,
walletReceiving,
amountToApprove
);
} catch (error: any) {
// Expectativas
expect(error?.reason).to.be.eq(
"Erro: Exceção da VM ao processar a transação: revertido com string de razão 'ERC20: o valor da transferência excede o saldo'"
);
expect(resultAllowance.toNumber()).to.be.eq(amountToApprove);
}
});
/**
* cenário transferFrom
*/
it("cenário transferFrom - deve APROVAR quando o gastador aprovado gasta o que o proprietário tem", async () => {
// Configuração
const initialSupply = 1000;
const amountToApprove = 10;
const walletOwner = OWNER_ADDRESS;
const walletApproved = RANDOM_ADDRESS;
const walletReceiving = ANOTHER_ADDRESS;
// - Implantar contrato
const Contract = await ethers.getContractFactory(`${CONTRACT_NAME}`);
const deployedContract = await Contract.deploy(initialSupply);
await deployedContract.deployed();
// - Carteira de configuração que irá interagir com o contrato como signatário
const provider = new ethers.providers.JsonRpcProvider();
const signerOwner = provider.getSigner(walletOwner);
const signerSpender = provider.getSigner(walletApproved);
// - Obtenha o contrato vinculado ao signatário
const contractOwner = new ethers.Contract(
(await deployedContract.deployed()).address,
ContractABI.abi,
signerOwner
);
// - Obtenha o contrato vinculado a outro signatário
const contractSpender = new ethers.Contract(
(await deployedContract.deployed()).address,ContractABI.abi, signerSpender
);
// Init
await contractOwner.approve(walletApproved, amountToApprove);
const resultAllowanceBefore = await contractOwner.allowance(
walletOwner,
walletApproved
);
await contractSpender.transferFrom(
walletOwner,
walletReceiving,
amountToApprove
);
const resultAllowanceAfter = await contractOwner.allowance(
walletOwner,
walletApproved
);
const resultBalanceReceiver = await contractOwner.balanceOf(
walletReceiving
);
const resusltBalanceOwner = await contractOwner.balanceOf(walletOwner);
// Expectativas
expect(resultAllowanceBefore).to.be.eq(amountToApprove);
expect(resultAllowanceAfter).to.be.eq(0);
expect(resultBalanceReceiver.toNumber()).to.be.eq(amountToApprove);
expect(resusltBalanceOwner.toNumber()).to.be.eq(
initialSupply - amountToApprove
);
});
});
NOTA: Você pode obter este erro do TypeScript.
ContractABI is declared but its value is never read.
Para corrigir isso, adicione resolveJsonModule
ao seu arquivo tsconfig.json
:
Arquivo: ./tsconfig.json
{
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"outDir": "dist",
"declaration": true,
"resolveJsonModule": true
},
"include": ["./scripts", "./test", "./typechain"],
"files": ["./hardhat.config.ts"]
}
Em outro Terminal, se executarmos os testes, devemos obter os seguintes resultados:
# Saída esperada
# Não há necessidade de gerar novas digitações.
#
# BuidlToken - Contract Tests
# ✔ mint - should FAIL when minting -1 (276ms)
# ✔ mint - should PASS when minting 10 (192ms)
# ✔ burn - should FAIL when burning -1 (75ms)
# ✔ burn - should FAIL when burning tokens that aren't owned (122ms)
# ✔ burn - should PASS when burning tokens that exist/owned (143ms)
# ✔ approve - should FAIL when approving -1 (67ms)
# ✔ approve - should PASS when approving 10 (144ms)
# ✔ transfer - should FAIL when transferring -1 (65ms)
# ✔ transfer - should FAIL when transferring more than owned (102ms)
# ✔ transfer - should PASS when transferring 10 (147ms)
# ✔ scenario transferFrom - should FAIL when approved spender spends more than the owner has (259ms)
# ✔ scenario transferFrom - should PASS when approved spender spends what the owner has (339ms)
#
# 12 passing (2s)
Implantando na Rede de Testes
Agora que temos nosso código e nossos testes validam os diferentes cenários, estamos prontos para implantar nosso contrato do Token ERC-20 na rede de testes Kovan da blockchain Optimism. Antes de começarmos, precisaremos configurar nossa carteira Metamask com a rede de testes da blockchain Optimism e adicionar tokens nativos da blockchain Optimism a ela com uma torneira (faucet).
Obtendo tokens para a rede de testes de uma torneira
Depois de ter a rede de testes da blockchain Optimism configurada em sua carteira Metamask, é necessário obter tokens para a rede de testes. Você pode fazer isso acessando https://faucet.paradigm.xyz/
. Você precisará de uma conta no Twitter, mas depois de fazer login, poderá inserir o endereço da carteira e clicar no botão para reivindicar.
⚠️ NOTA: Lembre-se de clicar na caixa de seleção "Drip on additional networks" (Gotejar em redes adicionais)
Se você não possui uma conta no Twitter, pode usar algumas dessas opções de torneiras como alternativas:
Configuração de implantação
Agora que temos os tokens em nossa carteira, só precisamos criar nosso arquivo de variável de ambiente (.env) e modificar nosso hardhat.config.ts para adicionar a rede Optimism a ele.
Vamos começar modificando nosso arquivo .env.example:
Arquivo: ./.env.example
ETHERSCAN_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1
OPTIMISM_KOVAN_URL=https://kovan.optimism.io
PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1
Em seguida, faremos uma cópia e colaremos nossa chave privada da carteira na seção PRIVATE_KEY.
LEMBRE-SE: Nunca compartilhe esta chave privada com ninguém, nem mesmo com sua mãe.
cp .env.example .env;
Em seguida, precisaremos obter a chave privada da carteira.
Assim que tivermos a chave, adicione-a ao nosso arquivo .env recém-criado.
Arquivo: ./.env
ETHERSCAN_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1
OPTIMISM_KOVAN_URL=https://kovan.optimism.io
PRIVATE_KEY=<CHAVE-PRIVADA-SECRETA-DA-SUA-CARTEIRA>
Com esses detalhes inseridos, precisamos apenas modificar nosso hardhat.config.ts para suportar esses novos valores e a rede de testes Kovan da blockchain Optimism.
Arquivo: ./hardhat.config.ts
import * as dotenv from "dotenv";
import { HardhatUserConfig, task } from "hardhat/config";
import "@nomiclabs/hardhat-etherscan";
import "@nomiclabs/hardhat-waffle";
import "@typechain/hardhat";
import "hardhat-gas-reporter";
import "solidity-coverage";
dotenv.config();
// Esta é uma tarefa de amostra do Hardhat. Para aprender a criar a sua, acesse
// https://hardhat.org/guides/create-task.html
task("accounts", "Imprime a lista de contas", async (taskArgs, hre) => {
const accounts = await hre.ethers.getSigners();
for (const account of accounts) {
console.log(account.address);
}
});
// Você precisa exportar um objeto para definir sua configuração
// Vá para https://hardhat.org/config/ para aprender mais
const config: HardhatUserConfig = {
solidity: "0.8.4",
networks: {
// ! COMECE AQUI - ADICIONE ISSO
optimismKovan: {
url: process.env.OPTIMISM_KOVAN_URL || "",
accounts:
process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
},
// ISSO TERMINA AQUI !
},
gasReporter: {
enabled: process.env.REPORT_GAS !== undefined,
currency: "USD",
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
};
export default config;
Finalmente implantando e verificando a implantação
Tudo está configurado, e agora podemos implantar nosso contrato para a rede de testes Kovan da blockchain Optimism.
Execute o seguinte e aproveite o milagre de implantar na blockchain.
./node_modules/.bin/hardhat run scripts/deploy.ts --network optimismKovan;
# Saída esperada
# Não há necessidade de gerar novas digitações.
# Contract deployed to: 0x375F01b156D9BdDDd41fd38c5CC74C514CB71f73
Usando o explorador de blockchain Etherscan da rede Kovan/Optimism, podemos procurar o contrato implantado.
Verificando nosso contrato
Agora que temos nosso contrato implantado, queremos poder interagir com ele por meio do explorador de blockchain e, para isso, precisamos verificar seu código-fonte. Esta parte requer a criação de uma conta no Optimistic Etherscan e a geração de uma chave de API.
Para fazer isso, você precisará acessar https://optimistic.etherscan.io/, inscrever-se para uma nova conta e, em seguida, ir até a seção Chave de API para gerar uma nova chave. Com esta chave, você a colará em seu arquivo .env.
Com a chave de API recém-criada, copie-a para este arquivo.
Arquivo: ./.env
ETHERSCAN_API_KEY=<SUA-CHAVE-DE-API-DO-OPTIMISTIC-ETHERSCAN>
OPTIMISM_KOVAN_URL=https://kovan.optimism.io
PRIVATE_KEY=<CHAVE-PRIVADA-SECRETA-DA-SUA-CARTEIRA>
Agora que temos todos os valores corretos, podemos executar o seguinte para validar o contrato.
# /optimism-erc20
# NOTA:
# 1 - 0x375... refere-se ao endereço do contrato implantado
# 2 - 1000 refere-se ao parâmetro de fornecimento 1000 original implantado em nosso
# arquivo deploy.ts
./node_modules/.bin/hardhat verify --network optimismKovan 0x375F01b156D9BdDDd41fd38c5CC74C514CB71f73 1000
# Saída esperada
# Nada a compilar
# Não há necessidade de gerar novas digitações.
# Successfully submitted source code for contract
# contracts/Buidl.sol:BuidlToken at 0x375F01b156D9BdDDd41fd38c5CC74C514CB71f73
# for verification on the block explorer. Waiting for verification result...
# Successfully verified contract BuidlToken on Etherscan.
# https://kovan-optimistic.etherscan.io/address/0x375F01b156D9BdDDd41fd38c5CC74C514CB71f73#code
Agora, se olharmos na rede de teste, veremos várias funções disponíveis para nós.
Cunhando Tokens
Nossa etapa final é aumentar o fornecimento total cunhando mais tokens por meio do explorador da blockchain. Para fazer isso, vá para a seção Contract e selecione Write contract. A partir daí, você precisará conectar sua carteira (o endereço da carteira que implantou o contrato) e usar a função mint.
Assim que a transação for concluída, você pode verificar o fornecimento total voltando à seção Read Contract e expandindo a seção totalSupply para ver o novo fornecimento sendo refletido.
Tutorial original escrito por Manny, para Ankr Docs. Traduzido por Paulinho Giovannini.
Top comments (0)