// SPDX-License-Identifier: MIT pragma solidity 0.8.20; import "@openzeppelin/contracts/access/Ownable.sol"; /** * @title Voting * @dev Contrat pour un système de vote décentralisé * @custom:dev-run-script ./scripts/deploy.js */ contract Voting is Ownable { /** * @dev Structure représentant un votant * @param isRegistered Si l'adresse est enregistrée comme électeur * @param hasVoted Si l'électeur a déjà voté * @param votedProposalId L'ID de la proposition pour laquelle l'électeur a voté */ struct Voter { bool isRegistered; bool hasVoted; uint votedProposalId; } /** * @dev Structure représentant une proposition * @param description La description textuelle de la proposition * @param voteCount Le nombre de votes reçus par la proposition */ struct Proposal { string description; uint voteCount; } /** * @dev Énumération des différentes étapes du processus de vote */ enum WorkflowStatus { RegisteringVoters, ProposalsRegistrationStarted, ProposalsRegistrationEnded, VotingSessionStarted, VotingSessionEnded, VotesTallied } /** * @dev Événement émis lors de l'enregistrement d'un électeur * @param voterAddress L'adresse de l'électeur enregistré */ event VoterRegistered(address voterAddress); /** * @dev Événement émis lors du changement de statut du workflow * @param previousStatus Le statut précédent * @param newStatus Le nouveau statut */ event WorkflowStatusChange(WorkflowStatus previousStatus, WorkflowStatus newStatus); /** * @dev Événement émis lors de l'enregistrement d'une proposition * @param proposalId L'ID de la proposition enregistrée */ event ProposalRegistered(uint proposalId); /** * @dev Événement émis lorsqu'un électeur vote * @param voter L'adresse de l'électeur * @param proposalId L'ID de la proposition pour laquelle il a voté */ event Voted(address voter, uint proposalId); // Mapping des électeurs mapping(address => Voter) private voters; // Liste des propositions Proposal[] private proposals; // Statut actuel du workflow WorkflowStatus public workflowStatus; // ID de la proposition gagnante uint public winningProposalId; // Dates limites pour les différentes phases (fonctionnalité supplémentaire 1) uint public registrationEndTime; uint public votingEndTime; // Mapping pour la délégation de vote (fonctionnalité supplémentaire 2) mapping(address => address) private delegations; /** * @dev Constructeur qui initialise le contrat */ constructor() Ownable() { workflowStatus = WorkflowStatus.RegisteringVoters; // Ajout d'une proposition vide à l'index 0 proposals.push(Proposal("Genesis Proposal", 0)); } /** * @dev Modificateur pour vérifier si l'appelant est un électeur enregistré */ modifier onlyVoters() { require(voters[msg.sender].isRegistered, "Vous n'etes pas un electeur enregistre"); _; } /** * @dev Enregistre un nouvel électeur * @param _voterAddress L'adresse de l'électeur à enregistrer */ function registerVoter(address _voterAddress) external onlyOwner { require(workflowStatus == WorkflowStatus.RegisteringVoters, "La periode d'inscription des electeurs est terminee"); require(!voters[_voterAddress].isRegistered, "L'electeur est deja enregistre"); voters[_voterAddress].isRegistered = true; emit VoterRegistered(_voterAddress); } /** * @dev Démarre la session d'enregistrement des propositions * @param _durationInMinutes Durée en minutes de la période d'enregistrement */ function startProposalsRegistration(uint _durationInMinutes) external onlyOwner { require(workflowStatus == WorkflowStatus.RegisteringVoters, "Impossible de demarrer la session d'enregistrement"); WorkflowStatus previousStatus = workflowStatus; workflowStatus = WorkflowStatus.ProposalsRegistrationStarted; // Définir une date limite pour l'enregistrement (fonctionnalité supplémentaire) registrationEndTime = block.timestamp + (_durationInMinutes * 1 minutes); emit WorkflowStatusChange(previousStatus, workflowStatus); } /** * @dev Enregistre une nouvelle proposition * @param _description La description de la proposition */ function registerProposal(string calldata _description) external onlyVoters { require(workflowStatus == WorkflowStatus.ProposalsRegistrationStarted, "La session d'enregistrement des propositions n'est pas active"); require(bytes(_description).length > 0, "La description ne peut pas etre vide"); require(block.timestamp <= registrationEndTime, "La periode d'enregistrement des propositions est terminee"); proposals.push(Proposal(_description, 0)); emit ProposalRegistered(proposals.length - 1); } /** * @dev Termine la session d'enregistrement des propositions */ function endProposalsRegistration() external onlyOwner { require(workflowStatus == WorkflowStatus.ProposalsRegistrationStarted, "Impossible de terminer la session d'enregistrement"); WorkflowStatus previousStatus = workflowStatus; workflowStatus = WorkflowStatus.ProposalsRegistrationEnded; emit WorkflowStatusChange(previousStatus, workflowStatus); } /** * @dev Démarre la session de vote * @param _durationInMinutes Durée en minutes de la période de vote */ function startVotingSession(uint _durationInMinutes) external onlyOwner { require(workflowStatus == WorkflowStatus.ProposalsRegistrationEnded, "Impossible de demarrer la session de vote"); WorkflowStatus previousStatus = workflowStatus; workflowStatus = WorkflowStatus.VotingSessionStarted; // Définir une date limite pour le vote (fonctionnalité supplémentaire) votingEndTime = block.timestamp + (_durationInMinutes * 1 minutes); emit WorkflowStatusChange(previousStatus, workflowStatus); } /** * @dev Soumet un vote pour une proposition * @param _proposalId L'ID de la proposition */ function vote(uint _proposalId) external onlyVoters { require(workflowStatus == WorkflowStatus.VotingSessionStarted, "La session de vote n'est pas active"); require(!voters[msg.sender].hasVoted, "Vous avez deja vote"); require(_proposalId < proposals.length, "La proposition n'existe pas"); require(block.timestamp <= votingEndTime, "La periode de vote est terminee"); voters[msg.sender].hasVoted = true; voters[msg.sender].votedProposalId = _proposalId; // Si l'électeur a délégué son vote à quelqu'un, ne pas compter le vote ici if (delegations[msg.sender] == address(0)) { proposals[_proposalId].voteCount++; } emit Voted(msg.sender, _proposalId); } /** * @dev Délègue son vote à un autre électeur (fonctionnalité supplémentaire 2) * @param _to L'adresse à qui déléguer le vote */ function delegateVoteTo(address _to) external onlyVoters { require(workflowStatus == WorkflowStatus.VotingSessionStarted, "La session de vote n'est pas active"); require(!voters[msg.sender].hasVoted, "Vous avez deja vote"); require(_to != msg.sender, "Vous ne pouvez pas deleguer votre vote a vous-meme"); require(voters[_to].isRegistered, "L'adresse cible n'est pas un electeur enregistre"); require(delegations[msg.sender] == address(0), "Vous avez deja delegue votre vote"); address currentDelegate = _to; // Vérifier qu'il n'y a pas de boucle de délégation while (delegations[currentDelegate] != address(0)) { require(delegations[currentDelegate] != msg.sender, "Boucle de delegation detectee"); currentDelegate = delegations[currentDelegate]; } delegations[msg.sender] = _to; // Si la personne à qui on délègue a déjà voté, ajouter immédiatement le vote if (voters[_to].hasVoted) { uint proposalId = voters[_to].votedProposalId; proposals[proposalId].voteCount++; voters[msg.sender].hasVoted = true; voters[msg.sender].votedProposalId = proposalId; emit Voted(msg.sender, proposalId); } } /** * @dev Termine la session de vote */ function endVotingSession() external onlyOwner { require(workflowStatus == WorkflowStatus.VotingSessionStarted, "Impossible de terminer la session de vote"); WorkflowStatus previousStatus = workflowStatus; workflowStatus = WorkflowStatus.VotingSessionEnded; emit WorkflowStatusChange(previousStatus, workflowStatus); } /** * @dev Comptabilise les votes et détermine la proposition gagnante */ function tallyVotes() external onlyOwner { require(workflowStatus == WorkflowStatus.VotingSessionEnded, "Le comptage des votes ne peut pas encore commencer"); WorkflowStatus previousStatus = workflowStatus; workflowStatus = WorkflowStatus.VotesTallied; uint winningVoteCount = 0; for (uint i = 0; i < proposals.length; i++) { if (proposals[i].voteCount > winningVoteCount) { winningVoteCount = proposals[i].voteCount; winningProposalId = i; } } emit WorkflowStatusChange(previousStatus, workflowStatus); } /** * @dev Termine automatiquement les phases si les délais sont dépassés (fonctionnalité supplémentaire 1) */ function checkAndUpdateStatus() external { if (workflowStatus == WorkflowStatus.ProposalsRegistrationStarted && block.timestamp > registrationEndTime) { WorkflowStatus previousStatus = workflowStatus; workflowStatus = WorkflowStatus.ProposalsRegistrationEnded; emit WorkflowStatusChange(previousStatus, workflowStatus); } else if (workflowStatus == WorkflowStatus.VotingSessionStarted && block.timestamp > votingEndTime) { WorkflowStatus previousStatus = workflowStatus; workflowStatus = WorkflowStatus.VotingSessionEnded; emit WorkflowStatusChange(previousStatus, workflowStatus); } } /** * @dev Récupère les informations d'un électeur * @param _voterAddress L'adresse de l'électeur * @return isRegistered Si l'électeur est enregistré * @return hasVoted Si l'électeur a voté * @return votedProposalId L'ID de la proposition pour laquelle l'électeur a voté */ function getVoter(address _voterAddress) external view onlyVoters returns (bool isRegistered, bool hasVoted, uint votedProposalId) { Voter memory voter = voters[_voterAddress]; return (voter.isRegistered, voter.hasVoted, voter.votedProposalId); } /** * @dev Récupère les informations d'une proposition * @param _proposalId L'ID de la proposition * @return description La description de la proposition * @return voteCount Le nombre de votes pour la proposition */ function getProposal(uint _proposalId) external view returns (string memory description, uint voteCount) { require(_proposalId < proposals.length, "La proposition n'existe pas"); Proposal memory proposal = proposals[_proposalId]; return (proposal.description, proposal.voteCount); } /** * @dev Récupère la proposition gagnante * @return winningProposalIndex L'ID de la proposition gagnante * @return description La description de la proposition gagnante * @return voteCount Le nombre de votes pour la proposition gagnante */ function getWinner() external view returns (uint winningProposalIndex, string memory description, uint voteCount) { require(workflowStatus == WorkflowStatus.VotesTallied, "Les votes n'ont pas encore ete comptabilises"); return (winningProposalId, proposals[winningProposalId].description, proposals[winningProposalId].voteCount); } /** * @dev Récupère le nombre total de propositions * @return Le nombre de propositions */ function getProposalsCount() external view returns (uint) { return proposals.length; } /** * @dev Vérifie à qui un électeur a délégué son vote (fonctionnalité supplémentaire 2) * @param _voterAddress L'adresse de l'électeur * @return L'adresse à qui le vote a été délégué, ou address(0) si pas de délégation */ function getDelegation(address _voterAddress) external view onlyVoters returns (address) { return delegations[_voterAddress]; } }