5

功能梳理

通过智能合约实现去中心化投票

  1. 主持人创建投票主题与内容
  2. 给予“选民”在选票上投票的权利。
  3. 把你的投票委托给投票人。
  4. 投票
  5. 计算投票结果
  6. 获取投票结果

数据结构

// 投票人集合
  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"
    );

方法

  1. 不需要消耗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){
        ...
    });
    
    
  1. 修改数据消耗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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容