Vai al contenuto principale

Come simulare i contratti intelligenti in Solidity per testarli

soliditycontratti intelligentitestsimulazione
Intermedio
Markus Waas
soliditydeveloper.com(opens in a new tab)
2 maggio 2020
4 minuti letti minute read

Gli oggetti Mock(opens in a new tab) sono un modello di progettazione comune nella programmazione orientata agli oggetti. Il termine inglese per "simulare" è "to mock", dal francese antico "mocquer" col significato di "prendersi gioco di". Questo significato si è poi evoluto in "imitare qualcosa di reale", che è ciò che in effetti facciamo nella programmazione. Prenditi gioco dei tuoi contratti intelligenti quanto vuoi, ma simulali ogni volta che puoi. Ti semplificherà la vita.

Condurre unit test dei contratti con le simulazioni

Simulare un contratto significa essenzialmente creare una seconda versione di quel contratto che si comporta in modo simile a quello originale, ma in modo che possa essere facilmente controllato dallo sviluppatore. Ci si ritrova spesso con contratti complessi in cui si desidera solo eseguire unit test di piccole parti del contratto. Ma cosa succede se il test di questa piccola parte richiede uno condizione molto specifica del contratto, difficile da ottenere?

Potresti scrivere una complessa logica di configurazione del test ogni volta che porta il contratto nello stato richiesto o scrivi una simulazione. Simulare un contratto tramite ereditarietà è semplice. Basta creare un secondo contratto simulato che erediti da quello originale. Ora puoi sovrascrivere le funzioni nella tua simulazione. Vediamolo con un esempio.

Esempio: ERC20 Privato

Usiamo un contratto ERC-20 di esempio dotato di un tempo privato iniziale. Il proprietario può gestire utenti privati, che saranno gli unici autorizzati a ricevere token all'inizio. Una volta trascorso un certo intervallo di tempo, a tutti sarà consentito utilizzare i token. Se sei curioso, stiamo usando l'hook _beforeTokenTransfer(opens in a new tab) dalla nuova libreria di contratti 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}
Mostra tutto
Copia

E ora, simuliamolo.

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}
Mostra tutto
Copia

Otterrai uno dei seguenti messaggi d'errore:

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

Poiché stiamo usando la nuova versione di Solidity 0.6, dobbiamo aggiungere la parola chiave virtual per le funzioni sovrascrivibili e override per la funzione di sovrascrizione. Quindi, aggiungiamoli a entrambe le funzioni isPublic.

Ora, nei tuoi unit test, puoi invece usare PrivateERC20Mock. Quando desideri testare il comportamento durante il tempo di utilizzo privato, usa setIsPublic(false e, allo stesso modo, setIsPublic(true) per testare il tempo di utilizzo pubblico. Ovviamente, nel nostro esempio, abbiamo potuto usare soltanto i time helper(opens in a new tab) per modificare anche i tempi di conseguenza. Ma l'idea della simulazione dovrebbe ora esserti chiara e puoi immaginarti scenari in cui non sia facile come far procedere semplicemente il tempo.

Simulare molti contratti

Può diventare caotico creare un altro contratto per ogni singola simulazione. Se ciò ti preoccupa, puoi dare un'occhiata alla libreria MockContract(opens in a new tab). Ti consente di sovrascrivere e modificare i comportamenti dei contratti al volo. Tuttavia, funziona solo per le chiamate di simulazione a un altro contratto, quindi non funzionerebbe per il nostro esempio.

La simulazione può essere ancora più potente

I poteri della simulazione non finiscono qui.

  • Aggiungere funzioni: è utile non solo sovrascrivere una funzione specifica, ma anche aggiungere funzioni aggiuntive. Un buon esempio per i token è proprio avere una funzione mint aggiuntiva per consentire a qualsiasi utente di ottenere nuovi token gratuitamente.
  • Uso nelle testnet: quando distribuisci e testi i tuoi contratti sulle reti di prova insieme alla tua dApp, prendi in considerazione l'uso di una versione simulata. Evita di sovrascrivere le funzioni, a meno che non sia davvero necessario. Dopotutto, vuoi testare la logica reale. Ma può essere utile aggiungere una funzione di ripristino che ripristini semplicemente lo stato del contratto all'inizio, senza necessità di alcuna nuova distribuzione. Ovviamente, non vorresti farlo in un contratto sulla Rete mainnet.

Ultima modifica: @nhsz(opens in a new tab), 15 agosto 2023

Questo tutorial è stato utile?