Skip to content

Commit

Permalink
Merge pull request #20 from devintegral3/feature/claimable_rewards
Browse files Browse the repository at this point in the history
Allow to claim rewards while they are locked, but not withdraw
  • Loading branch information
devintegral3 committed Feb 18, 2020
2 parents c4d61f8 + 9ba8e51 commit ec7a349
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 11 deletions.
49 changes: 38 additions & 11 deletions contracts/sfc/Staker.sol
Expand Up @@ -177,6 +177,12 @@ contract Stakers is Ownable, StakersConstants {

mapping(uint256 => bytes) public stakerMetadata;

struct StashedRewards {
uint256 amount;
}

mapping(address => StashedRewards) public rewardsStash; // addr -> StashedRewards

/*
Getters
*/
Expand Down Expand Up @@ -411,6 +417,18 @@ contract Stakers is Ownable, StakersConstants {
return (pendingRewards, fromEpoch, lastEpoch);
}

// _claimRewards transfers rewards directly if rewards are allowed, or stashes them until rewards are unlocked
function _claimRewards(address payable addr, uint256 amount) internal {
if (amount == 0) {
return;
}
if (rewardsAllowed()) {
addr.transfer(amount);
} else {
rewardsStash[addr].amount += amount;
}
}

event ClaimedDelegationReward(address indexed from, uint256 indexed stakerID, uint256 reward, uint256 fromEpoch, uint256 untilEpoch);

// Claim the pending rewards for a given delegator (sender)
Expand All @@ -421,19 +439,14 @@ contract Stakers is Ownable, StakersConstants {

require(delegations[delegator].amount != 0, "delegation doesn't exist");
require(delegations[delegator].deactivatedTime == 0, "delegation is deactivated");
require(rewardsAllowed(), "before minimum unlock period");
(uint256 pendingRewards, uint256 fromEpoch, uint256 untilEpoch) = calcDelegationRewards(delegator, _fromEpoch, maxEpochs);
require(delegations[delegator].paidUntilEpoch < fromEpoch, "epoch is already paid");
require(fromEpoch <= currentSealedEpoch, "future epoch");
require(untilEpoch >= fromEpoch, "no epochs claimed");
delegations[delegator].paidUntilEpoch = untilEpoch;
// It's important that we transfer after updating paidUntilEpoch (protection against Re-Entrancy)
if (pendingRewards != 0) {
delegator.transfer(pendingRewards);
}
_claimRewards(delegator, pendingRewards);
uint256 stakerID = delegations[delegator].toStakerID;
emit ClaimedDelegationReward(delegator, stakerID, pendingRewards, fromEpoch, untilEpoch);
Expand All @@ -451,7 +464,6 @@ contract Stakers is Ownable, StakersConstants {
uint256 stakerID = stakerIDs[staker];

require(stakerID != 0, "staker doesn't exist");
require(rewardsAllowed(), "before minimum unlock period");
(uint256 pendingRewards, uint256 fromEpoch, uint256 untilEpoch) = calcValidatorRewards(stakerID, _fromEpoch, maxEpochs);
Expand All @@ -460,14 +472,29 @@ contract Stakers is Ownable, StakersConstants {
require(untilEpoch >= fromEpoch, "no epochs claimed");
stakers[stakerID].paidUntilEpoch = untilEpoch;
// It's important that we transfer after updating paidUntilEpoch (protection against Re-Entrancy)
if (pendingRewards != 0) {
staker.transfer(pendingRewards);
}
_claimRewards(staker, pendingRewards);
emit ClaimedValidatorReward(stakerID, pendingRewards, fromEpoch, untilEpoch);
}
event UnstashedRewards(address indexed auth, address indexed receiver, uint256 rewards);
// Transfer the claimed rewards to account
function unstashRewards() external {
address auth = msg.sender;
address payable receiver = msg.sender;
uint256 rewards = rewardsStash[auth].amount;
require(rewards != 0, "no rewards");
require(rewardsAllowed(), "before minimum unlock period");
delete rewardsStash[auth];
// It's important that we transfer after erasing (protection against Re-Entrancy)
receiver.transfer(rewards);

emit UnstashedRewards(auth, receiver, rewards);
}

event PreparedToWithdrawStake(uint256 indexed stakerID); // previous name for DeactivatedStake
event DeactivatedStake(uint256 indexed stakerID);

Expand Down
4 changes: 4 additions & 0 deletions test/Stakers.js
Expand Up @@ -216,6 +216,7 @@ contract('Staker test', async ([firstStaker, secondStaker, thirdStaker, firstDep
expect(await balance.current(this.stakers.address)).to.be.bignumber.equal(ether('14.264583332713541667')); // 16 - 1.735416667286458333

await expectRevert(this.stakers.claimDelegationRewards(new BN('0'), new BN('1'), {from: firstDepositor}), 'future epoch');

let base = ether('1.735416667286458333');
let fee = ether('0.005');
const balanceAfter = await balance.current(firstDepositor);
Expand Down Expand Up @@ -244,9 +245,12 @@ contract('Staker test', async ([firstStaker, secondStaker, thirdStaker, firstDep
await this.stakers._makeEpochSnapshots(5);
await expectRevert(this.stakers.claimValidatorRewards(new BN('3'), new BN('4'), {from: firstStaker}), 'future epoch');
expect(await balance.current(this.stakers.address)).to.be.bignumber.equal(ether('16.0'));

const balanceBefore = await balance.current(firstStaker);

await this.stakers.claimValidatorRewards(new BN('0'), new BN('4'), {from: firstStaker});
expect(await balance.current(this.stakers.address)).to.be.bignumber.equal(ether('15.307291666419270834')); // 16 - 0.692708333580729166

await expectRevert(this.stakers.claimValidatorRewards(new BN('0'), new BN('4'), {from: firstStaker}), 'future epoch');

let base = ether('0.692708333580729166');
Expand Down

0 comments on commit ec7a349

Please sign in to comment.