La blockchain et ses applications sont aujourd'hui plus populaires que jamais. Ethereum en particulier, offrant des capacités de contrat intelligentes, ouvre les portes à de nouvelles idées qui peuvent être mises en œuvre de manière distribuée, immuable et sans confiance.
Premiers pas dans le Contrat intelligent Ethereum l'espace peut être un peu écrasant car la courbe d'apprentissage est assez raide. Nous espérons que cet article (et les prochains articles de la série Ethereum) pourront atténuer cette douleur et vous permettre de démarrer rapidement.
Dans cet article, nous supposons que vous avez une compréhension de base des applications blockchain et d'Ethereum. Si vous sentez que vous avez besoin de rafraîchir vos connaissances, nous vous recommandons cet aperçu Ethereum du framework Truffle .
Ce qui est couvert dans cet article:
Il existe de nombreuses applications différentes de Ethereum contrats intelligents. Les plus populaires à l'heure actuelle sont les crypto-monnaies (implémentées sous forme de jetons ERC20) et les ventes de jetons de financement participatif (a.k.a. offres de pièces initiales, ou ICO.) Un bon exemple de jeton utilitaire ERC20 est le Moteur à pièces . Dans cet article de blog, nous explorerons quelque chose de différent: l'idée de verrouiller des fonds dans des contrats de portefeuille crypto. Cette idée elle-même a divers cas d'utilisation.
Il existe plusieurs exemples, mais la raison la plus courante pour le moment de verrouiller des fonds est probablement le «vesting». Imaginez que vous venez de créer un ICO réussi et que votre entreprise détient toujours la majorité des jetons répartis entre les membres de votre équipe.
Il serait avantageux pour toutes les parties concernées de s'assurer que les jetons détenus par les employés ne peuvent pas être échangés immédiatement. S'il n'y a aucun contrôle en place, un employé donné peut prendre des mesures en vendant tous ses jetons, en encaissant et en quittant l'entreprise. Cela aurait un effet négatif sur le prix du marché et rendrait tous les contributeurs restants au projet mécontents.
Une autre idée est d'utiliser un contrat intelligent comme testament cryptographique. Imaginez que nous aimerions stocker nos économies de crypto-monnaie dans un contrat qui sera accessible aux membres de la famille, mais seulement après que quelque chose nous soit arrivé. Disons que nous devrions «nous enregistrer» avec le portefeuille, en évoquant de temps en temps un appel contractuel.
Si nous ne nous enregistrons pas à temps, quelque chose nous est vraisemblablement arrivé et ils peuvent retirer les fonds. La proportion de fonds qu'ils recevraient chacun pourrait être soit explicitement fixée dans le contrat, soit laissée à la décision d'un consensus entre les membres de la famille.
Une autre application du verrouillage des fonds pourrait être de créer un petit fonds de pension ou un compte d'épargne basé sur le temps, c'est-à-dire qui empêche le propriétaire de retirer des fonds avant un certain temps dans le futur. (Cela pourrait être particulièrement utile pour les traders crypto accros pour les aider à garder leur éther intact.)
Le cas d'utilisation que nous allons explorer pour le reste de cet article de blog est similaire: mettre de l'argent crypto pour plus tard pour quelqu'un d'autre, comme un futur cadeau d'anniversaire.
Imaginons que nous aimerions offrir un éther à quelqu'un pour son 18e anniversaire. Nous pourrions noter sur un morceau de papier la clé privée du compte et l’adresse du portefeuille contenant les fonds et la leur remettre dans une enveloppe. La seule chose qu'ils auraient à faire est d'appeler une fonction sur le contrat à partir de leur compte une fois qu'ils auront 18 ans et que tous les fonds leur seront transférés. Ou, à la place, nous pourrions simplement utiliser une simple ÐApp. Ça m'a l'air bien? Commençons!
Avant d'aller de l'avant avec le développement de contrats intelligents, vous devez avoir Node.js et Aller installé sur votre machine. Dans ce blog, nous allons utiliser le Truffe cadre. Même si vous pouvez vous en passer, Truffle réduit considérablement la barrière d'entrée au développement, aux tests et au déploiement de contrats intelligents Ethereum. Nous sommes totalement d'accord avec leur déclaration:
Truffle est le cadre de développement le plus populaire pour Ethereum avec pour mission de vous faciliter la vie.
Pour l'installer, exécutez la commande suivante:
npm install -g truffle
Maintenant, récupérez le code de ce projet:
git clone https://github.com/radek1st/time-locked-wallets cd time-locked-wallets
Il est important de noter que le projet suit la structure standard du projet Truffle et que les répertoires d'intérêt sont:
contracts
: Détient tous les contrats Soliditymigrations
: Contient des scripts décrivant les étapes de la migrationsrc
: Inclut le code ÐApptest
: Stocke tous les tests de contratIl y a plusieurs contrats inclus dans ce projet. Voici le résumé:
TimeLockedWallet.sol
est le contrat principal de ce projet et il est décrit en détail ci-dessous.TimeLockedWalletFactory.sol
est le factory
contrat qui permet à n'importe qui de déployer facilement son propre TimeLockedWallet
.ERC20.sol
est une interface de la norme ERC20 pour les jetons Ethereum.ApeeScapeToken.sol
est un jeton ERC20 personnalisé.SafeMath.sol
est une petite bibliothèque utilisée par ApeeScapeToken pour effectuer des opérations arithmétiques sûres.Migrations.sol
est un contrat interne Truffle facilitant les migrations.Pour toute question sur la rédaction de contrats Ethereum, veuillez vous référer au site officiel Documents sur les contrats intelligents de solidité .
Notre TimeLockedWallet.sol
Le contrat de solidité ressemble à ceci:
pragma solidity ^0.4.18;
La ligne ci-dessus indique la version minimale du compilateur Solidity requise pour ce contrat.
import './ERC20.sol';
Ici, nous importons d'autres définitions de contrat, utilisées plus tard dans le code.
contract TimeLockedWallet { ... }
Ce qui précède est notre principal objet. contract
porte le code de notre contrat. Le code décrit ci-dessous provient de l'intérieur des accolades.
address public creator; address public owner; uint public unlockDate; uint public createdAt;
Ici, nous définissons plusieurs public
variables qui par défaut génèrent les méthodes getter correspondantes. Quelques-uns sont de type uint
(entiers non signés) et un couple sont address
(Adresses Ethereum de 16 caractères.)
modifier onlyOwner { require(msg.sender == owner); _; }
En termes simples, modifier
est une condition préalable à remplir avant même de démarrer l'exécution de la fonction à laquelle elle est attachée.
function TimeLockedWallet( address _creator, address _owner, uint _unlockDate ) public { creator = _creator; owner = _owner; unlockDate = _unlockDate; createdAt = now; }
C'est notre première fonction. Comme le nom est exactement le même que le nom de notre contrat, il s'agit du constructeur et n'est appelé qu'une seule fois lorsque le contrat est créé.
Notez que si vous changiez le nom du contrat, cela deviendrait une fonction normale appelable par n'importe qui et formerait une porte dérobée dans votre contrat comme ce fut le cas dans le Bug de Parity Multisig Wallet . De plus, notez que la casse est également importante, donc si ce nom de fonction était en minuscules, il deviendrait également une fonction normale - encore une fois, pas quelque chose que vous voulez ici.
function() payable public { Received(msg.sender, msg.value); }
La fonction ci-dessus est d'un type spécial et s'appelle fallback
fonction. Si quelqu'un envoie des ETH à ce contrat, nous le recevrons avec plaisir. Le solde des ETH du contrat augmentera et cela déclenchera un Received
un événement. Pour permettre à toute autre fonction d'accepter l'ETH entrant, vous pouvez les marquer avec payable
mot-clé.
function info() public view returns(address, address, uint, uint, uint) { return (creator, owner, unlockDate, createdAt, this.balance); }
C'est notre première fonction régulière. Il n'a pas de paramètres de fonction et définit le tuple de sortie à renvoyer. Notez que this.balance
renvoie le solde éther actuel de ce contrat.
function withdraw() onlyOwner public { require(now >= unlockDate); msg.sender.transfer(this.balance); Withdrew(msg.sender, this.balance); }
La fonction ci-dessus ne peut être exécutée que si onlyOwner
le modificateur défini précédemment est satisfait. Si le require
déclaration n'est pas vraie, le contrat se termine avec une erreur. C’est là que nous vérifions si le unlockDate
est passé. msg.sender
est l'appelant de cette fonction et il est transféré la totalité du solde éther du contrat. Dans la dernière ligne, nous tirons également un Withdrew
un événement. Les événements sont décrits un peu plus tard.
qu'est-ce qui distingue le rythme progressif ?
Fait intéressant, now
—qui équivaut à block.timestamp
—peut ne pas être aussi précis qu'on pourrait le penser. C'est au mineur de le choisir, il peut donc s'écouler jusqu'à 15 minutes (900 secondes) comme expliqué ci-dessous. formule :
parent.timestamp >= block.timestamp <= now + 900 seconds
En conséquence, now
ne doit pas être utilisé pour mesurer de petites unités de temps.
function withdrawTokens(address _tokenContract) onlyOwner public { require(now >= unlockDate); ERC20 token = ERC20(_tokenContract); uint tokenBalance = token.balanceOf(this); token.transfer(owner, tokenBalance); WithdrewTokens(_tokenContract, msg.sender, tokenBalance); }
Voici notre fonction pour retirer les jetons ERC20. Comme le contrat lui-même n'a connaissance d'aucun jeton attribué à cette adresse, nous devons transmettre l'adresse du jeton ERC20 déployé que nous voulons retirer. Nous l'instancions avec ERC20(_tokenContract)
puis recherchez et transférez l'intégralité du solde du jeton au destinataire. Nous tirons également un WithdrewTokens
un événement.
event Received(address _from, uint _amount); event Withdrew(address _to, uint _amount); event WithdrewTokens(address _tokenContract, address _to, uint _amount);
Dans cet extrait, nous définissons plusieurs événements. Les événements déclenchés sont essentiellement des entrées de journal attachées aux reçus de transaction sur la blockchain. Chaque transaction peut joindre zéro ou plusieurs entrées de journal. Les principales utilisations des événements sont débogage et surveillance .
C’est tout ce dont nous avons besoin pour verrouiller le temps des jetons Ether et ERC20 - juste quelques lignes de code. Pas trop mal, hein? Voyons maintenant notre autre contrat, TimeLockedWalletFactory.sol
.
Il y a deux raisons principales à la création d'un contrat d'usine de niveau supérieur. Le premier est un problème de sécurité. En séparant les fonds dans différents portefeuilles, nous ne nous retrouverons pas avec un seul contrat avec une quantité massive d'éther et de jetons. Cela donnera 100% de contrôle au seul propriétaire du portefeuille et, espérons-le, découragera les pirates d'essayer de l'exploiter.
Deuxièmement, un contrat d'usine permet une création de contrat TimeLockedWallet facile et sans effort, sans avoir besoin d'avoir une configuration de développement présente. Tout ce que vous avez à faire est d'appeler une fonction à partir d'un autre portefeuille ou ĐApp.
pragma solidity ^0.4.18; import './TimeLockedWallet.sol'; contract TimeLockedWalletFactory { ... }
Ce qui précède est simple et très similaire au contrat précédent.
mapping(address => address[]) wallets;
Ici, nous définissons un mapping
type, qui est comme un dictionnaire ou une carte, mais avec toutes les touches possibles prédéfinies et pointant vers les valeurs par défaut. Dans le cas de address
type, la valeur par défaut est une adresse nulle 0x00
. Nous avons également un type de tableau, address[]
, qui contient address
es.
Dans le langage Solidity, les tableaux contiennent toujours un type et peuvent avoir une longueur fixe ou variable. Dans notre cas, le tableau est illimité.
Pour résumer ici notre logique métier, nous définissons un mappage appelé wallets
qui se compose d'adresses d'utilisateurs (créateurs et propriétaires de contrats), chacune pointant vers un tableau d'adresses de contrat de portefeuille associées.
function getWallets(address _user) public view returns(address[]) { return wallets[_user]; }
Ici, nous utilisons la fonction ci-dessus pour renvoyer tous les portefeuilles de contrat a _user
créé ou a des droits sur. Notez que view
(dans les anciennes versions de complier appelées constant
) indique qu'il s'agit d'une fonction qui ne change pas l'état de la blockchain et peut donc être appelée gratuitement, sans dépenser de gaz.
function newTimeLockedWallet(address _owner, uint _unlockDate) payable public returns(address wallet) { wallet = new TimeLockedWallet(msg.sender, _owner, _unlockDate); wallets[msg.sender].push(wallet); if(msg.sender != _owner){ wallets[_owner].push(wallet); } wallet.transfer(msg.value); Created(wallet, msg.sender, _owner, now, _unlockDate, msg.value); }
C'est la partie la plus importante du contrat: la méthode d'usine. Il nous permet de créer un nouveau portefeuille à durée limitée à la volée, en appelant son constructeur: new TimeLockedWallet(msg.sender, _owner, _unlockDate)
. Nous stockons ensuite son adresse pour le créateur et le destinataire. Plus tard, nous transférons tout l'éther facultatif passé dans cette exécution de fonction à l'adresse de portefeuille nouvellement créée. Enfin, nous signalons le Create
événement, défini comme:
event Created(address wallet, address from, address to, uint createdAt, uint unlockDate, uint amount);
Ce tutoriel ne serait pas tellement amusant si nous ne créions pas notre propre jeton Ethereum, donc pour être complet, nous donnons vie ApeeScapeToken
. ApeeScapeToken
est un token ERC20 standard implémentant l'interface présentée ci-dessous:
contract ERC20 { uint256 public totalSupply; function balanceOf(address who) public view returns (uint256); function transfer(address to, uint256 value) public returns (bool); function allowance(address owner, address spender) public view returns (uint256); function transferFrom(address from, address to, uint256 value) public returns (bool); function approve(address spender, uint256 value) public returns (bool); event Approval(address indexed owner, address indexed spender, uint256 value); event Transfer(address indexed from, address indexed to, uint256 value); }
Ce qui le distingue des autres jetons est défini ci-dessous:
string public constant name = 'ApeeScape Token'; string public constant symbol = 'TTT'; uint256 public constant decimals = 6; totalSupply = 1000000 * (10 ** decimals);
Nous lui avons donné un nom, un symbole, un approvisionnement total d'un million, et l'avons rendu divisible jusqu'à six décimales.
Pour découvrir différentes variantes de contrats de jetons, n'hésitez pas à explorer le Repo OpenZeppelin .
Pour commencer rapidement, exécutez Truffle avec la blockchain intégrée:
truffle develop
Vous devriez voir quelque chose comme ceci:
Truffle Develop started at http://localhost:9545/ Accounts: (0) 0x627306090abab3a6e1400e9345bc60c78a8bef57 (1) 0xf17f52151ebef6c7334fad080c5704d77216b732 (2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef (3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544 (4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2 (5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e (6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5 (7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5 (8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc (9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat
La graine mnémonique vous permet de recréer vos clés privées et publiques. Par exemple, importez-le dans MetaMask comme indiqué ici:
Pour compiler les contrats, exécutez:
> compile
Tu devrais voir:
Compiling ./contracts/ERC20.sol... Compiling ./contracts/Migrations.sol... Compiling ./contracts/SafeMath.sol... Compiling ./contracts/TimeLockedWallet.sol... Compiling ./contracts/TimeLockedWalletFactory.sol... Compiling ./contracts/ApeeScapeToken.sol... Writing artifacts to ./build/contracts
Maintenant, nous devons définir les contrats que nous voulons déployer. Ceci est fait dans migrations/2_deploy_contracts.js
:
var TimeLockedWalletFactory = artifacts.require('TimeLockedWalletFactory'); var ApeeScapeToken = artifacts.require('ApeeScapeToken'); module.exports = function(deployer) { deployer.deploy(TimeLockedWalletFactory); deployer.deploy(ApeeScapeToken); };
Nous importons d'abord nos deux artefacts contractuels TimeLockedWalletFactory
et ApeeScapeToken
. Ensuite, nous les déployons simplement. Nous avons manqué TimeLockedWallet
volontairement, car ce contrat est déployé de manière dynamique. Pour plus d'informations sur les migrations, reportez-vous au Documentation sur les migrations de truffes .
Pour migrer les contrats, exécutez:
> migrate
Cela devrait aboutir à quelque chose ressemblant à ce qui suit:
Running migration: 1_initial_migration.js Deploying Migrations... ... 0x1c55ae0eb870ac1baae86eeb15f3aba3f521df46d9816e04400e9b5951ecc099 Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0 Saving successful migration to network... ... 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956 Saving artifacts... Running migration: 2_deploy_contracts.js Deploying TimeLockedWalletFactory... ... 0xe9d9c37508bb58a1591d0f052d6870810118a0a19f728bf0cea4f4e5c17acd7a TimeLockedWalletFactory: 0x345ca3e014aaf5dca488057592ee47305d9b3e10 Deploying ApeeScapeToken... ... 0x0469ce110735f27bbb1a85c85a77ba4b0ba0d5aa52c3d67164045b849d8b2ed6 ApeeScapeToken: 0xf25186b5081ff5ce73482ad761db0eb0d25abfbf Saving successful migration to network... ... 0x059cf1bbc372b9348ce487de910358801bbbd1c89182853439bec0afaee6c7db Saving artifacts...
Vous pouvez dire que TimeLockedWalletFactory
et ApeeScapeToken
a été déployé avec succès.
Enfin, pour vous assurer que tout fonctionne bien, exécutons quelques tests. Les tests se trouvent dans le test
répertoire et correspondent aux principaux contrats TimeLockedWalletTest.js
et TimeLockedWalletFactoryTest.js
. Par souci de concision, nous n’entrerons pas dans les détails de l’écriture des tests et nous la laisserons comme un exercice pour le lecteur. Pour exécuter les tests, exécutez simplement:
> test
… Et j'espère que vous verrez tous les tests réussir comme ceci:
Contract: TimeLockedWalletFactory ✓ Factory created contract is working well (365ms) Contract: TimeLockedWallet ✓ Owner can withdraw the funds after the unlock date (668ms) ✓ Nobody can withdraw the funds before the unlock date (765ms) ✓ Nobody other than the owner can withdraw funds after the unlock date (756ms) ✓ Owner can withdraw the ApeeScapeToken after the unlock date (671ms) ✓ Allow getting info about the wallet (362ms) 6 passing (4s)
Il est temps de tout voir en action. Le moyen le plus simple d'interagir avec n'importe quelle blockchain consiste à utiliser des applications distribuées avec une interface utilisateur Web, appelées ÐApps (ou parfois «dapps»).
Pour exécuter cette ÐApp, vous aurez besoin d'un navigateur compatible Ethereum. Le moyen le plus simple d'y parvenir est d'installer le MetaMask Plugin Chrome. Il y a aussi un guide visuel sur l'installation et la configuration de MetaMask avec Truffle .
Pour en revenir à notre scénario, pourquoi ne pas présenter d’abord les acteurs? Supposons qu'Alice soit la créatrice du portefeuille à durée limitée et que Bob soit le destinataire / éventuel propriétaire des fonds.
Aperçu du scénario:
Tout d'abord, Alice crée un portefeuille à durée limitée pour Bob et envoie un premier éther. Nous pouvons voir qu'un nouveau portefeuille de contrats a été créé et appartient à Bob:
À tout moment après la création du contrat, le portefeuille peut être rechargé. La recharge peut provenir de n'importe qui et être sous forme de jetons éther ou ERC20. Laissez Alice envoyer 100 jetons ApeeScape au nouveau portefeuille de Bob, comme illustré ici:
Du point de vue d'Alice, le portefeuille ressemblera à ceci après la recharge:
Changeons de rôle maintenant et connectons-nous en tant que Bob. Bob devrait pouvoir voir tous les portefeuilles qu'il a créés ou dont il est le destinataire. Le contrat créé par Alice étant toujours limité dans le temps, il ne peut retirer aucun fonds:
Après avoir patiemment attendu que la serrure expire…
… Bob est maintenant prêt à retirer les jetons éther et ApeeScape:
Après avoir vidé le portefeuille verrouillé, son solde d'adresses a augmenté et l'a rendu très heureux et reconnaissant envers Alice:
Si vous souhaitez interagir avec les contrats décrits, vous n’avez pas à les exécuter localement: nous les avons déployés sur le réseau de test Ethereum Rinkeby. ApeeScapeToken est déployé Ici et TimeLockedWalletFactory est déployé Ici .
Vous pouvez utiliser notre déployé ÐApp , lié aux contrats ci-dessus servis par les pages GitHub. Notez que MetaMask doit être installé et connecté à Rinkeby.
Nous avons rencontré quelques problèmes lors du développement de ce projet. Le premier était la fragilité de MetaMask dans Chrome (comme se plaindre de l'invalidité nonce
). La solution la plus simple que nous ayons trouvée était de simplement réinstaller le plugin.
De plus, Truffle se désynchronisait parfois lors de la modification de contrats intelligents et se plaignait avec un invalid number of solidity parameters
Erreur. Nous avons trouvé qu'un simple rm -r build
et refaire une compilation / migration l'effacerait.
Nous espérons que cet article a piqué votre intérêt et que vous vous lancerez dans votre voyage de développeur au pays d'Ethereum. Le chemin vers la cyber-gloire sera raide et prend du temps, mais il existe de nombreuses ressources pour vous aider (comme Celui-ci ce qui nous a aidés un peu). N'hésitez pas à nous contacter via les commentaires ci-dessous.
Le code source de ce projet est disponible sur GitHub .
Si vous souhaitez savoir comment utiliser un uPort application mobile au lieu de MetaMask, jetez un œil à la démo et code source d'une version alternative et gagnante du hackathon de ce projet.
Je vous invite également à lire mon tutoriel de suivi , qui se concentre sur la création ĐApp.
Un grand merci à Maciek Zielinski pour sa contribution à ce projet.
Un contrat intelligent est un code informatique qui est exécuté au-dessus de la machine virtuelle Ethereum. Les contrats intelligents peuvent envoyer et accepter de l'éther et des données. Les contrats sont de nature immuable, sauf indication contraire.
La machine virtuelle Ethereum (EVM) est un environnement d'exécution en bac à sable pour les contrats intelligents mis en œuvre en tant que machine de pile qui exécute le bytecode. Il se concentre sur la sécurité et l'exécution de code non approuvé par des ordinateurs du monde entier.
Mist est le navigateur officiel des applications distribuées (ĐApps, parfois dapps) qui sont des interfaces / interfaces utilisateur conviviales pour le réseau Ethereum. Ethereum Wallet est l'une de ces ĐApps. Les deux sont développés par les mêmes personnes qui ont construit Ethereum.
Citant Vitalik Buterin, l'inventeur d'Ethereum: `` Tout chez Ethereum, y compris le site Web, les outils, les livres blancs et bien sûr tous les logiciels et compilateurs sont à 100%, open source mur à mur et sous GPL. ''
Oui, il est de nature entièrement décentralisée. Les opérations de lecture et d'écriture sont totalement décentralisées et actuellement sécurisées par un mécanisme de preuve de travail. Ethereum est conçu de telle manière qu'aucune personne ou groupe ne contrôle la blockchain.
La langue de choix d'Ethereum est actuellement Solidity. Solidity est un langage de programmation orienté contrat, principalement inspiré de JavaScript, C ++ et Python, utilisé pour écrire des contrats intelligents. Il y a aussi d'autres langues à l'horizon, comme Vyper.
Les jetons sont des contrats intelligents mettant en œuvre la norme ERC20. Ils comprennent des opérations telles que l'approvisionnement total et le solde et des méthodes de transfert des jetons. Les jetons ne quittent jamais vraiment le contrat, mais sont simplement réaffectés à l'adresse de portefeuille d'un autre titulaire dans un mappage interne.