@@ -10,9 +10,19 @@ library Groups {
1010 using SafeMath for uint256 ;
1111 using BytesLib for bytes ;
1212
13+ // The index of a group is flagged with the most significant bit set,
14+ // to distinguish the group `0` from null.
15+ // The flag is toggled with bitwise XOR (`^`)
16+ // which keeps all other bits intact but flips the flag bit.
17+ // The flag should be set before writing to `groupIndices`,
18+ // and unset after reading from `groupIndices`
19+ // before using the value.
20+ uint256 constant GROUP_INDEX_FLAG = 1 << 255 ;
21+
1322 struct Group {
1423 bytes groupPubKey;
15- uint registrationBlockHeight;
24+ uint64 registrationBlockHeight;
25+ bool terminated;
1626 }
1727
1828 struct Storage {
@@ -23,8 +33,10 @@ library Groups {
2333 // The value is set when the operator contract is added.
2434 uint256 relayEntryTimeout;
2535
36+ // Mapping of `groupPubKey` to flagged `groupIndex`
37+ mapping (bytes => uint256 ) groupIndices;
2638 Group[] groups;
27- uint256 [] terminatedGroups ;
39+ uint256 [] activeTerminatedGroups ;
2840 mapping (bytes => address []) groupMembers;
2941
3042 // Sum of all group member rewards earned so far. The value is the same for
@@ -33,6 +45,10 @@ library Groups {
3345 // this value.
3446 mapping (bytes => uint256 ) groupMemberRewards;
3547
48+ // Mapping of `groupPubKey, operator`
49+ // to whether the operator has withdrawn rewards from that group.
50+ mapping (bytes => mapping (address => bool )) withdrawn;
51+
3652 // expiredGroupOffset is pointing to the first active group, it is also the
3753 // expired groups counter
3854 uint256 expiredGroupOffset;
@@ -47,7 +63,8 @@ library Groups {
4763 Storage storage self ,
4864 bytes memory groupPubKey
4965 ) internal {
50- self.groups.push (Group (groupPubKey, block .number ));
66+ self.groupIndices[groupPubKey] = (self.groups.length ^ GROUP_INDEX_FLAG);
67+ self.groups.push (Group (groupPubKey, uint64 (block .number ), false ));
5168 }
5269
5370 /**
@@ -123,39 +140,15 @@ library Groups {
123140 return self.groupMembers[groupPubKey][memberIndex];
124141 }
125142
126- /**
127- * @dev Gets all indices in the provided group for a member.
128- */
129- function getGroupMemberIndices (
130- Storage storage self ,
131- bytes memory groupPubKey ,
132- address member
133- ) public view returns (uint256 [] memory indices ) {
134- uint256 counter;
135- for (uint i = 0 ; i < self.groupMembers[groupPubKey].length ; i++ ) {
136- if (self.groupMembers[groupPubKey][i] == member) {
137- counter++ ;
138- }
139- }
140-
141- indices = new uint256 [](counter);
142- counter = 0 ;
143- for (uint i = 0 ; i < self.groupMembers[groupPubKey].length ; i++ ) {
144- if (self.groupMembers[groupPubKey][i] == member) {
145- indices[counter] = i;
146- counter++ ;
147- }
148- }
149- }
150-
151143 /**
152144 * @dev Terminates group.
153145 */
154146 function terminateGroup (
155147 Storage storage self ,
156148 uint256 groupIndex
157149 ) internal {
158- self.terminatedGroups.push (groupIndex);
150+ self.groups[groupIndex].terminated = true ;
151+ self.activeTerminatedGroups.push (groupIndex);
159152 }
160153
161154 /**
@@ -165,12 +158,7 @@ library Groups {
165158 Storage storage self ,
166159 uint256 groupIndex
167160 ) internal view returns (bool ) {
168- for (uint i = 0 ; i < self.terminatedGroups.length ; i++ ) {
169- if (self.terminatedGroups[i] == groupIndex) {
170- return true ;
171- }
172- }
173- return false ;
161+ return self.groups[groupIndex].terminated;
174162 }
175163
176164 /**
@@ -180,12 +168,9 @@ library Groups {
180168 Storage storage self ,
181169 bytes memory groupPubKey
182170 ) internal view returns (bool ) {
183- for (uint i = 0 ; i < self.groups.length ; i++ ) {
184- if (self.groups[i].groupPubKey.equalStorage (groupPubKey)) {
185- return true ;
186- }
187- }
188- return false ;
171+ // Values in `groupIndices` are flagged with `GROUP_INDEX_FLAG`
172+ // and thus nonzero, even for group 0
173+ return self.groupIndices[groupPubKey] > 0 ;
189174 }
190175
191176 /**
@@ -196,7 +181,7 @@ library Groups {
196181 Storage storage self ,
197182 Group memory group
198183 ) internal view returns (uint256 ) {
199- return group.registrationBlockHeight.add (self.groupActiveTime);
184+ return uint256 ( group.registrationBlockHeight) .add (self.groupActiveTime);
200185 }
201186
202187 /**
@@ -224,15 +209,12 @@ library Groups {
224209 Storage storage self ,
225210 bytes memory groupPubKey
226211 ) public view returns (bool ) {
227- for (uint i = 0 ; i < self.groups.length ; i++ ) {
228- if (self.groups[i].groupPubKey.equalStorage (groupPubKey)) {
229- bool isExpired = self.expiredGroupOffset > i;
230- bool isStale = groupStaleTime (self, self.groups[i]) < block .number ;
231- return isExpired && isStale;
232- }
233- }
234-
235- revert ("Group does not exist " );
212+ uint256 flaggedIndex = self.groupIndices[groupPubKey];
213+ require (flaggedIndex != 0 , "Group does not exist " );
214+ uint256 index = flaggedIndex ^ GROUP_INDEX_FLAG;
215+ bool isExpired = self.expiredGroupOffset > index;
216+ bool isStale = groupStaleTime (self, self.groups[index]) < block .number ;
217+ return isExpired && isStale;
236218 }
237219
238220 /**
@@ -258,7 +240,7 @@ library Groups {
258240 function numberOfGroups (
259241 Storage storage self
260242 ) internal view returns (uint256 ) {
261- return self.groups.length .sub (self.expiredGroupOffset).sub (self.terminatedGroups .length );
243+ return self.groups.length .sub (self.expiredGroupOffset).sub (self.activeTerminatedGroups .length );
262244 }
263245
264246 /**
@@ -273,14 +255,14 @@ library Groups {
273255 self.expiredGroupOffset++ ;
274256 }
275257
276- // Go through all terminatedGroups and if some of the terminated
277- // groups are expired, remove them from terminatedGroups collection.
258+ // Go through all activeTerminatedGroups and if some of the terminated
259+ // groups are expired, remove them from activeTerminatedGroups collection.
278260 // This is needed because we evaluate the shift of selected group index
279261 // based on how many non-expired groups has been terminated.
280- for (uint i = 0 ; i < self.terminatedGroups .length ; i++ ) {
281- if (self.expiredGroupOffset > self.terminatedGroups [i]) {
282- self.terminatedGroups [i] = self.terminatedGroups [self.terminatedGroups .length - 1 ];
283- self.terminatedGroups .length -- ;
262+ for (uint i = 0 ; i < self.activeTerminatedGroups .length ; i++ ) {
263+ if (self.expiredGroupOffset > self.activeTerminatedGroups [i]) {
264+ self.activeTerminatedGroups [i] = self.activeTerminatedGroups [self.activeTerminatedGroups .length - 1 ];
265+ self.activeTerminatedGroups .length -- ;
284266 }
285267 }
286268 }
@@ -297,7 +279,7 @@ library Groups {
297279 Storage storage self ,
298280 uint256 seed
299281 ) public returns (uint256 ) {
300- require (numberOfGroups (self) > 0 , "At least one active group required " );
282+ require (numberOfGroups (self) > 0 , "No active groups " );
301283
302284 expireOldGroups (self);
303285 uint256 selectedGroup = seed % numberOfGroups (self);
@@ -324,8 +306,8 @@ library Groups {
324306 uint256 selectedIndex
325307 ) internal view returns (uint256 ) {
326308 uint256 shiftedIndex = selectedIndex;
327- for (uint i = 0 ; i < self.terminatedGroups .length ; i++ ) {
328- if (self.terminatedGroups [i] <= shiftedIndex) {
309+ for (uint i = 0 ; i < self.activeTerminatedGroups .length ; i++ ) {
310+ if (self.activeTerminatedGroups [i] <= shiftedIndex) {
329311 shiftedIndex++ ;
330312 }
331313 }
@@ -335,35 +317,40 @@ library Groups {
335317
336318 /**
337319 * @dev Withdraws accumulated group member rewards for operator
338- * using the provided group index and member indices. Once the
339- * accumulated reward is withdrawn from the selected group, member is
340- * removed from it. Rewards can be withdrawn only from stale group.
320+ * using the provided group index.
321+ * Once the accumulated reward is withdrawn from the selected group,
322+ * the operator is flagged as withdrawn.
323+ * Rewards can be withdrawn only from stale group.
341324 * @param operator Operator address.
342325 * @param groupIndex Group index.
343- * @param groupMemberIndices Array of member indices for the group member.
344326 */
345327 function withdrawFromGroup (
346328 Storage storage self ,
347329 address operator ,
348- uint256 groupIndex ,
349- uint256 [] memory groupMemberIndices
330+ uint256 groupIndex
350331 ) public returns (uint256 rewards ) {
351332 bool isExpired = self.expiredGroupOffset > groupIndex;
352333 bool isStale = isStaleGroup (self, groupIndex);
353334 require (isExpired && isStale, "Group must be expired and stale " );
354335 bytes memory groupPublicKey = getGroupPublicKey (self, groupIndex);
355- for (uint i = 0 ; i < groupMemberIndices.length ; i++ ) {
356- if (operator == self.groupMembers[groupPublicKey][groupMemberIndices[i]]) {
357- delete self.groupMembers[groupPublicKey][groupMemberIndices[i]];
336+ require (
337+ ! (self.withdrawn[groupPublicKey][operator]),
338+ "Rewards already withdrawn "
339+ );
340+ self.withdrawn[groupPublicKey][operator] = true ;
341+ for (uint i = 0 ; i < self.groupMembers[groupPublicKey].length ; i++ ) {
342+ if (operator == self.groupMembers[groupPublicKey][i]) {
358343 rewards = rewards.add (self.groupMemberRewards[groupPublicKey]);
359344 }
360345 }
361346 }
362347
363348 /**
364- * @dev Returns addresses of all the members in the provided group.
349+ * @dev Returns members of the given group by group public key.
350+ *
351+ * @param groupPubKey Group public key.
365352 */
366- function membersOf (
353+ function getGroupMembers (
367354 Storage storage self ,
368355 bytes memory groupPubKey
369356 ) public view returns (address [] memory members ) {
@@ -373,7 +360,7 @@ library Groups {
373360 /**
374361 * @dev Returns addresses of all the members in the provided group.
375362 */
376- function membersOf (
363+ function getGroupMembers (
377364 Storage storage self ,
378365 uint256 groupIndex
379366 ) public view returns (address [] memory members ) {
@@ -407,7 +394,7 @@ library Groups {
407394 terminateGroup (self, groupIndex);
408395 self.stakingContract.seize (minimumStake, 100 , msg .sender , self.groupMembers[groupPubKey]);
409396 } else {
410- revert ("Group is terminated or the signature is invalid " );
397+ revert ("Group terminated or sig invalid " );
411398 }
412399 }
413400
@@ -421,15 +408,18 @@ library Groups {
421408 // Reward is limited to min(1, 20 / group_size) of the maximum tattletale reward, see the Yellow Paper for more details.
422409 uint256 rewardAdjustment = uint256 (20 * 100 ).div (groupSize); // Reward adjustment in percentage
423410 rewardAdjustment = rewardAdjustment > 100 ? 100 :rewardAdjustment; // Reward adjustment can be 100% max
424- self.stakingContract.seize (minimumStake, rewardAdjustment, msg .sender , membersOf (self, groupIndex));
411+ self.stakingContract.seize (minimumStake, rewardAdjustment, msg .sender , getGroupMembers (self, groupIndex));
425412 }
426413
427414 /**
428- * @dev Returns members of the given group by group public key.
429- *
430- * @param groupPubKey Group public key.
415+ * @notice Return whether the given operator
416+ * has withdrawn their rewards from the given group.
431417 */
432- function getGroupMembers (Storage storage self , bytes memory groupPubKey ) public view returns (address [] memory ) {
433- return self.groupMembers[groupPubKey];
418+ function hasWithdrawnRewards (
419+ Storage storage self ,
420+ address operator ,
421+ uint256 groupIndex
422+ ) public view returns (bool ) {
423+ return self.withdrawn[getGroupPublicKey (self, groupIndex)][operator];
434424 }
435425}
0 commit comments