智能合約案例:投票

本節將介紹一個用 Solidity 語言編寫的智能合約案例。代碼來源於 Solidity 官方文檔 中的示例。

該智能合約實現了一個自動化的、透明的投票應用。投票發起人可以發起投票,將投票權賦予投票人;投票人可以自己投票,或將自己的票委託給其他投票人;任何人都可以公開查詢投票的結果。

智能合約代碼

實現上述功能的合約代碼如下所示,並不複雜,語法跟 JavaScript 十分類似。

pragma solidity ^0.4.11;

contract Ballot {
    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;

    // Create a new ballot to choose one of `proposalNames`
    function Ballot(bytes32[] proposalNames) {
        chairperson = msg.sender;
        voters[chairperson].weight = 1;

        for (uint i = 0; i < proposalNames.length; i++) {
            proposals.push(Proposal({
                name: proposalNames[i],
                voteCount: 0
            }));
        }
    }

    // Give `voter` the right to vote on this ballot.
    // May only be called by `chairperson`.
    function giveRightToVote(address voter) {
        require((msg.sender == chairperson) && !voters[voter].voted);
        voters[voter].weight = 1;
    }

    // Delegate your vote to the voter `to`.
    function delegate(address to) {
        Voter sender = voters[msg.sender];
        require(!sender.voted);
        require(to != msg.sender);

        while (voters[to].delegate != address(0)) {
            to = voters[to].delegate;

            // We found a loop in the delegation, not allowed.
            require(to != msg.sender);
        }

        sender.voted = true;
        sender.delegate = to;
        Voter delegate = voters[to];
        if (delegate.voted) {
            proposals[delegate.vote].voteCount += sender.weight;
        } else {
            delegate.weight += sender.weight;
        }
    }

    // Give your vote (including votes delegated to you)
    // to proposal `proposals[proposal].name`.
    function vote(uint proposal) {
        Voter sender = voters[msg.sender];
        require(!sender.voted);
        sender.voted = true;
        sender.vote = proposal;

        proposals[proposal].voteCount += sender.weight;
    }

    // @dev Computes the winning proposal taking all
    // previous votes into account.
    function winningProposal() constant
            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;
            }
        }
    }

    // Calls winningProposal() function to get the index
    // of the winner contained in the proposals array and then
    // returns the name of the winner
    function winnerName() constant
            returns (bytes32 winnerName)
    {
        winnerName = proposals[winningProposal()].name;
    }
}

代碼解析

指定版本

在第一行,pragma 關鍵字指定了和該合約兼容的編譯器版本。

pragma solidity ^0.4.11;

該合約指定,不兼容比 0.4.11 更舊的編譯器版本,且 ^ 符號表示也不兼容從 0.5.0 起的新編譯器版本。即兼容版本範圍是 0.4.11 <= version < 0.5.0。該語法與 npm 的版本描述語法一致。

結構體類型

Solidity 中的合約(contract)類似面向對象編程語言中的類。每個合約可以包含狀態變量、函數、事件、結構體類型和枚舉類型等。一個合約也可以繼承另一個合約。

在本例命名為 Ballot 的合約中,聲明瞭 2 個結構體類型:VoterProposal

  • struct Voter:投票人,其屬性包括 uint weight(該投票人的權重)、bool voted(是否已投票)、address delegate(如果該投票人將投票委託給他人,則記錄受委託人的賬戶地址)和 uint vote(投票做出的選擇,即相應提案的索引號)。

  • struct Proposal:提案,其屬性包括 bytes32 name(名稱)和 uint voteCount(已獲得的票數)。

需要注意,address 類型記錄了一個以太坊賬戶的地址。address 可看作一個數值類型,但也包括一些與以太幣相關的方法,如查詢餘額 <address>.balance、向該地址轉賬 <address>.transfer(uint256 amount) 等。

狀態變量

合約中的狀態變量會長期保存在區塊鏈中。通過調用合約中的函數,這些狀態變量可以被讀取和改寫。

本例中定義了 3 個狀態變量:chairpersonvotersproposals

  • address public chairperson:投票發起人,類型為 address

  • mapping(address => Voter) public voters:所有投票人,類型為 addressVoter 的映射。

  • Proposal[] public proposals:所有提案,類型為動態大小的 Proposal 數組。

3 個狀態變量都使用了 public 關鍵字,使得變量可以被外部訪問(即通過消息調用)。事實上,編譯器會自動為 public 的變量創建同名的 getter 函數,供外部直接讀取。

狀態變量還可設置為 internalprivateinternal 的狀態變量只能被該合約和繼承該合約的子合約訪問,private 的狀態變量只能被該合約訪問。狀態變量默認為 internal

將上述關鍵狀態信息設置為 public 能夠增加投票的公平性和透明性。

函數

合約中的函數用於處理業務邏輯。函數的可見性默認為 public,即可以從內部或外部調用,是合約的對外接口。函數可見性也可設置為 externalinternalprivate

本例實現了 6 個 public 函數,可看作 6 個對外接口,功能分別如下。

創建投票

函數 function Ballot(bytes32[] proposalNames) 用於創建一個新的投票。

所有提案的名稱通過參數 bytes32[] proposalNames 傳入,逐個記錄到狀態變量 proposals 中。同時用 msg.sender 獲取當前調用消息的發送者的地址,記錄為投票發起人 chairperson,該發起人投票權重設為 1。

賦予投票權

函數 function giveRightToVote(address voter) 實現給投票人賦予投票權。

該函數給 address voter 賦予投票權,即將 voter 的投票權重設為 1,存入 voters 狀態變量。

這個函數只有投票發起人 chairperson 可以調用。這裡用到了 require((msg.sender == chairperson) && !voters[voter].voted) 函數。如果 require 中表達式結果為 false,這次調用會中止,且回滾所有狀態和以太幣餘額的改變到調用前。但已消耗的 Gas 不會返還。

委託投票權

函數 function delegate(address to) 把投票委託給其他投票人。

其中,用 voters[msg.sender] 獲取委託人,即此次調用的發起人。用 require 確保發起人沒有投過票,且不是委託給自己。由於被委託人也可能已將投票委託出去,所以接下來,用 while 循環查找最終的投票代表。找到後,如果投票代表已投票,則將委託人的權重加到所投的提案上;如果投票代表還未投票,則將委託人的權重加到代表的權重上。

該函數使用了 while 循環,這裡合約編寫者需要十分謹慎,防止調用者消耗過多 Gas,甚至出現死循環。

進行投票

函數 function vote(uint proposal) 實現投票過程。

其中,用 voters[msg.sender] 獲取投票人,即此次調用的發起人。接下來檢查是否是重複投票,如果不是,進行投票後相關狀態變量的更新。

查詢獲勝提案

函數 function winningProposal() constant returns (uint winningProposal) 將返回獲勝提案的索引號。

這裡,returns (uint winningProposal) 指定了函數的返回值類型,constant 表示該函數不會改變合約狀態變量的值。

函數通過遍歷所有提案進行記票,得到獲勝提案。

查詢獲勝者名稱

函數 function winnerName() constant returns (bytes32 winnerName) 實現返回獲勝者的名稱。

這裡採用內部調用 winningProposal() 函數的方式獲得獲勝提案。如果需要採用外部調用,則需要寫為 this.winningProposal()

Last updated

Was this helpful?