Atelier Ethereum #1 - Un contrat pour la démocratie - [ Partie 1 : Création du contrat ]
-
Dans ce premier meetup, nous programmons un contrat simple de vote décentralisé.
Le meetup a eu lieu le 10 janvier : http://www.meetup.com/fr-FR/blockchains/events/228612405/
Pour poser des questions, rendez-vous sur le channel #atelier-ethereum sur le Slack de CryptoFR.
Pour s’inscrire sur le slack, inscrivez votre email à cette adresseLes commentaires seront activés sur ce sujet une fois le sujet terminé
Outil de développement
Pour développer, nous utilisons browser solidity : https://chriseth.github.io/browser-solidity/
Créez un nouveau fichier “Democracy”. Nous allons commencer à coder.
-
1 - Créons notre contrat
On créé tout d’abord le contrat en lui donnant une classe du même nom que notre fichier solidity (fichier en extension .sol)
contract Democracy { // Propriétaire du contrat address public owner; // Constructeur function Democracy() { owner = msg.sender; } }
Pour commencer on créé notre fonction
Democracy
qui est le constructeur du contrat.address
permet de désigner une adresse Ethereum (20bytes). Ici on donne l’adresse du propriétaire du contratowner
-
ffmad Bitcoiner Lightning Network AssoCryptoFR Admina répondu à ffmad le dernière édition par Tulsene
2 - Éditons le temps de vote
On va commencer à remplir notre contrat :
contract Democracy { uint public votingTimeInMinutes ; // Propriétaire du contrat address public owner; // Constructeur function Democracy() { owner = msg.sender; setVotingTime(votingTime); } // Fonction de modification du temps function setVotingTime(uint newVotingTime) { if(msg.sender != owner) throw; votingTimeInMinutes = newVotingTime; } }
uint
est un nombre entier non négatif.votingTimesInMinutes
est une variable destinée à déterminer le temps alloué pour le vote.la fonction
setVotingTime
prends en entrée le temps de vote et une condition si :if(msg.sender != owner) throw;
Cette condition permet d’éjecter tout utilisateur qui ne s’authentifierais pas avec l’adresse du propriétaire du contrat (donc avec sa clé privée).
Etant donné que cette condition va se retrouver souvent dans notre contrat, on va la factoriser :
contract Democracy { uint public votingTimeInMinutes ; // Propriétaire du contrat address public owner; modifier ownerOnly(){ if (msg.sender != owner) throw; _ } // Constructeur function Democracy() { owner = msg.sender; setVotingTime(votingTime); } // Fonction de modification du temps function setVotingTime(uint newVotingTime) ownerOnly() { votingTimeInMinutes = newVotingTime; } }
La fonction
modifier
de Solidity permet de créer une condition qui sera appliquée à toutes les fonctions étendues par celle-ci.Ici ownerOnly() reprends la condition que nous avons défini précédemment et qui donne l’accès uniquement au propriétaire du contrat.
modifier ownerOnly(){ if (msg.sender != owner) throw; _ }
-
3 - Ajout des membres
Le propriétaire du contrat va pouvoir ajouter des votants à notre “démocratie”.
contract Democracy { uint public votingTimeInMinutes ; // Propriétaire du contrat address public owner; // Les membres (tableau adresse / appartenance aux votants) mapping (address => bool) public members; // Auth propriétaire uniquement modifier ownerOnly(){ if (msg.sender != owner) throw; _ } // Auth membre uniquement modifier memberOnly(){ if (!members[msg.sender]) throw; _ } // Constructeur function Democracy() { owner = msg.sender; setVotingTime(votingTime); } // Fonction de modification du temps function setVotingTime(uint newVotingTime) ownerOnly() { votingTimeInMinutes = newVotingTime; } // Ajout des membres function addMember(address newMember) ownerOnly() { members[newMember] = true; } }
mapping
permet de créer un tableau clé -> valeur. Ici avec les membres et leur appartenances au groupe des votants.addMember
prends une adresse ethereum et lui associe la variable true, l’assignant ainsi à la liste des votants (fonction uniquement administrateur). -
4 - Ajout des propositions
On va désormais ajouter les propositions de votes
contract Democracy { uint public votingTimeInMinutes ; // Propriétaire du contrat address public owner; // Les membres (tableau adresse / appartenance aux votants) mapping (address => bool) public members; // Liste des propositions Proposal[] proposals; // Definition de l'objet proposal struct Proposal { string description; mapping (address => bool) voted; bool[] votes; uint end; bool adopted; } // Auth propriétaire uniquement modifier ownerOnly(){ if (msg.sender != owner) throw; _ } // Auth membre uniquement modifier memberOnly(){ if (!members[msg.sender]) throw; _ } // Constructeur function Democracy() { owner = msg.sender; setVotingTime(votingTime); } // Fonction de modification du temps function setVotingTime(uint newVotingTime) ownerOnly() { votingTimeInMinutes = newVotingTime; } // Ajout des membres function addMember(address newMember) ownerOnly() { members[newMember] = true; } // Ajouter une proposition function addProposal(string description) { uint proposalID = proposals.length++; Proposal p = proposals[proposalID]; // Donner la description p.description = description; // Donner le moment de fin de vote p.end = now + votingTimeInMinutes * 1 minutes; } }
On commence par définir notre objet
Proposal
:struct Proposal { string description; mapping (address => bool) voted; bool[] votes; uint end; bool adopted; }
struct
permet de définir un objet. Dans celui-ci on ajoute unedescription
(texte), un tableau associant adresses de votants et si ils ont votévoted
(booléen), un tableau de votesvotes
(booléens), une date de finend
, et un booléen pour dire si la proposition a été adoptée ou nonaopted
On peut ensuite créé un tableau de proposition à partir de notre object
Proposal
:Proposal[] proposals
fonction
addProposal()
Pour finir, on créé une fonction permettant d’ajouter une nouvelle proposition de vote :
function addProposal(string description) memberOnly() { uint proposalID = proposals.length++; Proposal p = proposals[proposalID]; // Donner la description p.description = description; // Donner le moment de fin de vote p.end = now + votingTimeInMinutes * 1 minutes; }
Intuitivement on devrait pouvoir faire proposals.push(Proposal(…)), mais solidity ne gère pas encore très bien les arrays de struct
On utilise donc une technique un peu moins directeLa seule variable que l’on va lui donner c’est la description de la proposition.
A noter :
now
est le timestamp du dernier block miné- le reste des paramètres est mis aux valeurs par défaut
p.adopted = false
,p.votes = []
, etc
-
5 - Votons !
contract Democracy { uint public votingTimeInMinutes ; // Propriétaire du contrat address public owner; // Les membres (tableau adresse / appartenance aux votants) mapping (address => bool) public members; // Liste des propositions Proposal[] proposals; // Definition de l'objet proposal struct Proposal { string description; mapping (address => bool) voted; bool[] votes; uint end; bool adopted; } // Auth propriétaire uniquement modifier ownerOnly(){ if (msg.sender != owner) throw; _ } // Auth membre uniquement modifier memberOnly(){ if (!members[msg.sender]) throw; _ } // Si la proposition correspondant à cet index n'est pas ouverte au vote, la fonction n'est pas exécutée modifier isOpen(uint index) { if(now > proposals[index].end) throw; _ } // Si la proposition correspondant à cet index est fermée au vote, la fonction est exécutée modifier isClosed(uint index) { if(now < proposals[index].end) throw; _ } // Si le compte (msg.sender) a déjà vôté pour cette proposition, la fonction n'est pas exécutée modifier didNotVoteYet(uint index) { if(proposals[index].voted[msg.sender]) throw; _ } // Constructeur function Democracy() { owner = msg.sender; setVotingTime(votingTime); } // Fonction de modification du temps function setVotingTime(uint newVotingTime) ownerOnly() { votingTimeInMinutes = newVotingTime; } // Ajout des membres function addMember(address newMember) ownerOnly() { members[newMember] = true; } // Ajouter une proposition function addProposal(string description) memberOnly() { uint proposalID = proposals.length++; Proposal p = proposals[proposalID]; // Donner la description p.description = description; // Donner le moment de fin de vote p.end = now + votingTimeInMinutes * 1 minutes; } // Voter pour une proposition function vote(uint index, bool vote) memberOnly() isOpen(index) didNotVoteYet(index) { proposals[index].votes.push(vote); proposals[index].voted[msg.sender] = true; } // Obtenir le résultat d'un vote function executeProposal(uint index) isClosed(index) { uint yes; uint no; bool[] votes = proposals[index].votes; // On compte les pour et les contre for(uint counter = 0; counter < votes.length; counter++) { if(votes[counter]) { yes++; } else { no++; } } if(yes > no) { proposals[index].adopted = true; } } }
Les conditions
isOpen
etisClosed
vont vérifier que la date de fin de vote de la propositionindex
est passée ou non. Selon le cas, on pourra faire un nouveau vote via la fonctionvote()
ou obtenir le résultat de la proposition via la fonctionexecuteProposal()
La condition
didNotVoteYet
va vérifier que le compte souhaitant voter ne l’a pas déjà fait.fonction
vote()
function vote(uint index, bool vote) memberOnly() isOpen(index) didNotVoteYet(index) { proposals[index].votes.push(vote); proposals[index].voted[msg.sender] = true; }
push
permet d’ajouter le vote (booléen) à la liste des votes de l’objetproposal
(trouvé à la positionindex
dans la liste des propositions)On va ensuite modifier la variable
voted
associée au votant entrue
(cette variable ayant été mise par défaut àfalse
)fonction
executeProposal()
// Obtenir le résultat d'un vote function executeProposal(uint index) isClosed(index) { uint yes; uint no; bool[] votes = proposals[index].votes; // On compte les pour et les contre for(uint counter = 0; counter < votes.length; counter++) { if(votes[counter]) { yes++; } else { no++; } } if(yes > no) { proposals[index].adopted = true; } }
Cette fonction ne peut être exécutée que si la proposition (trouvée à
index
) est terminée. On va tout simplement parcourir le tableau desvotes
de la proposition et compter combien de votes oui et votes non ont été faits.Si il y a plus de votes oui que de votes non, la fonction va retourner un résultat positif (booléen
true
), sinon rien.Suite
Avec ce code nous en avons donc finit avec notre contrat. Maintenant il serait agréable de pouvoir faire une application permettant de voter en utilisant ce contrat. vous pouvez voir cela dans la partie 2 :