Maintenant que nous avons une compréhension générale, créons un exemple pour solidifier nos connaissances. Nous allons faire cet exemple dans Remix pour le rendre plus simple.
Les contrats vont être extrêmement simples, le but ici est de comprendre la norme.
Ok… Pour cela nous avons besoin des contrats suivants :
- Mise en œuvre: C’est là que va être notre logique, nous l’appellerons Implémentation.sol.
- CloneFactory : Ce sera notre usine, nous aurons une fonction clone() que les utilisateurs déclencheront et par conséquent, l’usine affichera l’adresse du proxy. Le nom de l’usine sera CloneFactory.sol.
- Procuration: Il n’y a rien à voir avec le proxy, le proxy sera la sortie de la fonction clone() de CloneFactory.sol. Il peut y avoir autant de proxys différents, c’est tout le but, pour faire beaucoup de clones de l’Implementation.sol
Voici à quoi cela ressemblerait :
Un aspect très important à garder à l’esprit est que les clones ne connaissent pas le constructeur, donc au lieu d’utiliser un constructeur pour affecter des variables importantes, nous utilisons un initialiser() fonction « remplacement du constructeur ». Nous devons juste nous assurer que la fonction initialize() ne peut être appelée qu’une seule fois, afin que les gens ne puissent pas altérer le contrat, de la même manière que le constructeur fonctionne. Pour ce faire, nous utilisons normalement Initializable d’openZeppelin, vous pouvez le trouver ici. Pour cet exemple, nous n’allons pas utiliser de contrats tiers, juste pour que ce soit plus clair.
Commençons par le fichier Implementation.sol. La seule chose que le contrat fera est d’avoir une variable publique uint avec une fonction setter et un modificateur, restreignant l’accès pour modifier la variable uniquement pour le propriétaire.
Brisons-le :
uint public x → L’entier non signé que nous allons modifier dans la fonction setter (il vaut 0 par défaut).
bool public isBase → Ce booléen garantira que le contrat d’implémentation ne pourra jamais être initialisé. Si nous voyons dans le constructeur, nous définissons : isBase sur true et la première instruction require de la fonction initialize() est exiger(isBase ==false). Cela nous garantit que le contrat d’implémentation n’est utilisé que pour la logique et que personne ne peut le falsifier. N’oubliez pas que le contrat proxy ou clone ne connaît pas le constructeur, donc isBase sera défini sur sa valeur par défaut qui est false.
adresse publique propriétaire → Le propriétaire du contrat (compte détenu en externe). Le propriétaire est par défaut adresse(0). Dans Solidity, si vous laissez un type d’adresse sans affectation, la valeur par défaut est address(0).
modifier onlyOwner() → J’espère que vous n’avez pas besoin d’explications pour cela, mais en gros, cela signifie que seul le propriétaire peut appeler cette fonction.
initialiser(adresse _propriétaire) → La fonction d’initialisation doit être appelée immédiatement une fois le clone proxy créé. C’est comme notre constructeur, ce qui signifie que si quelqu’un appelle cette fonction avant, il aura le contrôle sur le contrat. Comme vous pouvez le voir, il a un argument (adresse _owner). Cet argument sera fourni dans la CloneFactory (vous verrez). Il y a 2 considérations importantes avec ceci:
- Vous devez vous assurer que la fonction d’initialisation est appelée UNE SEULE FOIS. La façon dont nous l’avons fait est de vérifier que le propriétaire est l’adresse (0). Une fois que le propriétaire est affecté et que vous essayez d’appeler à nouveau la fonction, la transaction sera annulée. Je recommande fortement de suivre cette architecture + celle d’OpenZppelin initialiseur() modificateur à l’intérieur du contrat Initializable. Cela garantit que la fonction ne peut être appelée qu’une seule fois.
- Rendre le contrat d’implémentation inutilisable : en attribuant isBase=true dans le constructeur et en exigeant isBase== false dans la fonction initialize(), nous nous assurons que personne ne peut altérer le contrat. Le seul but de ce contrat est de servir de contrat logique, si quelqu’un essaie d’appeler la fonction d’initialisation du contrat de base, il reviendra immédiatement.
Une fois que le fichier Implementation.sol est prêt, créons CloneFactory.sol. Pour ce faire, nous allons utiliser la fonction clone() de la bibliothèque Clone d’OpenZeppelin, vous pouvez la trouver ici.
Brisons-le :
Interface Implémentation → initialize() est la seule fonction dont nous aurons besoin de Implementation.sol. Nous l’appellerons immédiatement une fois le contrat de clonage créé.
aborder la mise en œuvre publique → L’adresse de Implementation.sol.
mapping(adresse => adresse[]) public allClones → Ceci est juste un mappage pour garder une trace de tous les clones déployés, la première adresse est le msg.sender ou le propriétaire du clone.
clone(adresse _implémentation) → Cette fonction est d’Open Zeppelin. À un niveau élevé, nous fournissons l’adresse d’implémentation (Implementation.sol) et elle renvoie une instance de cette adresse, en d’autres termes, elle renvoie un clone exact d’Implementation.sol.
_clone() → C’est la fonction que les utilisateurs appelleront. Une fois que quelqu’un appelle cette fonction, la première chose qui va se passer est de créer un nouveau clone et de l’enregistrer sous adresse identiqueEnfant . Cette adresse contiendra la même logique que Implementation.sol, mais avec son propre état de stockage. Comme nous pouvons le voir dans la troisième ligne de _clone(), nous appelons la fonction initialize :
Ceci est d’une importance cruciale, c’est pourquoi nous l’appelons immédiatement. Cela fera de l’appelant de la fonction _clone() le propriétaire du contrat de clonage. Une fois que c’est fait, il n’y a aucun moyen de revenir en arrière.
Vous pouvez cloner autant de contrats que vous le souhaitez, chacun gardant son propre espace de stockage. Celui qui appelle la fonction _clone() sera le seul compte ayant accès à la modification « x ».