https://github.com/laugharne/testvote
Projet #2 - Système de vote (test unit)
https://github.com/laugharne/testvote
alyra solidity solidity-language test test-unit test-unitaire test-units truffle truffle-framework truffle-testing units
Last synced: 6 months ago
JSON representation
Projet #2 - Système de vote (test unit)
- Host: GitHub
- URL: https://github.com/laugharne/testvote
- Owner: Laugharne
- Created: 2023-06-03T12:54:12.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2023-06-12T15:05:22.000Z (over 2 years ago)
- Last Synced: 2025-02-01T05:41:50.076Z (11 months ago)
- Topics: alyra, solidity, solidity-language, test, test-unit, test-unitaire, test-units, truffle, truffle-framework, truffle-testing, units
- Language: JavaScript
- Homepage:
- Size: 306 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Projet #2 - Système de vote (test unit)
## Sommaire
- [Projet #2 - Système de vote test unit](#projet-2---syst%C3%A8me-de-vote-test-unit)
- [Sommaire](#sommaire)
- [Enoncé](#enonc%C3%A9)
- [Lien énoncé](#lien-%C3%A9nonc%C3%A9)
- [Smart contract proposé](#smart-contract-propos%C3%A9)
- [Explications](#explications)
- [Constantes](#constantes)
- [Extrait #1](#extrait-1)
- [Extrait #2](#extrait-2)
- [Extrait #3](#extrait-3)
- [Les différents vecteurs d'états & variables](#les-diff%C3%A9rents-vecteurs-d%C3%A9tats--variables)
- [Extrait](#extrait)
- [Factorisation](#factorisation)
- [Extraits](#extraits)
- [Déploiement](#d%C3%A9ploiement)
- [Extrait](#extrait)
- [Évolution du processus de vote](#%C3%A9volution-du-processus-de-vote)
- [Les étapes du déroulement du vote sont les suivantes](#les-%C3%A9tapes-du-d%C3%A9roulement-du-vote-sont-les-suivantes)
- [Extrait #1](#extrait-1)
- [Extrait #2](#extrait-2)
- [onlyOwner](#onlyowner)
- [Accès aux fonctions concernées par le modifier **onlyOwner** :](#acc%C3%A8s-aux-fonctions-concern%C3%A9es-par-le-modifier-onlyowner-)
- [Extrait](#extrait)
- [onlyVoters](#onlyvoters)
- [Accès aux fonctions concernées par le modifier **onlyVoters**](#acc%C3%A8s-aux-fonctions-concern%C3%A9es-par-le-modifier-onlyvoters)
- [Extrait](#extrait)
- [Processus de vote](#processus-de-vote)
- [Scénario](#sc%C3%A9nario)
- [Tests avec **Truffle**](#tests-avec-truffle)
- [Versions des outils](#versions-des-outils)
- [Résultat](#r%C3%A9sultat)
### Enoncé
*Oui, nous allons repartir du défi “Système de vote” !*
*Vous repartirez du smart contract proposé en correction*
*Depuis la dernière version, vous avez vu la partie CI/CD avec les tests et le déploiement.*
*Vous devez alors fournir les tests unitaires de votre smart contract Nous n’attendons pas une couverture à 100% du smart contract mais veillez à bien tester les différentes possibilités de retours (event, revert).*
**A rendre :**
- votre lien Github (tests et explication rapide de ce qui a été fait dans un **readme.md**)
##### Lien énoncé
[https://formation.alyra.fr/products/developpeur-blockchain/categories/2149101531/posts/2153206159](https://formation.alyra.fr/products/developpeur-blockchain/categories/2149101531/posts/2153206159)
##### Smart contract proposé
[https://github.com/BenBktech/Promo-Buterin/blob/main/1.Solidity/Voting.sol](https://github.com/BenBktech/Promo-Buterin/blob/main/1.Solidity/Voting.sol)
## Explications
### Constantes
Afin de gagner en lisibilité de lecture du script de test, différentes constantes sont définies.
Les différents états du **Workflow** sont définis ainsi :
##### Extrait #1
```javascript
const RegisteringVoters = BN(0);
const ProposalsRegistrationStarted = BN(1);
const ProposalsRegistrationEnded = BN(2);
const VotingSessionStarted = BN(3);
const VotingSessionEnded = BN(4);
const VotesTallied = BN(5);
```
Les différentes adresses utilisées dans le script de test, sont aussi définies de manière explicites.
##### Extrait #2
```javascript
const OWNER = accounts[0];
const VOTER_1 = accounts[1];
const VOTER_2 = accounts[2];
const VOTER_3 = accounts[3];
```
De même que les différents indexs de proposition et autres constantes.
##### Extrait #3
```javascript
const INDEX_GENESIS = BN(0);
const INDEX_PROPOSAL_1 = BN(1);
const INDEX_PROPOSAL_WINNER = BN(1);
const NN_VOTE = BN(2);
```
### Les différents vecteurs d'états & variables
On peut noter les principaux **vecteurs d'états** dans le code du fichier source "*Voting.sol*"
- WorkflowStatus
- onlyOwner
- onlyVoters
Ainsi que les **variables** qui évoluent en fonctions du déroulement du processus de vote et des intéractions réalisées par le "*owner*" et les votants.
- winningProposalID
- proposalsArray
- voters
Le cas **proposalsArray** est intéressant car à partir du passage à l'étape **ProposalsRegistrationStarted** du déroulement du processus de vote (workflow) il y a **au moins une** proposition - la proposition **GENESIS** - il y a donc un état testable !
##### Extrait
```javascript
let proposalStruct = await voting.getOneProposal(0, {from: _voter1});
assert.equal(proposalStruct.description, "GENESIS", "Not GENESIS proposal");
```
### Factorisation
Des fonctions ont été également réalisées afin de "*regrouper*" des contrôles multiples et systématiques autour de certaines fonctionalités ou processus comme :
- Les changements d'état dans l'**évolution du processus de vote**.
- exceptDefinedStatus()
- expectStatusChangeOk()
- expectStatusScheduling()
- L'**ajout d'un votant** légitime :
- expectAddNewVoter
- Une fonction "*couteau suisse*" **checkGetVoterAndGetProposal()** éffectuant des contrôles sur un votant et la proposition votée, selon :
- Qu'une proposition de vote soit disponible ou pas
- Qu'un votant ait voté pour une proposition donnée
##### Extraits
```javascript
async function exceptDefinedStatus( _voting, _status) {
workflowStatus = (await _voting.workflowStatus());
expect(workflowStatus).to.be.bignumber.equal(_status);
}
async function expectStatusChangeOk( _voting, _prevStatus, _newStatus, _func) {
expectEvent(
_func,
"WorkflowStatusChange", {
previousStatus: _prevStatus,
newStatus : _newStatus,
}
);
exceptDefinedStatus( _voting, _newStatus);
}
```
```javascript
async function expectAddNewVoter( _voting, _address, _owner) {
expectEvent(
await _voting.addVoter( _address, {from: _owner}),
"VoterRegistered",
{voterAddress: _address}
);
voterStruct = await _voting.getVoter( _address, {from: _address});
expect(voterStruct.isRegistered).to.be.true;
expect(voterStruct.hasVoted).to.be.false;
expect(voterStruct.votedProposalId).to.be.bignumber.equal( BN(0));
}
```
### Déploiement
Au déploiement du **contrat**, celui-ci :
- ne possède ni votant
- ne possède ni proposition de vote
- ne possède aucun résultat de vote
- il est juste "*possédé*" par le **owner** qui l'a déployé
- l'état initiale du Workflow est **RegisteringVoters**
##### Extrait
```javascript
it("Ownership", async () => {
expect( await voting.owner()).to.be.bignumber.equal( BN(OWNER));
});
it("Initial workflow status", async () => {
exceptDefinedStatus( voting, RegisteringVoters);
});
it("No voter", async () => {
await expectRevert(
voting.getVoter(OWNER, {from: OWNER}),
"You're not a voter"
);
await expectRevert(
voting.getVoter(VOTER_1, {from: OWNER}),
"You're not a voter"
);
});
it("No proposal", async () => {
// No registered voter, so no access to getOneProposal()
await expectRevert(
voting.getOneProposal(0, {from: OWNER}),
"You're not a voter"
);
await expectRevert(
voting.getOneProposal(0, {from: VOTER_1}),
"You're not a voter"
);
});
it("No result", async () => {
// No result !
await expectRevert(
voting.tallyVotes( {from: OWNER}),
"Current status is not voting session ended"
);
let winningProposalID = (await voting.winningProposalID( {from: OWNER}));
expect(winningProposalID).to.be.bignumber.equal(BN(0));
});
```
### Évolution du processus de vote
Le déroulement du processus de vote, se fait dans une ordre bien défini, de *RegisteringVoters* vers *VotesTallied* (voir tableau plus bas) un **revert** se produit en cas de mauvais enchainement d'états.
Des events précis - **WorkflowStatusChange()** - sont émis lors des changements d'étape.
##### Les étapes du déroulement du vote sont les suivantes
| WorkflowStatus | Descriptions |
| ---------------------------- | ---------------------------- |
| RegisteringVoters | Enregistrement des électeurs |
| ProposalsRegistrationStarted | On récolte les propositions |
| ProposalsRegistrationEnded | On clos les propositions |
| VotingSessionStarted | Le vote est commencé |
| VotingSessionEnded | Le vote est clos |
| VotesTallied | Le dépouillement est fait |
##### Extrait #1
```javascript
expectEvent(
await voting.startProposalsRegistering(),
"WorkflowStatusChange", {
previousStatus: RegisteringVoters,
newStatus : ProposalsRegistrationStarted,
}
);
```
##### Extrait #2
```javascript
await expectRevert(
voting.endVotingSession(),
"Voting session havent started yet"
);
```
### onlyOwner
Les fonctions en accès onlyOwner sont pour la grosse majorité liées à l'évolution de processus de vote, à l'exception de **addVoter()**.
##### Accès aux fonctions concernées par le modifier **onlyOwner** :
| WorkflowStatus | addVoter | startProposalsRegistering | endProposalsRegistering | startVotingSession | endVotingSession | tallyVotes |
| ---------------------------- | -------- | ------------------------- | ----------------------- | ------------------ | ---------------- | ---------- |
| RegisteringVoters | ✅ | ✅ | | | | |
| ProposalsRegistrationStarted | | | ✅ | | | |
| ProposalsRegistrationEnded | | | | ✅ | | |
| VotingSessionStarted | | | | | ✅ | |
| VotingSessionEnded | | | | | | ✅ |
| VotesTallied | | | | | | |
##### Extrait
```javascript
it("If not owner", async () => {
await expectRevert(
voting.addVoter( VOTER_3, {from: VOTER_1}),
"caller is not the owner"
);
await expectRevert(
voting.startProposalsRegistering( {from: VOTER_1}),
"caller is not the owner"
);
await expectRevert(
voting.endProposalsRegistering( {from: VOTER_1}),
"caller is not the owner"
);
await expectRevert(
voting.startVotingSession( {from: VOTER_1}),
"caller is not the owner"
);
await expectRevert(
voting.endVotingSession( {from: VOTER_1}),
"caller is not the owner"
);
await expectRevert(
voting.tallyVotes( {from: VOTER_1}),
"caller is not the owner"
);
});
it("If owner", async () => {
await expectAddNewVoter( voting, VOTER_1, OWNER);
await expectStatusScheduling( voting);
});
```
### onlyVoters
Concernant le vecteur d'états **onlyVoters**, l'accès aux fonctions *getVoter()* et *getOneProposal()* sont testables sans condition particulière autre que **onlyVoters**.
*addProposal()* et *setVote()* nécessite par contre des états particuliers, pour être testés plus profondément. États liés au déroulement du processus de vote (*WorkflowStatus*).
##### Accès aux fonctions concernées par le modifier **onlyVoters**
| WorkflowStatus | getVoter | getOneProposal | addProposal | setVote |
| ---------------------------- | -------- | -------------- | ----------- | ------- |
| RegisteringVoters | ✅ | ✅ | | |
| ProposalsRegistrationStarted | ✅ | ✅ | ✅ | |
| ProposalsRegistrationEnded | ✅ | ✅ | | |
| VotingSessionStarted | ✅ | ✅ | | ✅ |
| VotingSessionEnded | ✅ | ✅ | | |
| VotesTallied | ✅ | ✅ | | |
##### Extrait
```javascript
expectEvent(
await voting.setVote( INDEX_PROPOSAL_1, {from: VOTER_1}),
"Voted", {
voter: VOTER_1,
proposalId: INDEX_PROPOSAL_1
}
);
// voter2 attempt to vote, and fail
await expectRevert(
voting.setVote( INDEX_PROPOSAL_1, {from: VOTER_2}),
"You're not a voter"
);
```
### Processus de vote
##### Scénario
- Deux votants (d'adresses **VOTER_1** et **VOTER_3**) sont ajoutés à la session de vote.
- La période de proposition est ouverte, tandis que l'ajout de votant est clot.
- **VOTER_1** fait une proposition de vote (proposition #1, indice 1), La proposition d'indice 0 étant la **GENESIS** depuis l'ouverture des propostions.
- **VOTER_1** fait à nouveau une proposition de vote, en vain.
- De même pour **VOTER_2** qui n'est même pas un votant enregistré.
- La période de proposition est close.
- La période de vote commence pour les votants enregistrés.
- **VOTER_1** vote pour la proposition #1, avec succès.
- **VOTER_2** vote pour la proposition #1, en vain.
- **VOTER_3** vote pour la proposition #1, avec succès.
- Il y a maintenant **2 votes** pour la **proposition #1**
- Après cloture des votes, le dépouillement est lancé.
- Le proposition **gagnante** est la **proposition #1** (d'indice 1) avec **2 votes**
### Tests avec **Truffle**
Le test du code a été réalisé l'aide des outils suivant :
- **Truffle** : [https://trufflesuite.com/](https://trufflesuite.com/)
- **Ganache** : [https://trufflesuite.com/ganache/](https://trufflesuite.com/ganache/)
- eth-gas-reporter : [https://www.npmjs.com/package/eth-gas-reporter](https://www.npmjs.com/package/eth-gas-reporter)
##### Versions des outils
```bash
Truffle v5.9.2 (core: 5.9.2)
Ganache v7.8.0
Solidity - 0.8.13 (solc-js)
Node v18.16.0
Web3.js v1.10.0
```
##### Résultat
