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合约
定义变量
-
最小投票数:一个提案需要设置最小投票数。如果没有达到规定的投票数量,说明该提案是没有太大意义的。
-
投票时间:设置一个允许投票的时间。到期后不允许投票。
-
设定赞同票数的区间值:
-
一个投票可能是赞同或者反对。
-
如果想让一个提案通过,必须得到票数的50%加上这个设定的区间值。如果只要50%是赞同票就能通过,设为0就好了。如果要求全票通过,则设为members - 1。
-
提案的投票结果currentResult初始为0,在获得一张赞同票时,currentResult++,获得反对票时,currentResult--。一半赞同一半反对,currentResult最后为0。如果区间值设为0,意味该提案只需要半数人赞同便可通过。
-
假如设置赞同票数的区间值为2,共有10张投票,如果提案通过,则说明至少有7张是赞同票。因为需要currentResult大于2。
-
// 最小的投票数
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