Dapp开发以太坊- Ethereum

以太坊开发(十六)打造去中心化组织的投票系统

2018-03-15  本文已影响403人  yuyangray

以太坊官方打造去中心化组织教程

本文基于作者阅读官方教程,根据理解而写,本身就有一些不太明白的地方需要解惑,只能作为一定的参考。另外如有疏漏请指正,感谢!

Decentralized Autonomous Organization 去中心化组织

这篇文章我们一起来打造一个投票智能合约。
边看代码边进行讲解。

基础版的投票合约

设置父类合约和接口

这部分的代码主要设置合约创建者为owner,并且提供替换owner的方法。定义了接收ether代币的方法。如果有疑问建议先阅读之前的文章。

区块链开发(二十)代币示例及讲解

区块链开发(二十一)众筹合约示例及讲解

代码如下:

contract owned {
    address public owner;

    function owned()  public {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    function transferOwnership(address newOwner) onlyOwner  public {
        owner = newOwner;
    }
}

contract tokenRecipient {
    event receivedEther(address sender, uint amount);
    event receivedTokens(address _from, uint256 _value, address _token, bytes _extraData);

    function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public {
        Token t = Token(_token);
        require(t.transferFrom(_from, this, _value));
        receivedTokens(_from, _value, _token, _extraData);
    }

    function () payable  public {
        receivedEther(msg.sender, msg.value);
    }
}

interface Token {
    function transferFrom(address _from, address _to, uint256 _value) external returns (bool success);
}

Congress合约

定义变量

    // 最小的投票数
    uint public minimumQuorum;
    // 投票时间,以分钟为单位
    uint public debatingPeriodInMinutes;
    // 设定赞同票数的区间值
    int public majorityMargin;
    // 提案的数组
    Proposal[] public proposals;
    // 提案的个数
    uint public numProposals;
    // 成员id及地址
    mapping (address => uint) public memberId;
    // 成员的数组
    Member[] public members;

定义事件

    // 增加提案,传入提案id,受益人地址,价格,描述
    event ProposalAdded(uint proposalID, address recipient, uint amount, string description);

    // 投票,传入提案id,赞同/反对,投票人地址,陈述理由
    event Voted(uint proposalID, bool position, address voter, string justification);

    // 提案归档,传入提案id,结果,投票数,是否激活
    event ProposalTallied(uint proposalID, int result, uint quorum, bool active);

    // 设置某人是否为成员,传入地址,是否为组员
    event MembershipChanged(address member, bool isMember);

    // 改变规则,传入新的最小投票数,新的讨论时间,新的赞同票的区间值
    event ChangeOfRules(uint newMinimumQuorum, uint 
    newDebatingPeriodInMinutes, int newMajorityMargin);

定义结构体

    // 提案的结构体
    struct Proposal {
        // 受益人地址
        address recipient;
        // 金额
        uint amount;
        // 描述
        string description;
        // 投票截止时间
        uint votingDeadline;
        // 是否已执行
        bool executed;
        // 是否通过
        bool proposalPassed;
        // 得票数
        uint numberOfVotes;
        // 赞同票数
        int currentResult;
        // 哈希值
        bytes32 proposalHash;
        // 投票数组
        Vote[] votes;
        // 对应的地址是否已投票
        mapping (address => bool) voted;
    }

    // 成员结构体
    struct Member {
        // 地址
        address member;
        // 姓名
        string name;
        // 加入时间
        uint memberSince;
    }

    // 投票结构体
    struct Vote {
        // 支持还是反对
        bool inSupport;
        // 投票人地址
        address voter;
        // 理由描述
        string justification;
    }

修改器

增加一个判断当前合约调用者是否为成员的限制。

  // Modifier that allows only shareholders to vote and create new proposals
    // 限定了只有成员才可以投票及创建新提案
    modifier onlyMembers {
        require(memberId[msg.sender] != 0);
        _;
    }

构造函数

     /**
     * Constructor function
     * 构造函数
     */
    function Congress (

        // is the minimum amount of votes a proposal needs to have before it can be executed.
        // 设定提案被执行所需要的最少投票数
        uint minimumQuorumForProposals,

        // is the minimum amount of time (in minutes) that needs to pass before it can be executed.
        // 设定投票持续时间,如果时间到了之后没有通过,则提案不会被执行。以分钟为单位
        uint minutesForDebate,


        // A proposal passes if there are more than 50% of the votes plus the margin. Leave at 0 for simple majority, put it at the number of members - 1 to require an absolute consensus.
        // 如果想让一个提案通过,必须得到票数的50%加上这个设定的区间值。如果只要50%是赞同票就能通过,设为0就好了。如果要求全票通过,则设为members - 1。
        // 提案的投票结果currentResult初始为0,在获得一张赞同票时,currentResult++,获得反对票时,currentResult--。一半赞同一半反对,currentResult最后为0。如果区间值设为0,意味该提案只需要半数人赞同便可通过。
        // 假如设置赞同票数的区间值为2,共有10张投票,如果要想提案通过,则说明至少有7张是赞同票。
        // 设定赞同票数的区间值
        int marginOfVotesForMajority

    )  payable public {
        // 设定投票规则
        changeVotingRules(minimumQuorumForProposals, minutesForDebate, marginOfVotesForMajority);
        // It’s necessary to add an empty first member
        addMember(0, "");
        // and let's add the founder, to save a step later
        addMember(owner, 'founder');
    }

新增组员

    /**
     * Add member 添加一个成员,传入成员地址和名称
     * 限定了只有owner才能调用此方法
     *
     * Make `targetMember` a member named `memberName`
     *
     * @param targetMember ethereum address to be added
     * @param memberName public name for that member
     */
    function addMember(address targetMember, string memberName) onlyOwner public {
        uint id = memberId[targetMember];

        // 如果是新成员,将memberId设为members数组长度
        if (id == 0) {
            memberId[targetMember] = members.length;
            id = members.length++;
        }

        // 无论是否为新成员还是已有成员,都重新设置地址加入时间及姓名
        members[id] = Member({member: targetMember, memberSince: now, name: memberName});
        MembershipChanged(targetMember, true);
    }

删除组员

    /**
     * Remove member 删除一个成员,传入成员地址
     * 限定了只有owner才能调用此方法
     *
     * @notice Remove membership from `targetMember`
     *
     * @param targetMember ethereum address to be removed
     */
    function removeMember(address targetMember) onlyOwner public {
        require(memberId[targetMember] != 0);

        for (uint i = memberId[targetMember]; i<members.length-1; i++){
            members[i] = members[i+1];
        }
        delete members[members.length-1];
        members.length--;
    }

改变投票规则

    /**
     * Change voting rules 改变投票规则
     *
     * Make so that proposals need to be discussed for at least `minutesForDebate/60` hours,
     * 保证一个提案至少需要讨论的时间为`minutesForDebate/60`小时

     * have at least `minimumQuorumForProposals` votes, and have 50% + `marginOfVotesForMajority` votes to be executed
     * 提案需要的最少得票数和得票中的指定赞成票数才可被执行

     *
     * @param minimumQuorumForProposals how many members must vote on a proposal for it to be executed
     * 提案被执行的最少得票数
     *
     * @param minutesForDebate the minimum amount of delay between when a proposal is made and when it can be executed
     * 提案的最少投票时间
     *
     * @param marginOfVotesForMajority the proposal needs to have 50% plus this number
     * 提案需要50%赞同票加上这个区间值才可通过
     *
     */
    function changeVotingRules(
        uint minimumQuorumForProposals,
        uint minutesForDebate,
        int marginOfVotesForMajority
    ) onlyOwner public {
        minimumQuorum = minimumQuorumForProposals;
        debatingPeriodInMinutes = minutesForDebate;
        majorityMargin = marginOfVotesForMajority;

        ChangeOfRules(minimumQuorum, debatingPeriodInMinutes, majorityMargin);
    }

新增提案

    /**
     * Add Proposal 增加提案
     *
     * Propose to send `weiAmount / 1e18` ether to `beneficiary` for `jobDescription`. `transactionBytecode ? Contains : Does not contain` code.
     *
     * @param beneficiary who to send the ether to
     * 受益人,如果提案顺利执行,可以获取到提案中的金额
     * @param weiAmount amount of ether to send, in wei
     * ether价格,单位是wei
     * @param jobDescription Description of job
     * 新提案的描述
     * @param transactionBytecode bytecode of transaction
     * 
     * 
     */
    function newProposal(
        address beneficiary,
        uint weiAmount,
        string jobDescription,
        bytes transactionBytecode
    )
        onlyMembers public
        returns (uint proposalID)
    {
        proposalID = proposals.length++;
        Proposal storage p = proposals[proposalID];
        p.recipient = beneficiary;
        p.amount = weiAmount;
        p.description = jobDescription;
        p.proposalHash = keccak256(beneficiary, weiAmount, transactionBytecode);
        p.votingDeadline = now + debatingPeriodInMinutes * 1 minutes;
        p.executed = false;
        p.proposalPassed = false;
        p.numberOfVotes = 0;
        ProposalAdded(proposalID, beneficiary, weiAmount, jobDescription);
        numProposals = proposalID+1;

        return proposalID;
    }

以ether为单位增加提案

    /**
     * Add proposal in Ether 以ether为单位增加提案
     *
     * Propose to send `etherAmount` ether to `beneficiary` for `jobDescription`. `transactionBytecode ? Contains : Does not contain` code.
     * This is a convenience function to use if the amount to be given is in round number of ether units.
     *
     * @param beneficiary who to send the ether to
     * @param etherAmount amount of ether to send
     * @param jobDescription Description of job
     * @param transactionBytecode bytecode of transaction
     */
    function newProposalInEther(
        address beneficiary,
        uint etherAmount,
        string jobDescription,
        bytes transactionBytecode
    )
        onlyMembers public
        returns (uint proposalID)
    {
        return newProposal(beneficiary, etherAmount * 1 ether, jobDescription, transactionBytecode);
    }  

检查是否指定提案的Hash值与传入的参数相等

    /**
     * Check if a proposal code matches
     * 检查是否指定提案的Hash值与传入的参数相等
     *
     * @param proposalNumber ID number of the proposal to query
     * @param beneficiary who to send the ether to
     * @param weiAmount amount of ether to send
     * @param transactionBytecode bytecode of transaction
     */
    function checkProposalCode(
        uint proposalNumber,
        address beneficiary,
        uint weiAmount,
        bytes transactionBytecode
    )
        constant public
        returns (bool codeChecksOut)
    {
        Proposal storage p = proposals[proposalNumber];
        return p.proposalHash == keccak256(beneficiary, weiAmount, transactionBytecode);
    }

投票

    /**
     * Log a vote for a proposal 进行投票
     *
     * Vote `supportsProposal? in support of : against` proposal #`proposalNumber`
     *
     * @param proposalNumber number of proposal 提案号
     * @param supportsProposal either in favor or against it 支持还是反对
     * @param justificationText optional justification text 陈述意见
     */
    function vote(
        uint proposalNumber,
        bool supportsProposal,
        string justificationText
    )
        onlyMembers public
        returns (uint voteID)
    {
        // Get the proposal 
        // 获取提案
        Proposal storage p = proposals[proposalNumber];  

       // 如果投票时间已过,退出
        require(now < p.votingDeadline);

        // If has already voted, cancel  
        // 如果已经投过票,退出      
        require(!p.voted[msg.sender]);  

        // Set this voter as having voted
        // 设置为已投票
        p.voted[msg.sender] = true;      

        // Increase the number of votes
        // 为此提案增加票数
        p.numberOfVotes++;       

        // If they support the proposal
        // 支持
        if (supportsProposal) {      

        // Increase score
        // 分数加1                   
            p.currentResult++;                          
        } else {             

        // If they don't
        // Decrease the score
        // 反对,分数减1
            p.currentResult--;                          
        }

        // Create a log of this event
        Voted(proposalNumber,  supportsProposal, msg.sender, justificationText);
        return p.numberOfVotes;
    }

执行提案

    /**
     * Finish vote 投票结束,执行提案
     *
     * Count the votes proposal #`proposalNumber` and execute it if approved
     * 清点某个提案的得票数,如果通过,执行此提案
     *
     * @param proposalNumber proposal number
     * @param transactionBytecode optional: if the transaction contained a bytecode, you need to send it
     * 可选参数。如果提案包含bytecode执行代码,需要执行此代码
     */
    function executeProposal(uint proposalNumber, bytes transactionBytecode) public {
        Proposal storage p = proposals[proposalNumber];

        // If it is past the voting deadline
        // 如果投票时间已过
        require(now > p.votingDeadline     

            // and it has not already been executed
            // 并且提案还未被执行                    
            && !p.executed     

            // and the supplied code matches the proposal
            // 并且传入的代码与提案中代码一致                                                    
            && p.proposalHash == keccak256(p.recipient, p.amount, transactionBytecode)  

            // and a minimum quorum has been reached...
            // 并且提案需要的投票数大于等于最小得票数
            && p.numberOfVotes >= minimumQuorum);                                  

        // ...then execute result

        if (p.currentResult > majorityMargin) {
            // Proposal passed; execute the transaction
            // 提案的结果大于赞同票区间值,提案通过,执行提案中的交易代码

            // Avoid recursive calling
            // 设置提案已经执行过了,以免递归执行
            p.executed = true; 

            require(p.recipient.call.value(p.amount)(transactionBytecode));

            p.proposalPassed = true;
        } else {
            // Proposal failed
            p.proposalPassed = false;
        }

        // Fire Events
        ProposalTallied(proposalNumber, p.currentResult, p.numberOfVotes, p.proposalPassed);
    }

全部代码

pragma solidity ^0.4.16;

contract owned {
    address public owner;

    function owned()  public {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    function transferOwnership(address newOwner) onlyOwner  public {
        owner = newOwner;
    }
}

contract tokenRecipient {
    event receivedEther(address sender, uint amount);
    event receivedTokens(address _from, uint256 _value, address _token, bytes _extraData);

    function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public {
        Token t = Token(_token);
        require(t.transferFrom(_from, this, _value));
        receivedTokens(_from, _value, _token, _extraData);
    }

    function () payable  public {
        receivedEther(msg.sender, msg.value);
    }
}

interface Token {
    function transferFrom(address _from, address _to, uint256 _value) external returns (bool success);
}

contract Congress is owned, tokenRecipient {

    // 定义变量和事件

    // 最小的投票数
    uint public minimumQuorum;
    // 投票时间,以分钟为单位
    uint public debatingPeriodInMinutes;
    // 设定赞同票数的区间值
    int public majorityMargin;
    // 提案的数组
    Proposal[] public proposals;
    // 提案的个数
    uint public numProposals;
    // 成员id及地址
    mapping (address => uint) public memberId;
    // 成员的数组
    Member[] public members;

    // 增加提案,传入提案id,受益人地址,价格,描述
    event ProposalAdded(uint proposalID, address recipient, uint amount, string description);

    // 投票,传入提案id,赞同/反对,投票人地址,陈述理由
    event Voted(uint proposalID, bool position, address voter, string justification);

    // 提案归档,传入提案id,结果,投票数,是否激活
    event ProposalTallied(uint proposalID, int result, uint quorum, bool active);

    // 设置某人是否为成员,传入地址,是否为组员
    event MembershipChanged(address member, bool isMember);

    // 改变规则,传入新的最小投票数,新的讨论时间,新的赞同票的区间值
    event ChangeOfRules(uint newMinimumQuorum, uint 
    newDebatingPeriodInMinutes, int newMajorityMargin);

    // 提案的结构体
    struct Proposal {
        // 受益人地址
        address recipient;
        // 金额
        uint amount;
        // 描述
        string description;
        // 投票截止时间
        uint votingDeadline;
        // 是否已执行
        bool executed;
        // 是否通过
        bool proposalPassed;
        // 得票数
        uint numberOfVotes;
        // 赞同票数
        int currentResult;
        // 哈希值
        bytes32 proposalHash;
        // 投票数组
        Vote[] votes;
        // 对应的地址是否已投票
        mapping (address => bool) voted;
    }

    // 成员结构体
    struct Member {
        // 地址
        address member;
        // 姓名
        string name;
        // 加入时间
        uint memberSince;
    }

    // 投票结构体
    struct Vote {
        // 支持还是反对
        bool inSupport;
        // 投票人地址
        address voter;
        // 理由描述
        string justification;
    }

    // Modifier that allows only shareholders to vote and create new proposals
    // 限定了只有成员才可以投票及创建新提案
    modifier onlyMembers {
        require(memberId[msg.sender] != 0);
        _;
    }

    /**
     * Constructor function
     * 构造函数
     */
    function Congress (

        // is the minimum amount of votes a proposal needs to have before it can be executed.
        // 设定提案被执行所需要的最少投票数
        uint minimumQuorumForProposals,

        // is the minimum amount of time (in minutes) that needs to pass before it can be executed.
        // 设定投票持续时间,如果时间到了之后没有通过,则提案不会被执行。以分钟为单位
        uint minutesForDebate,


        // A proposal passes if there are more than 50% of the votes plus the margin. Leave at 0 for simple majority, put it at the number of members - 1 to require an absolute consensus.
        // 如果想让一个提案通过,必须得到票数的50%加上这个设定的区间值。如果只要50%是赞同票就能通过,设为0就好了。如果要求全票通过,则设为members - 1。
        // 提案的投票结果currentResult初始为0,在获得一张赞同票时,currentResult++,获得反对票时,currentResult--。一半赞同一半反对,currentResult最后为0。如果区间值设为0,意味该提案只需要半数人赞同便可通过。
        // 假如设置赞同票数的区间值为2,共有10张投票,如果要想提案通过,则说明至少有7张是赞同票。
        // 设定赞同票数的区间值
        int marginOfVotesForMajority

    )  payable public {
        // 设定投票规则
        changeVotingRules(minimumQuorumForProposals, minutesForDebate, marginOfVotesForMajority);
        // It’s necessary to add an empty first member
        addMember(0, "");
        // and let's add the founder, to save a step later
        addMember(owner, 'founder');
    }

    /**
     * Add member 添加一个成员,传入成员地址和名称
     * 限定了只有owner才能调用此方法
     *
     * Make `targetMember` a member named `memberName`
     *
     * @param targetMember ethereum address to be added
     * @param memberName public name for that member
     */
    function addMember(address targetMember, string memberName) onlyOwner public {
        uint id = memberId[targetMember];

        // 如果是新成员,将memberId设为members数组长度
        if (id == 0) {
            memberId[targetMember] = members.length;
            id = members.length++;
        }

        // 无论是否为新成员还是已有成员,都重新设置地址加入时间及姓名
        members[id] = Member({member: targetMember, memberSince: now, name: memberName});
        MembershipChanged(targetMember, true);
    }

    /**
     * Remove member 删除一个成员,传入成员地址
     * 限定了只有owner才能调用此方法
     *
     * @notice Remove membership from `targetMember`
     *
     * @param targetMember ethereum address to be removed
     */
    function removeMember(address targetMember) onlyOwner public {
        require(memberId[targetMember] != 0);

        for (uint i = memberId[targetMember]; i<members.length-1; i++){
            members[i] = members[i+1];
        }
        delete members[members.length-1];
        members.length--;
    }

    /**
     * Change voting rules 改变投票规则
     *
     * Make so that proposals need to be discussed for at least `minutesForDebate/60` hours,
     * 保证一个提案至少需要讨论的时间为`minutesForDebate/60`小时

     * have at least `minimumQuorumForProposals` votes, and have 50% + `marginOfVotesForMajority` votes to be executed
     * 提案需要的最少得票数和得票中的指定赞成票数才可被执行

     *
     * @param minimumQuorumForProposals how many members must vote on a proposal for it to be executed
     * 提案被执行的最少得票数
     *
     * @param minutesForDebate the minimum amount of delay between when a proposal is made and when it can be executed
     * 提案的最少投票时间
     *
     * @param marginOfVotesForMajority the proposal needs to have 50% plus this number
     * 提案需要50%赞同票加上这个区间值才可通过
     *
     */
    function changeVotingRules(
        uint minimumQuorumForProposals,
        uint minutesForDebate,
        int marginOfVotesForMajority
    ) onlyOwner public {
        minimumQuorum = minimumQuorumForProposals;
        debatingPeriodInMinutes = minutesForDebate;
        majorityMargin = marginOfVotesForMajority;

        ChangeOfRules(minimumQuorum, debatingPeriodInMinutes, majorityMargin);
    }

    /**
     * Add Proposal 增加提案
     *
     * Propose to send `weiAmount / 1e18` ether to `beneficiary` for `jobDescription`. `transactionBytecode ? Contains : Does not contain` code.
     *
     * @param beneficiary who to send the ether to
     * 受益人,如果提案顺利执行,可以获取到提案中的金额
     * @param weiAmount amount of ether to send, in wei
     * ether价格,单位是wei
     * @param jobDescription Description of job
     * 新提案的描述
     * @param transactionBytecode bytecode of transaction
     * 
     * 
     */
    function newProposal(
        address beneficiary,
        uint weiAmount,
        string jobDescription,
        bytes transactionBytecode
    )
        onlyMembers public
        returns (uint proposalID)
    {
        proposalID = proposals.length++;
        Proposal storage p = proposals[proposalID];
        p.recipient = beneficiary;
        p.amount = weiAmount;
        p.description = jobDescription;
        p.proposalHash = keccak256(beneficiary, weiAmount, transactionBytecode);
        p.votingDeadline = now + debatingPeriodInMinutes * 1 minutes;
        p.executed = false;
        p.proposalPassed = false;
        p.numberOfVotes = 0;
        ProposalAdded(proposalID, beneficiary, weiAmount, jobDescription);
        numProposals = proposalID+1;

        return proposalID;
    }

    /**
     * Add proposal in Ether 以ether为单位增加提案
     *
     * Propose to send `etherAmount` ether to `beneficiary` for `jobDescription`. `transactionBytecode ? Contains : Does not contain` code.
     * This is a convenience function to use if the amount to be given is in round number of ether units.
     *
     * @param beneficiary who to send the ether to
     * @param etherAmount amount of ether to send
     * @param jobDescription Description of job
     * @param transactionBytecode bytecode of transaction
     */
    function newProposalInEther(
        address beneficiary,
        uint etherAmount,
        string jobDescription,
        bytes transactionBytecode
    )
        onlyMembers public
        returns (uint proposalID)
    {
        return newProposal(beneficiary, etherAmount * 1 ether, jobDescription, transactionBytecode);
    }

    /**
     * Check if a proposal code matches
     * 检查是否指定提案的Hash值与传入的参数相等
     *
     * @param proposalNumber ID number of the proposal to query
     * @param beneficiary who to send the ether to
     * @param weiAmount amount of ether to send
     * @param transactionBytecode bytecode of transaction
     */
    function checkProposalCode(
        uint proposalNumber,
        address beneficiary,
        uint weiAmount,
        bytes transactionBytecode
    )
        constant public
        returns (bool codeChecksOut)
    {
        Proposal storage p = proposals[proposalNumber];
        return p.proposalHash == keccak256(beneficiary, weiAmount, transactionBytecode);
    }

    /**
     * Log a vote for a proposal 进行投票
     *
     * Vote `supportsProposal? in support of : against` proposal #`proposalNumber`
     *
     * @param proposalNumber number of proposal 提案号
     * @param supportsProposal either in favor or against it 支持还是反对
     * @param justificationText optional justification text 陈述意见
     */
    function vote(
        uint proposalNumber,
        bool supportsProposal,
        string justificationText
    )
        onlyMembers public
        returns (uint voteID)
    {
        // Get the proposal 
        // 获取提案
        Proposal storage p = proposals[proposalNumber];  

         // 如果投票时间已过,退出
        require(now < p.votingDeadline);

        // If has already voted, cancel  
        // 如果已经投过票,退出      
        require(!p.voted[msg.sender]);  

        // Set this voter as having voted
        // 设置为已投票
        p.voted[msg.sender] = true;      

        // Increase the number of votes
        // 为此提案增加票数
        p.numberOfVotes++;       

        // If they support the proposal
        // 支持
        if (supportsProposal) {      

        // Increase score
        // 分数加1                   
            p.currentResult++;                          
        } else {             

        // If they don't
        // Decrease the score
        // 反对,分数减1
            p.currentResult--;                          
        }

        // Create a log of this event
        Voted(proposalNumber,  supportsProposal, msg.sender, justificationText);
        return p.numberOfVotes;
    }

    /**
     * Finish vote 投票结束,执行提案
     *
     * Count the votes proposal #`proposalNumber` and execute it if approved
     * 清点某个提案的得票数,如果通过,执行此提案
     *
     * @param proposalNumber proposal number
     * @param transactionBytecode optional: if the transaction contained a bytecode, you need to send it
     * 可选参数。如果提案包含bytecode执行代码,需要执行此代码
     */
    function executeProposal(uint proposalNumber, bytes transactionBytecode) public {
        Proposal storage p = proposals[proposalNumber];

        // If it is past the voting deadline
        // 如果投票时间已过
        require(now > p.votingDeadline     

            // and it has not already been executed
            // 并且提案还未被执行                    
            && !p.executed     

            // and the supplied code matches the proposal
            // 并且传入的代码与提案中代码一致                                                    
            && p.proposalHash == keccak256(p.recipient, p.amount, transactionBytecode)  

            // and a minimum quorum has been reached...
            // 并且提案需要的投票数大于等于最小得票数
            && p.numberOfVotes >= minimumQuorum);                                  

        // ...then execute result

        if (p.currentResult > majorityMargin) {
            // Proposal passed; execute the transaction
            // 提案的结果大于赞同票区间值,提案通过,执行提案中的交易代码

            // Avoid recursive calling
            // 设置提案已经执行过了,以免递归执行
            p.executed = true; 

            require(p.recipient.call.value(p.amount)(transactionBytecode));

            p.proposalPassed = true;
        } else {
            // Proposal failed
            p.proposalPassed = false;
        }

        // Fire Events
        ProposalTallied(proposalNumber, p.currentResult, p.numberOfVotes, p.proposalPassed);
    }
}

部署

部署方法和之前一样。这里设置投票时间为5分钟,其他暂时不管,默认为0。

分享给他人

部署成功后,如果想将此合约分享给他人,你需要将合约地址以及JSON字符串复制给他。

复制地址

JSON字符串

观察合约

其他人可以通过在合约页面点击新增观察合约,然后输入合约地址及Json字符串进行观察。

使用合约

增加一个简单的提案:发送ether

其次再使用合约中的Check proposal code方法,输入新增提案的参数来查看是否hash值不一致:

方法返回YES,说明hash值没问题。

接着查看提案需要的投票数是否大于等于最终得票数:

提案需要的最少投票数是0票,而我们已经投过1票:

说明投票数没问题。

接着再看赞同票是否大于赞同票区间值:

提案设置的赞同票区间值是0票,而我们已经投过1张赞同票票:

说明赞同票数也符合条件。

所以这里不知道问题出在哪里导致方法无法执行。

增加一个复杂的提案:发送代币交易

现在来演示如何在新增提案时,附带一笔交易。之前新增提案时,Transaction bytecode一直留空,提案执行时并没有执行除了发送ether外的其他交易。

这里我们在新增提案时,附带一笔发送代币的交易信息。提案被执行时会向指定的账户发送指定数量的代币。

在方法执行后,就可以到提案信息了。需要注意的是这里并没有显示transaction bytecode而是显示的Proposal hash。这是因为transaction bytecode可能会非常长,因此直接写入区块是一件非常奢侈的事情。所以这里对其进行了hash处理,对其长度进行了限制。

你可能会发现这里有一些安全漏洞:

读取合约中的Check proposal code方法便在此时派上了用场。

剩下的就和前面的例子一样,所有成员都可以进行投票。投票截止时间到了之后,某人可以执行此合约。唯一不同的是,这次执行提案的人必须提供之前提交的交易代码。也就是对执行人进行了限制,不是所有人都可以执行提案了。

根据股东的持股比例(权重)进行投票

在上面的例子中,我们采用的是类似邀请制的机制,邀请和禁止成员都是由一个中心化人物,类似总统,董事长,主席,来决定的。但是有一些缺陷:

我们会对合约进行一些修改,使用一种特殊的代币对它进行关联。创建一种初始值为100的代币,小数位是0,代币符号为百分比%。这个表示的是总的股份比例为100%,每个人的持股可能有多有少。比例高的意味着权重大,有更高的话语权。
如果希望保留小数位,可以为小数位加入几个0。然后部署代币合约,并复制代币地址。

股份制投票代码

pragma solidity ^0.4.16;

contract owned {
    address public owner;

    function owned() {
        owner = msg.sender;
    }

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    function transferOwnership(address newOwner) onlyOwner {
        owner = newOwner;
    }
}

contract tokenRecipient {
    event receivedEther(address sender, uint amount);
    event receivedTokens(address _from, uint256 _value, address _token, bytes _extraData);

    function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData){
        Token t = Token(_token);
        require(t.transferFrom(_from, this, _value));
        receivedTokens(_from, _value, _token, _extraData);
    }

    function () payable {
        receivedEther(msg.sender, msg.value);
    }
}

contract Token {
    mapping (address => uint256) public balanceOf;
    function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
}

/**
 * The shareholder association contract itself
 */
contract Association is owned, tokenRecipient {

    uint public minimumQuorum;
    uint public debatingPeriodInMinutes;
    Proposal[] public proposals;
    uint public numProposals;
    Token public sharesTokenAddress;

    event ProposalAdded(uint proposalID, address recipient, uint amount, string description);
    event Voted(uint proposalID, bool position, address voter);
    event ProposalTallied(uint proposalID, uint result, uint quorum, bool active);
    event ChangeOfRules(uint newMinimumQuorum, uint newDebatingPeriodInMinutes, address newSharesTokenAddress);

    struct Proposal {
        address recipient;
        uint amount;
        string description;
        uint votingDeadline;
        bool executed;
        bool proposalPassed;
        uint numberOfVotes;
        bytes32 proposalHash;
        Vote[] votes;
        mapping (address => bool) voted;
    }

    struct Vote {
        bool inSupport;
        address voter;
    }

    // Modifier that allows only shareholders to vote and create new proposals
    // 需要持有代币才可进行投票(意味着需要有股份才可投票)
    modifier onlyShareholders {
        require(sharesTokenAddress.balanceOf(msg.sender) > 0);
        _;
    }

    /**
     * Constructor function
     *
     * First time setup
     */
    function Association(Token sharesAddress, uint minimumSharesToPassAVote, uint minutesForDebate) payable {
        changeVotingRules(sharesAddress, minimumSharesToPassAVote, minutesForDebate);
    }

    /**
     * Change voting rules
     *
     * Make so that proposals need to be discussed for at least `minutesForDebate/60` hours
     * and all voters combined must own more than `minimumSharesToPassAVote` shares of token `sharesAddress` to be executed
     *
     * @param sharesAddress token address
     * @param minimumSharesToPassAVote proposal can vote only if the sum of shares held by all voters exceed this number
     * @param minutesForDebate the minimum amount of delay between when a proposal is made and when it can be executed
     *
     *
     * minimumSharesToPassAVote 定义了如果提案执行需要的最少股份比例。
     *
     */
    function changeVotingRules(Token sharesAddress, uint minimumSharesToPassAVote, uint minutesForDebate) onlyOwner {
        sharesTokenAddress = Token(sharesAddress);
        if (minimumSharesToPassAVote == 0 ) minimumSharesToPassAVote = 1;
        minimumQuorum = minimumSharesToPassAVote;
        debatingPeriodInMinutes = minutesForDebate;
        ChangeOfRules(minimumQuorum, debatingPeriodInMinutes, sharesTokenAddress);
    }

    /**
     * Add Proposal
     *
     * Propose to send `weiAmount / 1e18` ether to `beneficiary` for `jobDescription`. `transactionBytecode ? Contains : Does not contain` code.
     *
     * @param beneficiary who to send the ether to
     * @param weiAmount amount of ether to send, in wei
     * @param jobDescription Description of job
     * @param transactionBytecode bytecode of transaction
     */
    function newProposal(
        address beneficiary,
        uint weiAmount,
        string jobDescription,
        bytes transactionBytecode
    )
        onlyShareholders
        returns (uint proposalID)
    {
        proposalID = proposals.length++;
        Proposal storage p = proposals[proposalID];
        p.recipient = beneficiary;
        p.amount = weiAmount;
        p.description = jobDescription;
        p.proposalHash = sha3(beneficiary, weiAmount, transactionBytecode);
        p.votingDeadline = now + debatingPeriodInMinutes * 1 minutes;
        p.executed = false;
        p.proposalPassed = false;
        p.numberOfVotes = 0;
        ProposalAdded(proposalID, beneficiary, weiAmount, jobDescription);
        numProposals = proposalID+1;

        return proposalID;
    }

    /**
     * Add proposal in Ether
     *
     * Propose to send `etherAmount` ether to `beneficiary` for `jobDescription`. `transactionBytecode ? Contains : Does not contain` code.
     * This is a convenience function to use if the amount to be given is in round number of ether units.
     *
     * @param beneficiary who to send the ether to
     * @param etherAmount amount of ether to send
     * @param jobDescription Description of job
     * @param transactionBytecode bytecode of transaction
     */
    function newProposalInEther(
        address beneficiary,
        uint etherAmount,
        string jobDescription,
        bytes transactionBytecode
    )
        onlyShareholders
        returns (uint proposalID)
    {
        return newProposal(beneficiary, etherAmount * 1 ether, jobDescription, transactionBytecode);
    }

    /**
     * Check if a proposal code matches
     *
     * @param proposalNumber ID number of the proposal to query
     * @param beneficiary who to send the ether to
     * @param weiAmount amount of ether to send
     * @param transactionBytecode bytecode of transaction
     */
    function checkProposalCode(
        uint proposalNumber,
        address beneficiary,
        uint weiAmount,
        bytes transactionBytecode
    )
        constant
        returns (bool codeChecksOut)
    {
        Proposal storage p = proposals[proposalNumber];
        return p.proposalHash == sha3(beneficiary, weiAmount, transactionBytecode);
    }

    /**
     * Log a vote for a proposal
     *
     * Vote `supportsProposal? in support of : against` proposal #`proposalNumber`
     *
     * @param proposalNumber number of proposal
     * @param supportsProposal either in favor or against it
     */
    function vote(
        uint proposalNumber,
        bool supportsProposal
    )
        onlyShareholders
        returns (uint voteID)
    {
        Proposal storage p = proposals[proposalNumber];
        
        require(now < p.votingDeadline);
        require(p.voted[msg.sender] != true);

        voteID = p.votes.length++;
        p.votes[voteID] = Vote({inSupport: supportsProposal, voter: msg.sender});
        p.voted[msg.sender] = true;
        p.numberOfVotes = voteID +1;
        Voted(proposalNumber,  supportsProposal, msg.sender);
        return voteID;
    }

    /**
     * Finish vote
     *
     * Count the votes proposal #`proposalNumber` and execute it if approved
     *
     * @param proposalNumber proposal number
     * @param transactionBytecode optional: if the transaction contained a bytecode, you need to send it
     */
    function executeProposal(uint proposalNumber, bytes transactionBytecode) {
        Proposal storage p = proposals[proposalNumber];

        require(now > p.votingDeadline                                             // If it is past the voting deadline
            && !p.executed                                                          // and it has not already been executed
            && p.proposalHash == sha3(p.recipient, p.amount, transactionBytecode)); // and the supplied code matches the proposal...


        // ...then tally the results
        // 统计投票结果
        // 计算投票人的股份比例总和
        // 计算赞同票数比例和反对票数比例
        uint quorum = 0;
        uint yea = 0;
        uint nay = 0;

        for (uint i = 0; i <  p.votes.length; ++i) {
            Vote storage v = p.votes[i];
            uint voteWeight = sharesTokenAddress.balanceOf(v.voter);
            quorum += voteWeight;
            if (v.inSupport) {
                yea += voteWeight;
            } else {
                nay += voteWeight;
            }
        }

        // Check if a minimum quorum has been reached
        // 投票者的股份比例总和必须达到执行提案的最低要求
        require(quorum >= minimumQuorum); 

        // 如果赞同票数比例大于反对票数比例,则执行提案
        if (yea > nay ) {
            // Proposal passed; execute the transaction

            p.executed = true;
            require(p.recipient.call.value(p.amount)(transactionBytecode));

            p.proposalPassed = true;
        } else {
            // Proposal failed
            p.proposalPassed = false;
        }

        // Fire Events
        ProposalTallied(proposalNumber, yea - nay, quorum, p.proposalPassed);
    }
}

代码分析

代码和之前的很像,不同的是这次需要代币合约的地址,以便股东行使投票权。

我们定义了一个代币合约,提供一个balanceOf方法,以便获取股东的权重占比。

contract Token { mapping (address => uint256) public balanceOf; }

然后我们定义了一个类型为token的变量,将部署在区块链上的代币合约的地址指向它。在以太坊中,这是一种让合约之间交互的最简单的方式。

contract Association {
    token public sharesTokenAddress;
// ...
function Association(token sharesAddress, uint minimumSharesForVoting, uint minutesForDebate) {
        sharesTokenAddress = token(sharesAddress);

定义了如果提案执行需要的最少股份比例。

这里不再像之前需要最少投票数,而是根据投票人的股份比例总和来决定,看是否达到了提案执行的要求。

    /**
     * Change voting rules
     *
     * Make so that proposals need to be discussed for at least `minutesForDebate/60` hours
     * and all voters combined must own more than `minimumSharesToPassAVote` shares of token `sharesAddress` to be executed
     *
     * @param sharesAddress token address
     * @param minimumSharesToPassAVote proposal can vote only if the sum of shares held by all voters exceed this number
     * @param minutesForDebate the minimum amount of delay between when a proposal is made and when it can be executed
     *
     *
     * minimumSharesToPassAVote 定义了如果提案执行需要的最少股份比例。
     *
     */
    function changeVotingRules(Token sharesAddress, uint minimumSharesToPassAVote, uint minutesForDebate) onlyOwner {
        sharesTokenAddress = Token(sharesAddress);
        if (minimumSharesToPassAVote == 0 ) minimumSharesToPassAVote = 1;
        minimumQuorum = minimumSharesToPassAVote;
        debatingPeriodInMinutes = minutesForDebate;
        ChangeOfRules(minimumQuorum, debatingPeriodInMinutes, sharesTokenAddress);
    }

执行提案

首先检查投票人持股比例是否达标,其次统计赞同票数比例和反对票数比例。如果赞同票数比例大于反对票数比例,就执行提案。跟之前的投票系统相比,不再需要最低投票数和赞同票的区间值。

    /**
     * Finish vote
     *
     * Count the votes proposal #`proposalNumber` and execute it if approved
     *
     * @param proposalNumber proposal number
     * @param transactionBytecode optional: if the transaction contained a bytecode, you need to send it
     */
    function executeProposal(uint proposalNumber, bytes transactionBytecode) {
        Proposal storage p = proposals[proposalNumber];

        require(now > p.votingDeadline                                             // If it is past the voting deadline
            && !p.executed                                                          // and it has not already been executed
            && p.proposalHash == sha3(p.recipient, p.amount, transactionBytecode)); // and the supplied code matches the proposal...


        // ...then tally the results
        // 统计投票结果
        // 计算投票人的股份比例总和
        // 计算赞同票数比例和反对票数比例
        uint quorum = 0;
        uint yea = 0;
        uint nay = 0;

        for (uint i = 0; i <  p.votes.length; ++i) {
            Vote storage v = p.votes[i];
            uint voteWeight = sharesTokenAddress.balanceOf(v.voter);
            quorum += voteWeight;
            if (v.inSupport) {
                yea += voteWeight;
            } else {
                nay += voteWeight;
            }
        }

        // Check if a minimum quorum has been reached
        // 投票者的股份比例总和必须达到执行提案的最低要求
        require(quorum >= minimumQuorum); 

        // 如果赞同票数比例大于反对票数比例,则执行提案
        if (yea > nay ) {
            // Proposal passed; execute the transaction

            p.executed = true;
            require(p.recipient.call.value(p.amount)(transactionBytecode));

            p.proposalPassed = true;
        } else {
            // Proposal failed
            p.proposalPassed = false;
        }

        // Fire Events
        ProposalTallied(proposalNumber, yea - nay, quorum, p.proposalPassed);
    }

未完待续... ...

上一篇 下一篇

猜你喜欢

热点阅读