WEB3DEV

Cover image for Como criar um token ERC-20 na Blockchain Ethereum de Camada 2 Ultrarrápida Optimism
Paulo Gio
Paulo Gio

Posted on

Como criar um token ERC-20 na Blockchain Ethereum de Camada 2 Ultrarrápida Optimism

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?

https://www.ankr.com/docs/learn/c8r6BWBuQ.png

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.

​​https://www.ankr.com/docs/learn/rTVWirrkB.png

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
Enter fullscreen mode Exit fullscreen mode

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.

https://www.ankr.com/docs/learn/C2wDPA7xh.png

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 {
}
Enter fullscreen mode Exit fullscreen mode

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.

https://www.ankr.com/docs/learn/rbbL8ro7j.png

# /optimism-erc20
yarn add @openzeppelin/contracts;
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

É 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.

https://www.ankr.com/docs/learn/2GPJfTK3O.png

Isso deve abrir o arquivo localizado na pasta node_modules e mostrar o conteúdo de todo o arquivo que estamos estendendo.

https://www.ankr.com/docs/learn/lM8jBQ5Xr.png

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
});
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

https://www.ankr.com/docs/learn/LsPiAJsOm.png

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)
Enter fullscreen mode Exit fullscreen mode

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
    );
  });
});
Enter fullscreen mode Exit fullscreen mode

NOTA: Você pode obter este erro do TypeScript.

ContractABI is declared but its value is never read.
Enter fullscreen mode Exit fullscreen mode

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"]
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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:

https://kovan.optifaucet.com

https://optimismfaucet.xyz

https://www.ankr.com/docs/learn/DBijURIW-.png

https://www.ankr.com/docs/learn/5dYFHOWWv.png

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Em seguida, precisaremos obter a chave privada da carteira.

https://www.ankr.com/docs/learn/u1ZTG3u4J.png

https://www.ankr.com/docs/learn/QFjlhRkS0.png

https://www.ankr.com/docs/learn/dZgJfym-D.png

https://www.ankr.com/docs/learn/pQHpLyko_.png

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>
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Usando o explorador de blockchain Etherscan da rede Kovan/Optimism, podemos procurar o contrato implantado.

https://www.ankr.com/docs/learn/yRxH7z_qa.png

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.

https://www.ankr.com/docs/learn/C2yRmq9yu.png

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.

https://www.ankr.com/docs/learn/weUkOGtKX.png

https://www.ankr.com/docs/learn/UUf5rKRtq.png

https://www.ankr.com/docs/learn/QXKJN3C_f.png

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Agora, se olharmos na rede de teste, veremos várias funções disponíveis para nós.

https://www.ankr.com/docs/learn/AQAmipGq8.png

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.

https://www.ankr.com/docs/learn/X4qxLJCR0.png

https://www.ankr.com/docs/learn/KXl4DfKYG.png

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.

https://www.ankr.com/docs/learn/-wq0TVPlR.png

Tutorial original escrito por Manny, para Ankr Docs. Traduzido por Paulinho Giovannini.

Top comments (0)