5

2023-08-21  本文已影响0人  xin激流勇进

功能梳理

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

  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

上一篇 下一篇

猜你喜欢

热点阅读