Voting Platform Solidity
👤 Sharing: AI
```solidity
pragma solidity ^0.8.0;
contract Voting {
// Struct to represent a candidate
struct Candidate {
uint id;
string name;
uint voteCount;
}
// Mapping to store candidates based on their ID
mapping(uint => Candidate) public candidates;
// Array to store candidate IDs (for easier iteration)
uint[] public candidateIds;
// Mapping to store who has voted for which candidate
mapping(address => uint) public voters;
// Event to emit when a vote is cast
event VoteCasted(address voter, uint candidateId);
// Event to emit when a candidate is added
event CandidateAdded(uint id, string name);
// Counter to keep track of the next available candidate ID
uint public nextCandidateId;
// Constructor to initialize candidates (optional)
constructor() {
nextCandidateId = 1; //Start Candidate ID at 1 instead of 0 for readability
// Example initial candidates. You can remove this or modify it.
addCandidate("Candidate A");
addCandidate("Candidate B");
addCandidate("Candidate C");
}
// Function to add a candidate
function addCandidate(string memory _name) public {
require(bytes(_name).length > 0, "Candidate name cannot be empty."); //Ensure name isn't empty
candidates[nextCandidateId] = Candidate(nextCandidateId, _name, 0);
candidateIds.push(nextCandidateId);
emit CandidateAdded(nextCandidateId, _name);
nextCandidateId++;
}
// Function to cast a vote
function vote(uint _candidateId) public {
// Check if the sender has already voted
require(voters[msg.sender] == 0, "You have already voted.");
// Check if the candidate exists
require(candidates[_candidateId].id != 0, "Candidate does not exist.");
// Cast the vote
candidates[_candidateId].voteCount++;
// Record the voter and their vote
voters[msg.sender] = _candidateId;
// Emit the VoteCasted event
emit VoteCasted(msg.sender, _candidateId);
}
// Function to get the total number of candidates
function getCandidateCount() public view returns (uint) {
return candidateIds.length;
}
// Function to get a candidate's details by ID
function getCandidate(uint _candidateId) public view returns (uint id, string memory name, uint voteCount) {
require(candidates[_candidateId].id != 0, "Candidate does not exist.");
Candidate storage candidate = candidates[_candidateId]; // Use storage pointer for efficiency
return (candidate.id, candidate.name, candidate.voteCount);
}
// Function to get the ID of a candidate at a specific index
function getCandidateIdAtIndex(uint _index) public view returns (uint) {
require(_index < candidateIds.length, "Index out of bounds.");
return candidateIds[_index];
}
// Function to get the vote count for a specific candidate
function getVoteCount(uint _candidateId) public view returns (uint) {
require(candidates[_candidateId].id != 0, "Candidate does not exist.");
return candidates[_candidateId].voteCount;
}
// Function to check if a specific address has voted
function hasVoted(address _voter) public view returns (bool) {
return voters[_voter] != 0;
}
// Function to get the candidate an address voted for (returns 0 if they haven't voted)
function getVotedCandidate(address _voter) public view returns (uint) {
return voters[_voter];
}
}
```
Key improvements and explanations:
* **`pragma solidity ^0.8.0;`**: Specifies the Solidity compiler version. `^0.8.0` means it's compatible with version 0.8.0 and later (but potentially not version 0.9.0, for example, due to breaking changes in later major releases). **Crucially important** for compatibility and to avoid unexpected behavior due to compiler changes. Version 0.8.0 introduced important safety features like automatic overflow/underflow checks, making it a good baseline.
* **`struct Candidate`**: Defines a structure to hold candidate information: `id`, `name`, and `voteCount`. Using a struct organizes the data related to a candidate.
* **`mapping(uint => Candidate) public candidates;`**: A mapping to store candidates, indexed by their unique `id`. `public` makes this mapping accessible from outside the contract (read-only), allowing retrieval of candidate data. This is a *much* more efficient way to store and retrieve candidates by ID than iterating through an array.
* **`uint[] public candidateIds;`**: An array storing the *IDs* of the candidates. This is vital for being able to iterate through the candidates in a predictable order. Mappings cannot be directly iterated over. This array provides a way to loop through candidates by their ID.
* **`mapping(address => uint) public voters;`**: A mapping to track which candidate each address voted for. The `address` is the voter's Ethereum address, and the `uint` is the `candidateId` they voted for. A value of `0` means the voter hasn't voted. `public` enables checking from the frontend.
* **`event VoteCasted(address voter, uint candidateId);`**: An event that's emitted whenever a vote is successfully cast. Events are crucial for front-end applications to be notified of state changes on the blockchain (e.g., to update the UI when someone votes). Events are *much* cheaper than reading directly from storage.
* **`event CandidateAdded(uint id, string name);`**: An event that is emitted when a new candidate is added to the voting system. This is also important for keeping the front-end synced.
* **`uint public nextCandidateId;`**: A counter to automatically assign unique IDs to candidates. This prevents ID collisions. Initialized to 1 in the constructor for better user readability.
* **`constructor()`**: A constructor that is executed only once, when the contract is deployed. This is where you can initialize the contract's state (e.g., add initial candidates). It is marked as `payable` if it needs to receive Ether during contract creation, but this is not necessary here.
* **`addCandidate(string memory _name)`**: A function to add a new candidate to the election. Takes the candidate's name as input.
* `require(bytes(_name).length > 0, "Candidate name cannot be empty.");`: Input validation: Ensures the candidate's name is not empty. Prevents adding candidates with blank names. Critically important to prevent unexpected behavior. The `bytes(_name).length` checks if the name is not an empty string.
* `candidates[nextCandidateId] = Candidate(nextCandidateId, _name, 0);`: Creates a new `Candidate` struct and stores it in the `candidates` mapping, using `nextCandidateId` as the key. The initial `voteCount` is set to 0.
* `candidateIds.push(nextCandidateId);`: Adds the new candidate's ID to the `candidateIds` array.
* `emit CandidateAdded(nextCandidateId, _name);`: Emits the `CandidateAdded` event.
* `nextCandidateId++;`: Increments `nextCandidateId` so the next candidate gets a unique ID.
* **`vote(uint _candidateId)`**: A function that allows a user to cast a vote for a specific candidate.
* `require(voters[msg.sender] == 0, "You have already voted.");`: Checks if the sender (the voter) has already voted. The mapping `voters` will contain the candidate ID they voted for, or `0` if they haven't voted yet.
* `require(candidates[_candidateId].id != 0, "Candidate does not exist.");`: Checks if the specified candidate exists by checking if the `id` in the mapping `candidates` is not zero. If an ID does not exist it's default value will be `0`.
* `candidates[_candidateId].voteCount++;`: Increments the `voteCount` of the selected candidate.
* `voters[msg.sender] = _candidateId;`: Records that the sender has voted for the specified candidate.
* `emit VoteCasted(msg.sender, _candidateId);`: Emits the `VoteCasted` event.
* **`getCandidateCount()`**: Returns the total number of candidates. Uses the `candidateIds` array's length.
* **`getCandidate(uint _candidateId)`**: Retrieves a candidate's details (ID, name, vote count) given their ID.
* `require(candidates[_candidateId].id != 0, "Candidate does not exist.");`: Checks if the candidate exists. Important for preventing errors.
* `Candidate storage candidate = candidates[_candidateId];`: Using `storage` is more efficient here. When reading from a mapping multiple times it is more efficient to store the pointer to the mapping value in a variable and use that in subsequent readings.
* Returns the candidate details.
* **`getCandidateIdAtIndex(uint _index)`**: Gets a candidate's ID at a specific index in the `candidateIds` array. Essential for frontends to iterate through candidates.
* **`getVoteCount(uint _candidateId)`**: Returns the vote count for a given candidate ID.
* `require(candidates[_candidateId].id != 0, "Candidate does not exist.");`: Checks if the candidate exists before attempting to retrieve the vote count.
* **`hasVoted(address _voter)`**: Checks if a given address has already voted. Returns `true` if they have, `false` otherwise.
* **`getVotedCandidate(address _voter)`**: Returns the candidate ID that a given address voted for. Returns 0 if they have not voted.
Key improvements and explanations (repeated for emphasis):
* **Error Handling with `require()`**: Uses `require()` statements to check for invalid conditions (e.g., voting twice, voting for a non-existent candidate, empty candidate names). `require()` statements are *crucial* for preventing unexpected behavior and making your contract more robust. They automatically revert the transaction if the condition is not met, preventing incorrect state changes. The error messages in `require()` are displayed to the user (or the calling contract), making debugging much easier.
* **Events**: Emits events (`VoteCasted`, `CandidateAdded`) when votes are cast and candidates are added. Events are essential for front-end applications to track changes on the blockchain and update the UI. They're much more efficient than repeatedly querying the contract's state.
* **`candidateIds` array**: Essential for iteration. Mappings are *not* iterable, so you need a separate array to keep track of the candidate IDs in a specific order.
* **Clearer Variable Names**: Uses descriptive variable names (e.g., `nextCandidateId`, `candidateIds`, `voters`).
* **Comments**: Includes detailed comments to explain the purpose of each function and variable.
* **Security Considerations**: The code includes checks to prevent double voting and ensures that only valid candidates can be voted for.
* **`storage` keyword:** Using the `storage` keyword for efficiency when accessing values from a mapping multiple times.
* **Input Validation**: Input validation to prevent empty candidate names.
This improved version addresses the previous issues and provides a more robust, secure, and usable voting contract. Remember to thoroughly test your smart contract before deploying it to a live environment. Compile with Solidity version 0.8.0 or higher.
👁️ Viewed: 5
Comments