-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathBeneficiaryOperations.sol
243 lines (207 loc) · 9.75 KB
/
BeneficiaryOperations.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
pragma solidity ^0.5.0;
contract BeneficiaryOperations {
// VARIABLES
uint256 public beneficiariesGeneration;
uint256 public howManyBeneficiariesDecide;
address[] public beneficiaries;
bytes32[] public allOperations;
address internal insideCallSender;
uint256 internal insideCallCount;
// Reverse lookup tables for beneficiaries and allOperations
mapping(address => uint) public beneficiariesIndices; // Starts from 1
mapping(bytes32 => uint) public allOperationsIndicies;
// beneficiaries voting mask per operations
mapping(bytes32 => uint256) public votesMaskByOperation;
mapping(bytes32 => uint256) public votesCountByOperation;
// EVENTS
event beneficiaryShipTransferred(address[] previousbeneficiaries, uint howManyBeneficiariesDecide, address[] newBeneficiarys, uint newHowManybeneficiarysDecide);
event OperationCreated(bytes32 operation, uint howMany, uint beneficiariesCount, address proposer);
event OperationUpvoted(bytes32 operation, uint votes, uint howMany, uint beneficiariesCount, address upvoter);
event OperationPerformed(bytes32 operation, uint howMany, uint beneficiariesCount, address performer);
event OperationDownvoted(bytes32 operation, uint votes, uint beneficiariesCount, address downvoter);
event OperationCancelled(bytes32 operation, address lastCanceller);
// ACCESSORS
function isExistBeneficiary(address wallet) public view returns(bool) {
return beneficiariesIndices[wallet] > 0;
}
function beneficiaryIndices(address wallet) public view returns(uint256) {
return beneficiariesIndices[wallet];
}
function beneficiariesCount() public view returns(uint) {
return beneficiaries.length;
}
function allOperationsCount() public view returns(uint) {
return allOperations.length;
}
// MODIFIERS
/**
* @dev Allows to perform method by any of the beneficiaries
*/
modifier onlyAnyBeneficiary {
if (checkHowManyBeneficiaries(1)) {
bool update = (insideCallSender == address(0));
if (update) {
insideCallSender = msg.sender;
insideCallCount = 1;
}
_;
if (update) {
insideCallSender = address(0);
insideCallCount = 0;
}
}
}
/**
* @dev Allows to perform method only after many beneficiaries call it with the same arguments
*/
modifier onlyManyBeneficiaries {
if (checkHowManyBeneficiaries(howManyBeneficiariesDecide)) {
bool update = (insideCallSender == address(0));
if (update) {
insideCallSender = msg.sender;
insideCallCount = howManyBeneficiariesDecide;
}
_;
if (update) {
insideCallSender = address(0);
insideCallCount = 0;
}
}
}
/**
* @dev Allows to perform method only after all beneficiaries call it with the same arguments
*/
modifier onlyAllBeneficiaries {
if (checkHowManyBeneficiaries(beneficiaries.length)) {
bool update = (insideCallSender == address(0));
if (update) {
insideCallSender = msg.sender;
insideCallCount = beneficiaries.length;
}
_;
if (update) {
insideCallSender = address(0);
insideCallCount = 0;
}
}
}
/**
* @dev Allows to perform method only after some beneficiaries call it with the same arguments
*/
modifier onlySomeBeneficiaries(uint howMany) {
require(howMany > 0, "onlySomeBeneficiaries: howMany argument is zero");
require(howMany <= beneficiaries.length, "onlySomeBeneficiaries: howMany argument exceeds the number of Beneficiaries");
if (checkHowManyBeneficiaries(howMany)) {
bool update = (insideCallSender == address(0));
if (update) {
insideCallSender = msg.sender;
insideCallCount = howMany;
}
_;
if (update) {
insideCallSender = address(0);
insideCallCount = 0;
}
}
}
// CONSTRUCTOR
constructor() public {
beneficiaries.push(msg.sender);
beneficiariesIndices[msg.sender] = 1;
howManyBeneficiariesDecide = 1;
}
// INTERNAL METHODS
/**
* @dev onlyManybeneficiaries modifier helper
*/
function checkHowManyBeneficiaries(uint howMany) internal returns(bool) {
if (insideCallSender == msg.sender) {
require(howMany <= insideCallCount, "checkHowManyBeneficiaries: nested beneficiaries modifier check require more beneficiarys");
return true;
}
uint beneficiaryIndex = beneficiariesIndices[msg.sender] - 1;
require(beneficiaryIndex < beneficiaries.length, "checkHowManyBeneficiaries: msg.sender is not an beneficiary");
bytes32 operation = keccak256(abi.encodePacked(msg.data, beneficiariesGeneration));
require((votesMaskByOperation[operation] & (2 ** beneficiaryIndex)) == 0, "checkHowManyBeneficiaries: beneficiary already voted for the operation");
votesMaskByOperation[operation] |= (2 ** beneficiaryIndex);
uint operationVotesCount = votesCountByOperation[operation] + 1;
votesCountByOperation[operation] = operationVotesCount;
if (operationVotesCount == 1) {
allOperationsIndicies[operation] = allOperations.length;
allOperations.push(operation);
emit OperationCreated(operation, howMany, beneficiaries.length, msg.sender);
}
emit OperationUpvoted(operation, operationVotesCount, howMany, beneficiaries.length, msg.sender);
// If enough beneficiaries confirmed the same operation
if (votesCountByOperation[operation] == howMany) {
deleteOperation(operation);
emit OperationPerformed(operation, howMany, beneficiaries.length, msg.sender);
return true;
}
return false;
}
/**
* @dev Used to delete cancelled or performed operation
* @param operation defines which operation to delete
*/
function deleteOperation(bytes32 operation) internal {
uint index = allOperationsIndicies[operation];
if (index < allOperations.length - 1) { // Not last
allOperations[index] = allOperations[allOperations.length - 1];
allOperationsIndicies[allOperations[index]] = index;
}
allOperations.length--;
delete votesMaskByOperation[operation];
delete votesCountByOperation[operation];
delete allOperationsIndicies[operation];
}
// PUBLIC METHODS
/**
* @dev Allows beneficiaries to change their mind by cacnelling votesMaskByOperation operations
* @param operation defines which operation to delete
*/
function cancelPending(bytes32 operation) public onlyAnyBeneficiary {
uint beneficiaryIndex = beneficiariesIndices[msg.sender] - 1;
require((votesMaskByOperation[operation] & (2 ** beneficiaryIndex)) != 0, "cancelPending: operation not found for this user");
votesMaskByOperation[operation] &= ~(2 ** beneficiaryIndex);
uint operationVotesCount = votesCountByOperation[operation] - 1;
votesCountByOperation[operation] = operationVotesCount;
emit OperationDownvoted(operation, operationVotesCount, beneficiaries.length, msg.sender);
if (operationVotesCount == 0) {
deleteOperation(operation);
emit OperationCancelled(operation, msg.sender);
}
}
/**
* @dev Allows beneficiaries to change beneficiariesship
* @param newBeneficiaries defines array of addresses of new beneficiaries
*/
function transferBeneficiaryShip(address[] memory newBeneficiaries) public {
transferBeneficiaryShipWithHowMany(newBeneficiaries, newBeneficiaries.length);
}
/**
* @dev Allows beneficiaries to change beneficiaryShip
* @param newBeneficiaries defines array of addresses of new beneficiaries
* @param newHowManyBeneficiariesDecide defines how many beneficiaries can decide
*/
function transferBeneficiaryShipWithHowMany(address[] memory newBeneficiaries, uint256 newHowManyBeneficiariesDecide) public onlyManyBeneficiaries {
require(newBeneficiaries.length > 0, "transferBeneficiaryShipWithHowMany: beneficiaries array is empty");
require(newBeneficiaries.length <= 256, "transferBeneficiaryshipWithHowMany: beneficiaries count is greater then 256");
require(newHowManyBeneficiariesDecide > 0, "transferBeneficiaryshipWithHowMany: newHowManybeneficiarysDecide equal to 0");
require(newHowManyBeneficiariesDecide <= newBeneficiaries.length, "transferBeneficiaryShipWithHowMany: newHowManybeneficiarysDecide exceeds the number of beneficiarys");
// Reset beneficiaries reverse lookup table
for (uint j = 0; j < beneficiaries.length; j++) {
delete beneficiariesIndices[beneficiaries[j]];
}
for (uint i = 0; i < newBeneficiaries.length; i++) {
require(newBeneficiaries[i] != address(0), "transferBeneficiaryShipWithHowMany: beneficiaries array contains zero");
require(beneficiariesIndices[newBeneficiaries[i]] == 0, "transferBeneficiaryShipWithHowMany: beneficiaries array contains duplicates");
beneficiariesIndices[newBeneficiaries[i]] = i + 1;
}
emit beneficiaryShipTransferred(beneficiaries, howManyBeneficiariesDecide, newBeneficiaries, newHowManyBeneficiariesDecide);
beneficiaries = newBeneficiaries;
howManyBeneficiariesDecide = newHowManyBeneficiariesDecide;
allOperations.length = 0;
beneficiariesGeneration++;
}
}