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 adresse

    Les 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/

    0_1455138767160_dd8f59d05a28f5fb0d1d0abd0c5912ba.png

    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 contrat owner



  • 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 une description (texte), un tableau associant adresses de votants et si ils ont voté voted(booléen), un tableau de votes votes(booléens), une date de fin end, et un booléen pour dire si la proposition a été adoptée ou non aopted

    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 directe

    La 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 et isClosed vont vérifier que la date de fin de vote de la proposition index est passée ou non. Selon le cas, on pourra faire un nouveau vote via la fonction vote() ou obtenir le résultat de la proposition via la fonction executeProposal()

    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’objet proposal (trouvé à la position index dans la liste des propositions)

    On va ensuite modifier la variable voted associée au votant en true (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 des votes 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 :