以太坊DAO之区块链大会智能合约

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

“在区块链上,没有人知道你是一台冰箱”——理查德布朗

到目前为止,我们列出的所有合约都是由人类持有的其他账户拥有和执行的。但是在以太坊生态系统中不存在对机器人或人类的歧视,合约可以像任何其他帐户一样创造任意行为。合约可以拥有代币,参与众筹,甚至是其他合约的投票成员。

在本节中,我们将建立一个去中心化的民主组织机构,仅存在于区块链上,但这可以做任何简单账户所能做到的事情。该组织有一个中央经理,负责决定谁是成员和投票规则,但正如我们所看到的,这也可以改变。

这种特殊民主的运作方式是它拥有一个像管理员,首席执行官或总统一样工作的所有者Owner。所有者可以向组织添加(或删除)投票成员。任何成员都可以提出一个提议,该提议以以太坊交易的形式发送以太或执行某些合约,其他成员可以投票支持或反对该提案。一旦预定的时间量和一定数量的成员投票,就可以执行提案:合约计票,如果有足够的票数,它将执行给定的交易。

区块链大会

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
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
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);
}
}

interface Token {
function transferFrom(address _from, address _to, uint256 _value) external 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
*/
constructor (
uint minimumQuorumForProposals,
uint minutesForDebate,
int marginOfVotesForMajority
) payable public {
changeVotingRules(minimumQuorumForProposals, minutesForDebate, marginOfVotesForMajority);
// It’s necessary to add an empty first member
addMember(address(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 memory 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});
emit 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];
memberId[members[i].member] = i;
}
memberId[targetMember] = 0;
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;

emit 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 memory jobDescription,
bytes memory 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(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
)
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 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
* @param justificationText optional justification text
*/
function vote(
uint proposalNumber,
bool supportsProposal,
string memory 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
emit 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 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
&& 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

(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, p.currentResult, p.numberOfVotes, p.proposalPassed);
}
}

如何部署

打开钱包(如果你只是测试,请转到菜单开发>网络>testnet),转到合约选项卡Contracts,然后点击部署合约deploy contract ,在solidity code box上粘贴上面的代码。在合约选择器上,选择Congress ,你将看到设置变量。

  • 提案的最低法定人数是提案在执行之前需要的最低票数。
  • 争论的时间是在执行之前需要经过的最短时间(以分钟为单位)。
  • 多数票的保证金如果超过50%的票数加上保证金,则提案通过。在简单多数时保留为0,将其设为成员数-1要求绝对共识。

你可以稍后更改这些参数。首先,你可以选择5分钟进行辩论,并将剩余参数保留为0。在页面上稍微低一点,你将看到在以太网中部署合约的成本估算值。如果要保存,可以尝试降低价格,但这可能意味着必须等待更长时间才能创建合约。 单击部署Deploy ,键入密码并等待。

几秒钟后,你将被带到仪表板dashboard,向下滚动,你将能够看到正在创建的交易。在不到一分钟的时间内,你将看到交易成功,并且将创建一个新的唯一图标。单击合约的名称以查看它(你可以随时在合约选项卡Contracts上找到它)。

与他人分享

如果你想与他人共享你的DAO,那么他们需要合约地址和接口文件,这是一个小文本字符串,作为合约的使用说明书。单击复制地址copy address以获取前者并 显示界面show interface以显示后者。

在另一台计算机上,进入合约选项卡Contracts,然后单击监视合约watch contract 。添加正确的地址和界面,然后单击OK

与合约互动

在从合约中读取Read from contract中,你可以看到合约中可以免费执行的所有功能,因为它们只是从区块链中读取信息。例如,你可以在此处查看合约的当前所有者owner(应该是上载合约的帐户)。

在写入合约Write to contract中,你有一个列表,其中列出了将尝试进行某些计算以将数据保存到区块链的所有函数,因此将花费以太。选择New Proposal,它将显示该功能的所有选项。

在与合约交互之前,你需要添加新成员才能投票。 在Select function选择器上,选择Add Member。添加你要成为会员的人的地址(要删除会员,请选择Remove Member功能)。 在execute from时,请确保你拥有与所有者相同的帐户,因为这只是主要管理员可以执行的操作。点击执行execute并等待几秒钟,以便下一个块进行更改。

没有成员列表,但你可以通过将其地址放在Read from contract列的Members功能上来检查是否有人是成员。

此外,如果你想让合约有自己的钱,你需要存入一些以太(或其他代币),否则你将拥有一个非常无聊的组织。按右上角的transfer Ether & Tokens

添加一个简单的提案:发送以太

现在让我们将第一个提案添加到合约中。在函数选择器上,选择New Proposal

对于“受益人”,添加你要发送以太的人的地址,并在标有“Wei Amount”的框中输入你要发送的数量。Wei是以太的最小单位,等于10^-18以太,并且必须始终作为整数给出。例如,如果要发送1以太,请输入1000000000000000000(即18个零)。最后,添加一些描述你要执行此操作的原因的文本。暂时将“Transaction bytecode”留空。单击执行execute并键入密码。几秒钟后,numProposals将增加到1,第一个提案编号0将出现在左列上。当你添加更多提案时,只需将提案编号放在proposals字段中即可看到其中的任何提案,你可以阅读所有提案。

投票提案也很简单。在函数选择器上选择Vote。在第一个框中键入提议编号,如果你同意,请选中Yes框(或将其留空以对其进行投票)。点击execute发送你的投票。

投票时间过后,你可以选择executeProposal。如果提案只是发送以太,那么你也可以将transactionBytecode字段留空。在点击execute之后但在输入密码之前,请注意出现的屏幕。

如果estimated fee consumption即估计费用消耗字段上有警告,则表示由于某种原因,被调用的函数将不会执行并将突然终止。这可能意味着许多事情,但在本合约的上下文中,只要你在截止日期过后尝试执行合约,或者用户尝试发送的字节码数据与原始提案不同,就会显示此警告。出于安全原因,如果发生任何这些事情,合约执行将突然终止,并且尝试非法交易的用户将失去他发送的用于支付交易费用的所有以太费用。

如果交易被执行,那么几秒钟之后你应该能够看到结果:执行将变为真,并且应该从该合约的余额和收件人地址中减去正确的以太量。

添加复杂提案:拥有另一个代币

你可以使用此民主方式来执行以太坊上的任何交易,只要你能够找出该交易生成的字节码即可。幸运的是,你可以使用钱包做到这一点!

在这个例子中,我们将使用一个代币来表明这个合约可以容纳多于以太,并且可以在任何其他基于以太坊的资产中进行交易。首先,创建一个属于你的普通帐户的代币 。在合约页面上,单击转移以太网和代币以将其中一些转移到新的congress合约中(为简单起见,不要将超过一半的硬币发送到你的DAO)。之后,我们将模拟你要执行的操作。因此,如果你想建议DAO向个人发送500毫克黄金代币作为付款,请按照你从你拥有的帐户执行该交易的步骤,然后点击send但是当确认屏幕时弹出,不要输入密码 。

而是单击显示原始数据SHOW RAW DATA链接并复制RAW DATA字段上显示的代码并将其保存到文本文件或记事本中。取消交易。你还需要你将要为该操作调用的合约的地址,在这种情况下是代币合约。你可以在Contracts选项卡上找到它:将其保存在某处。

现在回到congress合约并使用以下参数创建新提案:

  • 作为受益人,请填写你的代币地址(如果它是相同的图标请注意)。
  • 将以太币量留空。
  • 在工作描述上,只需写下你想要完成内容的描述。
  • Transaction Bytecode上 ,粘贴你在上一步中从数据字段中保存的字节码。

几秒钟后,你应该能够看到提案的详细信息。你会注意到交易字节码不会在那里显示,而是只有一个交易哈希transaction hash。与其他字段不同,字节码可能非常冗长,因此存储在区块链上非常昂贵,因此稍后执行调用的人将提供字节码,而不是对其进行存档。

但是,这当然会造成一个安全漏洞:如果没有实际代码,投票如何投票?什么阻止用户在提案投票后执行不同的代码?这就是交易哈希的用武之地。在从合约中读取read from contract功能列表中滚动一下,你会看到一个提议检查功能,任何人都可以放置所有的功能参数并检查它们是否与被投票的匹配。这也保证了除非字节码的hash与提供的代码上的hash完全匹配,否则不会执行提议。

任何人都可以通过遵循相同的步骤来获取正确的字节码,然后将提议编号和其他参数添加到从合约中读取read from contract底部的名为 检查提案代码Check proposal code的功能,从而可以非常轻松地检查提案。

其余的投票过程保持不变:所有成员都可以投票,在截止日期之后,有人可以执行该投标。唯一的区别是,这次你必须提供之前提交的相同字节码。注意确认窗口上的任何警告:如果它说它不会执行你的代码,请检查截止日期是否已经过去,是否有足够的投票以及你的交易字节码是否已检出。

让它更好

以下是当前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之区块链大会