以太坊DAO之时间锁定Multisig

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

有时候,时间也可以用作一种很好的安全机制。以下代码基于DAO区块链大会,但有不同的变化。不是每个操作需要X个成员批准,而是任何交易都可以由单个成员发起,但它们在执行之前都需要最少的延迟,这取决于交易的支持。提案的批准越多,就越早执行。会员可以对交易进行投票,这意味着它将取消其他一个已批准的签名。

时间锁定Multisig

这意味着如果你没有紧急程度,则执行任何交易可能只需要一个或两个签名。但是,如果单个密钥被泄露,其他密钥可以将该交易延迟数月或数年,甚至可以阻止其执行。

这个怎么运作

所有密钥都已批准的交易可以在十分钟后执行(此金额是可配置的),并且每5%未投票的成员每次需要的时间加倍(如果他们主动投票,则为四倍)反对)。如果它是一个简单的ether交易,只要支持投票将其置于所需的时间内,就会执行交易,但更复杂的交易将要求使用正确的字节码手动执行交易。这些是默认值,但在创建合约时可以设置不同的值:

批准交易的成员数量:近似时间延迟

  • 100%批准:10分钟(最低默认值)
  • 90%批准:40分钟
  • 80%:2小时40分钟
  • 50%:大约一周
  • 40%:1个月
  • 30%:4个月
  • 20%:超过一年
  • 10%或更少:5年或从不 一旦最短的时间过去,任何人都可以执行交易(参见“国会”以获得更完整的步行)。这是故意的,因为它允许某人安排交易或雇用其他人来执行交易。

代码:

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
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 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
*
* First time setup
*/
constructor(
address founder,
address[] memory initialMembers,
uint minimumAmountOfMinutes
) payable public {
if (founder != address(0)) owner = founder;
if (minimumAmountOfMinutes !=0) minimumTime = minimumAmountOfMinutes;
// 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');
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 memory memberName) onlyOwner public
{
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});
emit MembershipChanged(targetMember, true);
}

/**
* Remove member
*
* @param targetMember the member to remove
*/
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--;
}

/**
* 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[] memory newMembers, bool canVote) public {
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 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.executed = false;
p.creationDate = now;
emit 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 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
{
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
emit Voted(proposalNumber, supportsProposal, msg.sender, justificationText);

// If you can execute it now, do it
if ( now > proposalDeadline(proposalNumber)
&& p.currentResult > 0
&& p.proposalHash == keccak256(abi.encodePacked(p.recipient, p.amount, ''))
&& supportsProposal) {
executeProposal(proposalNumber, '');
}
}

function proposalDeadline(uint proposalNumber) public view 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) public pure 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 memory transactionBytecode) public {
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;
(bool success, ) = p.recipient.call.value(p.amount)(transactionBytecode);
require(success);

// Fire Events
emit ProposalExecuted(proposalNumber, p.currentResult, proposalDeadline(proposalNumber));
}
}

部署和使用

像以前一样在这些教程上部署该代码。在部署参数上,将最小时间留空将默认为30分钟,如果你想要更快的锁定时间,则放1分钟。上传后,执行“添加成员”功能以添加组的新成员,他们可以是你认识的其他人,也可以是不同计算机上的帐户或离线存储。

设置为所有者owner的帐户非常强大,因为它可以随意添加或删除成员。因此,在添加主成员后,我们建议你通过执行Transfer Membership功能将owner设置为另一个帐户。如果你希望对所有成员的添加或删除进行投票,则将其设置为multisig本身,就像任何其他交易一样。另一种方法是将其设置为另一个受信任的multisig钱包,如果你希望永久修复成员数,则可以设置为0x000。请记住,此合约上的资金仅与“所有者”帐户一样安全。

与上述任何DAO一样,此合约可以持有以太币,任何基于以太坊的代币并执行任何合约。为此,请检查如何在国会DAO上执行复杂的提案。

警告和改进

为简单起见,对提案的投票仅仅算得少一点支持。如果你愿意,你可以玩弄负面投票更重要的想法,但这意味着少数成员可以对任何提议的交易拥有有效的否决权!

你怎么能改善这个合约?

我们去探索吧!

你已经到了本教程的末尾,但这只是一次伟大冒险的开始。回顾一下,看看你取得了多少成就:你创造了一个活生生的,有说服力的机器人,你自己的加密货币,通过无信息的众筹筹集资金,并用它来启动你自己的个人民主组织。

接下来会发生什么?

  • 你仍然控制的代币可以在分散的交易所出售,或者交易商品和服务,以资助第一份合约的进一步发展和发展组织。
  • 你的DAO可以在名称注册商处拥有自己的名称,然后更改它重定向的位置,以便在代币持有者批准时自行更新。
  • 该组织不仅可以拥有醚类,还可以拥有在以太坊上创造的任何其他类型的硬币,包括其价值与比特币或美元相关的资产。
  • 可以对DAO进行编程,以允许具有多个交易的提案,其中一些预定在未来。它还可以拥有其他DAO的股份,这意味着它可以对更大的组织投票或成为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之时间锁定的Multisig