Pular para o conteúdo principal

Como simular contratos inteligentes em Solidity para teste

Solidity
smart contracts
testando
simulando
Intermediário
Markus Waas
2 de maio de 2020
4 minutos de leitura

Objetos mock (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.

Teste de unidade de contratos com mocks

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 só quer fazer o teste 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: Private ERC20

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 tiver curiosidade, estamos usando o hook _beforeTokenTransfer (opens in a new tab) dos novos contratos v3 da OpenZeppelin.

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: destinatário inválido");
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

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

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 override para a função de sobrescrita. Então, vamos adicioná-los a ambas as funções isPublic.

Agora, em seus testes de unidade, você pode usar o PrivateERC20Mock. 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, também poderíamos usar auxiliares de tempo (opens in a new tab) para alterar os horários de acordo. 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.

Fazendo mock de muitos contratos

Pode ficar confuso se você tiver que criar outro contrato para cada mock. Se isso o incomoda, você pode dar 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.

O mocking pode ser ainda mais poderoso

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 é simplesmente ter uma função mint adicional 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 atualização da página: 25 de agosto de 2025

Este tutorial foi útil?