创建一个民主自治组织
匿名 · 更新于 2018/5/21
分散的自治组织
“在区块链上,没有人知道你是冰箱”
- 理查德布朗
到目前为止,我们列出的所有合约都由其他可能由人类持有的账户拥有并执行。但是,在以太坊生态系统中不存在对机器人或人类的歧视,合同可以创建任何其他帐户所能够执行的任意行为。合同可以拥有令牌,参与众包,甚至是其他合同的投票成员。
在本节中,我们将建立一个分散和民主的组织,仅存在于区块链中,但它可以做任何简单账户都能做到的事情。该组织有一个中央经理,负责决定谁是成员和投票规则,但正如我们将看到的,这也可以改变。
这个特定的民主运作的方式是它有一个所有者,其行为像一个管理者,首席执行官或总统。该业主可以添加(或删除)的投票成员的组织。任何成员都可以提出一个提案,以Ethereum交易的形式发送以太或执行某个合同,其他成员可以投票支持或反对该提案。一旦预先确定的时间和一定数量的成员投票,该提案就可以执行:合同计票,如果有足够票数,它将执行给定的交易。
区块链大会
代码
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) public returns (bool success);
}
contract Congress is owned, tokenRecipient {
// Contract Variables and events
uint public minimumQuorum;
uint public debatingPeriodInMinutes;
int public majorityMargin;
Proposal[] public proposals;
uint public numProposals;
mapping (address => uint) public memberId;
Member[] public members;
event ProposalAdded(uint proposalID, address recipient, uint amount, string description);
event Voted(uint proposalID, bool position, address voter, string justification);
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 minExecutionDate;
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 (
uint minimumQuorumForProposals,
uint minutesForDebate,
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
*
* 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];
if (id == 0) {
memberId[targetMember] = members.length;
id = members.length++;
}
members[id] = Member({member: targetMember, memberSince: now, name: memberName});
MembershipChanged(targetMember, true);
}
/**
* Remove member
*
* @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,
* 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
*/
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
* @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.minExecutionDate = 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
)
onlyMembers public
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 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)
{
Proposal storage p = proposals[proposalNumber]; // Get the proposal
require(!p.voted[msg.sender]); // If has already voted, cancel
p.voted[msg.sender] = true; // Set this voter as having voted
p.numberOfVotes++; // Increase the number of votes
if (supportsProposal) { // If they support the proposal
p.currentResult++; // Increase score
} else { // If they don't
p.currentResult--; // Decrease the score
}
// 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
*/
function executeProposal(uint proposalNumber, bytes transactionBytecode) public {
Proposal storage p = proposals[proposalNumber];
require(now > p.minExecutionDate // If it is past the voting deadline
&& !p.executed // and it has not already been executed
&& p.proposalHash == keccak256(p.recipient, p.amount, transactionBytecode) // and the supplied code matches the proposal
&& p.numberOfVotes >= minimumQuorum); // and a minimum quorum has been reached...
// ...then execute result
if (p.currentResult > majorityMargin) {
// Proposal passed; execute the transaction
p.executed = true; // Avoid recursive calling
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);
}
}
如何部署
打开钱包(如果你只是测试,进入菜单开发>网络> testnet),转到合同选项卡,然后按下部署合同,并在坚实代码框粘贴上面的代码。在合同选择器上,选择国会,你会看到设置变量。
- 提案的最低法定人数是提案在执行之前需要达到的最低投票数。
- 辩论分钟是在执行之前需要经过的最短时间(以分钟为单位)
- 投票多数票的保证金如果投票数超过50%加上保证金,投标书将通过。简单多数为0,将其置于成员数量上 - 1以要求绝对一致。

您可以稍后更改这些参数。首先,您可以选择5分钟的辩论时间,并将其余参数保留为0.在页面上略低一点时,您会看到以乙醚部署合同的成本估算值。如果要保存,可以尝试降低价格,但这可能意味着必须等待更长的时间才能创建合同。点击部署,输入您的密码并等待。
In a few seconds you'll be taken to the dashboard, scroll down and you'll be able to see your transaction being created. In under a minute you'll see the transaction successful and a new unique icon will have been created. Click the contract's name to see it (you can get to it at any time on the Contracts tab).

SHARING WITH OTHERS
If you want to share your DAO with others, then they need both the contract address and the interface file, a small text string that works as an instruction manual of the contract. Click copy address to get the former and show interface to reveal the latter.
On the other computer, go into the Contracts tab and then click on watch contract. Add the correct address and interface and press OK.

INTERACTING WITH THE CONTRACT
On the "Read from contract" you can see all the functions you can execute for free on the contract, as they are just reading information from the blockchain. Here you can see, for instance, the current "owner" of the contract (that should be the account that uploaded the contract).
On the "Write to contract" you have a list of all the functions that will attempt to do some computation that saves data to the blockchain, and therefore will cost ether. Select "New Proposal" and it will show all the options for that function.
Before interacting with the contract, you'll need to add new members so they can vote. On the "Select function" picker, choose "Add Member". Add the address of the person you want to make a member(to remove a member, pick the function "Remove Member"). On "execute from" make sure that you have the same account that is set as the owner as this is an action only the main administrator can execute. Press execute and wait a few seconds for the next block to go through with your change.
There's no list of members, but you can check if anyone is a member by putting their address on the Members function on the Read from contract column.
Also, if you want the contract to have any money of its own, you need to deposit some ether (or other token) into it, otherwise you'll have a pretty toothless organization. Press Transfer Ether & Tokens on the top right corner.
ADD A SIMPLE PROPOSAL: SEND ETHER
Now let's add the first proposal to the contract. On the function picker, select New Proposal.
For "beneficiary" add the address of someone you want to send ether to, and put how much you want to send in the box marked "Wei Amount." Wei is the smallest unit of ether, equal to 10^-18 ether, and must always be given as an integer. For example, if you want to send 1 ether, enter 1000000000000000000 (that's 18 zeroes). Finally, add some text describing the reason you want to do this. Leave "Transaction bytecode" blank for now. Click execute and type your password. After a few seconds the numProposals will increase to 1 and the first proposal, number 0, will appear on the left column. As you add more proposals, you can see any of them by simply putting the proposal number on the "proposals" field and you can read all about it.
Voting on a proposal is also very simple. Choose "Vote" on the function picker. Type the proposal Number in the first box and check the "Yes" box if you agree with it (or leave it blank to vote against it). Click "execute" to send your vote.

When the voting time has passed, you can select "executeProposal". If the proposal was simply sending ether, then you can also leave the "transactionBytecode" field blank. After pressing "execute" but before typing your password, pay attention to the screen that appears.
If there is a warning on the "estimated fee consumption" field, then this means that for some reason the function called will not execute and will be abruptly terminated. It can mean many things, but in the context of this contract this warning will show up whenever you try to execute a contract before its deadline has passed, or if the user is trying to send a different bytecode data than the original proposal had. For security reasons if any of these things happens, the contract execution is abruptly terminated and the user that attempted the illegal transaction will lose all the ethers he sent to pay transaction fees.
If the transaction was executed, then after a few seconds you should be able to see the result: executed will turn to true and the correct amount of ether should be subtracted from this contract's balance and into the recipient address.
ADD A COMPLEX PROPOSAL: OWN ANOTHER TOKEN
You can use this democracy to execute any transaction on ethereum, as long as you can figure out the bytecode that that transaction generates. Luckily for us, you can use the wallet to do precisely that!
In this example, we'll use a token to show that this contract can hold more than ether and can do transactions in any other ethereum-based asset. First, create a token that belongs to one of your normal accounts. On the contract page, click Transfer Ether & Tokens to transfer some of them to your new congress contract (for simplicity, don't send more than half your coins to your DAO). After that, we are going to simulate the action you want to execute. So if you want to propose that the DAO send 500mg of a gold token to a person as a payment, then follow the steps that you'd do to execute that transaction from an account you own and press "send" but when the confirmation screens pops up, don't type your password.

Instead, click "SHOW RAW DATA" link and copy the code displayed on the "RAW DATA" field and save it to a text file or notepad. Cancel the transaction. You'll also need the address of the contract you'll be calling for that operation, in this case the token contract. You can find it on the Contracts tab: save that somewhere too.
Now go back to the congress contract and create a new proposal with these parameters:
- As the beneficiary, put the address of your token (pay attention if it's the same icon)
- Leave Ether amount blank
- On the Job description just write a small description on what you want to accomplish
- On the Transaction Bytecode, paste the bytecode you saved from the data field on the previous step

In a few seconds you should be able to see the details on the proposal. You'll notice that the transaction bytecode won't be shown there and instead there's only a "transaction hash". Unlike the other fields, Bytecode can be extremely lengthy and therefore expensive to store on the blockchain, so instead of archiving it, the person executing the call later will provide the bytecode.
But that, of course, creates a security hole: how can a proposal be voted without the actual code being there? And what prevents a user from executing a different code after the proposal has been voted on? That's where transaction hash comes in. Scroll a bit on the "read from contract" function list and you'll see a proposal checker function, where anyone can put all the function parameters and check if they match the one being voted on. This also guarantees that proposals don't get executed unless the hash of the bytecode matches exactly the one on the provided code.

Anyone can actually check the proposal very easily by following the same steps to get the correct bytecode and then adding the proposal number and other parameters to the function called Check proposal code on the bottom of Read from contract.
The rest of the voting process remains the same: all members can vote and after the deadline, someone can execute the proposal. The only difference is that this time you'll have to provide the same bytecode you've submitted before. Pay attention to any warnings on the confirmation window: if it says it won't execute your code, check to see if the deadline has already passed, if there are enough votes and if your transaction bytecode checks out.
MAKE IT BETTER
Here are some drawbacks of this current DAO that we leave as an exercise to the reader:
- Can you make the member list public and indexed?
- Can you allow members to change their votes (after votes are cast but before the votes are tallied up)?
- Currently the vote message is only visible on logs, can you make a function that will display all votes?
The Shareholder Association
In the previous section we created a contract that works like an invitation-only club, where members are invited or banned by the whim of the president. But this has a few drawbacks: what if someone wants to change his main address? What if some members have more weight than others? What if you actually want to trade or sell memberships or shares on an open market? What if you wanted your organization to have work as a constant decision machine by shareholders?
我们将修改一下我们的合同,将它连接到一个特定的令牌,它将作为合同的持有份额。首先,我们需要创建此令牌:转至令牌教程并创建一个初始供应为100,小数点数为0,百分比符号(%)为符号的简单令牌。如果您希望能够以百分之几的百分比进行交易,那么将供应量增加100倍或1000倍,然后将相应数量的零作为小数添加。部署此合同并将其地址保存在文本文件中。
现在给股东代码:
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);
}
}
contract Token {
mapping (address => uint256) public balanceOf;
function transferFrom(address _from, address _to, uint256 _value) public 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 minExecutionDate;
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 public {
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
*/
function changeVotingRules(Token sharesAddress, uint minimumSharesToPassAVote, uint minutesForDebate) onlyOwner public {
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 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.minExecutionDate = 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 public
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 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
*/
function vote(
uint proposalNumber,
bool supportsProposal
)
onlyShareholders public
returns (uint voteID)
{
Proposal storage p = proposals[proposalNumber];
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) public {
Proposal storage p = proposals[proposalNumber];
require(now > p.minExecutionDate // If it is past the voting deadline
&& !p.executed // and it has not already been executed
&& p.proposalHash == keccak256(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;
}
}
require(quorum >= minimumQuorum); // Check if a minimum quorum has been reached
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; }
然后我们定义一个类型标记的变量,这意味着它将继承我们之前描述的所有函数。最后,我们将令牌变量指向区块链上的地址,以便它可以使用该地址并请求实时信息。这是让一个合约在ethereum中理解另一个的最简单方法。
contract Association {
token public sharesTokenAddress;
// ...
function Association(token sharesAddress, uint minimumSharesForVoting, uint minutesForDebate) {
sharesTokenAddress = token(sharesAddress);
这个协会提出了前一届大会没有的挑战:因为任何有代币的人都可以投票,余额可以很快改变,所以当股东投票时,提案的实际得分不能被计算在内,否则有人能够只需将他的份额发送到不同的地址,多次投票。所以在这份合同中只记录了投票的位置,然后在执行建议阶段计算真实分数。
uint quorum = 0;
uint yea = 0;
uint nay = 0;
for (uint i = 0; i < p.votes.length; ++i) {
Vote v = p.votes[i];
uint voteWeight = sharesTokenAddress.balanceOf(v.voter);
quorum += voteWeight;
if (v.inSupport) {
yea += voteWeight;
} else {
nay += voteWeight;
}
}
计算加权投票的另一种方法是创建一个单独的有符号整数来保留投票分数,并在最后检查它是正数还是负数,但是您必须将无符号整数 voteWeight转换为使用int的有符号整数score = int(voteWeight);
使用这个DAO就像以前一样:成员创建新的提案,对他们投票,等到截止日期过去,然后任何人都可以计票并执行它。

但是,我怎样才能限制业主的权力呢?
在该合同中,作为所有者的地址具有一些特殊权力:他们可以随意添加或禁止成员,更改获胜所需的保证金,更改辩论所需的时间以及投票通过所需的法定人数。但是,这可以通过使用拥有者的另一种力量来解决:改变所有权。
通过将新的所有者指向0x00000 ...,所有者可以将所有权更改为任何人。这将保证规则永远不会改变,但这是一个不可逆转的行动。所有者也可以将所有权更改为合同本身:只需点击“复制地址”并将其添加到“新所有者”字段。这将使所有者的所有权力可以通过创建提案来执行。
如果你愿意,你也可以设定一个合同作为另一个合同的所有者:假设你想要一个公司结构,你希望总统有权委任董事会成员,然后可以发行更多的股票,最后这些股票投票如何花费预算。你可以创建一个协会的合同,即使用mintable令牌由拥有美国国会终于通过一个帐户拥有。
但是如果你想要不同的投票规则呢?也许要改变投票规则,你需要80%的共识,或者成员可能不同。在这种情况下,您可以创建另一个相同的DAO或使用其他一些源代码并将其作为第一个的所有者。
液体民主
对合同的所有费用和行动进行投票需要时间,并且要求用户不断地积极,知情和专注。另一个有趣的方法是选择一个指定的账户来控制合同,然后能够迅速做出决定。
我们将实施一个通常称为液体民主的版本,这是一个更灵活的代表民主。在这种民主制度下,任何选民都可以成为潜在的代表:不要投票给你想要的候选人,而只需说出你信任哪个选民为你处理这个决定。你的投票权重被委托给他们,他们又可以将其委托给他们信任的另一位选民等等。最终的结果应该是,投票数最多的账户是与最多的选民建立信任关系的账户。
代码
pragma solidity ^0.4.16;
contract token {
mapping (address => uint256) public balanceOf;
}
contract LiquidDemocracy {
token public votingToken;
bool underExecution;
address public appointee;
mapping (address => uint) public voterId;
mapping (address => uint256) public voteWeight;
uint public delegatedPercent;
uint public lastWeightCalculation;
uint public numberOfDelegationRounds;
uint public numberOfVotes;
DelegatedVote[] public delegatedVotes;
string public forbiddenFunction;
event NewAppointee(address newAppointee, bool changed);
struct DelegatedVote {
address nominee;
address voter;
}
/**
* Constructor function
*/
function LiquidDemocracy(
address votingWeightToken,
string forbiddenFunctionCall,
uint percentLossInEachRound
) {
votingToken = token(votingWeightToken);
delegatedVotes.length++;
delegatedVotes[0] = DelegatedVote({nominee: 0, voter: 0});
forbiddenFunction = forbiddenFunctionCall;
delegatedPercent = 100 - percentLossInEachRound;
if (delegatedPercent > 100) delegatedPercent = 100;
}
/**
* Vote for an address
*
* Send your vote weight to another address
*
* @param nominatedAddress the destination address receiving the sender's vote
*/
function vote(address nominatedAddress) returns (uint voteIndex) {
if (voterId[msg.sender]== 0) {
voterId[msg.sender] = delegatedVotes.length;
numberOfVotes++;
voteIndex = delegatedVotes.length++;
numberOfVotes = voteIndex;
}
else {
voteIndex = voterId[msg.sender];
}
delegatedVotes[voteIndex] = DelegatedVote({nominee: nominatedAddress, voter: msg.sender});
return voteIndex;
}
/**
* Perform Executive Action
*
* @param target the destination address to interact with
* @param valueInWei the amount of ether to send along with the transaction
* @param bytecode the data bytecode for the transaction
*/
function execute(address target, uint valueInWei, bytes32 bytecode) {
require(msg.sender == appointee // If caller is the current appointee,
&& !underExecution // // if the call is being executed,
&& bytes4(bytecode) != bytes4(sha3(forbiddenFunction)) // and it's not trying to do the forbidden function
&& numberOfDelegationRounds >= 4); // and delegation has been calculated enough
underExecution = true;
assert(target.call.value(valueInWei)(bytecode)); // Then execute the command.
underExecution = false;
}
/**
* Calculate Votes
*
* Go thruogh all the delegated vote logs and tally up each address's total rank
*/
function calculateVotes() returns (address winner) {
address currentWinner = appointee;
uint currentMax = 0;
uint weight = 0;
DelegatedVote storage v = delegatedVotes[0];
if (now > lastWeightCalculation + 90 minutes) {
numberOfDelegationRounds = 0;
lastWeightCalculation = now;
// Distribute the initial weight
for (uint i=1; i< delegatedVotes.length; i++) {
voteWeight[delegatedVotes[i].nominee] = 0;
}
for (i=1; i< delegatedVotes.length; i++) {
voteWeight[delegatedVotes[i].voter] = votingToken.balanceOf(delegatedVotes[i].voter);
}
}
else {
numberOfDelegationRounds++;
uint lossRatio = 100 * delegatedPercent ** numberOfDelegationRounds / 100 ** numberOfDelegationRounds;
if (lossRatio > 0) {
for (i=1; i< delegatedVotes.length; i++){
v = delegatedVotes[i];
if (v.nominee != v.voter && voteWeight[v.voter] > 0) {
weight = voteWeight[v.voter] * lossRatio / 100;
voteWeight[v.voter] -= weight;
voteWeight[v.nominee] += weight;
}
if (numberOfDelegationRounds>3 && voteWeight[v.nominee] > currentMax) {
currentWinner = v.nominee;
currentMax = voteWeight[v.nominee];
}
}
}
}
if (numberOfDelegationRounds > 3) {
NewAppointee(currentWinner, appointee == currentWinner);
appointee = currentWinner;
}
return currentWinner;
}
}
部署
首先,你需要一个令牌。如果您按照上面的“ 股东关联”教程进行操作,则可以使用与以前相同的标记,否则只需部署新标记并将其分配给某些帐户。复制令牌地址。
部署民主合同,并把对令牌地址投票权重的令牌,把75作为每轮损失百分比和transferOwnership(地址)(没有任何空格或额外的字符!)为禁止功能。
选择一个委托
现在部署Liquid民主并进入其页面。首先让任何股东投票选出他们相信代表本合同作出决定的人。如果您想成为最终决策者,您可以对自己投票,如果您不想代表您担任这个角色,您可以在零地址投票。
在足够的人投票后,您可以执行计算投票功能,以便计算每个人的投票权重。这个功能需要多次运行,所以第一次运行它会将每个人的体重设置为他们在所选令牌中的平衡,在下一轮中投票权重将发送给您所投票委任的人,接下来它将转到由您选择的人投票的人等等。为了防止投票代表团的无限循环,每次投票都被转发时,它会失去一点权力,在合约启动时设置在百分之百的盈利回报。因此,如果损失设置为75%,这意味着您投票的人获得了您体重的100%,但如果他们将投票委托给其他人,则只有75%的体重被转发。该人可以委托给其他人,但他们只会得到56%的投票权,等等。如果比率低于100%,那么重新计算投票代表团不会再改变结果的时间会有限,但如果它是100%,则意味着投票权重将简单地在任何可能的循环周围循环。
如果自本轮调用计算投票开始后已经有一个半小时以上,所有权重将重置,并将根据原始令牌余额重新计算,因此如果您最近收到了更多令牌,则应该再次执行此功能。
众议院
这个投票代表团有什么用处?首先,你可以使用它来代替关联中的令牌权重。首先,获取股东协会的代码,但替换描述令牌的第一行:
contract Token {
mapping (address => uint256) public balanceOf;
function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
}
进入这个:
contract Token {
mapping (address => uint256) public voteWeight;
uint public numberOfDelegationRounds;
function balanceOf(address member) constant returns (uint256 balance) {
if (numberOfDelegationRounds < 3)
return 0;
else
return this.voteWeight(member);
}
function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
}
当你写合同时,你可以描述主合同使用的多个其他合同。有些可能是已经在目标合同上定义的函数和变量,如voteWeight和numberOfDelegationRounds。但请注意,balanceOf是一项新功能,在Liquid Democracy或Association合同中既不存在也不存在,现在我们正在定义它,作为一个函数,如果计算出至少三轮代表团的话,它将返回voteWeight。
使用液体民主作为令牌地址而不是原始令牌,并像往常一样继续部署股东协会。就像以前一样,用户可以针对这些问题做出投票或投票的新建议,但现在,我们不使用令牌余额作为投票权,而是使用委托流程。所以如果你是一个令牌持有者,而不必让自己随时了解所有问题,你可以选择一个你信任的人并指定他们,然后他们可以选择一个他们信任的人:结果是你的代表而不是被限制在一个给定的任意地理位置上,会是你的社交场合中的某个人。
此外,这意味着您可以随时切换您的投票:如果您的代表在某些问题上投票反对您的兴趣,则可以在提案投票结束前更换您的受托人,或者选择代表您自己处理问题并投射你自己的投票。
行政部门
代表民主国家是选择代表的一种很好的方式,但对于一些重要或较简单的决定,对个别提案的投票可能太慢。这就是为什么大多数民主政府都有一个行政部门,被指定的人有权代表国家。
经过四轮代表团之后,更重要的地址将被设定为委任人。如果有很多代表投票,那么可能需要再进行几轮计算投票才能在最终指定的地址进行结算。
被任命者是唯一可以调用Execute函数的地址,它将能够执行(几乎)任何代表整个民主的函数。如果液体民主合同中存有任何以太或令牌,被任命者将被允许在任何地方移动。
如果你遵循了我们的例子,并使用这种液体民主作为标志创建了一个股东协会,那么你应该能够以一种有趣的方式使用行政部门:转到主要协会地址,并执行转移所有权职能,以实现液体民主。
一旦完成转移,将功能切换为更改投票规则。这允许您更改一些基本投票规则,例如投票通过所需的最低法定人数或新提案需要留在场内的时间。尝试更改这些设置并单击执行:当确认窗口弹出时,它会告诉您事务无法执行。这当然会发生,因为只有作为所有者设置的地址才能更改这些设置,合同将拒绝此交易尝试。因此,不要输入密码,而是将数据字段上的代码复制并保存到文本文件中。点击取消,滚动到顶部并点击复制地址 并将其保存到文本文件中。
现在进入液体民主页面并选择执行。在目标上放置联系合同的地址,将以太量保留为0,并将您先前复制的代码粘贴到字节码数据字段中。确保您从作为被任命者的账户中执行它,然后单击执行。
一旦交易完成后,Liquid民主会将命令传递给协会,新的投票规则可能适用。被任命人有绝对的权力去做任何液体民主合同可以执行的事情。您可以使用相同的技巧创建代表民主所拥有的最低限度令牌,然后允许被任命人填写代币或冻结帐户。
为了防止权力滥用,您可以设置一个被任命者无法做到的禁止功能。如果你遵循我们的例子,那么禁止功能就是transferOwnership(地址),以防止被任命者将协会的所有权转让给他们自己(在政治上,当总统使用他的行政权力向自己转让过去属于总统,这是一场政变或盗用)。
时间锁定Multisig
Sometimes time can also be used as a great security mechanism. The following code is based on the congress DAO but with a different twist. Instead of every action requiring the approval of an X number of members, instead any transactions can be initiated by a single member, but they all will require a minimum amount of delay before they can be executed, which varies according to the support that transaction has. The more approvals a proposal has, the sooner it can be executed. A member can vote against a transaction, which will mean that it will cancel one of the other approved signatures.
This means that if you don't have urgency, one or two signatures might be all you need to execute any transaction. But if a single key is compromised, other keys can delay that transaction for months or year or even stop it from being executed.
HOW IT WORKS
A transaction that has been approved by all keys can be executed after ten minutes (this amount is configurable), and the amount of time it requires doubles every time for every 5% of members who don't vote (and quadruples if they actively vote against). If it's a simple ether transaction, the transaction is executed as soon as a vote of support puts it under the required time, but a more complex transaction will require it to be manually executed with the correct bytecode. These are the default values, but this can be set differently when creating the contract:
Number of members approving transaction: Approximate time delay
- 100% approval: 10 minutes (minimum default)
- 90% approval: 40 minutes
- 80%: 2hr 40min
- 50%:大约一周
- 40%:1个月
- 30%:4个月
- 20%:一年多
- 10%或更少:5年或从未
一旦最少的时间过去了,任何人都可以执行交易(参见“国会”以获得更完整的步行)。这是故意的,因为它允许某人安排交易或雇佣其他人执行交易。
代码
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);
}
}
interface Token {
function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
}
contract TimeLockMultisig is owned, tokenRecipient {
Proposal[] public proposals;
uint public numProposals;
mapping (address => uint) public memberId;
Member[] public members;
uint minimumTime = 10;
event ProposalAdded(uint proposalID, address recipient, uint amount, string description);
event Voted(uint proposalID, bool position, address voter, string justification);
event ProposalExecuted(uint proposalID, int result, uint deadline);
event MembershipChanged(address member, bool isMember);
struct Proposal {
address recipient;
uint amount;
string description;
bool executed;
int currentResult;
bytes32 proposalHash;
uint creationDate;
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
*
* First time setup
*/
function TimeLockMultisig(address founder, address[] initialMembers, uint minimumAmountOfMinutes) payable {
if (founder != 0) owner = founder;
if (minimumAmountOfMinutes !=0) minimumTime = minimumAmountOfMinutes;
// 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');
changeMembers(initialMembers, true);
}
/**
* Add member
*
* @param targetMember address to add as a member
* @param memberName label to give this member address
*/
function addMember(address targetMember, string memberName) onlyOwner {
uint id;
if (memberId[targetMember] == 0) {
memberId[targetMember] = members.length;
id = members.length++;
} else {
id = memberId[targetMember];
}
members[id] = Member({member: targetMember, memberSince: now, name: memberName});
MembershipChanged(targetMember, true);
}
/**
* Remove member
*
* @param targetMember the member to remove
*/
function removeMember(address targetMember) onlyOwner {
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--;
}
/**
* Edit existing members
*
* @param newMembers array of addresses to update
* @param canVote new voting value that all the values should be set to
*/
function changeMembers(address[] newMembers, bool canVote) {
for (uint i = 0; i < newMembers.length; i++) {
if (canVote)
addMember(newMembers[i], '');
else
removeMember(newMembers[i]);
}
}
/**
* 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
)
onlyMembers
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.executed = false;
p.creationDate = now;
ProposalAdded(proposalID, beneficiary, weiAmount, jobDescription);
numProposals = proposalID+1;
vote(proposalID, true, '');
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
)
onlyMembers
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
* @param justificationText optional justification text
*/
function vote(
uint proposalNumber,
bool supportsProposal,
string justificationText
)
onlyMembers
{
Proposal storage p = proposals[proposalNumber]; // Get the proposal
require(p.voted[msg.sender] != true); // If has already voted, cancel
p.voted[msg.sender] = true; // Set this voter as having voted
if (supportsProposal) { // If they support the proposal
p.currentResult++; // Increase score
} else { // If they don't
p.currentResult--; // Decrease the score
}
// Create a log of this event
Voted(proposalNumber, supportsProposal, msg.sender, justificationText);
// If you can execute it now, do it
if ( now > proposalDeadline(proposalNumber)
&& p.currentResult > 0
&& p.proposalHash == sha3(p.recipient, p.amount, '')
&& supportsProposal) {
executeProposal(proposalNumber, '');
}
}
function proposalDeadline(uint proposalNumber) constant returns(uint deadline) {
Proposal storage p = proposals[proposalNumber];
uint factor = calculateFactor(uint(p.currentResult), (members.length - 1));
return p.creationDate + uint(factor * minimumTime * 1 minutes);
}
function calculateFactor(uint a, uint b) constant returns (uint factor) {
return 2**(20 - (20 * a)/b);
}
/**
* 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 >= proposalDeadline(proposalNumber) // If it is past the voting deadline
&& p.currentResult > 0 // and a minimum quorum has been reached
&& !p.executed // and it is not currently being executed
&& checkProposalCode(proposalNumber, p.recipient, p.amount, transactionBytecode)); // and the supplied code matches the proposal...
p.executed = true;
assert(p.recipient.call.value(p.amount)(transactionBytecode));
// Fire Events
ProposalExecuted(proposalNumber, p.currentResult, proposalDeadline(proposalNumber));
}
}
部署和使用
像这些教程中所做的那样部署该代码。在部署参数上,如果希望更快的锁定时间,则将最小时间留空将默认为30分钟,然后放置1分钟。上传后,执行“添加成员”功能添加新组成员,他们可以是您认识的其他人,也可以是不同计算机上的帐户或离线存储的成员。
设置为“所有者”的帐户非常强大,因为它可以随意添加或删除成员。因此,在添加主要成员之后,我们建议您通过执行功能转移成员身份将“所有者”设置为另一个帐户。如果您希望将所有成员的增加或删除投票,就像其他任何交易一样,将其设置为多重投票。另一种方法是将其设置为另一个可信的多重金额钱包,或者如果您想要永久修复成员数量,则可能为0x000。请记住,这份合同的资金只与“所有者”账户一样安全。
与上述任何DAO一样,该合同可以持有醚,任何以太坊代币并执行任何合约。为此,请检查如何执行 DAO会议上的复杂提案。
注意事项和改进
为了简单起见,反对提案的投票仅仅被视为少一票支持。如果你愿意的话,你可以玩弄负面票数有更多权重的想法,但这意味着少数成员可以对任何拟议交易拥有有效的否决权。
你还能改善这份合同吗?
我们去探索吧!
你已经到了本教程的最后,但这只是一次伟大冒险的开始。回顾一下,看看你有多成就:你创造了一个活着的,说话的机器人,你自己的加密货币,通过无信任的众筹筹集资金,并用它来启动你自己的私人民主组织。
接下来会发生什么?
-
您仍然控制的代币可以在分散的交易所上出售,或者交易商品和服务,为第一份合同的进一步发展提供资金并发展组织。
-
您的DAO可以在名称注册商处拥有自己的名称,然后更改其重定向的位置,以便在令牌持有者批准后自行更新。
-
该组织不仅可以持有醚,也可以持有以太坊创建的任何其他类型的硬币,包括价值与比特币或美元挂钩的资产。
-
DAO可以被编程为允许多个交易的提案,一些计划在未来。它也可以拥有其他DAO的份额,这意味着它可以投票给更大的组织或成为DAO联盟的一部分。
-
令牌合同可以重新编程以保持以太币或持有其他代币并将其分发给代币持有者。这将把令牌的价值与其他资产的价值联系起来,因此只需将资金转移到令牌地址即可完成支付股息。
这一切都意味着你创建的这个小社会可以发展壮大,从第三方获得资金,支付经常性工资,拥有任何类型的加密资产,甚至使用众包为其活动提供资金。所有这些都具有完全的透明度,完整的问责制和完全免于任何人为干扰。虽然网络生活的合同将执行完全创建的代码执行,没有任何例外,永远。
那么你的合同会是什么?它会成为一个国家,一个公司还是一个非营利组织?你的代码会做什么?
这取决于你。
原文地址