在建立投票的智能合約前
首先對於使用智能合約來做為投票方式的優點
因此使用智能合約進行投票是有一定的優勢的
話不多說就來建立 一款 公平、公正的投票 智能合約吧!
接下來本篇就依照以下重點進行敘述:
另外關於此篇的GitHub 專案位置
https://github.com/weiawesome/hardhat_tutorial
這次期待做出一個合約 模擬真實選舉的投票
避免合約有漏洞時,能暫時停住所有功能,減少損失
檔案位置: "./contracts/Election.sol"
// SPDX-License-Identifier: MIT
// Solidity 版本
pragma solidity ^0.8.0;
// 合約名稱
contract Election {
}
// 候選人
struct Candidate {
// 候選人的地址
address addr;
// 候選人名稱
string name;
// 候選人政見
string manifesto;
// 候選人保證金
uint256 depositAmount;
// 登記參選與否
bool isRegistered;
}
// 選舉結果
struct VotesResult {
// 候選人的地址
address addr;
// 候選人得票數
uint votes;
}
// 候選人們
mapping(address => Candidate) public candidates;
// 是否已投過票
mapping(address => bool) public hasVoted;
// 當前票形狀態
mapping(address => uint) public voteStatus;
// 合約擁有者
address payable public owner;
// 投票時間
uint public voteTime;
// 開票時間
uint public voteTallyingTime;
// 候選人們的地址
address[] public candidatesAddress;
// 最少返還保證金的投票數
uint private leastVoteCount;
// 保證金的金額
uint private depositCount;
合約是否為暫停狀態
bool private isSuspended;
// 登記為候選人紀錄
event RegisterRecord(address addr,string name,string manifesto,uint time);
// 候選人更改資訊紀錄
event UpdateInfoRecord(address addr,string name,string manifesto,uint time);
// 投票事實記錄
event VoteRecord(address addr,uint time);
// 提領金額紀錄
event DepositRecord(address addr,uint count,uint time);
constructor(uint _voteTime,uint _voteTallyingTime,uint _leastVoteCount,uint _depositCount) payable {
require(
_voteTallyingTime > _voteTime,
"Vote tallying time should be after the vote time"
);
require(
block.timestamp < _voteTime,
"Vote time should be in the future"
);
voteTime = _voteTime;
voteTallyingTime=_voteTallyingTime;
leastVoteCount=_leastVoteCount;
depositCount=_depositCount;
owner = payable(msg.sender);
isSuspended =false;
}
// 僅允許合約擁有者的操作
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
// 檢查合約是否被合約擁有者暫停
modifier contractValid() {
require(isSuspended ==false,"The election is Stopping");
_;
}
// 投票前
modifier beforeVote(){
require(block.timestamp<voteTime,"Error vote has been started!");
_;
}
// 投票時間
modifier inVote(){
require(block.timestamp>voteTime && block.timestamp<voteTallyingTime,"Now is not vote time!");
_;
}
// 投票後(開票時間)
modifier afterVote(){
require(block.timestamp>voteTallyingTime,"Now is not vote tallying time!");
_;
}
// 登記參選
function registerCandidate(string memory _name, string memory _manifesto) public payable contractValid beforeVote{
// 檢查是否登記過與保證金金額
require(!candidates[msg.sender].isRegistered, "Candidate is already registered");
require(msg.value == depositCount, "Error deposit amount");
// 建立候選人資訊
Candidate memory newCandidate = Candidate({
addr : msg.sender,
name: _name,
manifesto: _manifesto,
depositAmount: msg.value,
isRegistered: true
});
// 新增候選人與提交登記參選事件
candidates[msg.sender] = newCandidate;
candidatesAddress.push(msg.sender);
emit RegisterRecord(msg.sender,_name,_manifesto,block.timestamp);
}
// 修改候選人資訊
function updateCandidateInfo(string memory _name, string memory _manifesto) public contractValid beforeVote{
// 檢查候選人是否已經登記
require(candidates[msg.sender].isRegistered, "Candidate is not registered");
// 更改候選人資料
Candidate storage candidateToUpdate = candidates[msg.sender];
candidateToUpdate.name = _name;
candidateToUpdate.manifesto = _manifesto;
// 提交更改資訊事件
emit UpdateInfoRecord(msg.sender,_name,_manifesto,block.timestamp);
}
// 進行投票
function vote(address addr) public contractValid inVote{
// 檢查是否已投過 與 被投票者是否為候選人
require(hasVoted[msg.sender]==false,"The voter has been voted");
require(candidates[addr].isRegistered==true,"The candidate is not exist");
// 完成投票並且設定為已投票
hasVoted[msg.sender]=true;
voteStatus[addr]+=1;
// 新增投票事件
emit VoteRecord(msg.sender,block.timestamp);
}
// 揭露投票結果
function revealVotes() public view contractValid afterVote returns (VotesResult[] memory) {
VotesResult[] memory votesResult = new VotesResult[](candidatesAddress.length);
for (uint i = 0; i < candidatesAddress.length; i++) {
votesResult[i].addr=candidatesAddress[i];
votesResult[i].votes=voteStatus[candidatesAddress[i]];
}
return votesResult;
}
// 保證金返還
function refundDeposit() public contractValid afterVote {
require(address(this).balance>0,"The contract has no more balance!");
for (uint i=0;i<candidatesAddress.length;i++){
if (voteStatus[candidatesAddress[i]]>=leastVoteCount){
address payable addr=payable(candidatesAddress[i]);
addr.transfer(candidates[candidatesAddress[i]].depositAmount);
emit DepositRecord(candidatesAddress[i],candidates[candidatesAddress[i]].depositAmount,block.timestamp);
candidates[candidatesAddress[i]].depositAmount=0;
}
}
uint amount=address(this).balance;
owner.transfer(amount);
emit DepositRecord(owner,amount,block.timestamp);
}
// 獲取所有候選人的資訊
function getCandidates() public view contractValid returns (Candidate[] memory) {
Candidate[] memory result = new Candidate[](candidatesAddress.length);
uint256 index = 0;
for (uint256 i = 0; i < candidatesAddress.length; i++) {
address addr = candidatesAddress[i];
Candidate storage candidate = candidates[addr];
if (candidate.isRegistered) {
result[index] = candidate;
index++;
}
}
return result;
}
// 獲取指定候選人 藉由地址
function getSpecificCandidate(address addr) public view contractValid returns (Candidate memory){
require(candidates[addr].isRegistered==true,"The candidate is not exist!");
return candidates[addr];
}
// 獲取指定候選人 藉由名字 (可能候選人有重名 因此回傳選擇List)
function getSpecificCandidateByName(string memory _name) public view contractValid returns (Candidate[] memory) {
Candidate[] memory result = new Candidate[](candidatesAddress.length);
uint index = 0;
for (uint i = 0; i < candidatesAddress.length; i++) {
if (candidates[candidatesAddress[i]].isRegistered == true && keccak256(bytes(candidates[candidatesAddress[i]].name)) == keccak256(bytes(_name))) {
address addr = candidatesAddress[i];
result[index] = candidates[addr];
index++;
}
}
Candidate[] memory finalResult = new Candidate[](index);
for (uint j = 0; j < index; j++) {
finalResult[j] = result[j];
}
return finalResult;
}
// 合約擁有者暫停合約所有功能
function suspend() public onlyOwner {
isSuspended=true;
}
// 合約擁有者繼續合約所有功能
function stopSuspend() public onlyOwner {
isSuspended=false;
}
在合約部署上 一樣透過 TypeScript 撰寫腳本部署
檔案位置: "./script/election/deploy.ts"
import { ethers } from "hardhat";
async function main() {
const currentTimestampInSeconds = Math.round(Date.now() / 1000);
const voteTime = currentTimestampInSeconds+60*5;
const voteTallyingTime = currentTimestampInSeconds+60*10;
const leastVoteCount = 1;
const basicFoundation = ethers.parseEther("10");
const Contract = await ethers.getContractFactory("Election");
const contract = await Contract.deploy(
voteTime,
voteTallyingTime,
leastVoteCount,
basicFoundation,
{ value: basicFoundation }
);
await contract.waitForDeployment();
console.log(
`Election with BasicFoundation: ${ethers.formatEther(basicFoundation)} ETH\nElection start in timestamp ${voteTime} vote tallying in timestamp ${voteTallyingTime} \nDeployed the contract to ${contract.target}`
);
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
(確保 Ganache 開啟 與 Config 網路設定)
// ./hardhat.config.ts
const config: HardhatUserConfig = {
solidity: "0.8.19",
networks: {
ganache: {
url: "http://localhost:7545",
},
}
};
進行部署:
# 部署合約指令
npx hardhat run .\scripts\election\vote_before\register.ts --network ganache
輸出結果:
重新檢查 Ganache :
帳戶資訊上:
交易資訊上:
// ./scripts/election/vote_before/register.ts
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import {ContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/Election.sol/Election.json").abi;
const [deployer,user_with_index1,user_with_index2,user_with_index3,user_with_index4,user_with_index5] = await ethers.getSigners();
console.log("User's Address: ",user_with_index1.address)
const contractAddress = ContractAddress;
const contract = new Contract(contractAddress, abi, user_with_index1);
const name = 'Tcweeei-Index-1';
const manifesto = '候選人政見';
const depositAmount = ethers.parseEther('10');
await contract.registerCandidate(name, manifesto, { value: depositAmount }).then((result)=>{
console.log("Result: ",result);
console.log('Candidate registered successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
執行指令
npx hardhat run .\scripts\election\vote_before\register.ts --network ganache
Ganache 結果分析
// ./scripts/election/vote_before/update_information.ts
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import {ContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/Election.sol/Election.json").abi;
const [deployer,user_with_index1,user_with_index2,user_with_index3,user_with_index4,user_with_index5] = await ethers.getSigners();
console.log("User's Address: ",deployer.address)
const contractAddress = ContractAddress;
const contract = new Contract(contractAddress, abi, user_with_index5);
const name = 'Super-Tcweeei';
const manifesto = '候選人政見';
await contract.updateCandidateInfo(name, manifesto).then((result)=>{
console.log("Result: ",result);
console.log('Candidate update successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
執行指令
npx hardhat run .\scripts\election\vote_before\update_information.ts --network ganache
// ./scripts/election/any_time/candidates.ts
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import {ContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/Election.sol/Election.json").abi;
const [deployer] = await ethers.getSigners();
console.log("User's Address: ",deployer.address)
const contractAddress = ContractAddress;
const contract = new Contract(contractAddress, abi, deployer);
await contract.getCandidates().then((result)=>{
console.log("Result: ",result);
console.log('Get candidates successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
輸出結果:
// ./scripts/election/any_time/candidates_by_address.ts
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import {ContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/Election.sol/Election.json").abi;
const [deployer,user_with_index1,user_with_index2,user_with_index3,user_with_index4,user_with_index5] = await ethers.getSigners();
console.log("User's Address: ",deployer.address)
const contractAddress = ContractAddress;
const contract = new Contract(contractAddress, abi, deployer);
await contract.getSpecificCandidate(user_with_index1.address).then((result)=>{
console.log("Result: ",result);
console.log('Get candidates by address successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
// ./scripts/election/any_time/candidates_by_name.ts
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import {ContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/Election.sol/Election.json").abi;
const [deployer] = await ethers.getSigners();
console.log("User's Address: ",deployer.address)
const CandidateName="Super-Tcweeei"
const contractAddress = ContractAddress;
const contract = new Contract(contractAddress, abi, deployer);
await contract.getSpecificCandidateByName(CandidateName).then((result)=>{
console.log("Result: ",result);
console.log('Get candidates by name successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
// ./scripts/election/vote/vote.ts
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import {ContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/Election.sol/Election.json").abi;
const [deployer,user_with_index1,user_with_index2,user_with_index3,user_with_index4,user_with_index5] = await ethers.getSigners();
console.log("User's Address: ",deployer.address)
const contractAddress = ContractAddress;
const contract = new Contract(contractAddress, abi, user_with_index5);
await contract.vote(user_with_index5).then((result)=>{
console.log("Result: ",result);
console.log('Vote successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
// ./scripts/election/vote_after/reveal.ts
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import {ContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/Election.sol/Election.json").abi;
const [deployer] = await ethers.getSigners();
const contractAddress = ContractAddress;
const contract = new Contract(contractAddress, abi, deployer);
await contract.revealVotes().then((result)=>{
console.log("Result: ",result)
console.log('Reveal votes successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
輸出結果:
// ./scripts/election/vote_after/refund_deposit.ts
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import {ContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/Election.sol/Election.json").abi;
const [deployer,user_with_index1] = await ethers.getSigners();
console.log("User's Address: ",deployer.address)
const contractAddress = ContractAddress;
const contract = new Contract(contractAddress, abi, user_with_index1);
await contract.refundDeposit().then((result)=>{
console.log("Result: ",result);
console.log('Refund deposit successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
輸出結果:
Ganache 資訊:
// ./scripts/election/admin/suspend.ts
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import {ContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/Election.sol/Election.json").abi;
const [deployer] = await ethers.getSigners();
console.log("User's Address: ",deployer.address)
const contractAddress = ContractAddress;
const contract = new Contract(contractAddress, abi, deployer);
await contract.suspend().then((result)=>{
console.log("Result: ",result);
console.log('Suspended successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
檢查其他合約功能:
// ./scripts/election/admin/continue.ts
import { ethers } from 'hardhat';
import {Contract} from "ethers";
import {ContractAddress} from "../parameters";
async function main() {
const abi = require("../../../artifacts/contracts/Election.sol/Election.json").abi;
const [deployer] = await ethers.getSigners();
console.log("User's Address: ",deployer.address)
const contractAddress = ContractAddress;
const contract = new Contract(contractAddress, abi, deployer);
await contract.stopSuspend().then((result)=>{
console.log("Result: ",result);
console.log('Stop suspended successfully');
}).catch((e)=>{
console.log("Error: ",e);
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
輸出結果:
// ./scripts/election/any_time/events_register.ts
import {ethers} from "hardhat";
import {ContractAddress} from "../parameters";
async function main() {
const [deployer]=await ethers.getSigners();
const contractAddress = ContractAddress;
const contractABI = require("../../../artifacts/contracts/Election.sol/Election.json").abi;
const contract = new ethers.Contract(contractAddress, contractABI, deployer);
const filter = contract.filters.RegisterRecord();
const events = await contract.queryFilter(filter);
for (let i = 0; i < events.length; i++) {
// @ts-ignore
console.log("Index: ",i," Value: ",events[i].args)
}
}
main().then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
輸出結果:
// ./scripts/election/any_time/events_update.ts
import {ethers} from "hardhat";
import {ContractAddress} from "../parameters";
async function main() {
const [deployer]=await ethers.getSigners();
const contractAddress = ContractAddress;
const contractABI = require("../../../artifacts/contracts/Election.sol/Election.json").abi;
const contract = new ethers.Contract(contractAddress, contractABI, deployer);
const filter = contract.filters.UpdateInfoRecord();
const events = await contract.queryFilter(filter);
for (let i = 0; i < events.length; i++) {
// @ts-ignore
console.log("Index: ",i," Value: ",events[i].args)
}
}
main().then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
輸出結果:
// ./scripts/election/any_time/events_vote.ts
import {ethers} from "hardhat";
import {ContractAddress} from "../parameters";
async function main() {
const [deployer]=await ethers.getSigners();
const contractAddress = ContractAddress;
const contractABI = require("../../../artifacts/contracts/Election.sol/Election.json").abi;
const contract = new ethers.Contract(contractAddress, contractABI, deployer);
const filter = contract.filters.VoteRecord();
const events = await contract.queryFilter(filter);
for (let i = 0; i < events.length; i++) {
// @ts-ignore
console.log("Index: ",i," Value: ",events[i].args)
}
}
main().then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
輸出結果:
// ./scripts/election/any_time/events_deposit.ts
import {ethers} from "hardhat";
import {ContractAddress} from "../parameters";
async function main() {
const [deployer]=await ethers.getSigners();
const contractAddress = ContractAddress;
const contractABI = require("../../../artifacts/contracts/Election.sol/Election.json").abi;
const contract = new ethers.Contract(contractAddress, contractABI, deployer);
const filter = contract.filters.DepositRecord();
const events = await contract.queryFilter(filter);
for (let i = 0; i < events.length; i++) {
// @ts-ignore
console.log("Index: ",i," Value: ",events[i].args)
}
}
main().then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
輸出結果:
GitHub 專案位置: https://github.com/weiawesome/hardhat_tutorial
開始真心感覺區塊鏈對於現實生活還是有正面影響了嗎?
(並不是都只有炒幣)
透過智能合約確實能達到許多效果
可以達到自動化、安全、去中心化等等效果
但其實像是種透過投票的合約
還能配合 零知識證明 可以更具有匿名且認證的效果
希望透過這篇能理解
剛結束關於 透過 智能合約 撰寫 完成投票的例子
是不是對於 智能合約 還躍躍欲試啊!!!
感覺這個還有須多優點呢! 感覺還能做點甚麼!
不錯不錯! 那就再來一回吧!
知道甚麼是群眾募資(眾籌)(Crowdfunding)
這如果尬上區塊鏈 可以長成甚麼樣子呢?