功能梳理
通过智能合约实现去中心化投票
- 主持人创建投票主题与内容
- 给予“选民”在选票上投票的权利。
- 把你的投票委托给投票人。
- 投票
- 计算投票结果
- 获取投票结果
数据结构
// 投票人集合
mapping(address => Voter) public voters;
{
"0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2": {
weight: 1,
voted: false,
delegate:0x0000000000000000000000000000000000000000,
vote:0
},
"0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db": {
weight: 1,
voted: true
delegate:0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB, //委托
vote:0
},
"0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB": {
weight: 1 + 1,
voted: false,
delegate:0x0000000000000000000000000000000000000000,
vote:0
},
....
}
// 主题集合
[{
name: "海王",
voteCount: 1
},
{
name: "下水道",
voteCount: 1
},
{
name: "灌篮高手",
voteCount: 4
}]
合约代码
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Ballot {
// 创建投票人结构体
struct Voter {
uint256 weight; // 权重
bool voted; // 是否投票
address delegate; //委托人地址
uint256 vote; // 主题id
}
// 创建主题结构体
struct Proposal {
string name; // 主题名称
uint256 voteCount; // 票数
}
// 主持人地址 也就是投票发起人地址
address public chairperson;
// 创建投票人集合
mapping(address => Voter) public voters;
// 主题集合
Proposal[] public proposals;
// 构造函数中保持主持人地址
constructor(string[] memory proposalNames) {
chairperson = msg.sender;
// 其余的结构数据会给与默认值
voters[chairperson].weight = 1;
for (uint256 i = 0; i < proposalNames.length; i++) {
Proposal memory proposalItem = Proposal(proposalNames[i], 0);
proposals.push(proposalItem);
}
}
// 返回主题集合
function proposalList() public view returns (Proposal[] memory) {
return proposals;
}
// 给某些地址赋予选票
function giveRightToVote(address[] memory voteAddressList) public {
// 只有算有着可以调用方法
require(msg.sender == chairperson, "only ower can give right");
for (uint256 i = 0; i < voteAddressList.length; i++) {
// 如果该地址已经投过票 不处理,未投过票 赋予权
if (!voters[voteAddressList[i]].voted) {
voters[voteAddressList[i]].weight = 1;
}
}
}
// 将投票权委托给别人
function delegate(address to) public {
// 获取委托人信息
Voter storage sender = voters[msg.sender];
require(!sender.voted, "you already voted");
require(msg.sender != to, "self delegate is no allow");
// 循环判断 委托人不能为空 也不能为自己
while (voters[to].delegate != address(0)) {
//
to = voters[to].delegate;
require(to == msg.sender, "Found loop in delegete");
}
// 将委托人信息修改 投票状态 和 委托人信息
sender.voted = true;
sender.delegate = to;
// 获取被委托人信息
Voter storage delegate_ = voters[to];
if (delegate_.voted) {
// 被委托人如果投票直接将票数相加
proposals[delegate_.vote].voteCount += sender.weight;
} else {
delegate_.weight += sender.weight;
}
}
// 投票
function vote(uint256 idx) public {
Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "Has no right to vote");
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = idx;
proposals[idx].voteCount += sender.weight;
}
// 计算投票结果
function winningProposal() public view
returns (uint winningProposal_)
{
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal_ = p;
}
}
}
}
合约地址
合约地址:0xD68848E9DCbE305F4ff782d3194292Bcb1Bd6D41
web3与智能合约
https://web3js.readthedocs.io/en/v1.8.1/
实例web3
const Web3 = require("web3");
import mtcContract from "./contracts/contract_Ballot.json";
// 链接上web3 格尔丽的环境
const geerliWS =
"wss://goerli.infura.io/ws/v3/e4f789009c9245eeaad1d93ce9d059bb";
var web3 = new Web3(Web3.givenProvider || geerliWS);
账户链接
const account = await web3.eth.requestAccounts();
实例合约
new web3.eth.Contract(智能合约abi,合约地址)
this.votoContract = new web3.eth.Contract(
mtcContract.abi,
"0x1D108E4B9162668e1adACD07727b3de749818d0a"
);
方法
-
不需要消耗gas的方法 call (不修改数据的)
myContract.methods.myMethod([param1[, param2[, ...]]]).call(options [, defaultBlock] [, callback])
// using the callback myContract.methods.myMethod(123).call({from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'}, function(error, result){ ... }); // using the promise myContract.methods.myMethod(123).call({from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'}) .then(function(result){ ... });
-
修改数据消耗gas的方法 send
myContract.methods.myMethod([param1[, param2[, ...]]]).send(options[, callback])
// using the callback myContract.methods.myMethod(123).send({from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'}, function(error, transactionHash){ ... }); // using the promise myContract.methods.myMethod(123).send({from: '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe'}) .then(function(receipt){ // receipt can also be a new contract instance, when coming from a "contract.deploy({...}).send()" });
注意:数据修改完成后根据需求监听
receipt
事件
事件
myContract.events.MyEvent([options][, callback])
myContract.events.MyEvent({
filter: {myIndexedParam: [20,23], myOtherIndexedParam: '0x123456789...'}, // Using an array means OR: e.g. 20 or 23
fromBlock: 0
}, function(error, event){ console.log(event); })
.on("connected", function(subscriptionId){
console.log(subscriptionId);
})
.on('data', function(event){
console.log(event); // same results as the optional callback above
})
.on('changed', function(event){
// remove event from local database
})
.on('error', function(error, receipt) { // If the transaction was rejected by the network with a receipt, the second parameter will be the receipt.
...
});
前端代码
<template>
<div>
<h1>千锋去中心投票系统</h1>
主持人: {{ chairperson }}<br />
<button v-if="chairperson === account" @click="giveRightToVote">
分发选票
</button>
<hr />
当前账户:{{ account }}<br />
权重:{{ voteInfo.weight }}<br />
委托账户:{{ voteInfo.delegate }}<br />
是否已投票:{{ voteInfo.voted }}<br />
投票id:{{ voteInfo.vote }}<br />
<hr />
<div class="votolist">
<div
class="votolist-item"
v-for="(item, index) in proposals"
:key="index"
>
<div>
{{ item.name }}
</div>
<div>
{{ item.voteCount }}
</div>
<div>
<button @click="vote(index)">投票</button>
<button @click="delegate">委托投票</button>
</div>
</div>
</div>
</div>
</template>
<script>
const Web3 = require("web3");
// 合约地址 0xD68848E9DCbE305F4ff782d3194292Bcb1Bd6D41
// 0x61C682E657c44021279DaE8a7652336ddD0b5d2e
import Ballot from "./contracts/contract_Ballot.json";
console.log(Ballot.abi);
var web3 = new Web3(Web3.givenProvider || geerliWS);
const geerliWS =
"wss://goerli.infura.io/ws/v3/e4f789009c9245eeaad1d93ce9d059bb";
export default {
data() {
return {
account: "",
proposals: [],
voteInfo: {},
chairperson: "",
};
},
methods: {
delegate() {
const address = prompt('请输入委托人的地址')
this.Ballot.methods.delegate(address)
.send({from:this.account})
.on("receipt",(event) => {
alert("委托成功");
console.log(event)
this.getVoteInfo();
})
},
// 投票
async vote(index) {
const res = await this.Ballot.methods.vote(index).send({from: this.account});
console.log(res);
},
// 分发选票
async giveRightToVote() {
const str = prompt("请输入选民地址用,分开");
const arr = str.split(",");
console.log(arr);
this.Ballot.methods
.giveRightToVote(arr)
.send({ from: this.account })
.on("receipt", (event) => {
alert("选票分发成功");
console.log(event);
// 根据需求做一些页面刷新的处理
});
},
// 获取主持人地址
async getChairperson() {
this.chairperson = await this.Ballot.methods.chairperson().call();
},
// 获取投票主题信息
async getProposalsData() {
const proposals = await this.Ballot.methods.getProposals().call();
this.proposals = proposals;
},
// 根据地址获取个人投票者信息
async getVoteInfo() {
this.voteInfo = await this.Ballot.methods.voters(this.account).call();
},
// 事件监听
initEventListen() {
this.Ballot.events
.voteSuccess({ fromBlock: 0 }, (err, event) => {
console.log("监听执行");
console.log(event);
})
.on("data", (event) => {
console.log("智能合约触发事件", event);
this.getProposalsData();
this.getVoteInfo();
});
},
},
async created() {
// 获取metamask钱包的账户信息
const accounts = await web3.eth.requestAccounts();
this.account = accounts[0];
// 创建智能合约实例
this.Ballot = new web3.eth.Contract(
Ballot.abi,
"0xD68848E9DCbE305F4ff782d3194292Bcb1Bd6D41"
);
this.initEventListen();
this.getProposalsData();
this.getVoteInfo();
this.getChairperson();
},
};
</script>
<style>
.votolist {
display: flex;
}
.votolist-item {
width: 200px;
height: 200px;
border: 1px solid red;
}
</style>
// 测试连接
0x9B0DbF610175F5c783ec169DAdDa5E8B17055626,0x61C682E657c44021279DaE8a7652336ddD0b5d2e,0x29D789aE3243cBfF3C3f85E0Bde2e835FFA7D202
网友评论