Pular para o conteúdo principal

Como simular contratos inteligentes em Solidity para teste

solidezcontratos inteligentestestandosimulando
Intermediário
Markus Waas
soliditydeveloper.com(opens in a new tab)
2 de maio de 2020
4 minutos de leitura minute read

Mock de objetos (opens in a new tab) são um padrão de design comum na programação orientada a objetos. Vindo da velha palavra francesa "mocquer" com o significado de "diversão de", evoluiu para a "imitação de algo real", que é na realidade, o que estamos fazendo na programação. Por favor, só se divirta de seus contratos inteligentes se você quiser, mas faça o mock deles sempre que puder. Isso torna sua vida mais fácil.

Testes de unidade de contratos com simulações

Simular um contrato (mocking) significa essencialmente criar uma segunda versão desse contrato que se comporta de maneira muito semelhante ao original, mas de uma maneira que pode ser facilmente controlada pelo desenvolvedor. Muitas vezes, você acaba com contratos complexos nos quais você só quer fazer testes de unidade de pequenas partes do contrato. O problema é: e se o teste desta pequena parte exigir um estado de contrato muito específico que seja difícil de alcançar?

Você poderia escrever uma lógica de configuração de testes complexa toda vez que apresentasse o contrato no estado necessário ou você escreveria uma simulação (mock, em inglês). Simular um contrato é fácil com herança. Basta criar um segundo contrato mock que herda do original. Agora você pode substituir funções de seu mock. Vejamos com um exemplo.

Exemplo: ERC20 Privado

Usamos um exemplo de contrato ERC-20 que tem um tempo privado inicial. O proprietário pode gerenciar usuários privados e apenas esses terão permissão para receber tokens no início. Uma vez que um certo tempo tenha passado, todos poderão utilizar os tokens. Se você estiver curioso, estamos usando o hook (código modificado) _beforeTokenTransfer(opens in a new tab) dos novos contratos OpenZeppelin v3.

1pragma solidity ^0.6.0;
2
3import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
4import "@openzeppelin/contracts/access/Ownable.sol";
5
6contract PrivateERC20 is ERC20, Ownable {
7 mapping (address => bool) public isPrivateUser;
8 uint256 private publicAfterTime;
9
10 constructor(uint256 privateERC20timeInSec) ERC20("PrivateERC20", "PRIV") public {
11 publicAfterTime = now + privateERC20timeInSec;
12 }
13
14 function addUser(address user) external onlyOwner {
15 isPrivateUser[user] = true;
16 }
17
18 function isPublic() public view returns (bool) {
19 return now >= publicAfterTime;
20 }
21
22 function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {
23 super._beforeTokenTransfer(from, to, amount);
24
25 require(_validRecipient(to), "PrivateERC20: invalid recipient");
26 }
27
28 function _validRecipient(address to) private view returns (bool) {
29 if (isPublic()) {
30 return true;
31 }
32
33 return isPrivateUser[to];
34 }
35}
Exibir tudo
Copiar

E agora vamos fazer o mock disso.

1pragma solidity ^0.6.0;
2import "../PrivateERC20.sol";
3
4contract PrivateERC20Mock is PrivateERC20 {
5 bool isPublicConfig;
6
7 constructor() public PrivateERC20(0) {}
8
9 function setIsPublic(bool isPublic) external {
10 isPublicConfig = isPublic;
11 }
12
13 function isPublic() public view returns (bool) {
14 return isPublicConfig;
15 }
16}
Exibir tudo
Copiar

Você receberá uma das seguintes mensagens de erro:

  • PrivateERC20Mock.sol: TypeError: Overriding function is missing "override" specifier.
  • PrivateERC20.sol: TypeError: Trying to override non-virtual function. Did you forget to add "virtual"?.

Como estamos usando a nova versão 0.6 do Solidity, temos que adicionar a palavra-chave virtual para funções que podem ser sobrescritas e substituídas pela função substituta. Então vamos adicioná-los para ambas as funções isPublic.

Agora você pode usar PrivateERC20Mock nos seus testes de unidade. Quando você quiser testar o comportamento durante o tempo de uso privado, use setIsPublic(false) e, da mesma forma, setIsPublic(true) para testar o tempo de uso público. É claro que em nosso exemplo, poderíamos usar simplesmente auxiliares de tempo(opens in a new tab) para alterar os tempos de acordo também. Mas a ideia de mocking deve estar clara agora e você pode imaginar cenários em que não é tão fácil quanto simplesmente avançar no tempo.

Mocking em muitos contratos

Pode ficar confuso se você tiver que criar outro contrato para cada mock. Se isso incomoda você, dê uma olhada na biblioteca MockContract(opens in a new tab). Ele permite que você sobrescreva e modifique comportamentos de contratos em tempo real. No entanto, ele só funciona para chamadas mocking para outro contrato, portanto, não funcionaria para o nosso exemplo.

Mocking podem ser ainda mais poderosas

Os poderes de mocking não terminam aí.

  • Adicionando funções: sobrescrever uma função específica é útil, mas apenas acrescentar funções adicionais também poderá ser. Um bom exemplo para tokens é ter apenas uma função adicional mint para permitir que qualquer usuário obtenha novos tokens gratuitamente.
  • Uso em testnets: ao implantar e testar seus contratos em testnets juntamente com seu Dapp, considere usar uma versão mock. Evite sobrescrever funções, a menos que você realmente precise. Afinal, você quer testar a lógica real. Mas adicionar, por exemplo, uma função de redefinição pode ser útil que simplesmente redefine o estado do contrato para o início, sem necessidade de nova implantação. Obviamente, você não gostaria de ter isso em um contrato na mainnet (rede principal).

Última edição: @rafarocha(opens in a new tab), 19 de janeiro de 2024

Este tutorial foi útil?