WEB3DEV

Cover image for TESTANDO TOKENS ERC-20 — Foundry
Diogo Jorge
Diogo Jorge

Posted on • Atualizado em

TESTANDO TOKENS ERC-20 — Foundry

Sumário
  1. O projeto Foundry
  2. Instale o Foundry
  3. Crie um novo projeto
  4. Implementando um ERC-20
  5. Estabeleça uma configuração base de teste
  6. Testes de transferência de token
  7. Simulando uma chamada
  8. A recuperação direta de dados
  9. Teste de fuzz
  10. Testes de execução

O projeto Foundry

O projeto Foundry é um garoto novo no bairro, mas rapidamente ganhou popularidade graças à implementação Rust de ferramentas para dapp**

Talvez você seja novo em programação e esteja apenas começando a aprender Solidity. Um aborrecimento para você pode ter sido que você basicamente precisa aprender uma segunda linguagem (JavaScript/TypeScript) para escrever testes. A estrutura Foundry torna a escrita de testes mais fácil, permitindo que você use menos linhas de código e nunca seja incomodado por BigNumber.js / bn.js. Além de ser muito rápido, também é escrito em Rust e muito usável, mesmo sendo bastante novo.

Instale o Foundry

Os comandos necessários para instalar o Foundry variam dependendo do seu sistema. Cliqueaqui para ver os detalhes.

Crie um novo projeto

Usando o forge init, podemos criar novos projetos. Você pode escolher entre projetos em branco e modelos básicos.

$ forge init --template https://github.com/FrankieIsLost/forge-template
Enter fullscreen mode Exit fullscreen mode

Implementando um ERC-20

O próximo passo é criar um contrato ERC-20 e alguns testes para ele. Para começar, vamos instalar os contratos do Openzeppelin e atualizar a biblioteca padrão com o Forge:

$ forge install OpenZeppelin/[email protected]
$ forge update foundry-rs/forge-std
Enter fullscreen mode Exit fullscreen mode

Agora você precisa adicionar a biblioteca ao arquivo de remapeamento existente:

forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/
Enter fullscreen mode Exit fullscreen mode

Crie um novo contrato usando contratos Openzeppelin. Renomeie o arquivo existente para MyERC20.sol e o arquivo de teste para MyErc20.t.sol.

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol";

contract MyERC20 is ERC20 {
   constructor() ERC20("Name", "SYM") {
       this;
   }
Enter fullscreen mode Exit fullscreen mode

Estabeleça uma configuração base de teste

Para configurar o contrato, precisamos definir a função setUp no arquivo MyErc20.t.sol. Temos uma função setUp no Foundry. Conforme definimos a função setUp, o contrato entra em outro estado e alguns endereços são criados. Além do próprio contrato ERC20, importaremos coisas do forge-std, ds-test e utils.

Nossa função setUp base usa as funções util que acompanham o modelo, o que nos permite criar alguns endereços de usuário que contêm Ether. Vamos chamar o primeiro de Alice e o segundo de Bob.

Usando o contrato Vm, podemos modificar itens EVM de baixo nível, como rotular um endereço, para que possa ser identificado facilmente com a etiqueta no rastreamento de pilha.

/// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {console} from "forge-std/console.sol";
import {stdStorage, StdStorage, Test} from "forge-std/Test.sol";

import {Utils} from "./utils/Utils.sol";
import {MyERC20} from "../MyERC20.sol";

contract BaseSetup is MyERC20, DSTest {
   Utils internal utils;
   address payable[] internal users;

   address internal alice;
   address internal bob;

   function setUp() public virtual {
       utils = new Utils();
       users = utils.createUsers(5);

       alice = users[0];
       vm.label(alice, "Alice");
       bob = users[1];
       vm.label(bob, "Bob");
   }
}
Enter fullscreen mode Exit fullscreen mode

Agora é hora de configurar as coisas para que os tokens possam ser transferidos…

contract WhenTransferringTokens is BaseSetup {
   uint256 internal maxTransferAmount = 12e18;

   function setUp() public virtual override {
       BaseSetup.setUp();
       console.log("When transferring tokens");
   }

   function transferToken(
       address from,
       address to,
       uint256 transferAmount
   ) public returns (bool) {
       vm.prank(from);
       return this.transfer(to, transferAmount);
   }
}
Enter fullscreen mode Exit fullscreen mode

As transferências de token agora podem ser configuradas. Como você pode ver, essa configuração pode ser semelhante ao teste mocha do JavaScript com beforeEach e describe. No entanto, é tudo Solidity com uma função setUp pública e contratos. Não se esqueça de chamar a função de setup base ao realizar a configuração.

Os rastreamentos de pilha também incluirão console.log, para que você possa console.log em qual cenário você está atualmente.

Em nossos testes, agora também temos uma função de transferência simples que podemos usar. Por favor, note que para vm.prank funcionar, você deve fazer uma chamada real, então use this.transfer em vez de apenas transferir.

Testes de transferência de token

Criamos dois cenários: o primeiro com fundos adequados, o segundo com fundos insuficientes

Certifique-se de chamar a configuração anterior durante a configuração. Como alternativa, use super(), mas gosto de ser explícito.

Nosso próximo passo será usar auxiliares de asserção da biblioteca ds-test. Com a biblioteca ds-test, podemos usar auxiliares de asserção para afirmar igualdade (assertEq), menor que (assertLe) e maior que (assertGe), bem como opções com decimais para tokens.

contract WhenAliceHasSufficientFunds is WhenTransferringTokens {
 uint256 internal mintAmount = maxTransferAmount;

 function setUp() public override {
   WhenTransferringTokens.setUp();
   console.log("When Alice has sufficient funds");
   _mint(alice, mintAmount);
 }

 function itTransfersAmountCorrectly(
   address from,
   address to,
   uint256 amount
 ) public {
   uint256 fromBalance = balanceOf(from);
   bool success = transferToken(from, to, amount);

   assertTrue(success);
   assertEqDecimal(
     balanceOf(from),
     fromBalance - amount, decimals()
   );
   assertEqDecimal(
     balanceOf(to),
     transferAmount, decimals()
   );
 }

 function testTransferAllTokens() public {
   uint256 t = maxTransferAmount;
   itTransfersAmountCorrectly(alice, bob, t);
 }

 function testTransferHalfTokens() public {
   uint256 t = maxTransferAmount / 2;
   itTransfersAmountCorrectly(alice, bob, amount);
 }

 function testTransferOneToken() public {
   itTransfersAmountCorrectly(alice, bob, 1);
 }
}

contract WhenAliceHasInsufficientFunds is WhenTransferringTokens {
 uint256 internal mintAmount = maxTransferAmount - 1e18;

 function setUp() public override {
   WhenTransferringTokens.setUp();
   console.log("When Alice has insufficient funds");
   _mint(alice, mintAmount);
 }

 function itRevertsTransfer(
   address from,
   address to,
   uint256 amount,
   string memory expRevertMessage
 ) public {
   vm.expectRevert(abi.encodePacked(expRevertMessage));
   transferToken(from, to, amount);
 }

 function testCannotTransferMoreThanAvailable() public {
   itRevertsTransfer({
     from: alice,
     to: bob,
     amount: maxTransferAmount,
     expRevertMessage: "[...] exceeds balance"
   });
 }

 function testCannotTransferToZero() public {
   itRevertsTransfer({
     from: alice,
     to: address(0),
     amount: mintAmount,
     expRevertMessage: "[...] zero address"
   });
 }
}
Enter fullscreen mode Exit fullscreen mode

Simulando uma chamada

Com vm, você pode simular uma chamada, por exemplo, se esta transferência de token receber uma chamada com uma transferência para bob e amount, basta retornar false. Além disso, usando clearMockedCalls (), você pode limpar mocks.

function testTransferWithMockedCall() public {
   vm.prank(alice);
   vm.mockCall(
       address(this),
       abi.encodeWithSelector(
           this.transfer.selector,
           bob,
           maxTransferAmount
       ),
       abi.encode(false)
   );
   bool success = this.transfer(bob, maxTransferAmount);
   assertTrue(!success);
   vm.clearMockedCalls();
}
Enter fullscreen mode Exit fullscreen mode

A recuperação direta de dados

Use o recurso stdStorage se desejar obter dados diretamente do estado. Por exemplo, se você deseja ler o saldo diretamente do estado, você determinará o slot de armazenamento e o carregará usando vm.load.

usando stdStorage para StdStorage;

function testFindMapping() public {
   uint256 slot = stdstore
       .target(address(this))
       .sig(this.balanceOf.selector)
       .with_key(alice)
       .find();
   bytes32 data = vm.load(address(this), bytes32(slot));
   assertEqDecimal(uint256(data), mintAmount, decimals());
}
Enter fullscreen mode Exit fullscreen mode

Teste de fuzz

Forge permite que você use fuzzing. Tudo o que você precisa fazer é criar uma função de teste com variáveis ​​de entrada e o Forge fará o teste fuzz para você. Você pode restringir o intervalo para tipos de entrada específicos se precisar ter restrições específicas; alternativamente, você pode usar vm.assume para eliminar valores únicos e/ou módulomodulo para restringir o input a um intervalo preciso.

function testTransferFuzzing(uint64 amount) public {
   vm.assume(amount != 0);
   itTransfersAmountCorrectly(
       alice,
       bob,
       amount % maxTransferAmount
   );
}
Enter fullscreen mode Exit fullscreen mode

Testes de execução

$ forge test -vvvvv
Enter fullscreen mode Exit fullscreen mode

Os testes do Forge podem ser executados em diferentes configurações de verbosidade. O número de vs deve ser aumentado para 5:

2: Imprimir todos os logs de teste

3: Imprimir rastreamentos de execução para testes que falharam.

4: Imprimir rastreamentos de configuração para testes com falha, bem como rastreamentos de execução para todos os testes.

5: Imprima os rastreamentos de configuração e execução para cada teste.

Referência

Este artigo foi escrito por Harbor e traduzido por Diogo Jorge. O artigo original pode ser encontrado aqui.


Abrace a oportunidade de elevar sua jornada de desenvolvimento para um nível superior. Testar tokens ERC-20 é apenas o começo; os builds incríveis da WEB3DEV representam a chave de entrada para o emocionante cenário web3. 🚀🧑‍💻

Não perca tempo, 👉inscreva-se👈 agora mesmo e comece a desbravar o universo Blockchain!
 
Seja também WEB3DEV!

Top comments (0)