Comment simuler des contrats intelligents Solidity pour les tests
Les objets simulés(opens in a new tab) sont un modèle de conception commun en programmation orientée objet. Provenant du vieux mot français "mocquer", qui signifiait "se moquer de", sa signification a évolué en "imiter quelque chose de réel", ce qui est en fait ce que nous faisons en programmation. Vous pouvez vous moquer de vos contrats intelligents si vous le souhaitez, mais simulez-les dès que vous le pouvez. Cela vous facilite la vie.
Contrats de test unitaire avec simulation
Simuler un contrat signifie essentiellement créer une seconde version de ce contrat qui se comporte d'une manière très similaire à la version originale, mais qui peut être facilement contrôlé par le développeur. Vous vous retrouvez souvent avec des contrats complexes où vous ne voulez que tester de petites parties du contrat. Le problème est le suivant : que se passe-t-il si le test de cette petite partie exige un état de contrat très spécifique dans lequel il est difficile de s'y retrouver ?
Vous pouvez écrire une logique de configuration de test complexe à chaque fois que le contrat est dans l'état requis ou vous pouvez écrire une simulation. Il est facile de simuler un contrat en utilisant l'héritage. Il suffit de créer un second contrat fictif qui hérite du contrat original. Vous pouvez maintenant remplacer les fonctions sur votre contrat fictif. Voyons cela avec un exemple.
Exemple : ERC20 privé
Notre exemple est celui d'un contrat ERC-20 ayant une durée de vie privée initiale. Le propriétaire peut gérer les utilisateurs privés et seuls ces derniers seront autorisés à recevoir des jetons au début. Une fois un certain temps écoulé, tout le monde sera autorisé à utiliser les jetons. Si vous êtes curieux, sachez que nous utilisons le crochet _beforeTokenTransfer
(opens in a new tab) des nouveaux contrats 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}Afficher toutCopier
Nous allons maintenant en créer une version fictive.
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}Afficher toutCopier
Vous obtiendrez l'un des messages d'erreur suivants :
PrivateERC20Mock.sol: TypeError: Overriding function is missing "override" specifier.
PrivateERC20.sol: TypeError: Trying to override non-virtual function. Did you forget to add "virtual"?.
Comme nous utilisons la nouvelle version 0.6 de Solidity, nous devons ajouter le mot clé virtual
pour les fonctions qui peuvent être remplacées et remplacer pour la fonction de remplacement. Alors ajoutons-les aux deux fonctions isPublic
.
Dans vos tests unitaires, vous pouvez désormais utiliser PrivateERC20Mock
à la place. Pour tester le comportement pendant le temps d'utilisation privée, utilisez setIsPublic(false)
et, de la même manière, setIsPublic(true)
pour tester le temps d'utilisation publique. Bien sûr, dans notre exemple, nous pourrions juste utiliser des aides de temps(opens in a new tab) pour également changer les temps correspondants. Mais l'utilisation d'une version fictive devrait désormais être plus claire. Vous pouvez imaginer des scénarios où il n'est pas aussi simple de faire avancer le temps.
Simuler de nombreux contrats
La situation peut devenir confuse si vous devez créer un autre contrat pour chaque simulation. Si cela vous dérange, vous pouvez jeter un coup d'oeil à la bibliothèque MockContract(opens in a new tab). Elle vous permet de remplacer et de modifier les comportements des contrats à la volée. Cependant, cela ne fonctionne que pour simuler des appels à un autre contrat, cela ne fonctionnerait donc pas dans notre exemple.
La simulation peut être encore plus puissante
Les pouvoirs de la simulation ne s'arrêtent pas là.
- Ajout de fonctions : Il peut être utile non seulement de remplacer une fonction spécifique, mais aussi d'ajouter des fonctions supplémentaires. Pour les jetons, un bon exemple est simplement d'avoir une fonction
mint
supplémentaire pour permettre à tout utilisateur d'obtenir de nouveaux jetons gratuitement. - Utilisation dans les réseaux de test : Lorsque vous déployez et testez vos contrats sur des réseaux de test avec votre dapp, envisagez d'utiliser une version fictive. Évitez de remplacer des fonctions à moins que cela ne soit indispensable. Après tout, vous voulez tester la logique réelle. Il peut néanmoins être utile d'ajouter, par exemple, une fonction de réinitialisation, celle-ci vous permettant de réinitialiser simplement l'état du contrat au début, sans avoir à effectuer de nouveau déploiement. Évidemment, vous ne voudriez pas de cela dans un contrat de réseau principal.
Dernière modification: @nhsz(opens in a new tab), 15 août 2023