Como simular contratos inteligentes em Solidity para teste
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;23import "@openzeppelin/contracts/token/ERC20/ERC20.sol";4import "@openzeppelin/contracts/access/Ownable.sol";56contract PrivateERC20 is ERC20, Ownable {7 mapping (address => bool) public isPrivateUser;8 uint256 private publicAfterTime;910 constructor(uint256 privateERC20timeInSec) ERC20("PrivateERC20", "PRIV") public {11 publicAfterTime = now + privateERC20timeInSec;12 }1314 function addUser(address user) external onlyOwner {15 isPrivateUser[user] = true;16 }1718 function isPublic() public view returns (bool) {19 return now >= publicAfterTime;20 }2122 function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {23 super._beforeTokenTransfer(from, to, amount);2425 require(_validRecipient(to), "PrivateERC20: invalid recipient");26 }2728 function _validRecipient(address to) private view returns (bool) {29 if (isPublic()) {30 return true;31 }3233 return isPrivateUser[to];34 }35}Exibir tudoCopiar
E agora vamos fazer o mock disso.
1pragma solidity ^0.6.0;2import "../PrivateERC20.sol";34contract PrivateERC20Mock is PrivateERC20 {5 bool isPublicConfig;67 constructor() public PrivateERC20(0) {}89 function setIsPublic(bool isPublic) external {10 isPublicConfig = isPublic;11 }1213 function isPublic() public view returns (bool) {14 return isPublicConfig;15 }16}Exibir tudoCopiar
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: @nhsz(opens in a new tab), 15 de agosto de 2023