Un Projet de Vote sur la Blockchain Ethereum

ESENS
14 min readMar 21, 2019
La blockchain Ethereum pour implémenter un système de vote transparent et infalsifiable

Depuis que j’ai commencé à m’intéresser à Ethereum et ses ‘Smart Contracts’, j’ai découvert un nouveau monde qui se construit et qui se prépare à remplacer tout ce que l’on connaît. Sans parler de tendance, un nouveau paradigme apparaît avec ses opportunités et c’est la chose à retenir quand on a dépassé tout le bruit qui peut entourer la blockchain.

Aujourd’hui nous allons développer le Smart Contract d’une Application Décentralisée (ou ÐApp). On utilisera la blockchain Ethereum pour implémenter un système de vote transparent et infalsifiable. Cela peut paraître simple, mais une bonne implémentation est loin de l’être.

Si vous n’êtes pas familier avec les concepts de la blockchain, n’hésitez pas à regarder cette vidéo que je trouve très intéressante.

Et si vous avez besoin d’une introduction à Ethereum et Solidity, essayez le tutoriel de CryptoZombies qui est vraiment génial pour apprendre ce nouveau langage et ses subtilités.

Accrochez vos ceintures, on est prêt à partir !

Qu’est-ce qui fait qu’une Ðapp est une Ðapp ?

La Ðapp que nous allons développer permettra aux utilisateurs de créer et de participer à des scrutins avec n’importe qui dans le monde. Mais à quoi ça ressemble ?

- Les Smart Contracts de la dapp ne sont contrôlés par aucune autorité centrale (même pas nous).

- Il n’y a pas de “back-end”, toutes les transactions données sont envoyées directement avec la blockchain et les mineurs les exécutent sur le smart contract impliqué.

- Les Smart Contracts définissent les règles appliquées sur les données et les échanges de monnaie et personne ne peut les modifier.

- Le code des Smart Contracts est publique, auditable et vérifiable.

- Le site web de la Ðapp doit être composé uniquement de fichiers statiques et ne reposer que sur la blockchain en tant que base de données.

Développement du Smart Contract

Lorsque l’on développe des Smart Contracts, il faut s’assurer qu’il n’y a pas de faille dans les méthodes exposées car toute transaction minée ne peut plus être modifiée ou annulée.

De plus, chaque opération a un coût pour l’utilisateur. Cela peut paraître déroutant, mais c’est bien dans ce mécanisme que réside la force d’Ethereum. Celle-ci introduit une unité de puissance de calcul pour chaque appel de contrat. Cette unité permet de mesurer l’effort de calcul et d’espace de stockage que les mineurs devront délivrer aux utilisateurs pour traiter leurs transactions. Il faut donc faire très attention au code de notre smart contract pour ne pas vider les portefeuilles de nos utilisateurs avec des fonctions inutilement gourmandes en ressources. N’hésitez pas à vérifier la complexité algorithmique et d’espace de vos algorithmes.

En multipliant le coût en gaz de la complexité de la transaction avec les frais du mineur, vous obtenez la rémunération du mineur.

Mais alors en quoi est-ce une force pour Ethereum ? Et bien tout d’abord, chaque transactions coûtant de “l’argent”, le gaz est donc un rempart concret aux attaques par déni de service. Le réseau Ethereum ne risque donc pas de tomber. Deuxième effet direct, les mineurs sont rémunérés pour mettre à disposition de la puissance de calcul.

Pour plus de détail, jetez un œil à cet article.

Voilà pourquoi je vous conseille de bien tester votre contrat avant de l’uploader sur Ethereum. Il serait même judicieux de déployer votre smart contrat sur un réseau de test comme Ropsten qui vous permettra d’utiliser votre smart contrat dans les mêmes conditions que le réseau principal d’Ethereum.

Nous utiliserons le Truffle Framework pour développer la logique et tester notre Ðapp.

Truffle Framework

Assurez vous d’avoir installé NodeJS sur votre machine, et installez Truffle :

[sudo] npm install -g truffle

Créez un répertoire de travail et réinitialisez le projet

$ cd blockchain
$ truffle init
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!
Commands:
Compile: truffle compile
Migrate: truffle migrate
Test contracts: truffle test

Ensuite si nous exécutons ‘ truffle test’, plusieurs choses vont se dérouler :

- Truffle compilera tous les smart contracts dans ‘contracts’ avec ‘solc’.

- Les Smart contracts seront déployés sur une blockchain locale éphémère.

- Tous les tests dans les fichiers ‘test/*.sol’ seront lancés.

- Tous les tests dans les fichiers ‘test/*.js’ seront lancés par Mocha.

Etant donné qu’aucun test n’est défini, les tests termineront en succès 0/0.

Fonctionnalités de la Ðapp

Avant de se lancer dans le code, nous allons définir ce que l’application est censée faire.

Les fonctionnalités principales seront :

- Jean peut créer un scrutin

- Jean peut ajouter des propositions à un scrutin

- Jean peut soumettre un vote

- Jean peut suivre les résultats d’un scrutin

Cas d’erreurs

Les cas d’erreurs que le smart contracts devra gérer sont les suivants :

- Jean ne peut pas rajouter de proposition à un scrutin qu’il n’a pas créé

- Jean ne peut soumettre un vote dans un scrutin qu’une seule fois

Opérations du contrat

createScrutin (_name)

Créer un scrutin avec le nom spécifié.

createProposal(_scrutinId, _description)

Ajouter une proposition à un scrutin donné.

submitVote(_propositionId)

Soumettre un vote sur une proposition.

Les événements suivants seront également disponibles :

event VoteSubmitted(uint _scrutinId, uint _propositionId, uint _counter);
event ScrutinCreated(uint _scrutinId, bytes32 _name, address _scrutinOwner);
event ProposalCreated(uint _propositionId, uint _scrutinId, bytes32 _description);

Vous devriez avoir un contrat minimal qui ressemble à ceci :

// contracts/Vote.sol
pragma solidity ^0.4.24;

contract Vote {
address public owner;

event VoteSubmitted(uint _scrutinId, uint _propositionId, uint _counter);
event ScrutinCreated(uint _scrutinId, string _name, address _scrutinOwner);
event ProposalCreated(uint _propositionId, uint _scrutinId, string _description);

constructor() public {
owner = msg.sender;
}

function kill() external {
require(msg.sender == owner, "Only the owner can kill this contract");
selfdestruct(owner);
}

function createScrutin(string _name) public {
revert();
}

function createProposal(uint _scrutinId, string _description) public {
revert();
}

function submitVote(uint _propositionId) public {
revert();
}
}

Tout cela devra être implémenté de la manière la plus simple, efficace et la moins onéreuse possible car chaque transaction coûte du gas. Nous essayerons de faire une implémentation légère.

Tests du contrat

Le grand avantage des Smart Contracts face à des contrats traditionnels est qu’ils sont testables comme n’importe quel code.

C’est comme si un avocat pouvait simuler 1000 procès en 30 secondes pour s’assurer que son contrat n’ait aucune faille.

Le Test Driven Development est donc tout indiqué pour nous. Créons donc un fichier

test/TestVote.js

Vous aurez remarqué que j’ai décidé de faire des tests en javascript car je trouve qu’ils couvrent plus de fonctionnalités que les tests en solidity qui ne permettent que de tester le code interne du smart contract.

En effet le test en javascript se placera au même niveau que l’application que nous écrirons plus tard pour interagir avec le contrat. Si le test passe, il y’a de grandes chances que l’implémentation de l’application frontend se passe sans encombre.

On parle donc de test d’intégration et plus précisément de Narrow Testing car les tests du smart contract s’effectueront sur un contrat fraîchement déployé sur le client de truffle, c’est la notion de clean-room.

Cependant si vous voulez également tester le contrat en solidity pour faire des test unitaire et ainsi renforcer la couverture de test, n’hésitez pas à regarder la documentation.

Création d’un scrutin

// test/TestVote.js
const Vote = artifacts.require('Vote');
const truffleAssert = require('truffle-assertions');

contract('Vote', (accounts) => {
let contract;
const ownerAccount = accounts[0];

// build up and tear down a new Vote contract before each test
beforeEach(async () => {
contract = await Vote.new({from: ownerAccount});
});

it("should emit a ScrutinCreated event when createScrutin", async () => {
let newScrutinName = 'new scrutin';
tx = await contract.createScrutin(newScrutinName);
truffleAssert.eventEmitted(tx, "ScrutinCreated", (ev) => {
return ev._scrutinId.c[0] === 0 && ev._scrutinOwner === ownerAccount && ev._name === 'new scrutin';
});
});

afterEach(async () => {
await contract.kill({from: ownerAccount});
});
});

Nous avons donc initié la suite de tests pour le contrat de Vote et spécifié un premier test. Celui-ci vérifie que si l’on appelle la méthode ‘createScrutin()’ du contrat, un scrutin sera bel et bien créé et qu’un événement ‘ScrutinCreated’ sera envoyé.

Lançons maintenant ce test :

truffle testContract: Vote
1) should emit a ScrutinCreated event when createScrutin
> No events were emitted
0 passing (130ms)
1 failing
1) Contract: Vote
should emit a ScrutinCreated event when createScrutin:
Error: VM Exception while processing transaction: revert

Le test a donc échoué sur la transaction, ce qui est tout à fait normal car nous n’avons pas encore implémenté la fonction.

Repassons donc maintenant sur le smart contract afin de faire passer le test.

// contracts/Vote.sol
pragma solidity ^0.4.24;

contract Vote {
struct Scrutin {
string name;
address scrutinOwner;
bool isStarted;
}

Scrutin[] public scrutins;

event ScrutinCreated(uint _scrutinId, string _name, address _scrutinOwner);

// ...

function createScrutin(string _name) public {
uint _scrutinId = scrutins.push(Scrutin(_name, msg.sender, false)) - 1;
emit ScrutinCreated(_scrutinId, _name, msg.sender);
}

// ...
}

Et relançons les tests :

truffle testContract: Vote
✓ should emit a ScrutinCreated event when createScrutin (57ms)
1 passing (141ms)

Bravo, notre première fonctionnalité est correctement implémentée !

Nous avons introduit une struct nommée Scrutin et une liste de celle-ci afin de stocker tous les scrutins dans notre contrat :

struct Scrutin {
string name;
address scrutinOwner;
bool isStarted;
}
Scrutin[] public scrutins;

Et enfin dans la méthode ‘createScrutin()’ nous initialisons un nouveau scrutin avec le nom spécifié, nous ajoutons celui-ci à la liste des scrutins du contrats et enfin nous émettons un événement de création de contrat.

Vous aurez également remarqué que je ne mets pas à disposition de méthode pour retrouver un scrutin par son identifiant mais que j’utilise un événement pour renvoyer l’objet créé. J’aime utiliser cette méthode car du côté de l’application frontend, je peux récupérer tous les événements émis depuis la création du smart contract et récupérer ainsi tous les scrutins créés. J’utiliserai ce mécanisme tout au long du développement de ce smart contract…

Nous allons voir à présent comment ajouter une proposition à ce scrutin et comment soumettre un vote.

Ajout d’une proposition à un scrutin

Pour ce deuxième test nous allons introduire la notion de proposition d’un scrutin. Un scrutin peut contenir plusieurs propositions pour lesquelles les utilisateurs pourront voter.

Retournons donc sur la suite de tests et rajoutons un test couvrant cette fonctionnalité.

// test/TestVote.js
const Vote = artifacts.require('Vote');
const truffleAssert = require('truffle-assertions');

contract('Vote', (accounts) => {
let contract;
const ownerAccount = accounts[0];

// build up and tear down a new Vote contract before each test
beforeEach(async () => {
contract = await Vote.new({from: ownerAccount});
});

// ...

it("should emit a ProposalCreated event when createProposal is called on an existing Scrutin", async () => {
const newScrutinName = 'Best fast food ?';
let newScrutinId;

const tx = await contract.createScrutin(newScrutinName);
truffleAssert.eventEmitted(tx, "ScrutinCreated", ev => {
newScrutinId = ev._scrutinId.c[0];
return newScrutinId === 0 && ev._scrutinOwner === ownerAccount && ev._name === newScrutinName;
});
const newProposalDescription = 'Macdonalds';
const txProposal = await contract.createProposal(newScrutinId, newProposalDescription);
truffleAssert.eventEmitted(txProposal, "ProposalCreated", ev => {
return ev._propositionId.c[0] === 0 &&
ev._scrutinId.c[0] === newScrutinId &&
ev._description === newProposalDescription;
});
});

afterEach(async () => {
await contract.kill({from: ownerAccount});
});
});

Relançons les tests :

truffle testContract: Vote
✓ should emit a ScrutinCreated event when createScrutin (60ms)
1) should emit a ProposalCreated event when createProposal is called on an existing Scrutin
Events emitted during test:
---------------------------
ScrutinCreated(_scrutinId: 0, _name: Best fast food ?, _scrutinOwner: 0x627306090abab3a6e1400e9345bc60c78a8bef57)
---------------------------
1 passing (286ms)
1 failing
1) Contract: Vote
should emit a ProposalCreated event when createProposal is called on an existing Scrutin:
Error: VM Exception while processing transaction: revert

Évidemment, le second test a échoué. Allons implémenter la fonctionnalité sur le Smart Contract :

// contracts/Vote.sol
pragma solidity ^0.4.24;

contract Vote {
address public owner;

struct Scrutin {
string name;
address scrutinOwner;
bool isStarted;
}

struct Proposition {
uint scrutinId;
string description;
uint counter;
}

Scrutin[] public scrutins;
Proposition[] propositions;

event ScrutinCreated(uint _scrutinId, string _name, address _scrutinOwner);
event ProposalCreated(uint _propositionId, uint _scrutinId, string _description);

constructor() public {
owner = msg.sender;
}

function kill() external {
require(msg.sender == owner, "Only the owner can kill this contract");
selfdestruct(owner);
}

function createScrutin(string _name) public {
uint _scrutinId = scrutins.push(Scrutin(_name, msg.sender, false)) - 1;
emit ScrutinCreated(_scrutinId, _name, msg.sender);
}

function createProposal(uint _scrutinId, string _description) public {
Scrutin storage scrutin = scrutins[_scrutinId];
require(scrutin.scrutinOwner == msg.sender);
uint _propositionId = propositions.push(Proposition(_scrutinId, _description, 0)) - 1;
emit ProposalCreated(_propositionId, _scrutinId, _description);
}

// ...
}

Relançons les tests :

truffle testContract: Vote    
✓ should emit a ScrutinCreated event when createScrutin (58ms)
✓ should emit a ProposalCreated event when createProposal is called on an existing Scrutin (88ms)
2 passing (303ms)

Félicitations ! Notre deuxième fonctionnalité est implémentée.

Pour cela nous avons défini une nouvelle ‘struct’ et une liste pour la stocker :

struct Proposition {   
uint scrutinId;
string description;
uint counter;
}
Proposition[] propositions;

Et dans la méthode ‘createProposal()’ nous vérifions que le scrutin appartient bien à la même personne qui soumet la proposition puis nous initialisons la proposition et lançons un événement de création de proposition.

function createProposal(uint _scrutinId, string _description) public {   
Scrutin storage scrutin = scrutins[_scrutinId];
require(scrutin.scrutinOwner == msg.sender);
uint _propositionId = propositions.push(Proposition(_scrutinId, _description, 0)) - 1;
emit ProposalCreated(_propositionId, _scrutinId, _description);
}

Afin de s’assurer de la partie vérification nous pouvons rajouter un test sur l’ajout d’une proposition sur un scrutin que nous n’avons pas créé :

// test/TestVote.js
const Vote = artifacts.require('Vote');
const truffleAssert = require('truffle-assertions');

contract('Vote', (accounts) => {
let contract;
const ownerAccount = accounts[0];

// build up and tear down a new Vote contract before each test
beforeEach(async () => {
contract = await Vote.new({from: ownerAccount});
});

// …

it("should fail if proposal is submitted on a scrutin that we don't own", async () => {
const newScrutinName = 'Best fast food ?';
let newScrutinId;

let scrutinOwner = accounts[1];
newScrutinId = await scrutinCreated(newScrutinName, scrutinOwner, newScrutinId);
const newProposalDescription = 'Macdonalds';
truffleAssert.fails(contract.createProposal(newScrutinId, newProposalDescription));
});

async function scrutinCreated(newScrutinName, scrutinOwner, newScrutinId) {
const tx = await contract.createScrutin(newScrutinName, {from: scrutinOwner});
truffleAssert.eventEmitted(tx, "ScrutinCreated", ev => {
newScrutinId = ev._scrutinId.c[0];
return newScrutinId === 0 && ev._scrutinOwner === scrutinOwner && ev._name === newScrutinName;
});
return newScrutinId;
}

afterEach(async () => {
await contract.kill({from: ownerAccount});
});
});

Avec pour résultat :

truffle testContract: Vote    
✓ should emit a ScrutinCreated event when createScrutin (60ms)
✓ should emit a ProposalCreated event when createProposal is called on an existing Scrutin (92ms)
✓ should fail if proposal is submitted on a scrutin that we don't own (49ms)
3 passing (435ms)

Soumettre un vote

Jusqu’à maintenant, nous pouvons créer un scrutin et ajouter des propositions à celui-ci.

La suite logique serait de pouvoir voter pour une proposition.

Commençons par écrire un petit test d’un cas passant :

// test/TestVote.js
const Vote = artifacts.require('Vote');
const truffleAssert = require('truffle-assertions');

contract('Vote', (accounts) => {
let contract;
const ownerAccount = accounts[0];

// build up and tear down a new Vote contract before each test
beforeEach(async () => {
contract = await Vote.new({from: ownerAccount});
});

// ...

it("should emit a VoteSubmitted event if proposal exist", async () => {
const newScrutinName = 'Best fast food ?';
let newScrutinId;
let newProposalId;

newScrutinId = await scrutinCreated(newScrutinName, ownerAccount);
const newProposalDescription = 'Macdonalds';
newProposalId = await proposalCreated(newScrutinId, newProposalDescription);
const txVoteSubmission = await contract.submitVote(newProposalId);
truffleAssert.eventEmitted(txVoteSubmission, "VoteSubmitted", ev => {
return ev._scrutinId.c[0] === newScrutinId &&
ev._propositionId.c[0] === newProposalId &&
ev._counter.c[0] === 1;
});
});

async function scrutinCreated(newScrutinName, scrutinOwner) {
let newScrutinId;
const tx = await contract.createScrutin(newScrutinName, {from: scrutinOwner});
truffleAssert.eventEmitted(tx, "ScrutinCreated", ev => {
newScrutinId = ev._scrutinId.c[0];
return newScrutinId === 0 && ev._scrutinOwner === scrutinOwner && ev._name === newScrutinName;
});
return newScrutinId;
}

async function proposalCreated(newScrutinId, newProposalDescription) {
let newProposalId;
const txProposal = await contract.createProposal(newScrutinId, newProposalDescription);
truffleAssert.eventEmitted(txProposal, "ProposalCreated", ev => {
newProposalId = ev._propositionId.c[0];
return ev._propositionId.c[0] === 0 &&
ev._scrutinId.c[0] === newScrutinId &&
ev._description === newProposalDescription;
});
return newProposalId;
}

afterEach(async () => {
await contract.kill({from: ownerAccount});
});
});

Je vous passe l’exécution du test, mais évidemment il est en échec.

Passons à l’implémentation côté Smart Contrat :

// contracts/Vote.sol
pragma solidity ^0.4.24;

contract Vote {
// ...
event VoteSubmitted(uint _scrutinId, uint _propositionId, uint _counter);
// ...
function submitVote(uint _propositionId) public {
Proposition storage proposition = propositions[_propositionId];
proposition.counter++;
emit VoteSubmitted(proposition.scrutinId, _propositionId, proposition.counter);
}
}

Maintenons lançons le test :

truffle testContract: Vote    
✓ should emit a ScrutinCreated event when createScrutin (55ms)
✓ should emit a ProposalCreated event when createProposal is called on an existing Scrutin (89ms)
✓ should fail if proposal is submitted on a scrutin that we don't own (45ms)
✓ should emit a VoteSubmitted event if proposal exist (96ms)
4 passing (584ms)

Bravo ! Nous pouvons maintenant voter !

Cependant on aimerait peut être limiter la participation à un seul vote par scrutin par utilisateur.

Ajoutons donc un test pour cette vérification :

// test/TestVote.js

// ...
it("should fail if user has already voted on a scrutin", async () => {
const newScrutinName = 'Best fast food ?';
let newScrutinId;
let newProposalId;

newScrutinId = await scrutinCreated(newScrutinName, ownerAccount);
const newProposalDescription = 'Macdonalds';
newProposalId = await proposalCreated(newScrutinId, newProposalDescription);
await contract.submitVote(newProposalId);
truffleAssert.fails(contract.submitVote(newProposalId));
});

// ...

En exécutant le test,nous aurons comme message :

Contract: Vote    
✓ should emit a ScrutinCreated event when createScrutin (56ms)
✓ should emit a ProposalCreated event when createProposal is called on an existing Scrutin (92ms)
✓ should fail if proposal is submitted on a scrutin that we don't own (45ms)
✓ should emit a VoteSubmitted event if proposal exist (110ms)
✓ should fail if user has already voted on a scrutin (91ms)
1) "after each" hook 5 passing (765ms) 1 failing
1) Contract: Vote "after each" hook: Uncaught AssertionError: Did not fail

L’appel n’a pas échoué, modifions maintenant la méthode pour prendre en compte ce cas :

// contracts/Vote.sol
pragma solidity ^0.4.24;

contract Vote {

// …

Proposition[] propositions;

mapping(address => mapping(uint => bool)) private isScrutinVoted;

event VoteSubmitted(uint _scrutinId, uint _propositionId, uint _counter);

// ...

function submitVote(uint _propositionId) public {
Proposition storage proposition = propositions[_propositionId];
require(isScrutinVoted[msg.sender][proposition.scrutinId] == false);
proposition.counter++;
isScrutinVoted[msg.sender][proposition.scrutinId] = true;
emit VoteSubmitted(proposition.scrutinId, _propositionId, proposition.counter);
}
}

Le résultat des tests :

Contract: Vote    
✓ should emit a ScrutinCreated event when createScrutin (58ms)
✓ should emit a ProposalCreated event when createProposal is called on an existing Scrutin (94ms)
✓ should fail if proposal is submitted on a scrutin that we don't own (44ms)
✓ should emit a VoteSubmitted event if proposal exist (98ms)
✓ should fail if user has already voted on a scrutin (94ms)
5 passing (754ms)

Félicitations ! Nous ne pouvons voter qu’une seule fois par scrutin. Pour faire cette vérification, j’ai utilisé un ‘mapping’. C’est une structure de donnée semblable à une map qui va me donner pour une entrée donnée, le résultat associé.

Ici, j’ai enchaîné deux mapping de la façon suivante :

mapping(address => mapping(uint => bool)) private isScrutinVoted;

Le mapping ‘isScrutinVoted’ va tout d’abord prendre en entrée l’adresse d’un utilisateur pour nous donner un deuxième mapping qui prendra en entrée l’identifiant d’un scrutin pour enfin nous retourner ‘vrai’ ou ‘faux’ à la question ‘est-ce que cette utilisateur a déjà participer à ce scrutin ?’.

La notion de mapping est très importante car, comme dit plus haut, chaque transaction coûte du gaz. Si nous avions codé une boucle for pour vérifier si l’utilisateur avait déjà participé au scrutin, nous aurions dû maintenir une liste de vote et itérer sur chaque élément. Notre algorithme aurait donc eu une complexité de O(n). Cela veut dire que dans le pire des cas, nous aurions parcouru tous les éléments de la liste si celui qui nous intéressait était en dernier. Chaque opération inutile aurait donc été facturée directement à l’utilisateur.

Notre but étant de réduire au maximum les coûts d’utilisation de notre Ðapp, j’utilise donc un mapping qui permet de retrouver l’élément que nous cherchons avec une complexité de 0(1). L’élément est toujours retrouvé du premier coup, nous ne gâchons donc pas de puissance de calcul chez les mineurs.

Revenons en à la méthode ‘submitVote()’. Au premier appel, la vérification passera en succès car la valeur du mapping est à ‘false’, c’est la valeur par défaut.

require(isScrutinVoted[msg.sender][proposition.scrutinId] == false);

Et une fois que le compteur de vote de la proposition est incrémenté, nous passons la valeur du mapping à ‘vrai’, l’utilisateur a déjà participé au scrutin :

isScrutinVoted[msg.sender][proposition.scrutinId] = true

On ne peut donc pas voter plusieurs fois dans un même scrutin.

Conclusion

Notre Smart Contract est implémenté, testé et prêt à être déployé. Nous avons donc 99 lignes de tests pour 55 lignes pour le Smart Contract.

Ces tests ont permis de s’assurer que les interactions avec le Smart Contract soient bien implémentées comme défini et éviter quelques bugs qui auraient pu se glisser sans tests.

Donc s’il vous plaît, n’oubliez pas de tester votre code avant la mise à disposition à vos utilisateurs !

Le code est sûrement améliorable, n’hésitez pas à me faire des commentaires sur le GitHub du projet.

Pour continuer à jouer avec le contrat, je vous propose quelques pistes pour l’enrichir :

  • ajouter une date de début d’un scrutin
  • ajouter une date de clôture d’un scrutin
  • proposer des scrutins ouvert aux propositions d’autres utilisateurs

Dans un prochain article nous développerons la partie front-end web en JavaScript pour interagir avec le Smart Contract.

Le code complet est disponible ici : https://github.com/esensconsulting/tdd-vote-on-ethereum

Article rédigé par Anthony Marques | Retrouvez tous nos articles sur le Blog ESENS

Vous êtes à la recherche d’un nouveau challenge ? Rejoignez l’équipe ESENS en postulant à nos offres d’emploi !

--

--