以太坊DAO之股东协会智能合约

Decentralized Autonomous Organization,简称DAO,以太坊中重要的概念。一般翻译为去中心化的自治组织。

上一节中,我们为了展示什么是DAO创建了一个合约,就像一个采用邀请制的俱乐部,会员被总统的心血来潮邀请或禁止。但这有一些缺点:如果有人想改变他的主要地址怎么办?如果一些成员比其他成员更重要?怎么办? 如果你真的想在公开市场上交易或出售会员资格或股票怎么办?如果你希望你的组织作为股东的不改变决策的持续工作,该怎么办?

股东协会

我们将修改我们的合约以将其连接到特定代币,该代币将作为合约的持有份额。首先,我们需要创建此代币:转到代币教程并创建一个简单代币,初始供应为100,小数为0,百分号(%)为符号。如果你希望能够以百分比的百分比进行交易,则将供应量增加100倍或1000倍,然后将相应数量的零添加为小数。部署此合约并将其地址保存在文本文件中。

那么修改后的股东代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
pragma solidity >=0.4.22 <0.6.0;

contract owned {
address public owner;

constructor() 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 memory _extraData) public {
Token t = Token(_token);
require(t.transferFrom(_from, address(this), _value));
emit receivedTokens(_from, _value, _token, _extraData);
}

function () payable external {
emit 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
*
* First time setup
*/
constructor(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;
emit ChangeOfRules(minimumQuorum, debatingPeriodInMinutes, address(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 memory jobDescription,
bytes memory 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(abi.encodePacked(beneficiary, weiAmount, transactionBytecode));
p.minExecutionDate = now + debatingPeriodInMinutes * 1 minutes;
p.executed = false;
p.proposalPassed = false;
p.numberOfVotes = 0;
emit 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 memory jobDescription,
bytes memory 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 memory transactionBytecode
)
view public
returns (bool codeChecksOut)
{
Proposal storage p = proposals[proposalNumber];
return p.proposalHash == keccak256(abi.encodePacked(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;
emit 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 memory 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(abi.encodePacked(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;

(bool success, ) = p.recipient.call.value(p.amount)(transactionBytecode);
require(success);

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

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

部署和使用

代码的部署几乎与前面的代码完全相同,但你还需要放置一个共享代币地址shares token address,该地址是代币的地址,它将作为具有投票权的共享。

注意这些代码行:首先我们描述新合约的代币合约。由于它只使用了balanceOf函数,我们只需要添加那一行。

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

然后我们定义一个类型标记的变量,这意味着它将继承我们之前描述的所有函数。最后,我们将代币变量指向区块链上的地址,因此它可以使用它并请求实时信息。这是使一个合约在以太坊中理解另一个的最简单方法。

1
2
3
4
5
contract Association {
token public sharesTokenAddress;
// ...
constructor(token sharesAddress, uint minimumSharesForVoting, uint minutesForDebate) {
sharesTokenAddress = token(sharesAddress);

这个协会association提出了前一届大会congress没有的挑战:因为任何拥有代币的人都可以投票而且余额可以很快变化,当股东投票时,提案的实际分数不能计算,否则有人能够通过简单地将他的份额发送到不同的地址来多次投票。因此,在本合约中,仅记录投票位置,然后在执行提案阶段计算实际得分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}

计算加权投票的另一种方法是创建一个单一的有符号整数来保持投票得分并检查结果是正面还是负面,但你必须使用int将无符号整数 voteWeight转换为有符号整数 得分= int(voteWeight);

使用这个DAO就像以前一样:成员创建新的提案,对它们进行投票,等到截止日期过去,然后任何人都可以计算投票并执行它。

但是我如何限制所有者的权力呢?

在此合约中,设置为所有者owner的地址具有一些特殊权力:他们可以随意添加或禁止成员,更改获胜所需的保证金,更改辩论所需的时间以及投票通过所需的法定人数。但这可以通过使用业主拥有的另一种权力来解决:改变所有权。

所有者可以通过将新所有者指向0x00000来将所有权更改为任何人….这将保证规则永远不会改变,但这是不可逆转的行动。所有者还可以将所有权更改为合约本身:只需单击复制地址copy address并将其添加到新所有者new owner字段即可。这将使得所有者的所有权力都可以通过创建提案来执行。

如果你愿意,你也可以设置一个合约作为另一个合约的所有者:假设你想要一个公司结构,你想要一个有权任命董事会成员的终身总统,然后可以发行更多的股票,最后这些股票被投票关于如何花费预算。你可以创建一个关联合约Association,该合约mintable token使用最终由单个帐户拥有的会议congress所拥有的mintable代币。

但是如果你想要不同的投票规则呢?也许改变投票规则你需要80%的共识,或者成员可能不同。在这种情况下,你可以创建另一个相同的DAO或使用其他一些源代码并将其作为第一个的所有者插入。

======================================================================

分享一些以太坊、EOS、比特币等区块链相关的交互式在线编程实战教程:

  • java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。
  • 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。
  • EOS教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
  • java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。
  • php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
  • tendermint区块链开发详解,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。

汇智网原创翻译,转载请标明出处。这里是原文以太坊DAO之股东协会