Decentralized Crowd Voting Platform Solidity, JavaScript
👤 Sharing: AI
Okay, here's an example of a decentralized crowd voting platform implemented using Solidity and JavaScript, along with explanations:
**Solidity Smart Contract (CrowdVote.sol):**
```solidity
pragma solidity ^0.8.0;
contract CrowdVote {
// Struct to represent a proposal
struct Proposal {
string name;
uint256 voteCount;
bool exists;
}
// Mapping from proposal name to Proposal struct
mapping(string => Proposal) public proposals;
// Array to store proposal names for easy iteration
string[] public proposalNames;
// Mapping from voter address to a boolean indicating whether they've voted
mapping(address => bool) public hasVoted;
// Address of the contract owner (deployer)
address public owner;
// Event emitted when a vote is cast
event VoteCast(address indexed voter, string indexed proposalName);
// Event emitted when a proposal is added
event ProposalAdded(string proposalName);
// Modifier to restrict certain functions to the owner only
modifier onlyOwner() {
require(msg.sender == owner, "Only the contract owner can call this function.");
_;
}
// Constructor: sets the owner
constructor() {
owner = msg.sender;
}
// Function to add a proposal (only callable by the owner)
function addProposal(string memory _name) public onlyOwner {
require(bytes(_name).length > 0, "Proposal name cannot be empty.");
require(!proposals[_name].exists, "Proposal already exists.");
proposals[_name] = Proposal(_name, 0, true);
proposalNames.push(_name);
emit ProposalAdded(_name);
}
// Function to cast a vote
function vote(string memory _proposalName) public {
require(!hasVoted[msg.sender], "You have already voted.");
require(proposals[_proposalName].exists, "Proposal does not exist.");
proposals[_proposalName].voteCount++;
hasVoted[msg.sender] = true;
emit VoteCast(msg.sender, _proposalName);
}
// Function to get the vote count for a proposal
function getVoteCount(string memory _proposalName) public view returns (uint256) {
require(proposals[_proposalName].exists, "Proposal does not exist.");
return proposals[_proposalName].voteCount;
}
// Function to get all proposal names
function getAllProposalNames() public view returns (string[] memory) {
return proposalNames;
}
// Function to check if a user has voted
function hasUserVoted(address _user) public view returns (bool) {
return hasVoted[_user];
}
}
```
**Explanation of the Solidity Contract:**
1. **`pragma solidity ^0.8.0;`**: Specifies the Solidity compiler version to use. It's good practice to lock in a specific version or range.
2. **`contract CrowdVote { ... }`**: Defines the smart contract named `CrowdVote`.
3. **`struct Proposal { ... }`**: Defines a structure (`struct`) to hold information about each proposal.
* `string name`: The name of the proposal.
* `uint256 voteCount`: The number of votes the proposal has received.
* `bool exists`: Indicates if the proposal has been added to the contract. This helps prevent errors when trying to access a non-existent proposal.
4. **`mapping(string => Proposal) public proposals;`**: A mapping (like a dictionary) that associates proposal names (strings) with their corresponding `Proposal` structs. The `public` keyword automatically creates a getter function for this mapping (e.g., you can call `proposals("Proposal 1")` from JavaScript to get the `Proposal` struct).
5. **`string[] public proposalNames;`**: An array to store proposal names in the order they were added. This allows you to easily iterate through the proposals.
6. **`mapping(address => bool) public hasVoted;`**: A mapping that keeps track of which addresses have already voted. This prevents double voting.
7. **`address public owner;`**: Stores the address of the contract's owner (the account that deployed the contract).
8. **`event VoteCast(address indexed voter, string indexed proposalName);`**: Defines an event that is emitted when a vote is successfully cast. Events are used to notify the outside world (like your JavaScript application) about changes that have occurred on the blockchain. `indexed` keyword allows for efficient filtering of events.
9. **`event ProposalAdded(string proposalName);`**: Defines an event that is emitted when a proposal is successfully added.
10. **`modifier onlyOwner() { ... }`**: Defines a modifier. Modifiers are used to add checks to functions and make sure that only authorized accounts can call them. In this case, it checks if `msg.sender` (the address calling the function) is the same as the `owner` address.
11. **`constructor() { ... }`**: The constructor is a special function that is executed only once, when the contract is deployed. Here, it sets the `owner` to the address that deployed the contract.
12. **`function addProposal(string memory _name) public onlyOwner { ... }`**: Allows the owner to add a new proposal to the voting system.
* It checks if the proposal name is empty and if a proposal with the same name already exists.
* It creates a new `Proposal` struct and stores it in the `proposals` mapping.
* It adds the proposal name to the `proposalNames` array.
* It emits the `ProposalAdded` event.
13. **`function vote(string memory _proposalName) public { ... }`**: Allows a user to cast a vote for a proposal.
* It checks if the user has already voted and if the proposal exists.
* It increments the `voteCount` for the selected proposal.
* It sets `hasVoted[msg.sender]` to `true` to prevent the user from voting again.
* It emits the `VoteCast` event.
14. **`function getVoteCount(string memory _proposalName) public view returns (uint256) { ... }`**: Allows anyone to check the number of votes a proposal has received. The `view` keyword indicates that this function does not modify the contract's state.
15. **`function getAllProposalNames() public view returns (string[] memory) { ... }`**: Returns a list of all proposal names. This is useful for displaying the list of options to the user.
16. **`function hasUserVoted(address _user) public view returns (bool) { ... }`**: Checks whether a specific user has voted or not. This is useful for displaying voting status on the client side.
**JavaScript (Web3.js/Ethers.js) Frontend:**
This is a basic example that uses Web3.js (or Ethers.js) to interact with the smart contract from a web page. You'll need to have a local Ethereum development environment (like Ganache) running and a web server to serve the HTML file. You'll also need Metamask installed in your browser.
```html
<!DOCTYPE html>
<html>
<head>
<title>Decentralized Crowd Voting</title>
<script src="https://cdn.jsdelivr.net/npm/web3@1.7.0/dist/web3.min.js"></script> <!-- Or use Ethers.js -->
<style>
body { font-family: sans-serif; }
#voteInterface { display: none; }
#results { margin-top: 20px; }
</style>
</head>
<body>
<h1>Decentralized Crowd Voting</h1>
<div id="connectionStatus">Connecting to Metamask...</div>
<div id="adminInterface" style="display: none;">
<h2>Admin Panel</h2>
<input type="text" id="newProposalName" placeholder="Proposal Name">
<button onclick="addProposal()">Add Proposal</button>
</div>
<div id="voteInterface">
<h2>Vote</h2>
<select id="proposalSelect"></select>
<button onclick="castVote()">Vote</button>
<p id="voteStatus"></p>
</div>
<div id="results">
<h2>Results</h2>
<ul id="resultsList"></ul>
</div>
<script>
// Contract address and ABI (replace with your deployed contract's information)
const contractAddress = "YOUR_CONTRACT_ADDRESS"; // Replace with the address of your deployed contract
const contractABI = [
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "string",
"name": "proposalName",
"type": "string"
}
],
"name": "ProposalAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "voter",
"type": "address"
},
{
"indexed": true,
"internalType": "string",
"name": "proposalName",
"type": "string"
}
],
"name": "VoteCast",
"type": "event"
},
{
"inputs": [
{
"internalType": "string",
"name": "_name",
"type": "string"
}
],
"name": "addProposal",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getAllProposalNames",
"outputs": [
{
"internalType": "string[]",
"name": "",
"type": "string[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_proposalName",
"type": "string"
}
],
"name": "getVoteCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_user",
"type": "address"
}
],
"name": "hasUserVoted",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"name": "proposals",
"outputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "uint256",
"name": "voteCount",
"type": "uint256"
},
{
"internalType": "bool",
"name": "exists",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "proposalNames",
"outputs": [
{
"internalType": "string[]",
"name": "",
"type": "string[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_proposalName",
"type": "string"
}
],
"name": "vote",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
];
let web3;
let contract;
let accounts;
async function init() {
// Check if Metamask is installed
if (typeof window.ethereum !== 'undefined') {
try {
// Request account access
await window.ethereum.request({ method: 'eth_requestAccounts' });
// Initialize Web3
web3 = new Web3(window.ethereum);
// Get accounts
accounts = await web3.eth.getAccounts();
// Initialize contract
contract = new web3.eth.Contract(contractABI, contractAddress);
// Update connection status
document.getElementById("connectionStatus").innerText = "Connected to Metamask!";
// Check if current account is the owner
const owner = await contract.methods.owner().call();
if (accounts[0].toLowerCase() === owner.toLowerCase()) {
document.getElementById("adminInterface").style.display = "block";
}
// Load proposals and results
await loadProposals();
await updateResults();
document.getElementById("voteInterface").style.display = "block";
} catch (error) {
console.error("Error connecting to Metamask:", error);
document.getElementById("connectionStatus").innerText = "Error connecting to Metamask.";
}
} else {
document.getElementById("connectionStatus").innerText = "Metamask not detected. Please install Metamask.";
}
}
async function addProposal() {
const proposalName = document.getElementById("newProposalName").value;
if (!proposalName) {
alert("Please enter a proposal name.");
return;
}
try {
await contract.methods.addProposal(proposalName).send({ from: accounts[0] });
alert("Proposal added!");
document.getElementById("newProposalName").value = "";
await loadProposals(); // Reload proposals after adding
await updateResults();
} catch (error) {
console.error("Error adding proposal:", error);
alert("Error adding proposal.");
}
}
async function loadProposals() {
const proposalNames = await contract.methods.getAllProposalNames().call();
const proposalSelect = document.getElementById("proposalSelect");
proposalSelect.innerHTML = ""; // Clear existing options
for (const name of proposalNames) {
const option = document.createElement("option");
option.value = name;
option.text = name;
proposalSelect.appendChild(option);
}
}
async function castVote() {
const proposalName = document.getElementById("proposalSelect").value;
try {
// Check if the user has already voted
const hasVoted = await contract.methods.hasUserVoted(accounts[0]).call();
if (hasVoted) {
alert("You have already voted!");
return;
}
document.getElementById("voteStatus").innerText = "Voting...";
await contract.methods.vote(proposalName).send({ from: accounts[0] });
document.getElementById("voteStatus").innerText = "Vote cast!";
await updateResults();
} catch (error) {
console.error("Error casting vote:", error);
document.getElementById("voteStatus").innerText = "Error casting vote.";
}
}
async function updateResults() {
const proposalNames = await contract.methods.getAllProposalNames().call();
const resultsList = document.getElementById("resultsList");
resultsList.innerHTML = ""; // Clear existing results
for (const name of proposalNames) {
const voteCount = await contract.methods.getVoteCount(name).call();
const listItem = document.createElement("li");
listItem.innerText = `${name}: ${voteCount} votes`;
resultsList.appendChild(listItem);
}
}
// Initialize the application when the page loads
window.onload = init;
</script>
</body>
</html>
```
**Explanation of the JavaScript Frontend:**
1. **HTML Structure:**
* Basic HTML structure with headings, input fields, buttons, and sections for displaying voting options and results.
* Includes a link to the Web3.js library from a CDN.
* Includes a `style` section for basic CSS styling.
2. **Web3.js Setup:**
* `contractAddress`: **Important:** Replace `"YOUR_CONTRACT_ADDRESS"` with the actual address of your deployed smart contract on the blockchain.
* `contractABI`: The ABI (Application Binary Interface) is a JSON array that describes the contract's functions, events, and data structures. You can get the ABI from the Solidity compiler (e.g., Remix IDE). The ABI tells Web3.js how to interact with your smart contract.
* `web3`: The Web3.js instance, which provides the connection to the Ethereum blockchain through Metamask.
* `contract`: A Web3.js contract object that represents your deployed `CrowdVote` contract. This object allows you to call functions on the smart contract.
* `accounts`: An array containing the user's Ethereum accounts (addresses) from Metamask.
3. **`init()` Function:**
* This function is called when the page loads (`window.onload = init;`).
* It checks if Metamask is installed.
* It requests account access from Metamask (`window.ethereum.request({ method: 'eth_requestAccounts' });`).
* It initializes the `web3` and `contract` objects.
* It calls `loadProposals()` and `updateResults()` to populate the UI with data from the contract.
* It fetches the owner of the contract and show the admin panel only to the owner.
4. **`addProposal()` Function:**
* This function is called when the "Add Proposal" button is clicked.
* It retrieves the proposal name from the input field.
* It calls the `addProposal()` function on the smart contract, sending the proposal name as a parameter. The `send({ from: accounts[0] })` part specifies which account is calling the function (this account will pay the gas fee).
* After successfully adding the proposal, it calls `loadProposals()` and `updateResults()` to refresh the UI.
5. **`loadProposals()` Function:**
* This function retrieves the list of proposal names from the `getAllProposalNames()` function on the smart contract.
* It populates the `<select>` element (the dropdown list) with the proposal options.
6. **`castVote()` Function:**
* This function is called when the "Vote" button is clicked.
* It retrieves the selected proposal name from the dropdown list.
* It calls the `vote()` function on the smart contract, sending the proposal name as a parameter.
* It updates the UI to show a "Voting..." message and then "Vote cast!" after the transaction is confirmed.
* It calls `updateResults()` to refresh the vote counts.
7. **`updateResults()` Function:**
* This function retrieves the list of proposal names from the `getAllProposalNames()` function on the smart contract.
* It iterates through the proposal names and calls the `getVoteCount()` function on the smart contract for each proposal.
* It updates the `<ul>` element (the results list) with the proposal names and their corresponding vote counts.
**How to Run This Example:**
1. **Install Dependencies:** You'll need Node.js and npm (Node Package Manager) installed.
2. **Install Ganache:** Ganache is a local Ethereum blockchain simulator. You can download and install it from [https://www.trufflesuite.com/ganache](https://www.trufflesuite.com/ganache).
3. **Install Truffle:** Truffle is a development framework for Ethereum. Open your terminal and run:
```bash
npm install -g truffle
```
4. **Create a Project Directory:** Create a new directory for your project (e.g., `crowd-voting`).
5. **Initialize Truffle:** Inside the project directory, run:
```bash
truffle init
```
This will create a basic Truffle project structure.
6. **Create the Solidity Contract:** Save the `CrowdVote.sol` file into the `contracts` directory in your Truffle project.
7. **Create a Migration File:** Create a new file in the `migrations` directory (e.g., `2_deploy_crowd_vote.js`) with the following content:
```javascript
const CrowdVote = artifacts.require("CrowdVote");
module.exports = function (deployer) {
deployer.deploy(CrowdVote);
};
```
8. **Compile the Contract:** Run:
```bash
truffle compile
```
This will compile your Solidity contract into bytecode.
9. **Deploy the Contract:**
* Start Ganache.
* Run:
```bash
truffle migrate
```
This will deploy your contract to the Ganache blockchain. Note the contract address that is output in the terminal.
10. **Update the JavaScript Frontend:**
* In the `index.html` file, replace `"YOUR_CONTRACT_ADDRESS"` with the contract address that was output during the deployment process.
* Make sure the `contractABI` variable in the JavaScript code matches the ABI generated by the Solidity compiler. You can find this ABI in the `build/contracts/CrowdVote.json` file after compiling.
11. **Set up a Web Server:** You can use a simple HTTP server to serve the HTML file. For example, you can use `http-server`:
```bash
npm install -g http-server
http-server . // Run this from your project directory
```
12. **Open in Browser:** Open your web browser and go to the address provided by `http-server` (usually `http://localhost:8080`).
13. **Use Metamask:**
* Make sure you have Metamask installed in your browser.
* Connect Metamask to the Ganache network (usually `http://127.0.0.1:7545`).
* Import one of the Ganache accounts into Metamask (Ganache provides a set of pre-funded accounts).
Now you should be able to interact with your decentralized crowd voting application!
**Important Considerations:**
* **Security:** This is a simplified example. Real-world smart contracts require careful security audits to prevent vulnerabilities.
* **Gas Costs:** Each transaction on the Ethereum blockchain costs gas. You'll need to consider gas optimization in your smart contract design.
* **Scalability:** Ethereum has limitations in terms of transaction throughput. For large-scale voting, you might need to explore layer-2 scaling solutions.
* **User Experience:** The user experience can be improved with more sophisticated front-end frameworks (e.g., React, Vue.js) and better error handling.
* **Decentralization:** While the contract logic is decentralized, relying on a centralized web server to host the front-end can be a single point of failure. Consider using decentralized storage solutions like IPFS for hosting the front-end.
This example provides a basic foundation. You can extend it with features like:
* Time-based voting periods
* Weighted voting (based on token ownership, for example)
* More robust error handling
* A more user-friendly interface.
👁️ Viewed: 8
Comments