Removed explicit dependency for delegator on the next epoch after staking.

This commit is contained in:
Greg Hysen
2019-09-24 10:22:05 -07:00
parent 5b595dd080
commit 08c805a489
3 changed files with 128 additions and 88 deletions

View File

@@ -165,22 +165,22 @@ contract MixinCumulativeRewards is
view
returns (uint256 reward)
{
if (memberStakeOverInterval == 0) {
// Sanity check if we can skip computation, as it will result in zero.
if (memberStakeOverInterval == 0 || beginEpoch == endEpoch) {
return 0;
}
// Sanity check interval
require(beginEpoch <= endEpoch, "CR_INTERVAL_INVALID");
require(beginEpoch < endEpoch, "CR_INTERVAL_INVALID");
// Sanity check begin reward
IStructs.Fraction memory beginReward =
_cumulativeRewardsByPool[poolId][beginEpoch];
require(_isCumulativeRewardSet(beginReward), "CR_INTERVAL_INVALID_BEGIN");
(IStructs.Fraction memory beginReward, uint256 beginRewardStoredAt) = _getCumulativeRewardAtEpoch(poolId, beginEpoch);
(IStructs.Fraction memory endReward, uint256 endRewardStoredAt) = _getCumulativeRewardAtEpoch(poolId, endEpoch);
// Sanity check end reward
IStructs.Fraction memory endReward =
_cumulativeRewardsByPool[poolId][endEpoch];
require(_isCumulativeRewardSet(endReward), "CR_INTERVAL_INVALID_END");
// If the rewards were stored at the same epoch then the computation will result in zero.
if (beginRewardStoredAt == endRewardStoredAt) {
return 0;
}
// Compute reward
reward = LibFractions.scaleDifference(
@@ -203,4 +203,45 @@ contract MixinCumulativeRewards is
uint256 lastStoredEpoch = _cumulativeRewardsByPoolLastStored[poolId];
return _cumulativeRewardsByPool[poolId][lastStoredEpoch];
}
/// @dev Fetch the cumulative reward for a given epoch.
/// If the corresponding CR does not exist in state, then we backtrack
/// to find its value by querying `epoch-1` and then most recent CR.
/// @param poolId Unique ID of pool.
/// @param epoch The epoch to find the
/// @return cumulativeReward The cumulative reward for `poolId` at `epoch`.
/// @return cumulativeRewardStoredAt Epoch that the `cumulativeReward` is stored at.
function _getCumulativeRewardAtEpoch(bytes32 poolId, uint256 epoch)
internal
view
returns (
IStructs.Fraction memory cumulativeReward,
uint256 cumulativeRewardStoredAt
)
{
// Return CR at `epoch`, given it's set.
cumulativeReward = _cumulativeRewardsByPool[poolId][epoch];
if (_isCumulativeRewardSet(cumulativeReward)) {
return (cumulativeReward, epoch);
}
// Return CR at `epoch-1`, given it's set.
uint256 lastEpoch = epoch.safeSub(1);
cumulativeReward = _cumulativeRewardsByPool[poolId][lastEpoch];
if (_isCumulativeRewardSet(cumulativeReward)) {
return (cumulativeReward, lastEpoch);
}
// Return the most recent CR, given it's less than `epoch`.
uint256 mostRecentEpoch = _cumulativeRewardsByPoolLastStored[poolId];
if (mostRecentEpoch < epoch) {
cumulativeReward = _cumulativeRewardsByPool[poolId][mostRecentEpoch];
if (_isCumulativeRewardSet(cumulativeReward)) {
return (cumulativeReward, mostRecentEpoch);
}
}
// Could not find a CR for `epoch`
revert("CR_INVALID_EPOCH");
}
}

View File

@@ -294,10 +294,9 @@ contract MixinStakingPoolRewards is
/// @param poolId Unique id of pool.
/// @param unsyncedStake Unsynced delegated stake to pool by owner
/// @param currentEpoch The epoch in which this call is executing
/// @param unfinalizedMembersReward Unfinalized total members reward
/// (if any).
/// @param unfinalizedMembersReward Unfinalized total members reward (if any).
/// @param unfinalizedMembersStake Unfinalized total members stake (if any).
/// @return totalReward Balance in ETH.
/// @return reward Balance in WETH.
function _computeDelegatorReward(
bytes32 poolId,
IStructs.StoredBalance memory unsyncedStake,
@@ -322,56 +321,81 @@ contract MixinStakingPoolRewards is
return 0;
}
// If there are unfinalized rewards this epoch, compute the member's
// share.
if (unfinalizedMembersReward != 0 && unfinalizedMembersStake != 0) {
// Unfinalized rewards are always earned from stake in
// the prior epoch so we want the stake at `currentEpoch-1`.
uint256 _stake = unsyncedStake.currentEpoch >= currentEpoch.safeSub(1) ?
unsyncedStake.currentEpochBalance :
unsyncedStake.nextEpochBalance;
if (_stake != 0) {
reward = LibMath.getPartialAmountFloor(
unfinalizedMembersReward,
unfinalizedMembersStake,
_stake
);
}
}
// We account for rewards over 3 intervals, below.
// Get the last epoch where a reward was credited to this pool, which
// also happens to be when we last created a cumulative reward entry.
uint256 lastRewardEpoch = _cumulativeRewardsByPoolLastStored[poolId];
// 1/3 Unfinalized rewards earned in `currentEpoch - 1`.
reward = _computeUnfinalizedDelegatorReward(
unsyncedStake,
currentEpoch,
unfinalizedMembersReward,
unfinalizedMembersStake
);
// If the stake has been touched since the last reward epoch,
// it has already been claimed.
if (unsyncedStake.currentEpoch >= lastRewardEpoch) {
return reward;
}
// From here we know: `unsyncedStake.currentEpoch < currentEpoch > 0`.
uint256 nextStakeEpoch = uint256(unsyncedStake.currentEpoch).safeAdd(1);
// 2/3 Finalized rewards earned in epochs [`unsyncedStake.currentEpoch + 1` .. `currentEpoch - 1`]
uint256 unsyncedStakeNextEpoch = uint256(unsyncedStake.currentEpoch).safeAdd(1);
reward = reward.safeAdd(
_computeMemberRewardOverInterval(
poolId,
unsyncedStake.currentEpochBalance,
unsyncedStake.currentEpoch,
nextStakeEpoch
unsyncedStakeNextEpoch
)
);
if (nextStakeEpoch < lastRewardEpoch) {
reward = reward.safeAdd(
_computeMemberRewardOverInterval(
poolId,
unsyncedStake.nextEpochBalance,
nextStakeEpoch,
lastRewardEpoch
)
);
}
// 3/3 Finalized rewards earned in epoch `unsyncedStake.currentEpoch`.
reward = reward.safeAdd(
_computeMemberRewardOverInterval(
poolId,
unsyncedStake.nextEpochBalance,
unsyncedStakeNextEpoch,
currentEpoch
)
);
return reward;
}
/// @dev Computes the unfinalized rewards earned by a delegator in the last epoch.
/// @param unsyncedStake Unsynced delegated stake to pool by owner
/// @param currentEpoch The epoch in which this call is executing
/// @param unfinalizedMembersReward Unfinalized total members reward (if any).
/// @param unfinalizedMembersStake Unfinalized total members stake (if any).
/// @return reward Balance in WETH.
function _computeUnfinalizedDelegatorReward(
IStructs.StoredBalance memory unsyncedStake,
uint256 currentEpoch,
uint256 unfinalizedMembersReward,
uint256 unfinalizedMembersStake
)
private
view
returns (uint256)
{
// If there are unfinalized rewards this epoch, compute the member's
// share.
if (unfinalizedMembersReward == 0 || unfinalizedMembersStake == 0) {
return 0;
}
// Unfinalized rewards are always earned from stake in
// the prior epoch so we want the stake at `currentEpoch-1`.
uint256 unfinalizedStakeBalance = unsyncedStake.currentEpoch >= currentEpoch.safeSub(1) ?
unsyncedStake.currentEpochBalance :
unsyncedStake.nextEpochBalance;
// Sanity check to save gas on computation
if (unfinalizedStakeBalance == 0) {
return 0;
}
// Compute unfinalized reward
return LibMath.getPartialAmountFloor(
unfinalizedMembersReward,
unfinalizedMembersStake,
unfinalizedStakeBalance
);
}
/// @dev Adds or removes cumulative reward dependencies for a delegator.
/// A delegator always depends on the cumulative reward for the current
/// and next epoch, if they would still have stake in the next epoch.
@@ -388,25 +412,18 @@ contract MixinStakingPoolRewards is
// reference point when updating dependencies
IStructs.Fraction memory mostRecentCumulativeReward = _getMostRecentCumulativeReward(poolId);
// Record dependency on current epoch.
if (_delegatedStakeToPoolByOwner.currentEpochBalance != 0
|| _delegatedStakeToPoolByOwner.nextEpochBalance != 0)
{
_trySetCumulativeReward(
poolId,
_delegatedStakeToPoolByOwner.currentEpoch,
mostRecentCumulativeReward
);
// The delegator depends on the Cumulative Reward for this epoch
// only if they are currently staked or will be staked next epoch.
if (_delegatedStakeToPoolByOwner.currentEpochBalance == 0 && _delegatedStakeToPoolByOwner.nextEpochBalance == 0) {
return;
}
// Record dependency on the next epoch
if (_delegatedStakeToPoolByOwner.nextEpochBalance != 0) {
_trySetCumulativeReward(
poolId,
uint256(_delegatedStakeToPoolByOwner.currentEpoch).safeAdd(1),
mostRecentCumulativeReward
);
}
// Delegator depends on the Cumulative Reward for this epoch - ensure it is set.
_trySetCumulativeReward(
poolId,
_delegatedStakeToPoolByOwner.currentEpoch,
mostRecentCumulativeReward
);
}
/// @dev Increments rewards for a pool.

View File

@@ -53,7 +53,7 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
// Creates CR for epoch 1
TestAction.Delegate,
],
[{ event: 'SetCumulativeReward', epoch: 0 }, { event: 'SetCumulativeReward', epoch: 1 }],
[{ event: 'SetCumulativeReward', epoch: 0 }],
);
});
it('re-delegating in the same epoch', async () => {
@@ -64,17 +64,13 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
],
[
// Updates CR for epoch 0
// Creates CR for epoch 1
TestAction.Delegate,
// Updates CR for epoch 0
// Updates CR for epoch 1
TestAction.Delegate,
],
[
{ event: 'SetCumulativeReward', epoch: 0 },
{ event: 'SetCumulativeReward', epoch: 1 },
{ event: 'SetCumulativeReward', epoch: 0 },
{ event: 'SetCumulativeReward', epoch: 1 },
],
);
});
@@ -91,13 +87,11 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
// Creates a CR for epoch 1
// Sets MRCR to epoch 1
// Unsets the CR for epoch 0
// Creates a CR for epoch 2
TestAction.Delegate,
],
[
{ event: 'SetCumulativeReward', epoch: 1 },
{ event: 'SetMostRecentCumulativeReward', epoch: 1 },
{ event: 'SetCumulativeReward', epoch: 2 },
],
);
});
@@ -115,14 +109,11 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
[
// Updates CR for epoch 1
// Sets MRCR to epoch 1
// Creates CR for epoch 2
// Clears CR for epoch 0
TestAction.Delegate,
],
[
{ event: 'SetCumulativeReward', epoch: 1 },
{ event: 'SetMostRecentCumulativeReward', epoch: 1 },
{ event: 'SetCumulativeReward', epoch: 2 },
],
);
});
@@ -135,8 +126,6 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
TestAction.Finalize,
// Creates CR for epoch 1
// Sets MRCR to epoch 1
// Clears CR for epoch 0
// Creates CR for epoch 2
TestAction.Delegate,
// Move to epoch 2
TestAction.Finalize,
@@ -151,7 +140,6 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
[
{ event: 'SetCumulativeReward', epoch: 2 },
{ event: 'SetMostRecentCumulativeReward', epoch: 2 },
{ event: 'SetCumulativeReward', epoch: 3 },
],
);
});
@@ -208,7 +196,6 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
[
{ event: 'SetCumulativeReward', epoch: 2 },
{ event: 'SetMostRecentCumulativeReward', epoch: 2 },
{ event: 'SetCumulativeReward', epoch: 3 },
],
);
});
@@ -241,7 +228,6 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
[
{ event: 'SetCumulativeReward', epoch: 2 },
{ event: 'SetMostRecentCumulativeReward', epoch: 2 },
{ event: 'SetCumulativeReward', epoch: 3 },
],
);
});
@@ -300,7 +286,7 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
// Clears CR for epoch 2
TestAction.Delegate,
],
[{ event: 'SetCumulativeReward', epoch: 3 }, { event: 'SetCumulativeReward', epoch: 4 }],
[{ event: 'SetCumulativeReward', epoch: 3 }],
);
});
it('delegate in epoch 0 and 1, earn reward in epoch 3, then undelegate half', async () => {
@@ -335,7 +321,7 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
// Clears CR for epoch 2
TestAction.Undelegate,
],
[{ event: 'SetCumulativeReward', epoch: 3 }, { event: 'SetCumulativeReward', epoch: 4 }],
[{ event: 'SetCumulativeReward', epoch: 3 }],
);
});
it('delegate in epoch 1, 2, earn rewards in epoch 3, skip to epoch 4, then delegate', async () => {
@@ -376,7 +362,6 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
[
{ event: 'SetCumulativeReward', epoch: 4 },
{ event: 'SetMostRecentCumulativeReward', epoch: 4 },
{ event: 'SetCumulativeReward', epoch: 5 },
],
);
});
@@ -401,7 +386,6 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
[
{ event: 'SetCumulativeReward', epoch: 1 },
{ event: 'SetMostRecentCumulativeReward', epoch: 1 },
{ event: 'SetCumulativeReward', epoch: 2 },
],
);
});
@@ -440,7 +424,6 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
[
{ event: 'SetCumulativeReward', epoch: 4 },
{ event: 'SetMostRecentCumulativeReward', epoch: 4 },
{ event: 'SetCumulativeReward', epoch: 5 },
],
);
});
@@ -472,7 +455,6 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
[
{ event: 'SetCumulativeReward', epoch: 3 },
{ event: 'SetMostRecentCumulativeReward', epoch: 3 },
{ event: 'SetCumulativeReward', epoch: 4 },
],
);
});