forked from blockchainsllc/DAO
-
Notifications
You must be signed in to change notification settings - Fork 0
/
DAO.sol
577 lines (485 loc) · 21.3 KB
/
DAO.sol
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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
/*
This file is part of the DAO.
The DAO is free software: you can redistribute it and/or modify
it under the terms of the GNU lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
The DAO is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU lesser General Public License for more details.
You should have received a copy of the GNU lesser General Public License
along with the DAO. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Standard smart contract for a Decentralized Autonomous Organization (DAO)
to automate organizational governance and decision-making.
*/
import "./TokenCreation.sol";
pragma solidity ^0.4.4;
contract DAOInterface {
// The minimum debate period that a generic proposal can have
uint constant minProposalDebatePeriod = 2 weeks;
// The minimum debate period that a split proposal can have
uint constant quorumHalvingPeriod = 25 weeks;
// Period after which a proposal is closed
// (used in the case `executeProposal` fails because it throws)
uint constant executeProposalPeriod = 10 days;
// Time for vote freeze. A proposal needs to have majority support before votingDeadline - preSupportTime
uint constant preSupportTime = 2 days;
// Denotes the maximum proposal deposit that can be given. It is given as
// a fraction of total Ether spent plus balance of the DAO
uint constant maxDepositDivisor = 100;
//Token contract
Token token;
// Proposals to spend the DAO's ether
Proposal[] public proposals;
// The quorum needed for each proposal is partially calculated by
// totalSupply / minQuorumDivisor
uint public minQuorumDivisor;
// The unix time of the last time quorum was reached on a proposal
uint public lastTimeMinQuorumMet;
// Address of the curator
address public curator;
// The whitelist: List of addresses the DAO is allowed to send ether to
mapping (address => bool) public allowedRecipients;
// Map of addresses blocked during a vote (not allowed to transfer DAO
// tokens). The address points to the proposal ID.
mapping (address => uint) public blocked;
// Map of addresses and proposal voted on by this address
mapping (address => uint[]) public votingRegister;
// The minimum deposit (in wei) required to submit any proposal that is not
// requesting a new Curator (no deposit is required for splits)
uint public proposalDeposit;
// the accumulated sum of all current proposal deposits
uint sumOfProposalDeposits;
// A proposal with `newCurator == false` represents a transaction
// to be issued by this DAO
// A proposal with `newCurator == true` represents a DAO split
struct Proposal {
// The address where the `amount` will go to if the proposal is accepted
address recipient;
// The amount to transfer to `recipient` if the proposal is accepted.
uint amount;
// A plain text description of the proposal
string description;
// A unix timestamp, denoting the end of the voting period
uint votingDeadline;
// True if the proposal's votes have yet to be counted, otherwise False
bool open;
// True if quorum has been reached, the votes have been counted, and
// the majority said yes
bool proposalPassed;
// A hash to check validity of a proposal
bytes32 proposalHash;
// Deposit in wei the creator added when submitting their proposal. It
// is taken from the msg.value of a newProposal call.
uint proposalDeposit;
// True if this proposal is to assign a new Curator
bool newCurator;
// true if more tokens are in favour of the proposal than opposed to it at
// least `preSupportTime` before the voting deadline
bool preSupport;
// Number of Tokens in favor of the proposal
uint yea;
// Number of Tokens opposed to the proposal
uint nay;
// Simple mapping to check if a shareholder has voted for it
mapping (address => bool) votedYes;
// Simple mapping to check if a shareholder has voted against it
mapping (address => bool) votedNo;
// Address of the shareholder who created the proposal
address creator;
}
/// @dev Constructor setting the Curator and the address
/// for the contract able to create another DAO as well as the parameters
/// for the DAO Token Creation
/// @param _curator The Curator
/// @param _daoCreator The contract able to (re)create this DAO
/// @param _proposalDeposit The deposit to be paid for a regular proposal
/// @param _minTokensToCreate Minimum required wei-equivalent tokens
/// to be created for a successful DAO Token Creation
/// @param _closingTime Date (in Unix time) of the end of the DAO Token Creation
/// @param _parentDAO If zero the DAO Token Creation is open to public, a
/// non-zero address represents the parentDAO that can buy tokens in the
/// creation phase.
/// @param _tokenName The name that the DAO's token will have
/// @param _tokenSymbol The ticker symbol that this DAO token should have
/// @param _decimalPlaces The number of decimal places that the token is
/// counted from.
// This is the constructor: it can not be overloaded so it is commented out
// function DAO(
// address _curator,
// DAO_Creator _daoCreator,
// uint _proposalDeposit,
// uint _minTokensToCreate,
// uint _closingTime,
// address _parentDAO,
// string _tokenName,
// string _tokenSymbol,
// uint8 _decimalPlaces
// );
/// @notice donate without getting tokens
function() payable;
/// @notice `msg.sender` creates a proposal to send `_amount` Wei to
/// `_recipient` with the transaction data `_transactionData`. If
/// `_newCurator` is true, then this is a proposal that splits the
/// DAO and sets `_recipient` as the new DAO's Curator.
/// @param _recipient Address of the recipient of the proposed transaction
/// @param _amount Amount of wei to be sent with the proposed transaction
/// @param _description String describing the proposal
/// @param _transactionData Data of the proposed transaction
/// @param _debatingPeriod Time used for debating a proposal, at least 2
/// weeks for a regular proposal, 10 days for new Curator proposal
/// @param _newCurator Bool defining whether this proposal is about
/// a new Curator or not
/// @return The proposal ID. Needed for voting on the proposal
function newProposal(
address _recipient,
uint _amount,
string _description,
bytes _transactionData,
uint _debatingPeriod,
bool _newCurator
) payable returns (uint _proposalID);
/// @notice Check that the proposal with the ID `_proposalID` matches the
/// transaction which sends `_amount` with data `_transactionData`
/// to `_recipient`
/// @param _proposalID The proposal ID
/// @param _recipient The recipient of the proposed transaction
/// @param _amount The amount of wei to be sent in the proposed transaction
/// @param _transactionData The data of the proposed transaction
/// @return Whether the proposal ID matches the transaction data or not
function checkProposalCode(
uint _proposalID,
address _recipient,
uint _amount,
bytes _transactionData
) constant returns (bool _codeChecksOut);
/// @notice Vote on proposal `_proposalID` with `_supportsProposal`
/// @param _proposalID The proposal ID
/// @param _supportsProposal Yes/No - support of the proposal
function vote(uint _proposalID, bool _supportsProposal);
/// @notice Checks whether proposal `_proposalID` with transaction data
/// `_transactionData` has been voted for or rejected, and executes the
/// transaction in the case it has been voted for.
/// @param _proposalID The proposal ID
/// @param _transactionData The data of the proposed transaction
/// @return Whether the proposed transaction has been executed or not
function executeProposal(
uint _proposalID,
bytes _transactionData
) returns (bool _success);
/// @dev can only be called by the DAO itself through a proposal
/// updates the contract of the DAO by sending all ether and rewardTokens
/// to the new DAO. The new DAO needs to be approved by the Curator
/// @param _newContract the address of the new contract
function newContract(address _newContract);
/// @notice Add a new possible recipient `_recipient` to the whitelist so
/// that the DAO can send transactions to them (using proposals)
/// @param _recipient New recipient address
/// @dev Can only be called by the current Curator
/// @return Whether successful or not
function changeAllowedRecipients(address _recipient, bool _allowed) external returns (bool _success);
/// @notice Change the minimum deposit required to submit a proposal
/// @param _proposalDeposit The new proposal deposit
/// @dev Can only be called by this DAO (through proposals with the
/// recipient being this DAO itself)
function changeProposalDeposit(uint _proposalDeposit) external;
/// @notice Doubles the 'minQuorumDivisor' in the case quorum has not been
/// achieved in 52 weeks
/// @return Whether the change was successful or not
function halveMinQuorum() returns (bool _success);
/// @return total number of proposals ever created
function numberOfProposals() constant returns (uint _numberOfProposals);
/// @param _account The address of the account which is checked.
/// @return Whether the account is blocked (not allowed to transfer tokens) or not.
function getOrModifyBlocked(address _account) internal returns (bool);
/// @notice If the caller is blocked by a proposal whose voting deadline
/// has exprired then unblock him.
/// @return Whether the account is blocked (not allowed to transfer tokens) or not.
function unblockMe() returns (bool);
event ProposalAdded(
uint indexed proposalID,
address recipient,
uint amount,
string description
);
event Voted(uint indexed proposalID, bool position, address indexed voter);
event ProposalTallied(uint indexed proposalID, bool result, uint quorum);
event AllowedRecipientChanged(address indexed _recipient, bool _allowed);
}
// The DAO contract itself
contract DAO is DAOInterface{
// Modifier that allows only shareholders to vote and create new proposals
modifier onlyTokenholders {
if (token.balanceOf(msg.sender) == 0) throw;
_;
}
function DAO(
address _curator,
uint _proposalDeposit,
Token _token
) {
token = _token;
curator = _curator;
proposalDeposit = _proposalDeposit;
lastTimeMinQuorumMet = now;
minQuorumDivisor = 7; // sets the minimal quorum to 14.3%
proposals.length = 1; // avoids a proposal with ID 0 because it is used
allowedRecipients[address(this)] = true;
allowedRecipients[curator] = true;
}
function() payable {
}
function newProposal(
address _recipient,
uint _amount,
string _description,
bytes _transactionData,
uint64 _debatingPeriod
) onlyTokenholders payable returns (uint _proposalID) {
if (!allowedRecipients[_recipient]
|| _debatingPeriod < minProposalDebatePeriod
|| _debatingPeriod > 8 weeks
|| msg.value < proposalDeposit
|| msg.sender == address(this) //to prevent a 51% attacker to convert the ether into deposit
)
throw;
// to prevent curator from halving quorum before first proposal
if (proposals.length == 1) // initial length is 1 (see constructor)
lastTimeMinQuorumMet = now;
_proposalID = proposals.length++;
Proposal p = proposals[_proposalID];
p.recipient = _recipient;
p.amount = _amount;
p.description = _description;
p.proposalHash = sha3(_recipient, _amount, _transactionData);
p.votingDeadline = now + _debatingPeriod;
p.open = true;
//p.proposalPassed = False; // that's default
p.creator = msg.sender;
p.proposalDeposit = msg.value;
sumOfProposalDeposits += msg.value;
ProposalAdded(
_proposalID,
_recipient,
_amount,
_description
);
}
function checkProposalCode(
uint _proposalID,
address _recipient,
uint _amount,
bytes _transactionData
) constant returns (bool _codeChecksOut) {
Proposal p = proposals[_proposalID];
return p.proposalHash == sha3(_recipient, _amount, _transactionData);
}
function vote(uint _proposalID, bool _supportsProposal) {
Proposal p = proposals[_proposalID];
unVote(_proposalID);
if (_supportsProposal) {
p.yea += token.balanceOf(msg.sender);
p.votedYes[msg.sender] = true;
} else {
p.nay += token.balanceOf(msg.sender);
p.votedNo[msg.sender] = true;
}
if (blocked[msg.sender] == 0) {
blocked[msg.sender] = _proposalID;
} else if (p.votingDeadline > proposals[blocked[msg.sender]].votingDeadline) {
// this proposal's voting deadline is further into the future than
// the proposal that blocks the sender so make it the blocker
blocked[msg.sender] = _proposalID;
}
votingRegister[msg.sender].push(_proposalID);
Voted(_proposalID, _supportsProposal, msg.sender);
}
function unVote(uint _proposalID){
Proposal p = proposals[_proposalID];
if (now >= p.votingDeadline) {
throw;
}
if (p.votedYes[msg.sender]) {
p.yea -= token.balanceOf(msg.sender);
p.votedYes[msg.sender] = false;
}
if (p.votedNo[msg.sender]) {
p.nay -= token.balanceOf(msg.sender);
p.votedNo[msg.sender] = false;
}
}
function unVoteAll() {
// DANGEROUS loop with dynamic length - needs improvement
for (uint i = 0; i < votingRegister[msg.sender].length; i++) {
Proposal p = proposals[votingRegister[msg.sender][i]];
if (now < p.votingDeadline)
unVote(i);
}
votingRegister[msg.sender].length = 0;
blocked[msg.sender] = 0;
}
function verifyPreSupport(uint _proposalID) {
Proposal p = proposals[_proposalID];
if (now < p.votingDeadline - preSupportTime) {
if (p.yea > p.nay) {
p.preSupport = true;
}
else
p.preSupport = false;
}
}
function executeProposal(
uint _proposalID,
bytes _transactionData
) returns (bool _success) {
Proposal p = proposals[_proposalID];
// If we are over deadline and waiting period, assert proposal is closed
if (p.open && now > p.votingDeadline + executeProposalPeriod) {
closeProposal(_proposalID);
return;
}
// Check if the proposal can be executed
if (now < p.votingDeadline // has the voting deadline arrived?
// Have the votes been counted?
|| !p.open
|| p.proposalPassed // anyone trying to call us recursively?
// Does the transaction code match the proposal?
|| p.proposalHash != sha3(p.recipient, p.amount, _transactionData)
)
throw;
// If the curator removed the recipient from the whitelist, close the proposal
// in order to free the deposit and allow unblocking of voters
if (!allowedRecipients[p.recipient]) {
closeProposal(_proposalID);
// the return value is not checked to prevent a malicious creator
// from delaying the closing of the proposal
p.creator.send(p.proposalDeposit);
return;
}
bool proposalCheck = true;
if (p.amount > actualBalance() || p.preSupport == false)
proposalCheck = false;
uint quorum = p.yea;
// require max quorum for calling newContract()
if (_transactionData.length >= 4 && _transactionData[0] == 0x68
&& _transactionData[1] == 0x37 && _transactionData[2] == 0xff
&& _transactionData[3] == 0x1e
&& quorum < minQuorum(actualBalance())
)
proposalCheck = false;
if (quorum >= minQuorum(p.amount)) {
if (!p.creator.send(p.proposalDeposit))
throw;
lastTimeMinQuorumMet = now;
// set the minQuorum to 14.3% again, in the case it has been reached
if (quorum > token.totalSupply() / 7)
minQuorumDivisor = 7;
}
// Execute result
if (quorum >= minQuorum(p.amount) && p.yea > p.nay && proposalCheck) {
// we are setting this here before the CALL() value transfer to
// assure that in the case of a malicious recipient contract trying
// to call executeProposal() recursively money can't be transferred
// multiple times out of the DAO
p.proposalPassed = true;
// this call is as generic as any transaction. It sends all gas and
// can do everything a transaction can do. It can be used to reenter
// the DAO. The `p.proposalPassed` variable prevents the call from
// reaching this line again
if (!p.recipient.call.value(p.amount)(_transactionData))
throw;
_success = true;
}
closeProposal(_proposalID);
// Initiate event
ProposalTallied(_proposalID, _success, quorum);
}
function closeProposal(uint _proposalID) internal {
Proposal p = proposals[_proposalID];
if (p.open)
sumOfProposalDeposits -= p.proposalDeposit;
p.open = false;
}
/*
Since it is possible to continuously send ETH to the contract and create tokens,
this withdraw functions is flawed and needs to be replaced by an improved version
function withdraw() onlyTokenholders returns (bool _success) {
unVoteAll();
// Move ether
uint senderBalance = balances[msg.sender];
// TODO this is flawed
uint fundsToBeMoved = (senderBalance * actualBalance()) / totalSupply;
balances[msg.sender] = 0;
msg.sender.send(fundsToBeMoved);
// Burn DAO Tokens
totalSupply -= senderBalance;
// event for light client notification
Transfer(msg.sender, 0, senderBalance);
return true;
}
*/
function newContract(address _newContract){
if (msg.sender != address(this) || !allowedRecipients[_newContract]) return;
// move all ether
if (!_newContract.call.value(address(this).balance)()) {
throw;
}
}
function changeProposalDeposit(uint _proposalDeposit) external {
if (msg.sender != address(this) || _proposalDeposit > (actualBalance())
/ maxDepositDivisor) {
throw;
}
proposalDeposit = _proposalDeposit;
}
function changeAllowedRecipients(address _recipient, bool _allowed) external returns (bool _success) {
if (msg.sender != curator)
throw;
allowedRecipients[_recipient] = _allowed;
AllowedRecipientChanged(_recipient, _allowed);
return true;
}
function actualBalance() constant returns (uint _actualBalance) {
return this.balance - sumOfProposalDeposits;
}
function minQuorum(uint _value) internal constant returns (uint _minQuorum) {
// minimum of 14.3% and maximum of 47.6%
return token.totalSupply() / minQuorumDivisor +
(_value * token.totalSupply()) / (3 * (actualBalance()));
}
function halveMinQuorum() returns (bool _success) {
// this can only be called after `quorumHalvingPeriod` has passed or at anytime after
// fueling by the curator with a delay of at least `minProposalDebatePeriod`
// between the calls
if ((lastTimeMinQuorumMet < (now - quorumHalvingPeriod) || msg.sender == curator)
&& lastTimeMinQuorumMet < (now - minProposalDebatePeriod)
&& proposals.length > 1) {
lastTimeMinQuorumMet = now;
minQuorumDivisor *= 2;
return true;
} else {
return false;
}
}
function numberOfProposals() constant returns (uint _numberOfProposals) {
// Don't count index 0. It's used by getOrModifyBlocked() and exists from start
return proposals.length - 1;
}
function getOrModifyBlocked(address _account) internal returns (bool) {
if (blocked[_account] == 0)
return false;
Proposal p = proposals[blocked[_account]];
if (!p.open) {
blocked[_account] = 0;
return false;
} else {
return true;
}
}
function unblockMe() returns (bool) {
return getOrModifyBlocked(msg.sender);
}
}