Comment déterminer l'adresse d'un contrat intelligent avant le déploiement: utiliser CREATE2 pour l'échange cryptographique

Le sujet de la blockchain ne cesse pas d'être une source non seulement de toutes sortes de battage médiatique, mais aussi d'idées très précieuses d'un point de vue technologique. Par conséquent, elle n'a pas contourné les habitants de la ville ensoleillée. Les gens regardent, étudient, tentent de déplacer leur expertise dans l'analyse informationnelle traditionnelle vers des systèmes de blockchain. Jusqu'à présent, juste un point: l'un des développements de Rostelecom-Solar est capable de vérifier la sécurité des logiciels basés sur la blockchain. Et en cours de route, certaines réflexions surgissent sur la résolution des problèmes appliqués de la communauté de la blockchain. L'un de ces hacks de la vie - comment déterminer l'adresse d'un contrat intelligent avant le déploiement à l'aide de CREATE2 - aujourd'hui, je veux partager avec vous sous la coupe.

image
L'opcode CREATE2 a été ajouté au hard fork de Constantinople le 28 février de cette année. Comme indiqué dans l'EIP, cet opcode a été introduit principalement pour les canaux d'état. Cependant, nous l'avons utilisé pour résoudre un autre problème.

Il y a des utilisateurs sur l'échange avec des soldes. Nous devons fournir à chaque utilisateur une adresse Ethereum à laquelle n'importe qui peut envoyer des jetons, reconstituant ainsi son compte. Appelons ces adresses «portefeuilles». Lorsque des jetons arrivent dans des portefeuilles, nous devons les envoyer vers un seul portefeuille (hotwallet).

Dans les sections suivantes, j'analyse les solutions à ce problème sans CREATE2 et explique pourquoi nous les avons abandonnées. Si vous n'êtes intéressé que par le résultat final, vous pouvez le trouver dans la section «Solution finale».

Adresses Ethereum


La solution la plus simple consiste à générer de nouvelles adresses ethereum pour les nouveaux utilisateurs. Ces adresses seront des portefeuilles. Pour transférer des jetons d'un portefeuille vers un hotwallet, vous devez signer la transaction en appelant la fonction transfer () avec la clé privée du portefeuille depuis le backend.

Cette approche présente les avantages suivants:

  • c'est juste
  • le coût du transfert de jetons du portefeuille vers le hotwallet est égal au prix de l'appel de la fonction transfer ()

Cependant, nous avons abandonné cette approche car elle présente un inconvénient majeur: vous devez stocker des clés privées quelque part. Et le fait n'est pas seulement qu'ils peuvent être perdus, mais aussi que vous devez contrôler soigneusement l'accès à ces clés. Si au moins l'un d'entre eux est compromis, les jetons d'un certain utilisateur n'atteindront pas un portefeuille chaud.

image

Créez un contrat intelligent distinct pour chaque utilisateur


Le déploiement d'un contrat intelligent distinct pour chaque utilisateur élimine la nécessité de stocker les clés de portefeuille privées sur le serveur. L'échange appellera ce contrat intelligent pour transférer des jetons vers le hotwallet.

Nous avons également refusé cette décision, car l'utilisateur ne peut pas voir l'adresse de son portefeuille sans déployer un contrat intelligent (c'est en fait possible, mais de manière assez compliquée avec d'autres lacunes que nous ne discuterons pas ici). Lors de l'échange, un utilisateur peut créer autant de comptes qu'il le souhaite et chacun a besoin de son propre portefeuille. Cela signifie que nous devons dépenser de l'argent pour déployer le contrat, sans même être sûr que l'utilisateur utilisera ce compte.

Opcode CREATE2


Pour résoudre le problème de la méthode précédente, nous avons décidé d'utiliser l'opcode CREATE2. CREATE2 vous permet de prédéterminer l'adresse à laquelle le contrat intelligent sera déployé. L'adresse est calculée à l'aide de la formule suivante:

keccak256 (0xff ++ address ++ salt ++ keccak256 (init_code)) [12:] 

où:
  • address - l'adresse du contrat intelligent qui appellera CREATE2
  • sel - valeur aléatoire
  • init_code - bytecode de contrat intelligent pour le déploiement

Cela garantit que l'adresse que nous fournissons à l'utilisateur contiendra bien le bytecode souhaité. De plus, ce contrat intelligent peut être déployé lorsque nous en avons besoin. Par exemple, lorsqu'un utilisateur décide d'utiliser son portefeuille pour la première fois.
image
De plus, vous pouvez calculer l'adresse d'un contrat intelligent à chaque fois au lieu de le stocker, car:

  • L'adresse dans la formule est constante, car c'est l'adresse de notre usine de portefeuille
  • salt - hachage de user_id
  • init_code est persistant puisque nous utilisons le même portefeuille

Plus d'améliorations


La solution précédente a toujours un inconvénient: vous devez payer pour déployer un contrat intelligent. Cependant, vous pouvez vous en débarrasser. Pour ce faire, vous pouvez appeler la fonction transfer () , puis selfdestruct () dans le constructeur de portefeuille. Et puis le gaz pour le déploiement du contrat intelligent sera restitué.

Contrairement à la croyance populaire, vous pouvez déployer plusieurs fois un contrat intelligent à la même adresse avec l'opcode CREATE2. En effet, CREATE2 vérifie que le nonce de l'adresse de destination est zéro (la valeur "1" lui est affectée au début du constructeur). Dans le même temps, la fonction selfdestruct () réinitialise les adresses nonce à chaque fois. Ainsi, si vous appelez à nouveau CREATE2 avec les mêmes arguments, la vérification de nonce passera.

Notez que cette solution est similaire à l'option d'adresse ethereum, mais sans avoir besoin de stocker des clés privées. Le coût du transfert d'argent d'un portefeuille vers un hotwallet est approximativement égal au coût de l'appel de la fonction transfer () , car nous ne payons pas pour le déploiement d'un contrat intelligent.

Décision finale


image

Initialement préparé par:

  • fonction pour obtenir du sel par user_id
  • un contrat intelligent qui appellera l'opcode CREATE2 avec le sel approprié (c'est-à-dire l'usine de portefeuille)
  • code octet de portefeuille correspondant au contrat avec le constructeur suivant:

 constructor () { address hotWallet = 0x…; address token = 0x…; token.transfer (hotWallet, token.balanceOf (address (this))); selfdestruct (address (0)); } 

Pour chaque nouvel utilisateur, nous montrons son adresse de portefeuille en calculant

 keccak256 (0xff ++ address ++ salt ++ keccak256 (init_code)) [12:] 

Lorsqu'un utilisateur transfère des jetons à l'adresse de portefeuille correspondante, notre serveur voit l'événement Transfer avec le paramètre _to égal à l'adresse de portefeuille. À ce stade, il est déjà possible d'augmenter le solde de l'utilisateur sur l'échange avant de déployer le portefeuille.

Lorsqu'un nombre suffisant de jetons s'accumule à l'adresse du portefeuille, nous pouvons tous les transférer à la fois vers un hotwallet. Pour ce faire, le backend appelle la fonction de la fabrique de contrats intelligents, qui effectue les actions suivantes:

 function deployWallet ( uint256) { bytes memory walletBytecode =…; // invoke CREATE2 with wallet bytecode and salt } 

Ainsi, le constructeur du contrat de portefeuille intelligent est appelé, qui transfère tous ses jetons à l'adresse du portefeuille actif, puis s'autodétruit.

Le code complet peut être trouvé ici . Veuillez noter que ce n'est pas notre code de production, car nous avons décidé d'optimiser le bytecode du portefeuille et l'avons écrit dans les opcodes.

Publié par Pavel Kondratenkov, spécialiste Ethereum

Source: https://habr.com/ru/post/fr475496/


All Articles