-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathVoteDelegator.sol
339 lines (277 loc) · 9.49 KB
/
VoteDelegator.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
/*
This is a proof-of-concept.
The contract compiles, but:
- it has never been deployed
- it has not been tested properly
- it has not been security-audited
- it has not been optimised for gas usage
- it needs events
Use it at your own risk
*/
/*
The VoteDelegator contract provides way of delegating your votes in theDAO
It is meant as a quick way of adding delegation to the DAO
without having to change theDAO contract itself
It works as follows.
(Here, "theDAO" refers to the DAO contract on the blockchain, and
"theDelegator" to a VoteDelegator contract on the blockchain)
1) Tell the The DAO that VoteDelegator is allowed to handle your DAO tokens
theDAO.approve(address(theDelegator), amount_of_tokens)
2) put your DAO tokens in control of the VoteDelegator:
theDelegator.delegate(amount_of_tokens)
3) Now you can vote by simply calling the "vote" function on the
delegator contract (exactly as you would vote in the DAO)
theDelegator.vote(_proposalID, _supportsProposal)
the Delegator contract will then register your vote internally,
the Delegator will only vote in the DAO itself when certain
conditions are met (in this contract, a simple majority count)
4) If you want to remove your delegation and have your DAO tokens back, call:
theDelegator.undelegate()
This will make it so that the VoteDelegator will not use your tokens
for new votes any more. Your tokens my still be locked in a current vote,
so you'll have to wait until they are unlocked to call "undelegate" again
to transfer them back to you.
*/
/*
Some use cases are:
- last-minute voting to keep you tokens locked up as little as possible
- "tentative 'no' voting"
- liquid democracy style delegation
- delegate your vote to a Backfeed reputation system
*/
import "./theDAO/DAO.sol";
contract VoteDelegatorInterface {
// the address of TheDAO
address thedao_address;
// proposals that have been voted on
Proposal[] proposals;
// maps daoProposalIDs to an index in proposals
mapping (uint => uint) proposal_idx;
// accounts with tokens managed by this contract
address[] accounts;
// map delegator addresses to in indexes in accounts
mapping (address => uint) accounts_idx;
struct Proposal {
// the proposalID in the DAO
uint proposalID;
// True if the proposal's vote has been cast in the DAO
bool closed;
// 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;
}
/// Total amount of tokens delegated
uint256 totalSupply;
/// @notice Delegate tokens to this contract
/// @param _value the amount of tokens to delegate
function delegate(uint256 _value);
/// @notice Undo the delegation of your tokens
/// - your tokens will be used for any new votes
/// - if your tokens are not locked up in a vote, they will be return to you
function undelegate();
/// @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
);
}
contract VoteDelegator is VoteDelegatorInterface {
function VoteDelegator(address _thedao_address) {
// constructor function
thedao_address = _thedao_address;
// TODO: check that there is really a DAO-like object at this address
}
function getAccount(address _delegator) internal returns (DelegatedTokenAccount) {
// get a DelegatedTokenAccount if this _delegator
DelegatedTokenAccount account;
address account_address;
uint account_idx = accounts_idx[_delegator];
if (account_idx == 0) {
// we create a new account
account_idx = accounts.length + 1;
accounts_idx[_delegator] = account_idx;
account = new DelegatedTokenAccount(thedao_address, _delegator);
accounts[account_idx] = address(account);
} else {
account_address = accounts[accounts_idx[_delegator]];
account = DelegatedTokenAccount(account_address);
}
return account;
}
function delegate(uint256 _value) public {
DAO thedao = DAO(thedao_address);
// get the account of the sender
DelegatedTokenAccount account = getAccount(msg.sender);
if (account.getBalance() > 0) {
// for reasons of mental sanity, a user can only delegate his tokens
// once to this delegator. (This can be fixed with some more bookkeeping)
throw;
}
if (thedao.allowance(msg.sender, address(this)) < _value) {
// Please call approve(address(this), _value) on the DAO contract
throw;
}
// transfer _value tokens to the senders Delegatedaccount
if (thedao.transferFrom(msg.sender, address(account), _value)) {
totalSupply += _value;
} else {
// if transfer fails, do nothing
throw;
}
}
function undelegate() public {
// get the account of the sender
DelegatedTokenAccount account = getAccount(msg.sender);
// close the account if it is active - it will not participate in any new votes
if (account.is_active()) {
account.disactivate();
totalSupply -= account.getBalance();
// update all talleys of open proposals where this user has voted
for (uint i=0;i<proposals.length;i++) {
Proposal p = proposals[i];
if (!p.closed) {
if (p.votedYes[msg.sender]) {
p.yea -= account.getBalance();
}
if (p.votedNo[msg.sender]) {
p.nay -= account.getBalance();
}
}
}
}
// now try to give the tokens back to msg.sender
account.returnTokens();
}
function vote(uint _proposalID, bool _supportsProposal) public {
// get the account of the sender
DelegatedTokenAccount account = getAccount(msg.sender);
if (account.getBalance() == 0) {
throw;
}
// get (or create) the Proposal
Proposal proposal;
if (proposal_idx[_proposalID] == 0) {
// we have a new proposal
// TODO: check if the proposal exists in the DAO, is open, etc.
proposal_idx[_proposalID] = proposals.length + 1;
proposal = proposals[proposal_idx[_proposalID]];
proposal.proposalID = _proposalID;
} else {
proposal = proposals[proposal_idx[_proposalID]];
}
// if the proposal is closed, there is nothing to do
if (proposal.closed) {
throw;
}
// if the user already voted, don't let her vote again
if (proposal.votedYes[msg.sender] || proposal.votedNo[msg.sender]) {
throw;
}
if (_supportsProposal) {
proposal.yea += account.getBalance();
proposal.votedYes[msg.sender] = true;
} else {
proposal.nay += account.getBalance();
proposal.votedNo[msg.sender] = true;
}
copyVote(proposal);
}
function vote_in_thedao(
uint _proposalID,
bool _supportsProposal
) internal {
// vote with *all* active accounts for this proposal
for (uint i=0;i<accounts.length;i++) {
address account_adress = accounts[i];
DelegatedTokenAccount account = DelegatedTokenAccount(account_adress);
if (account.is_active()) {
account.vote(_proposalID, _supportsProposal);
}
}
}
function copyVote(Proposal proposal) internal {
/*
Actually vote for the DAO
this is where the magic of the contract is supposed to happen
we can insert here any logic that seems reasonable:
- majority votes
- follow the leader votes
- an implementation of the Backfeed Protocol
for demonstration purposes, we implement a simple majority rule
if more than half of the tokens votes yea (or nay), we copy
that vote to the DAO for *all* tokens
*/
if (proposal.closed) {
throw;
}
DAO thedao = DAO(thedao_address);
if (2 * proposal.yea >= totalSupply) {
vote_in_thedao(proposal.proposalID, true);
proposal.closed = true;
}
if (2 * proposal.nay >= totalSupply) {
vote_in_thedao(proposal.proposalID, false);
proposal.closed = true;
}
}
}
contract DelegatedTokenAccount {
/*
an account to manage DAO tokens for a delegator
*/
// the address that the tokens are delegated to
address owner;
// the address of the delegator of the tokens
address delegator;
// the address of the DAO
address thedao_address;
// true if this account is used for voting
bool active;
// The constructor sets the address of the dao and the delegator
function DelegatedTokenAccount(
address _thedao_address,
address _delegator) {
owner = msg.sender;
thedao_address = _thedao_address;
delegator = _delegator;
active = true;
}
function returnTokens() {
// return all tokens in this account to the delegator
if (msg.sender != owner) {
throw;
}
DAO thedao = DAO(thedao_address);
uint balance = thedao.balanceOf(address(this));
thedao.transfer(delegator, balance);
}
function getBalance() returns (uint _balance) {
// return the balance of this acccount (in the DAO)
DAO thedao = DAO(thedao_address);
uint balance = thedao.balanceOf(address(this));
}
function disactivate() {
active = false;
}
function is_active() returns (bool _is_active) {
return active;
}
function vote(
uint _proposalID,
bool _supportsProposal
) returns (uint _voteID) {
// vote in the DAO
if (msg.sender != owner) {
throw;
}
DAO thedao = DAO(thedao_address);
return thedao.vote(_proposalID, _supportsProposal);
}
}