@@ -87,9 +87,25 @@ contract ECDSARewards is Rewards {
8787
8888 uint256 internal constant minimumECDSAKeepsPerInterval = 1000 ;
8989
90- BondedECDSAKeepFactory factory;
90+ // The amount of tokens each individual beneficiary address
91+ // can receive in a single interval is capped.
92+ // TODO: set actual value
93+ uint256 internal constant beneficiaryRewardCap = 400000 * 10 ** 18 ;
94+ // The total amount of rewards allocated to the given beneficiary address,
95+ // in the given interval.
96+ // `allocatedRewards[beneficiary][interval] -> amount`
97+ mapping (address => mapping (uint256 => uint256 )) internal allocatedRewards;
98+ // The amount of interval rewards withdrawn to the given beneficiary.
99+ mapping (address => mapping (uint256 => uint256 )) internal withdrawnRewards;
91100
92- constructor (address _token , address payable _factoryAddress )
101+ BondedECDSAKeepFactory internal factory;
102+ TokenStaking internal tokenStaking;
103+
104+ constructor (
105+ address _token ,
106+ address payable _factoryAddress ,
107+ address _tokenStakingAddress
108+ )
93109 public
94110 Rewards (
95111 _token,
@@ -100,6 +116,70 @@ contract ECDSARewards is Rewards {
100116 )
101117 {
102118 factory = BondedECDSAKeepFactory (_factoryAddress);
119+ tokenStaking = TokenStaking (_tokenStakingAddress);
120+ }
121+
122+ /// @notice Get the amount of rewards allocated
123+ /// for the specified operator's beneficiary in the specified interval.
124+ /// @param interval The interval
125+ /// @param operator The operator
126+ /// @return The amount allocated
127+ function getAllocatedRewards (uint256 interval , address operator )
128+ external
129+ view
130+ returns (uint256 )
131+ {
132+ address beneficiary = tokenStaking.beneficiaryOf (operator);
133+ return allocatedRewards[beneficiary][interval];
134+ }
135+
136+ /// @notice Get the amount of rewards already withdrawn
137+ /// for the specified operator's beneficiary in the specified interval.
138+ /// @param interval The interval
139+ /// @param operator The operator
140+ /// @return The amount already withdrawn
141+ function getWithdrawnRewards (uint256 interval , address operator )
142+ external
143+ view
144+ returns (uint256 )
145+ {
146+ address beneficiary = tokenStaking.beneficiaryOf (operator);
147+ return withdrawnRewards[beneficiary][interval];
148+ }
149+
150+ /// @notice Get the amount of rewards withdrawable
151+ /// for the specified operator's beneficiary in the specified interval.
152+ /// @param interval The interval
153+ /// @param operator The operator
154+ /// @return The amount withdrawable
155+ function getWithdrawableRewards (uint256 interval , address operator )
156+ external
157+ view
158+ returns (uint256 )
159+ {
160+ address beneficiary = tokenStaking.beneficiaryOf (operator);
161+ uint256 allocated = allocatedRewards[beneficiary][interval];
162+ uint256 withdrawn = withdrawnRewards[beneficiary][interval];
163+ return allocated.sub (withdrawn);
164+ }
165+
166+ /// @notice Withdraw all available rewards for the given interval.
167+ /// The rewards will be paid to the beneficiary of the specified operator.
168+ /// @param interval The interval
169+ /// @param operator The operator
170+ function withdrawRewards (uint256 interval , address operator ) external {
171+ address beneficiary = tokenStaking.beneficiaryOf (operator);
172+
173+ uint256 allocated = allocatedRewards[beneficiary][interval];
174+ uint256 alreadyWithdrawn = withdrawnRewards[beneficiary][interval];
175+
176+ require (allocated > alreadyWithdrawn, "No rewards to withdraw " );
177+
178+ uint256 withdrawableRewards = allocated.sub (alreadyWithdrawn);
179+
180+ withdrawnRewards[beneficiary][interval] = allocated;
181+
182+ token.safeTransfer (beneficiary, withdrawableRewards);
103183 }
104184
105185 /// @notice Stakers can receive KEEP rewards from multiple keeps of their choice
@@ -158,16 +238,43 @@ contract ECDSARewards is Rewards {
158238 return factory.getKeepOpenedTimestamp (toAddress (_keep)) != 0 ;
159239 }
160240
241+ /// @notice Get the members of the specified keep, and distribute the reward
242+ /// amount between them. The reward isn't paid out immediately,
243+ /// but is instead kept in the reward contract until each operator
244+ /// individually requests to withdraw the rewards.
161245 function _distributeReward (bytes32 _keep , uint256 amount )
162246 internal
163247 isAddress (_keep)
164248 {
165- token.approve (toAddress (_keep), amount);
249+ address [] memory members = BondedECDSAKeep (toAddress (_keep))
250+ .getMembers ();
251+ uint256 interval = intervalOf (_getCreationTime (_keep));
252+
253+ uint256 memberCount = members.length ;
254+ uint256 dividend = amount.div (memberCount);
255+ uint256 remainder = amount.mod (memberCount);
256+
257+ uint256 [] memory allocations = new uint256 [](memberCount);
166258
167- BondedECDSAKeep (toAddress (_keep)).distributeERC20Reward (
168- address (token),
169- amount
170- );
259+ for (uint256 i = 0 ; i < memberCount - 1 ; i++ ) {
260+ allocations[i] = dividend;
261+ }
262+ allocations[memberCount - 1 ] = dividend.add (remainder);
263+
264+ for (uint256 i = 0 ; i < memberCount; i++ ) {
265+ address beneficiary = tokenStaking.beneficiaryOf (members[i]);
266+ uint256 addedAllocation = allocations[i];
267+ uint256 prevAllocated = allocatedRewards[beneficiary][interval];
268+ uint256 newAllocation = prevAllocated.add (addedAllocation);
269+ if (newAllocation > beneficiaryRewardCap) {
270+ uint256 deallocatedAmount = newAllocation.sub (
271+ beneficiaryRewardCap
272+ );
273+ newAllocation = beneficiaryRewardCap;
274+ deallocate (deallocatedAmount);
275+ }
276+ allocatedRewards[beneficiary][interval] = newAllocation;
277+ }
171278 }
172279
173280 function toAddress (bytes32 keepBytes ) internal pure returns (address ) {
0 commit comments