iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 11
0
Blockchain

30天30個Smart contract 系列 第 11

Day10-Vote

導言

此範例參照Solidity 其中一個example,使用環境設定在投票現場,可能是多位參選人、多個提案...,主辦方將每個參選項目轉換成32bytes,集合成array,一併放到合約,投票者可以根據項目序號投給支持的對象。在這份合約中,主辦方可以給予指定的人投票權,投票者可以自己投票也可以託人投票,不過託人投票只限定在雙方有共同支持的對象,投票時間結束,找到投票數最多的項目。

Vote reference

程式碼

pragma solidity^0.4.25;
contract Vote{
    struct Voter{
        uint weight; //投票數
        bool voted;
        address delegate;
        uint vote;
    }
    struct Proposal{
        bytes32 name;
        uint voteCount;
    }

    address public chairperson;
    mapping(address=> Voter) public voters;
    Proposal[] public proposals;

    constructor(bytes32[] proposalName) public{
        chairperson = msg.sender;
        voters[chairperson].weight = 1;
        
        //將每個提案hash push 到proposals變數
        for(uint i = 0; i< proposalName.length ; i++){
            proposals.push(Proposal(proposalName[i],0));
        }
    }

    //給予投票權
    function getRightToVote(address voter) public {
        require(msg.sender == chairperson,"你不是發起人");
        require(!voters[voter].voted,"指定的voter已經投過票了");
        require(voters[voter].weight == 0,"這個voter已經有投票權");
        voters[voter].weight = 1;
    }

    //託人投票
    function delegate(address _to) public{
        require(!voters[msg.sender]].voted,"你已經投過票了");    
        require(_to != msg.sender,"不行指定本人");
        while(voters[_to].delegate == address(0)){
            _to = voters[_to].delegate;
            require(_to != msg.sender );
        }
        
        voters[msg.sender].voted = true;
        voters[msg.sender].delegate = _to;
        if(!voters[_to].voted){
            proposals[voters[_to].vote].voteCount += voters[msg.sender].weight;
        }else{
            voters[_to].weight += voters[msg.sender].weight;
        }

    }

    //投票
    function vote(uint _proposalIndex) public{
        require(!voters[msg.sender].voted,"已經投過票了");
        voters[msg.sender].voted = true;
        voters[msg.sender].vote = _proposalIndex;

        proposals[_proposalIndex].voteCount += voters[msg.sender].weight;
    }

    //計算最多票
    function winnerProposal() public view returns(uint winner){
        require(msg.sender == chairperson,"你不是發起人");
        uint winnerCount;
        for(uint i = 0; i < proposals.length ; i++){
            if(proposals[i].voteCount > winnerCount){
                winnerCount = proposals[i].voteCount;
                winner = i;
            }
        }   
    }
}

解說

struct Voter{
    uint weight; //投票數
    bool voted;
    address delegate;
    uint vote;
}
struct Proposal{
    bytes32 name;
    uint voteCount;
}

address public chairperson;
mapping(address=> Voter) public voters;
Proposal[] public proposals;

constructor(bytes32[] proposalName) public{
    chairperson = msg.sender;
    voters[chairperson].weight = 1;
    
    //將每個提案hash push 到proposals變數
    for(uint i = 0; i< proposalName.length ; i++){
        proposals.push(Proposal(proposalName[i],0));
    }
}
  • 將每一個投票方案轉換成32 bytes的value,集中在一個array當作參數,由主辦單位deploy 合約到blockchain
  • 兩個struct分別代表Voter投票者和Proposal投票方案,mapping voters表示投票者的address對應到struct Voter的資料結構,合約內的每一個投票方案會被集中在porposal array,並且以struct Proposal紀錄方案名稱及獲得的票數。
  • address chairperson為主辦單位的身分,可以給予投票權,及宣布獲選方案。
//給予投票權
function getRightToVote(address voter) public {
    require(msg.sender == chairperson,"你不是發起人");
    require(!voters[voter].voted,"指定的voter已經投過票了");
    require(voters[voter].weight == 0,"這個voter已經有投票權");
    voters[voter].weight = 1;
}
  • getRightToVote(address voter)為主辦方給予address參數投票權的function

執行function時會判斷執行function的身分是不是發起人、判斷參數有無投過票、判斷有沒有投票權,如果都符合條件,指定的address就可以獲得投票權

//託人投票
function delegate(address _to) public{
    require(!voters[msg.sender].voted,"你已經投過票了");    
    require(_to != msg.sender,"不行指定本人");
    while(voters[_to].delegate == address(0)){
        _to = voters[_to].delegate;
        require(_to != msg.sender );
    }
    
    voters[msg.sender].voted = true;
    voters[msg.sender].delegate = _to;
    if(!voters[_to].voted){
        proposals[voters[_to].vote].voteCount += voters[msg.sender].weight;
    }else{
        voters[_to].weight += voters[msg.sender].weight;
    }

}
  • delegate(address _to)為託人投票的function

先判斷被代理人有沒有投過票,並且參數不能指定被代理人,符合條件後,如果代理人Voter struct裏頭delegate的欄位是空的,將目前,將被代理人的voted投票狀態改為truedelegate填入代理人的address,並判斷代理人有沒有投過票,如果為true將票一併投給代理人支持的方案序號,如果為false代理人多獲得一次投票權

//投票
function vote(uint _proposalIndex) public{
    require(!voters[msg.sender].voted,"已經投過票了");
    voters[msg.sender].voted = true;
    voters[msg.sender].vote = _proposalIndex;

    proposals[_proposalIndex].voteCount += voters[msg.sender].weight;
}

//計算最多票
function winnerProposal() public view returns(uint winner){
    require(msg.sender == chairperson,"你不是發起人");
    uint winnerCount;
    for(uint i = 0; i < proposals.length ; i++){
        if(proposals[i].voteCount > winnerCount){
            winnerCount = proposals[i].voteCount;
            winner = i;
        }
    }   
}

  • vote() 為投票function,輸入支持的投票方案序號,即代表投給對應的投票方案

判斷當前執行function的address有沒有投過票,如果還沒投票,Voter structvoted 投票狀態 = truevote = 支持的投票方案序號並將當前address的投票數,轉移給mapping proposals對應的方案序號

  • winnerProposal() 為計算最多票的function

先判斷是不是發起人,再來for迴圈會將所有的proposal獲得的票數做比對,並以參數winnerCount紀錄最多票,最後回傳得最多票的方案序號,公布獲勝方案

address public Host; //發起人-主辦單位
uint256 public TicketPrice; //當日票價
uint256 public MaxParticipants; //當日票數
uint256 public SalesTime; //當日販售結束時間
uint256 public SalesDate; //當日販售日期


uint256 public amountOfParticipant; //參與者數量
mapping(address => bytes32[]) public tickets; //value為byts32陣列,一個address可以買多張票,每個票都有一個tickets hash
mapping(uint => address) public IdToUser; //票券ID對應的購買者

constructor(uint256 _price, uint256 _participants, uint256 _time) public{
        Host = msg.sender;
        TicketPrice = _price*10**15;
        MaxParticipants = _participants;
        SalesTime = now + _time;
        SalesDate = now;
    }

發起合約depoly contract時,需要輸入當日售票票價(以finney為計價單位,1 finney == 0.001 ether)、當日票券總數量(以參與者數量計算)、販售時間(以毫秒為單位)

function () external payable{
        buyTikcet();
    }

function buyTikcet() private{
    require(now <= SalesTime,"超出時間"); //確認有無超過販售時間
    require(amountOfParticipant < MaxParticipants,"今日票價已售完"); //確認沒有超過總售票量
    require(msg.value == TicketPrice,"不符合票價"); //確認支付的錢跟售價一樣
    amountOfParticipant++;
    
    //插入轉換hash function
    bytes32 ticketHash = keccak256(abi.encodePacked(amountOfParticipant,msg.sender)); //將userId和user address一同hash
    tickets[msg.sender].push(ticketHash); //紀錄
    IdToUser[amountOfParticipant] = msg.sender;
}

  • 執行合約時沒有指定function就會自動執行fallback function並支付發起合約時所訂的價格,一次買一張票。

fallback functionexternal,代表只提供給外部call function,搭配payable執行時需要支付一筆ether;buyTicket() function 為private 代表只能在合約內部執行且不公開,當fallback function執行時,會執行buyTicket() function,判斷目前有沒有超過販售時間、有沒有剩餘的票券、支付的錢有沒有符合售票價格。通過條件判斷之後,增加參與者數量,以keccak256 hash function 將 參與者數量(也可以當作票券ID) 和買票的address 共同HASH,產生出32bytes的票券hash value,作為當日驗票的證明。最後,票券hash value 會push到 mapping tickets[msg.sender]紀錄在買票的address的bytes32 array

function verify(bytes32 _ticketHash) public view returns(bool){
    for(uint i = 0 ; i < tickets[msg.sender].length ; i++){
        if(_ticketHash == tickets[msg.sender][i]){
            return true;
        }
    }
}


function withdraw() public{
    Host.transfer(address(this).balance);
}
  • 驗票時輸入票券的HASH來辨別是不是由目前CALL FUNCTION的ADDRESS所購買。

verify()function 輸入票券hash,for loop會先根據mapping tickets[msg.sender]找到目前呼叫function的address,所擁有的票券hash,如果輸入的參數符合address擁有的票券之一,回傳true


上一篇
Day9- Auction
下一篇
Day11-P2P
系列文
30天30個Smart contract 20
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言