@0x/contracts-staking: All tests passing!

This commit is contained in:
Lawrence Forman
2019-09-17 14:04:07 -04:00
committed by Lawrence Forman
parent 527ec28915
commit a43b494302
8 changed files with 260 additions and 250 deletions

View File

@@ -57,6 +57,10 @@
{ {
"note": "Refactored out `_cobbDouglas()` into its own library", "note": "Refactored out `_cobbDouglas()` into its own library",
"pr": 2179 "pr": 2179
},
{
"note": "Introduce multi-block finalization.",
"pr": 2155
} }
] ]
} }

View File

@@ -271,29 +271,12 @@ contract MixinStakingPoolRewards is
return reward = 0; return reward = 0;
} }
// From here we know:
// 1. `currentEpoch > 0`
// 2. `stake.currentEpoch < currentEpoch`.
// Get the last epoch where a reward was credited to this pool.
uint256 lastRewardEpoch = lastPoolRewardEpoch[poolId];
// Get the last reward epoch for which we collected rewards from.
uint256 lastCollectedRewardEpoch =
lastCollectedRewardsEpochToPoolByOwner[member][poolId];
// If either of these are true, the most recent reward has already been
// claimed.
if (lastCollectedRewardEpoch == lastRewardEpoch
|| stake.currentEpoch >= lastRewardEpoch) {
return reward = 0;
}
// If there are unfinalized rewards this epoch, compute the member's // If there are unfinalized rewards this epoch, compute the member's
// share. // share.
if (unfinalizedMembersReward != 0 && unfinalizedDelegatedStake != 0) { if (unfinalizedMembersReward != 0 && unfinalizedDelegatedStake != 0) {
// Unfinalized rewards are always earned from stake in // Unfinalized rewards are always earned from stake in
// the prior epoch so we want the stake at `currentEpoch-1`. // the prior epoch so we want the stake at `currentEpoch-1`.
uint256 _stake = stake.currentEpoch == currentEpoch - 1 ? uint256 _stake = stake.currentEpoch >= currentEpoch - 1 ?
stake.currentEpochBalance : stake.currentEpochBalance :
stake.nextEpochBalance; stake.nextEpochBalance;
if (_stake != 0) { if (_stake != 0) {
@@ -303,8 +286,26 @@ contract MixinStakingPoolRewards is
} }
} }
// Add rewards up to the last reward epoch. // Get the last epoch where a reward was credited to this pool.
if (lastRewardEpoch != 0) { uint256 lastRewardEpoch = lastPoolRewardEpoch[poolId];
// If the stake has been touched since the last reward epoch,
// it has already been claimed.
if (stake.currentEpoch >= lastRewardEpoch) {
return reward;
}
// From here we know: `stake.currentEpoch < currentEpoch > 0`.
if (stake.currentEpoch < lastRewardEpoch) {
reward = reward.safeAdd(
_computeMemberRewardOverInterval(
poolId,
stake,
stake.currentEpoch,
stake.currentEpoch + 1
)
);
if (stake.currentEpoch + 1 < lastRewardEpoch) {
reward = reward.safeAdd( reward = reward.safeAdd(
_computeMemberRewardOverInterval( _computeMemberRewardOverInterval(
poolId, poolId,
@@ -315,6 +316,7 @@ contract MixinStakingPoolRewards is
); );
} }
} }
}
/// @dev Adds or removes cumulative reward dependencies for a delegator. /// @dev Adds or removes cumulative reward dependencies for a delegator.
/// A delegator always depends on the cumulative reward for the current epoch. /// A delegator always depends on the cumulative reward for the current epoch.
@@ -356,5 +358,9 @@ contract MixinStakingPoolRewards is
isDependent isDependent
); );
} }
uint256 nextEpoch = epoch.safeAdd(1);
if (!_isCumulativeRewardSet(cumulativeRewardsByPoolPtr[nextEpoch])) {
cumulativeRewardsByPoolPtr[nextEpoch] = mostRecentCumulativeRewards;
}
} }
} }

View File

@@ -165,14 +165,11 @@ contract MixinFinalizer is
poolsRemaining = poolsRemaining.safeSub(1); poolsRemaining = poolsRemaining.safeSub(1);
} }
// Deposit all the rewards at once into the RewardVault.
if (rewardsPaid != 0) {
_depositIntoStakingPoolRewardVault(rewardsPaid);
}
// Update finalization states. // Update finalization states.
if (rewardsPaid != 0) {
totalRewardsPaidLastEpoch = totalRewardsPaidLastEpoch =
totalRewardsPaidLastEpoch.safeAdd(rewardsPaid); totalRewardsPaidLastEpoch.safeAdd(rewardsPaid);
}
unfinalizedPoolsRemaining = _unfinalizedPoolsRemaining = poolsRemaining; unfinalizedPoolsRemaining = _unfinalizedPoolsRemaining = poolsRemaining;
// If there are no more unfinalized pools remaining, the epoch is // If there are no more unfinalized pools remaining, the epoch is
@@ -184,6 +181,12 @@ contract MixinFinalizer is
unfinalizedRewardsAvailable.safeSub(totalRewardsPaidLastEpoch) unfinalizedRewardsAvailable.safeSub(totalRewardsPaidLastEpoch)
); );
} }
// Deposit all the rewards at once into the RewardVault.
if (rewardsPaid != 0) {
_depositIntoStakingPoolRewardVault(rewardsPaid);
}
} }
/// @dev Instantly finalizes a single pool that was active in the previous /// @dev Instantly finalizes a single pool that was active in the previous

View File

@@ -43,25 +43,18 @@ export class FinalizerActor extends BaseActor {
public async finalizeAsync(rewards: Reward[] = []): Promise<void> { public async finalizeAsync(rewards: Reward[] = []): Promise<void> {
// cache initial info and balances // cache initial info and balances
const operatorShareByPoolId = const operatorShareByPoolId = await this._getOperatorShareByPoolIdAsync(this._poolIds);
await this._getOperatorShareByPoolIdAsync(this._poolIds); const rewardVaultBalanceByPoolId = await this._getRewardVaultBalanceByPoolIdAsync(this._poolIds);
const rewardVaultBalanceByPoolId = const delegatorBalancesByPoolId = await this._getDelegatorBalancesByPoolIdAsync(this._delegatorsByPoolId);
await this._getRewardVaultBalanceByPoolIdAsync(this._poolIds); const delegatorStakesByPoolId = await this._getDelegatorStakesByPoolIdAsync(this._delegatorsByPoolId);
const delegatorBalancesByPoolId =
await this._getDelegatorBalancesByPoolIdAsync(this._delegatorsByPoolId);
const delegatorStakesByPoolId =
await this._getDelegatorStakesByPoolIdAsync(this._delegatorsByPoolId);
// compute expected changes // compute expected changes
const expectedRewardVaultBalanceByPoolId = const expectedRewardVaultBalanceByPoolId = await this._computeExpectedRewardVaultBalanceAsyncByPoolIdAsync(
await this._computeExpectedRewardVaultBalanceAsyncByPoolIdAsync(
rewards, rewards,
rewardVaultBalanceByPoolId, rewardVaultBalanceByPoolId,
operatorShareByPoolId, operatorShareByPoolId,
); );
const totalRewardsByPoolId = const totalRewardsByPoolId = _.zipObject(_.map(rewards, 'poolId'), _.map(rewards, 'reward'));
_.zipObject(_.map(rewards, 'poolId'), _.map(rewards, 'reward')); const expectedDelegatorBalancesByPoolId = await this._computeExpectedDelegatorBalancesByPoolIdAsync(
const expectedDelegatorBalancesByPoolId =
await this._computeExpectedDelegatorBalancesByPoolIdAsync(
this._delegatorsByPoolId, this._delegatorsByPoolId,
delegatorBalancesByPoolId, delegatorBalancesByPoolId,
delegatorStakesByPoolId, delegatorStakesByPoolId,
@@ -71,14 +64,12 @@ export class FinalizerActor extends BaseActor {
// finalize // finalize
await this._stakingApiWrapper.utils.skipToNextEpochAndFinalizeAsync(); await this._stakingApiWrapper.utils.skipToNextEpochAndFinalizeAsync();
// assert reward vault changes // assert reward vault changes
const finalRewardVaultBalanceByPoolId = const finalRewardVaultBalanceByPoolId = await this._getRewardVaultBalanceByPoolIdAsync(this._poolIds);
await this._getRewardVaultBalanceByPoolIdAsync(this._poolIds);
expect(finalRewardVaultBalanceByPoolId, 'final pool balances in reward vault').to.be.deep.equal( expect(finalRewardVaultBalanceByPoolId, 'final pool balances in reward vault').to.be.deep.equal(
expectedRewardVaultBalanceByPoolId, expectedRewardVaultBalanceByPoolId,
); );
// assert delegator balances // assert delegator balances
const finalDelegatorBalancesByPoolId = const finalDelegatorBalancesByPoolId = await this._getDelegatorBalancesByPoolIdAsync(this._delegatorsByPoolId);
await this._getDelegatorBalancesByPoolIdAsync(this._delegatorsByPoolId);
expect(finalDelegatorBalancesByPoolId, 'final delegator balances in reward vault').to.be.deep.equal( expect(finalDelegatorBalancesByPoolId, 'final delegator balances in reward vault').to.be.deep.equal(
expectedDelegatorBalancesByPoolId, expectedDelegatorBalancesByPoolId,
); );
@@ -103,13 +94,12 @@ export class FinalizerActor extends BaseActor {
} }
const operator = this._operatorByPoolId[poolId]; const operator = this._operatorByPoolId[poolId];
const [, membersStakeInPool] = const [, membersStakeInPool] = await this._getOperatorAndDelegatorsStakeInPoolAsync(poolId);
await this._getOperatorAndDelegatorsStakeInPoolAsync(poolId);
const operatorShare = operatorShareByPoolId[poolId].dividedBy(PPM_100_PERCENT); const operatorShare = operatorShareByPoolId[poolId].dividedBy(PPM_100_PERCENT);
const totalReward = totalRewardByPoolId[poolId]; const totalReward = totalRewardByPoolId[poolId];
const operatorReward = membersStakeInPool.eq(0) ? const operatorReward = membersStakeInPool.eq(0)
totalReward : ? totalReward
totalReward.times(operatorShare).integerValue(BigNumber.ROUND_DOWN); : totalReward.times(operatorShare).integerValue(BigNumber.ROUND_DOWN);
const membersTotalReward = totalReward.minus(operatorReward); const membersTotalReward = totalReward.minus(operatorReward);
for (const delegator of delegatorsByPoolId[poolId]) { for (const delegator of delegatorsByPoolId[poolId]) {
@@ -133,10 +123,8 @@ export class FinalizerActor extends BaseActor {
private async _getDelegatorBalancesByPoolIdAsync( private async _getDelegatorBalancesByPoolIdAsync(
delegatorsByPoolId: DelegatorsByPoolId, delegatorsByPoolId: DelegatorsByPoolId,
): Promise<DelegatorBalancesByPoolId> { ): Promise<DelegatorBalancesByPoolId> {
const computeRewardBalanceOfDelegator = const computeRewardBalanceOfDelegator = this._stakingApiWrapper.stakingContract.computeRewardBalanceOfDelegator;
this._stakingApiWrapper.stakingContract.computeRewardBalanceOfDelegator; const rewardVaultBalanceOfOperator = this._stakingApiWrapper.rewardVaultContract.balanceOfOperator;
const rewardVaultBalanceOfOperator =
this._stakingApiWrapper.rewardVaultContract.balanceOfOperator;
const delegatorBalancesByPoolId: DelegatorBalancesByPoolId = {}; const delegatorBalancesByPoolId: DelegatorBalancesByPoolId = {};
for (const poolId of Object.keys(delegatorsByPoolId)) { for (const poolId of Object.keys(delegatorsByPoolId)) {
@@ -144,19 +132,11 @@ export class FinalizerActor extends BaseActor {
const delegators = delegatorsByPoolId[poolId]; const delegators = delegatorsByPoolId[poolId];
delegatorBalancesByPoolId[poolId] = {}; delegatorBalancesByPoolId[poolId] = {};
for (const delegator of delegators) { for (const delegator of delegators) {
let balance = let balance = new BigNumber(delegatorBalancesByPoolId[poolId][delegator] || 0);
new BigNumber(delegatorBalancesByPoolId[poolId][delegator] || 0);
if (delegator === operator) { if (delegator === operator) {
balance = balance.plus( balance = balance.plus(await rewardVaultBalanceOfOperator.callAsync(poolId));
await rewardVaultBalanceOfOperator.callAsync(poolId),
);
} else { } else {
balance = balance.plus( balance = balance.plus(await computeRewardBalanceOfDelegator.callAsync(poolId, delegator));
await computeRewardBalanceOfDelegator.callAsync(
poolId,
delegator,
),
);
} }
delegatorBalancesByPoolId[poolId][delegator] = balance; delegatorBalancesByPoolId[poolId][delegator] = balance;
} }
@@ -167,16 +147,13 @@ export class FinalizerActor extends BaseActor {
private async _getDelegatorStakesByPoolIdAsync( private async _getDelegatorStakesByPoolIdAsync(
delegatorsByPoolId: DelegatorsByPoolId, delegatorsByPoolId: DelegatorsByPoolId,
): Promise<DelegatorBalancesByPoolId> { ): Promise<DelegatorBalancesByPoolId> {
const getStakeDelegatedToPoolByOwner = const getStakeDelegatedToPoolByOwner = this._stakingApiWrapper.stakingContract.getStakeDelegatedToPoolByOwner;
this._stakingApiWrapper.stakingContract.getStakeDelegatedToPoolByOwner;
const delegatorBalancesByPoolId: DelegatorBalancesByPoolId = {}; const delegatorBalancesByPoolId: DelegatorBalancesByPoolId = {};
for (const poolId of Object.keys(delegatorsByPoolId)) { for (const poolId of Object.keys(delegatorsByPoolId)) {
const delegators = delegatorsByPoolId[poolId]; const delegators = delegatorsByPoolId[poolId];
delegatorBalancesByPoolId[poolId] = {}; delegatorBalancesByPoolId[poolId] = {};
for (const delegator of delegators) { for (const delegator of delegators) {
delegatorBalancesByPoolId[poolId][ delegatorBalancesByPoolId[poolId][delegator] = (await getStakeDelegatedToPoolByOwner.callAsync(
delegator
] = (await getStakeDelegatedToPoolByOwner.callAsync(
delegator, delegator,
poolId, poolId,
)).currentEpochBalance; )).currentEpochBalance;
@@ -195,8 +172,7 @@ export class FinalizerActor extends BaseActor {
const expectedRewardVaultBalanceByPoolId = _.cloneDeep(rewardVaultBalanceByPoolId); const expectedRewardVaultBalanceByPoolId = _.cloneDeep(rewardVaultBalanceByPoolId);
for (const reward of rewards) { for (const reward of rewards) {
const operatorShare = operatorShareByPoolId[reward.poolId]; const operatorShare = operatorShareByPoolId[reward.poolId];
expectedRewardVaultBalanceByPoolId[reward.poolId] = expectedRewardVaultBalanceByPoolId[reward.poolId] = await this._computeExpectedRewardVaultBalanceAsync(
await this._computeExpectedRewardVaultBalanceAsync(
reward.poolId, reward.poolId,
reward.reward, reward.reward,
expectedRewardVaultBalanceByPoolId[reward.poolId], expectedRewardVaultBalanceByPoolId[reward.poolId],
@@ -233,18 +209,13 @@ export class FinalizerActor extends BaseActor {
return operatorBalanceByPoolId; return operatorBalanceByPoolId;
} }
private async _getOperatorAndDelegatorsStakeInPoolAsync( private async _getOperatorAndDelegatorsStakeInPoolAsync(poolId: string): Promise<[BigNumber, BigNumber]> {
poolId: string,
): Promise<[BigNumber, BigNumber]> {
const stakingContract = this._stakingApiWrapper.stakingContract; const stakingContract = this._stakingApiWrapper.stakingContract;
const operator = await stakingContract.getPoolOperator.callAsync(poolId); const operator = await stakingContract.getPoolOperator.callAsync(poolId);
const totalStakeInPool = (await stakingContract.getTotalStakeDelegatedToPool.callAsync( const totalStakeInPool = (await stakingContract.getTotalStakeDelegatedToPool.callAsync(poolId))
poolId, .currentEpochBalance;
)).currentEpochBalance; const operatorStakeInPool = (await stakingContract.getStakeDelegatedToPoolByOwner.callAsync(operator, poolId))
const operatorStakeInPool = (await stakingContract.getStakeDelegatedToPoolByOwner.callAsync( .currentEpochBalance;
operator,
poolId,
)).currentEpochBalance;
const membersStakeInPool = totalStakeInPool.minus(operatorStakeInPool); const membersStakeInPool = totalStakeInPool.minus(operatorStakeInPool);
return [operatorStakeInPool, membersStakeInPool]; return [operatorStakeInPool, membersStakeInPool];
} }

View File

@@ -74,7 +74,7 @@ blockchainTests.resets('Testing Rewards', env => {
// Skip to next epoch so operator stake is realized. // Skip to next epoch so operator stake is realized.
await stakingApiWrapper.utils.skipToNextEpochAndFinalizeAsync(); await stakingApiWrapper.utils.skipToNextEpochAndFinalizeAsync();
}); });
describe.skip('Reward Simulation', () => { describe('Reward Simulation', () => {
interface EndBalances { interface EndBalances {
// staker 1 // staker 1
stakerRewardVaultBalance_1?: BigNumber; stakerRewardVaultBalance_1?: BigNumber;
@@ -494,7 +494,7 @@ blockchainTests.resets('Testing Rewards', env => {
operatorEthVaultBalance: totalRewardsNotForDelegator, operatorEthVaultBalance: totalRewardsNotForDelegator,
}); });
}); });
it.only('Should collect fees correctly when leaving and returning to a pool', async () => { it('Should collect fees correctly when leaving and returning to a pool', async () => {
// first staker delegates (epoch 0) // first staker delegates (epoch 0)
const rewardsForDelegator = [toBaseUnitAmount(10), toBaseUnitAmount(15)]; const rewardsForDelegator = [toBaseUnitAmount(10), toBaseUnitAmount(15)];
const rewardNotForDelegator = toBaseUnitAmount(7); const rewardNotForDelegator = toBaseUnitAmount(7);
@@ -628,13 +628,13 @@ blockchainTests.resets('Testing Rewards', env => {
for (const [staker, stakeAmount] of stakersAndStake) { for (const [staker, stakeAmount] of stakersAndStake) {
await staker.stakeWithPoolAsync(poolId, stakeAmount); await staker.stakeWithPoolAsync(poolId, stakeAmount);
} }
// skip epoch, so staker can start earning rewards // skip epoch, so stakers can start earning rewards
await payProtocolFeeAndFinalize(); await payProtocolFeeAndFinalize();
// finalize // finalize
const reward = toBaseUnitAmount(10); const reward = toBaseUnitAmount(10);
await payProtocolFeeAndFinalize(reward); await payProtocolFeeAndFinalize(reward);
// Undelegate 0 stake to move rewards from RewardVault into the EthVault. // Undelegate 0 stake to move rewards from RewardVault into the EthVault.
for (const [staker] of stakersAndStake) { for (const [staker] of _.reverse(stakersAndStake)) {
await staker.moveStakeAsync( await staker.moveStakeAsync(
new StakeInfo(StakeStatus.Delegated, poolId), new StakeInfo(StakeStatus.Delegated, poolId),
new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Active),

View File

@@ -36,9 +36,7 @@ blockchainTests.resets.only('delegator unit rewards', env => {
stake: Numberish; stake: Numberish;
} }
async function rewardPoolMembersAsync( async function rewardPoolMembersAsync(opts?: Partial<RewardPoolMembersOpts>): Promise<RewardPoolMembersOpts> {
opts?: Partial<RewardPoolMembersOpts>,
): Promise<RewardPoolMembersOpts> {
const _opts = { const _opts = {
poolId: hexRandom(), poolId: hexRandom(),
reward: getRandomInteger(1, toBaseUnitAmount(100)), reward: getRandomInteger(1, toBaseUnitAmount(100)),
@@ -103,11 +101,7 @@ blockchainTests.resets.only('delegator unit rewards', env => {
...opts, ...opts,
}; };
const fn = now ? testContract.delegateStakeNow : testContract.delegateStake; const fn = now ? testContract.delegateStakeNow : testContract.delegateStake;
const receipt = await fn.awaitTransactionSuccessAsync( const receipt = await fn.awaitTransactionSuccessAsync(_opts.delegator, poolId, new BigNumber(_opts.stake));
_opts.delegator,
poolId,
new BigNumber(_opts.stake),
);
return { return {
..._opts, ..._opts,
deposit: getDepositFromLogs(receipt.logs, poolId, _opts.delegator), deposit: getDepositFromLogs(receipt.logs, poolId, _opts.delegator),
@@ -120,17 +114,10 @@ blockchainTests.resets.only('delegator unit rewards', env => {
stake?: Numberish, stake?: Numberish,
): Promise<ResultWithDeposit<{ stake: BigNumber }>> { ): Promise<ResultWithDeposit<{ stake: BigNumber }>> {
const _stake = new BigNumber( const _stake = new BigNumber(
stake || (await stake ||
testContract (await testContract.getStakeDelegatedToPoolByOwner.callAsync(delegator, poolId)).currentEpochBalance,
.getStakeDelegatedToPoolByOwner
.callAsync(delegator, poolId)
).currentEpochBalance,
);
const receipt = await testContract.undelegateStake.awaitTransactionSuccessAsync(
delegator,
poolId,
_stake,
); );
const receipt = await testContract.undelegateStake.awaitTransactionSuccessAsync(delegator, poolId, _stake);
return { return {
stake: _stake, stake: _stake,
deposit: getDepositFromLogs(receipt.logs, poolId, delegator), deposit: getDepositFromLogs(receipt.logs, poolId, delegator),
@@ -138,8 +125,7 @@ blockchainTests.resets.only('delegator unit rewards', env => {
} }
function getDepositFromLogs(logs: LogEntry[], poolId: string, delegator?: string): BigNumber { function getDepositFromLogs(logs: LogEntry[], poolId: string, delegator?: string): BigNumber {
const events = const events = filterLogsToArguments<TestDelegatorRewardsDepositEventArgs>(
filterLogsToArguments<TestDelegatorRewardsDepositEventArgs>(
logs, logs,
TestDelegatorRewardsEvents.Deposit, TestDelegatorRewardsEvents.Deposit,
); );
@@ -160,11 +146,8 @@ blockchainTests.resets.only('delegator unit rewards', env => {
return epoch.toNumber(); return epoch.toNumber();
} }
async function getDelegatorRewardAsync(poolId: string, delegator: string): Promise<BigNumber> { async function getDelegatorRewardBalanceAsync(poolId: string, delegator: string): Promise<BigNumber> {
return testContract.computeRewardBalanceOfDelegator.callAsync( return testContract.computeRewardBalanceOfDelegator.callAsync(poolId, delegator);
poolId,
delegator,
);
} }
async function touchStakeAsync(poolId: string, delegator: string): Promise<ResultWithDeposit<{}>> { async function touchStakeAsync(poolId: string, delegator: string): Promise<ResultWithDeposit<{}>> {
@@ -197,7 +180,7 @@ blockchainTests.resets.only('delegator unit rewards', env => {
it('nothing in epoch 0 for delegator with no stake', async () => { it('nothing in epoch 0 for delegator with no stake', async () => {
const { poolId } = await rewardPoolMembersAsync(); const { poolId } = await rewardPoolMembersAsync();
const delegator = randomAddress(); const delegator = randomAddress();
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator); const delegatorReward = await getDelegatorRewardBalanceAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(0); expect(delegatorReward).to.bignumber.eq(0);
}); });
@@ -205,7 +188,7 @@ blockchainTests.resets.only('delegator unit rewards', env => {
await advanceEpochAsync(); // epoch 1 await advanceEpochAsync(); // epoch 1
const { poolId } = await rewardPoolMembersAsync(); const { poolId } = await rewardPoolMembersAsync();
const delegator = randomAddress(); const delegator = randomAddress();
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator); const delegatorReward = await getDelegatorRewardBalanceAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(0); expect(delegatorReward).to.bignumber.eq(0);
}); });
@@ -214,7 +197,7 @@ blockchainTests.resets.only('delegator unit rewards', env => {
// Assign active stake to pool in epoch 0, which is usuaslly not // Assign active stake to pool in epoch 0, which is usuaslly not
// possible due to delegating delays. // possible due to delegating delays.
const { delegator } = await delegateStakeNowAsync(poolId); const { delegator } = await delegateStakeNowAsync(poolId);
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator); const delegatorReward = await getDelegatorRewardBalanceAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(0); expect(delegatorReward).to.bignumber.eq(0);
}); });
@@ -222,7 +205,7 @@ blockchainTests.resets.only('delegator unit rewards', env => {
await advanceEpochAsync(); // epoch 1 await advanceEpochAsync(); // epoch 1
const { poolId } = await rewardPoolMembersAsync(); const { poolId } = await rewardPoolMembersAsync();
const { delegator } = await delegateStakeAsync(poolId); const { delegator } = await delegateStakeAsync(poolId);
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator); const delegatorReward = await getDelegatorRewardBalanceAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(0); expect(delegatorReward).to.bignumber.eq(0);
}); });
@@ -232,7 +215,7 @@ blockchainTests.resets.only('delegator unit rewards', env => {
await advanceEpochAsync(); // epoch 1 (stake now active) await advanceEpochAsync(); // epoch 1 (stake now active)
// rewards paid for stake in epoch 0. // rewards paid for stake in epoch 0.
await rewardPoolMembersAsync({ poolId, stake }); await rewardPoolMembersAsync({ poolId, stake });
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator); const delegatorReward = await getDelegatorRewardBalanceAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(0); expect(delegatorReward).to.bignumber.eq(0);
}); });
@@ -243,7 +226,7 @@ blockchainTests.resets.only('delegator unit rewards', env => {
await advanceEpochAsync(); // epoch 2 await advanceEpochAsync(); // epoch 2
// rewards paid for stake in epoch 1. // rewards paid for stake in epoch 1.
const { reward } = await rewardPoolMembersAsync({ poolId, stake }); const { reward } = await rewardPoolMembersAsync({ poolId, stake });
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator); const delegatorReward = await getDelegatorRewardBalanceAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(reward); expect(delegatorReward).to.bignumber.eq(reward);
}); });
@@ -255,7 +238,7 @@ blockchainTests.resets.only('delegator unit rewards', env => {
const { reward: reward1 } = await rewardPoolMembersAsync({ poolId, stake }); const { reward: reward1 } = await rewardPoolMembersAsync({ poolId, stake });
await advanceEpochAsync(); // epoch 3 await advanceEpochAsync(); // epoch 3
const { reward: reward2 } = await rewardPoolMembersAsync({ poolId, stake }); const { reward: reward2 } = await rewardPoolMembersAsync({ poolId, stake });
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator); const delegatorReward = await getDelegatorRewardBalanceAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(BigNumber.sum(reward1, reward2)); expect(delegatorReward).to.bignumber.eq(BigNumber.sum(reward1, reward2));
}); });
@@ -265,10 +248,11 @@ blockchainTests.resets.only('delegator unit rewards', env => {
await advanceEpochAsync(); // epoch 1 (stake now active) await advanceEpochAsync(); // epoch 1 (stake now active)
await advanceEpochAsync(); // epoch 2 await advanceEpochAsync(); // epoch 2
// rewards paid for stake in epoch 1. // rewards paid for stake in epoch 1.
const { reward, stake: rewardStake } = await rewardPoolMembersAsync( const { reward, stake: rewardStake } = await rewardPoolMembersAsync({
{ poolId, stake: new BigNumber(delegatorStake).times(2) }, poolId,
); stake: new BigNumber(delegatorStake).times(2),
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator); });
const delegatorReward = await getDelegatorRewardBalanceAsync(poolId, delegator);
const expectedDelegatorRewards = computeDelegatorRewards(reward, delegatorStake, rewardStake); const expectedDelegatorRewards = computeDelegatorRewards(reward, delegatorStake, rewardStake);
assertRoughlyEquals(delegatorReward, expectedDelegatorRewards); assertRoughlyEquals(delegatorReward, expectedDelegatorRewards);
}); });
@@ -279,12 +263,10 @@ blockchainTests.resets.only('delegator unit rewards', env => {
await advanceEpochAsync(); // epoch 1 (stake now active) await advanceEpochAsync(); // epoch 1 (stake now active)
await advanceEpochAsync(); // epoch 2 await advanceEpochAsync(); // epoch 2
// rewards paid for stake in epoch 1. // rewards paid for stake in epoch 1.
const { reward } = await rewardPoolMembersAsync( const { reward } = await rewardPoolMembersAsync({ poolId, stake });
{ poolId, stake },
);
const { deposit } = await undelegateStakeAsync(poolId, delegator); const { deposit } = await undelegateStakeAsync(poolId, delegator);
expect(deposit).to.bignumber.eq(reward); expect(deposit).to.bignumber.eq(reward);
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator); const delegatorReward = await getDelegatorRewardBalanceAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(0); expect(delegatorReward).to.bignumber.eq(0);
}); });
@@ -294,13 +276,11 @@ blockchainTests.resets.only('delegator unit rewards', env => {
await advanceEpochAsync(); // epoch 1 (stake now active) await advanceEpochAsync(); // epoch 1 (stake now active)
await advanceEpochAsync(); // epoch 2 await advanceEpochAsync(); // epoch 2
// rewards paid for stake in epoch 1. // rewards paid for stake in epoch 1.
const { reward } = await rewardPoolMembersAsync( const { reward } = await rewardPoolMembersAsync({ poolId, stake });
{ poolId, stake },
);
const { deposit } = await undelegateStakeAsync(poolId, delegator); const { deposit } = await undelegateStakeAsync(poolId, delegator);
expect(deposit).to.bignumber.eq(reward); expect(deposit).to.bignumber.eq(reward);
await delegateStakeAsync(poolId, { delegator, stake }); await delegateStakeAsync(poolId, { delegator, stake });
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator); const delegatorReward = await getDelegatorRewardBalanceAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(0); expect(delegatorReward).to.bignumber.eq(0);
}); });
@@ -316,14 +296,50 @@ blockchainTests.resets.only('delegator unit rewards', env => {
await advanceEpochAsync(); // epoch 3 await advanceEpochAsync(); // epoch 3
await advanceEpochAsync(); // epoch 4 await advanceEpochAsync(); // epoch 4
// rewards paid for stake in epoch 3. // rewards paid for stake in epoch 3.
const { reward } = await rewardPoolMembersAsync( const { reward } = await rewardPoolMembersAsync({ poolId, stake });
{ poolId, stake }, const delegatorReward = await getDelegatorRewardBalanceAsync(poolId, delegator);
);
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(reward); expect(delegatorReward).to.bignumber.eq(reward);
}); });
it.only('computes correct rewards for 2 staggered delegators', async () => { it('ignores rewards paid in the same epoch the stake was first active in', async () => {
const poolId = hexRandom();
// stake at 0
const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 (stake now active)
// Pay rewards for epoch 0.
await advanceEpochAsync(); // epoch 2
// Pay rewards for epoch 1.
const { reward } = await rewardPoolMembersAsync({ poolId, stake });
const delegatorReward = await getDelegatorRewardBalanceAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(reward);
});
it('uses old stake for rewards paid in the same epoch EXTRA stake was first active in', async () => {
const poolId = hexRandom();
// stake at 0
const { delegator, stake: stake1 } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 (stake1 now active)
// add extra stake
const { stake: stake2 } = await delegateStakeAsync(poolId, { delegator });
const totalStake = BigNumber.sum(stake1, stake2);
// Make the total stake in rewards > totalStake so delegator never
// receives 100% of rewards.
const rewardStake = totalStake.times(2);
await advanceEpochAsync(); // epoch 2 (stake2 now active)
// Pay rewards for epoch 1.
const { reward: reward1 } = await rewardPoolMembersAsync({ poolId, stake: rewardStake });
await advanceEpochAsync(); // epoch 3
// Pay rewards for epoch 2.
const { reward: reward2 } = await rewardPoolMembersAsync({ poolId, stake: rewardStake });
const delegatorReward = await getDelegatorRewardBalanceAsync(poolId, delegator);
const expectedDelegatorReward = BigNumber.sum(
computeDelegatorRewards(reward1, stake1, rewardStake),
computeDelegatorRewards(reward2, totalStake, rewardStake),
);
expect(delegatorReward).to.bignumber.eq(expectedDelegatorReward);
});
it('computes correct rewards for 2 staggered delegators', async () => {
const poolId = hexRandom(); const poolId = hexRandom();
const { delegator: delegatorA, stake: stakeA } = await delegateStakeAsync(poolId); const { delegator: delegatorA, stake: stakeA } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 (stake A now active) await advanceEpochAsync(); // epoch 1 (stake A now active)
@@ -331,24 +347,18 @@ blockchainTests.resets.only('delegator unit rewards', env => {
const totalStake = BigNumber.sum(stakeA, stakeB); const totalStake = BigNumber.sum(stakeA, stakeB);
await advanceEpochAsync(); // epoch 2 (stake B now active) await advanceEpochAsync(); // epoch 2 (stake B now active)
// rewards paid for stake in epoch 1 (delegator A only) // rewards paid for stake in epoch 1 (delegator A only)
const { reward: reward1 } = await rewardPoolMembersAsync( const { reward: reward1 } = await rewardPoolMembersAsync({ poolId, stake: stakeA });
{ poolId, stake: stakeA },
);
await advanceEpochAsync(); // epoch 3 await advanceEpochAsync(); // epoch 3
// rewards paid for stake in epoch 2 (delegator A and B) // rewards paid for stake in epoch 2 (delegator A and B)
const { reward: reward2 } = await rewardPoolMembersAsync( const { reward: reward2 } = await rewardPoolMembersAsync({ poolId, stake: totalStake });
{ poolId, stake: totalStake }, const delegatorRewardA = await getDelegatorRewardBalanceAsync(poolId, delegatorA);
);
const delegatorRewardA = await getDelegatorRewardAsync(poolId, delegatorA);
const expectedDelegatorRewardA = BigNumber.sum( const expectedDelegatorRewardA = BigNumber.sum(
computeDelegatorRewards(reward1, stakeA, stakeA), computeDelegatorRewards(reward1, stakeA, stakeA),
computeDelegatorRewards(reward2, stakeA, totalStake), computeDelegatorRewards(reward2, stakeA, totalStake),
); );
assertRoughlyEquals(delegatorRewardA, expectedDelegatorRewardA); assertRoughlyEquals(delegatorRewardA, expectedDelegatorRewardA);
const delegatorRewardB = await getDelegatorRewardAsync(poolId, delegatorB); const delegatorRewardB = await getDelegatorRewardBalanceAsync(poolId, delegatorB);
const expectedDelegatorRewardB = BigNumber.sum( const expectedDelegatorRewardB = BigNumber.sum(computeDelegatorRewards(reward2, stakeB, totalStake));
computeDelegatorRewards(BigNumber.sum(reward1, reward2), stakeB, totalStake),
);
assertRoughlyEquals(delegatorRewardB, expectedDelegatorRewardB); assertRoughlyEquals(delegatorRewardB, expectedDelegatorRewardB);
}); });
@@ -360,25 +370,19 @@ blockchainTests.resets.only('delegator unit rewards', env => {
const totalStake = BigNumber.sum(stakeA, stakeB); const totalStake = BigNumber.sum(stakeA, stakeB);
await advanceEpochAsync(); // epoch 2 (stake B now active) await advanceEpochAsync(); // epoch 2 (stake B now active)
// rewards paid for stake in epoch 1 (delegator A only) // rewards paid for stake in epoch 1 (delegator A only)
const { reward: reward1 } = await rewardPoolMembersAsync( const { reward: reward1 } = await rewardPoolMembersAsync({ poolId, stake: stakeA });
{ poolId, stake: stakeA },
);
await advanceEpochAsync(); // epoch 3 await advanceEpochAsync(); // epoch 3
await advanceEpochAsync(); // epoch 4 await advanceEpochAsync(); // epoch 4
// rewards paid for stake in epoch 3 (delegator A and B) // rewards paid for stake in epoch 3 (delegator A and B)
const { reward: reward2 } = await rewardPoolMembersAsync( const { reward: reward2 } = await rewardPoolMembersAsync({ poolId, stake: totalStake });
{ poolId, stake: totalStake }, const delegatorRewardA = await getDelegatorRewardBalanceAsync(poolId, delegatorA);
);
const delegatorRewardA = await getDelegatorRewardAsync(poolId, delegatorA);
const expectedDelegatorRewardA = BigNumber.sum( const expectedDelegatorRewardA = BigNumber.sum(
computeDelegatorRewards(reward1, stakeA, stakeA), computeDelegatorRewards(reward1, stakeA, stakeA),
computeDelegatorRewards(reward2, stakeA, totalStake), computeDelegatorRewards(reward2, stakeA, totalStake),
); );
assertRoughlyEquals(delegatorRewardA, expectedDelegatorRewardA); assertRoughlyEquals(delegatorRewardA, expectedDelegatorRewardA);
const delegatorRewardB = await getDelegatorRewardAsync(poolId, delegatorB); const delegatorRewardB = await getDelegatorRewardBalanceAsync(poolId, delegatorB);
const expectedDelegatorRewardB = BigNumber.sum( const expectedDelegatorRewardB = BigNumber.sum(computeDelegatorRewards(reward2, stakeB, totalStake));
computeDelegatorRewards(reward2, stakeB, totalStake),
);
assertRoughlyEquals(delegatorRewardB, expectedDelegatorRewardB); assertRoughlyEquals(delegatorRewardB, expectedDelegatorRewardB);
}); });
@@ -388,15 +392,17 @@ blockchainTests.resets.only('delegator unit rewards', env => {
await advanceEpochAsync(); // epoch 1 (stake now active) await advanceEpochAsync(); // epoch 1 (stake now active)
await advanceEpochAsync(); // epoch 2 await advanceEpochAsync(); // epoch 2
// rewards paid for stake in epoch 1. // rewards paid for stake in epoch 1.
const { reward: reward1, stake: rewardStake1 } = await rewardPoolMembersAsync( const { reward: reward1, stake: rewardStake1 } = await rewardPoolMembersAsync({
{ poolId, stake: new BigNumber(delegatorStake).times(2) }, poolId,
); stake: new BigNumber(delegatorStake).times(2),
});
await advanceEpochAsync(); // epoch 3 await advanceEpochAsync(); // epoch 3
// rewards paid for stake in epoch 2 // rewards paid for stake in epoch 2
const { reward: reward2, stake: rewardStake2 } = await rewardPoolMembersAsync( const { reward: reward2, stake: rewardStake2 } = await rewardPoolMembersAsync({
{ poolId, stake: new BigNumber(delegatorStake).times(3) }, poolId,
); stake: new BigNumber(delegatorStake).times(3),
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator); });
const delegatorReward = await getDelegatorRewardBalanceAsync(poolId, delegator);
const expectedDelegatorReward = BigNumber.sum( const expectedDelegatorReward = BigNumber.sum(
computeDelegatorRewards(reward1, delegatorStake, rewardStake1), computeDelegatorRewards(reward1, delegatorStake, rewardStake1),
computeDelegatorRewards(reward2, delegatorStake, rewardStake2), computeDelegatorRewards(reward2, delegatorStake, rewardStake2),
@@ -410,7 +416,7 @@ blockchainTests.resets.only('delegator unit rewards', env => {
const { delegator, stake } = await delegateStakeAsync(poolId, { stake: 0 }); const { delegator, stake } = await delegateStakeAsync(poolId, { stake: 0 });
await advanceEpochAsync(); // epoch 1 await advanceEpochAsync(); // epoch 1
await setUnfinalizedMembersRewardsAsync({ poolId, stake }); await setUnfinalizedMembersRewardsAsync({ poolId, stake });
const reward = await getDelegatorRewardAsync(poolId, delegator); const reward = await getDelegatorRewardBalanceAsync(poolId, delegator);
expect(reward).to.bignumber.eq(0); expect(reward).to.bignumber.eq(0);
}); });
@@ -419,32 +425,32 @@ blockchainTests.resets.only('delegator unit rewards', env => {
const { delegator, stake } = await delegateStakeAsync(poolId); const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 await advanceEpochAsync(); // epoch 1
await setUnfinalizedMembersRewardsAsync({ poolId, stake }); await setUnfinalizedMembersRewardsAsync({ poolId, stake });
const reward = await getDelegatorRewardAsync(poolId, delegator); const reward = await getDelegatorRewardBalanceAsync(poolId, delegator);
expect(reward).to.bignumber.eq(0); expect(reward).to.bignumber.eq(0);
}); });
it('returns only unfinalized rewards from epoch 2 for delegator delegating in epoch 1', async () => { it('returns unfinalized rewards from epoch 2 for delegator delegating in epoch 0', async () => {
const poolId = hexRandom(); const poolId = hexRandom();
const { delegator, stake } = await delegateStakeAsync(poolId); const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 await advanceEpochAsync(); // epoch 1
await advanceEpochAsync(); // epoch 2 await advanceEpochAsync(); // epoch 2
const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake }); const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake });
const reward = await getDelegatorRewardAsync(poolId, delegator); const reward = await getDelegatorRewardBalanceAsync(poolId, delegator);
expect(reward).to.bignumber.eq(unfinalizedReward); expect(reward).to.bignumber.eq(unfinalizedReward);
}); });
it('returns only unfinalized rewards from epoch 3 for delegator delegating in epoch 1', async () => { it('returns unfinalized rewards from epoch 3 for delegator delegating in epoch 0', async () => {
const poolId = hexRandom(); const poolId = hexRandom();
const { delegator, stake } = await delegateStakeAsync(poolId); const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 await advanceEpochAsync(); // epoch 1
await advanceEpochAsync(); // epoch 2 await advanceEpochAsync(); // epoch 2
await advanceEpochAsync(); // epoch 3 await advanceEpochAsync(); // epoch 3
const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake }); const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake });
const reward = await getDelegatorRewardAsync(poolId, delegator); const reward = await getDelegatorRewardBalanceAsync(poolId, delegator);
expect(reward).to.bignumber.eq(unfinalizedReward); expect(reward).to.bignumber.eq(unfinalizedReward);
}); });
it('returns unfinalized rewards from epoch 3 + rewards from epoch 2 for delegator delegating in epoch 1', async () => { it('returns unfinalized rewards from epoch 3 + rewards from epoch 2 for delegator delegating in epoch 0', async () => {
const poolId = hexRandom(); const poolId = hexRandom();
const { delegator, stake } = await delegateStakeAsync(poolId); const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 await advanceEpochAsync(); // epoch 1
@@ -452,7 +458,7 @@ blockchainTests.resets.only('delegator unit rewards', env => {
const { reward: prevReward } = await rewardPoolMembersAsync({ poolId, stake }); const { reward: prevReward } = await rewardPoolMembersAsync({ poolId, stake });
await advanceEpochAsync(); // epoch 3 await advanceEpochAsync(); // epoch 3
const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake }); const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake });
const reward = await getDelegatorRewardAsync(poolId, delegator); const reward = await getDelegatorRewardBalanceAsync(poolId, delegator);
const expectedReward = BigNumber.sum(prevReward, unfinalizedReward); const expectedReward = BigNumber.sum(prevReward, unfinalizedReward);
expect(reward).to.bignumber.eq(expectedReward); expect(reward).to.bignumber.eq(expectedReward);
}); });
@@ -466,7 +472,7 @@ blockchainTests.resets.only('delegator unit rewards', env => {
await advanceEpochAsync(); // epoch 3 await advanceEpochAsync(); // epoch 3
await advanceEpochAsync(); // epoch 4 await advanceEpochAsync(); // epoch 4
const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake }); const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake });
const reward = await getDelegatorRewardAsync(poolId, delegator); const reward = await getDelegatorRewardBalanceAsync(poolId, delegator);
const expectedReward = BigNumber.sum(prevReward, unfinalizedReward); const expectedReward = BigNumber.sum(prevReward, unfinalizedReward);
expect(reward).to.bignumber.eq(expectedReward); expect(reward).to.bignumber.eq(expectedReward);
}); });
@@ -476,16 +482,17 @@ blockchainTests.resets.only('delegator unit rewards', env => {
const { delegator, stake } = await delegateStakeAsync(poolId); const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 await advanceEpochAsync(); // epoch 1
await advanceEpochAsync(); // epoch 2 await advanceEpochAsync(); // epoch 2
const { reward: prevReward, stake: prevStake } = await rewardPoolMembersAsync( const { reward: prevReward, stake: prevStake } = await rewardPoolMembersAsync({
{ poolId, stake: new BigNumber(stake).times(2) }, poolId,
); stake: new BigNumber(stake).times(2),
});
await advanceEpochAsync(); // epoch 3 await advanceEpochAsync(); // epoch 3
await advanceEpochAsync(); // epoch 4 await advanceEpochAsync(); // epoch 4
const { reward: unfinalizedReward, stake: unfinalizedStake } = const { reward: unfinalizedReward, stake: unfinalizedStake } = await setUnfinalizedMembersRewardsAsync({
await setUnfinalizedMembersRewardsAsync( poolId,
{ poolId, stake: new BigNumber(stake).times(5) }, stake: new BigNumber(stake).times(5),
); });
const reward = await getDelegatorRewardAsync(poolId, delegator); const reward = await getDelegatorRewardBalanceAsync(poolId, delegator);
const expectedReward = BigNumber.sum( const expectedReward = BigNumber.sum(
computeDelegatorRewards(prevReward, stake, prevStake), computeDelegatorRewards(prevReward, stake, prevStake),
computeDelegatorRewards(unfinalizedReward, stake, unfinalizedStake), computeDelegatorRewards(unfinalizedReward, stake, unfinalizedStake),
@@ -504,7 +511,9 @@ blockchainTests.resets.only('delegator unit rewards', env => {
// rewards paid for stake in epoch 1 // rewards paid for stake in epoch 1
const { reward } = await rewardPoolMembersAsync({ poolId, stake }); const { reward } = await rewardPoolMembersAsync({ poolId, stake });
const { deposit } = await touchStakeAsync(poolId, delegator); const { deposit } = await touchStakeAsync(poolId, delegator);
const finalRewardBalance = await getDelegatorRewardBalanceAsync(poolId, delegator);
expect(deposit).to.bignumber.eq(reward); expect(deposit).to.bignumber.eq(reward);
expect(finalRewardBalance).to.bignumber.eq(0);
}); });
it('does not collect extra rewards from delegating more stake in the reward epoch', async () => { it('does not collect extra rewards from delegating more stake in the reward epoch', async () => {
@@ -513,21 +522,21 @@ blockchainTests.resets.only('delegator unit rewards', env => {
// stake // stake
stakeResults.push(await delegateStakeAsync(poolId)); stakeResults.push(await delegateStakeAsync(poolId));
const { delegator, stake } = stakeResults[0]; const { delegator, stake } = stakeResults[0];
const totalStake = new BigNumber(stake).times(2); const rewardStake = new BigNumber(stake).times(2);
await advanceEpochAsync(); // epoch 1 (stake now active) await advanceEpochAsync(); // epoch 1 (stake now active)
// add more stake. // add more stake.
stakeResults.push(await delegateStakeAsync(poolId, { delegator, stake })); stakeResults.push(await delegateStakeAsync(poolId, { delegator, stake }));
await advanceEpochAsync(); // epoch 1 (2 * stake now active) await advanceEpochAsync(); // epoch 1 (2 * stake now active)
// reward for epoch 1, using 2 * stake so delegator should // reward for epoch 1, using 2 * stake so delegator should
// only be entitled to a fraction of the rewards. // only be entitled to a fraction of the rewards.
const { reward } = await rewardPoolMembersAsync({ poolId, stake: totalStake }); const { reward } = await rewardPoolMembersAsync({ poolId, stake: rewardStake });
await advanceEpochAsync(); // epoch 2 await advanceEpochAsync(); // epoch 2
// touch the stake one last time // touch the stake one last time
stakeResults.push(await touchStakeAsync(poolId, delegator)); stakeResults.push(await touchStakeAsync(poolId, delegator));
// Should only see deposits for epoch 2. // Should only see deposits for epoch 2.
const expectedDeposit = computeDelegatorRewards(reward, stake, totalStake);
const allDeposits = stakeResults.map(r => r.deposit); const allDeposits = stakeResults.map(r => r.deposit);
assertRoughlyEquals(BigNumber.sum(...allDeposits), expectedDeposit); const expectedReward = computeDelegatorRewards(reward, stake, rewardStake);
assertRoughlyEquals(BigNumber.sum(...allDeposits), expectedReward);
}); });
it('only collects rewards from staked epochs', async () => { it('only collects rewards from staked epochs', async () => {
@@ -536,27 +545,46 @@ blockchainTests.resets.only('delegator unit rewards', env => {
// stake // stake
stakeResults.push(await delegateStakeAsync(poolId)); stakeResults.push(await delegateStakeAsync(poolId));
const { delegator, stake } = stakeResults[0]; const { delegator, stake } = stakeResults[0];
await advanceEpochAsync(); // epoch 1 (stake now active) const rewardStake = new BigNumber(stake).times(2);
// unstake before and after reward payout, to be extra sneaky. await advanceEpochAsync(); // epoch 1 (full stake now active)
const unstake1 = new BigNumber(stake).dividedToIntegerBy(2);
stakeResults.push(await undelegateStakeAsync(poolId, delegator, unstake1));
// reward for epoch 0 // reward for epoch 0
await rewardPoolMembersAsync({ poolId, stake }); await rewardPoolMembersAsync({ poolId, stake: rewardStake });
const unstake2 = new BigNumber(stake).minus(unstake1); // unstake some
stakeResults.push(await undelegateStakeAsync(poolId, delegator, unstake2)); const unstake = new BigNumber(stake).dividedToIntegerBy(2);
await advanceEpochAsync(); // epoch 2 (no active stake) stakeResults.push(await undelegateStakeAsync(poolId, delegator, unstake));
await advanceEpochAsync(); // epoch 2 (half active stake)
// reward for epoch 1 // reward for epoch 1
const { reward } = await rewardPoolMembersAsync({ poolId, stake }); const { reward: reward1 } = await rewardPoolMembersAsync({ poolId, stake: rewardStake });
// re-stake // re-stake
stakeResults.push(await delegateStakeAsync(poolId, { delegator, stake })); stakeResults.push(await delegateStakeAsync(poolId, { delegator, stake: unstake }));
await advanceEpochAsync(); // epoch 3 (stake now active) await advanceEpochAsync(); // epoch 3 (full stake now active)
// reward for epoch 2 // reward for epoch 2
await rewardPoolMembersAsync({ poolId, stake }); const { reward: reward2 } = await rewardPoolMembersAsync({ poolId, stake: rewardStake });
// touch the stake one last time // touch the stake to claim rewards
stakeResults.push(await touchStakeAsync(poolId, delegator)); stakeResults.push(await touchStakeAsync(poolId, delegator));
// Should only see deposits for epoch 2.
const allDeposits = stakeResults.map(r => r.deposit); const allDeposits = stakeResults.map(r => r.deposit);
assertRoughlyEquals(BigNumber.sum(...allDeposits), reward); const expectedReward = BigNumber.sum(
computeDelegatorRewards(reward1, stake, rewardStake),
computeDelegatorRewards(reward2, new BigNumber(stake).minus(unstake), rewardStake),
);
assertRoughlyEquals(BigNumber.sum(...allDeposits), expectedReward);
});
it('two delegators can collect split rewards as soon as available', async () => {
const poolId = hexRandom();
const { delegator: delegatorA, stake: stakeA } = await delegateStakeAsync(poolId);
const { delegator: delegatorB, stake: stakeB } = await delegateStakeAsync(poolId);
const totalStake = BigNumber.sum(stakeA, stakeB);
await advanceEpochAsync(); // epoch 1 (stakes now active)
await advanceEpochAsync(); // epoch 2
// rewards paid for stake in epoch 1
const { reward } = await rewardPoolMembersAsync({ poolId, stake: totalStake });
// delegator A will finalize and collect rewards by touching stake.
const { deposit: depositA } = await touchStakeAsync(poolId, delegatorA);
assertRoughlyEquals(depositA, computeDelegatorRewards(reward, stakeA, totalStake));
// delegator B will collect rewards by touching stake
const { deposit: depositB } = await touchStakeAsync(poolId, delegatorB);
assertRoughlyEquals(depositB, computeDelegatorRewards(reward, stakeB, totalStake));
}); });
it('delegator B collects correct rewards after delegator A finalizes', async () => { it('delegator B collects correct rewards after delegator A finalizes', async () => {
@@ -570,7 +598,10 @@ blockchainTests.resets.only('delegator unit rewards', env => {
const { reward: prevReward } = await rewardPoolMembersAsync({ poolId, stake: totalStake }); const { reward: prevReward } = await rewardPoolMembersAsync({ poolId, stake: totalStake });
await advanceEpochAsync(); // epoch 3 await advanceEpochAsync(); // epoch 3
// unfinalized rewards for stake in epoch 2 // unfinalized rewards for stake in epoch 2
const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake: totalStake }); const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({
poolId,
stake: totalStake,
});
const totalRewards = BigNumber.sum(prevReward, unfinalizedReward); const totalRewards = BigNumber.sum(prevReward, unfinalizedReward);
// delegator A will finalize and collect rewards by touching stake. // delegator A will finalize and collect rewards by touching stake.
const { deposit: depositA } = await touchStakeAsync(poolId, delegatorA); const { deposit: depositA } = await touchStakeAsync(poolId, delegatorA);
@@ -591,7 +622,10 @@ blockchainTests.resets.only('delegator unit rewards', env => {
const { reward: prevReward } = await rewardPoolMembersAsync({ poolId, stake: totalStake }); const { reward: prevReward } = await rewardPoolMembersAsync({ poolId, stake: totalStake });
await advanceEpochAsync(); // epoch 3 await advanceEpochAsync(); // epoch 3
// unfinalized rewards for stake in epoch 2 // unfinalized rewards for stake in epoch 2
const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake: totalStake }); const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({
poolId,
stake: totalStake,
});
const totalRewards = BigNumber.sum(prevReward, unfinalizedReward); const totalRewards = BigNumber.sum(prevReward, unfinalizedReward);
// finalize // finalize
await finalizePoolAsync(poolId); await finalizePoolAsync(poolId);

View File

@@ -255,7 +255,7 @@ blockchainTests.resets('finalizer unit tests', env => {
// Add a pool so there is state to clear. // Add a pool so there is state to clear.
await addActivePoolAsync(); await addActivePoolAsync();
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
assertFinalizationStateAsync({ return assertFinalizationStateAsync({
currentEpoch: INITIAL_EPOCH + 1, currentEpoch: INITIAL_EPOCH + 1,
closingEpoch: INITIAL_EPOCH, closingEpoch: INITIAL_EPOCH,
numActivePoolsThisEpoch: 0, numActivePoolsThisEpoch: 0,
@@ -268,7 +268,7 @@ blockchainTests.resets('finalizer unit tests', env => {
// Add a pool so there is state to clear. // Add a pool so there is state to clear.
const pool = await addActivePoolAsync(); const pool = await addActivePoolAsync();
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
assertFinalizationStateAsync({ return assertFinalizationStateAsync({
unfinalizedPoolsRemaining: 1, unfinalizedPoolsRemaining: 1,
unfinalizedRewardsAvailable: INITIAL_BALANCE, unfinalizedRewardsAvailable: INITIAL_BALANCE,
unfinalizedTotalFeesCollected: pool.feesCollected, unfinalizedTotalFeesCollected: pool.feesCollected,
@@ -318,7 +318,7 @@ blockchainTests.resets('finalizer unit tests', env => {
it('can finalize multiple pools', async () => { it('can finalize multiple pools', async () => {
const nextEpoch = INITIAL_EPOCH + 1; const nextEpoch = INITIAL_EPOCH + 1;
const pools = await Promise.all(_.times(3, () => addActivePoolAsync())); const pools = await Promise.all(_.times(3, async () => addActivePoolAsync()));
const poolIds = pools.map(p => p.poolId); const poolIds = pools.map(p => p.poolId);
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
const receipt = await testContract.finalizePools.awaitTransactionSuccessAsync(poolIds); const receipt = await testContract.finalizePools.awaitTransactionSuccessAsync(poolIds);
@@ -338,7 +338,7 @@ blockchainTests.resets('finalizer unit tests', env => {
it('can finalize multiple pools over multiple transactions', async () => { it('can finalize multiple pools over multiple transactions', async () => {
const nextEpoch = INITIAL_EPOCH + 1; const nextEpoch = INITIAL_EPOCH + 1;
const pools = await Promise.all(_.times(2, () => addActivePoolAsync())); const pools = await Promise.all(_.times(2, async () => addActivePoolAsync()));
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
const receipts = await Promise.all( const receipts = await Promise.all(
pools.map(pool => testContract.finalizePools.awaitTransactionSuccessAsync([pool.poolId])), pools.map(pool => testContract.finalizePools.awaitTransactionSuccessAsync([pool.poolId])),
@@ -358,7 +358,7 @@ blockchainTests.resets('finalizer unit tests', env => {
}); });
it('ignores a non-active pool', async () => { it('ignores a non-active pool', async () => {
const pools = await Promise.all(_.times(3, () => addActivePoolAsync())); const pools = await Promise.all(_.times(3, async () => addActivePoolAsync()));
const nonActivePoolId = hexRandom(); const nonActivePoolId = hexRandom();
const poolIds = _.shuffle([...pools.map(p => p.poolId), nonActivePoolId]); const poolIds = _.shuffle([...pools.map(p => p.poolId), nonActivePoolId]);
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
@@ -371,7 +371,7 @@ blockchainTests.resets('finalizer unit tests', env => {
}); });
it('ignores a finalized pool', async () => { it('ignores a finalized pool', async () => {
const pools = await Promise.all(_.times(3, () => addActivePoolAsync())); const pools = await Promise.all(_.times(3, async () => addActivePoolAsync()));
const poolIds = pools.map(p => p.poolId); const poolIds = pools.map(p => p.poolId);
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
const [finalizedPool] = _.sampleSize(pools, 1); const [finalizedPool] = _.sampleSize(pools, 1);
@@ -385,7 +385,7 @@ blockchainTests.resets('finalizer unit tests', env => {
}); });
it('resets pool state after finalizing it', async () => { it('resets pool state after finalizing it', async () => {
const pools = await Promise.all(_.times(3, () => addActivePoolAsync())); const pools = await Promise.all(_.times(3, async () => addActivePoolAsync()));
const pool = _.sample(pools) as ActivePoolOpts; const pool = _.sample(pools) as ActivePoolOpts;
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
await testContract.finalizePools.awaitTransactionSuccessAsync([pool.poolId]); await testContract.finalizePools.awaitTransactionSuccessAsync([pool.poolId]);
@@ -399,7 +399,7 @@ blockchainTests.resets('finalizer unit tests', env => {
}); });
it('`rewardsPaid` is the sum of all pool rewards', async () => { it('`rewardsPaid` is the sum of all pool rewards', async () => {
const pools = await Promise.all(_.times(3, () => addActivePoolAsync())); const pools = await Promise.all(_.times(3, async () => addActivePoolAsync()));
const poolIds = pools.map(p => p.poolId); const poolIds = pools.map(p => p.poolId);
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
const receipt = await testContract.finalizePools.awaitTransactionSuccessAsync(poolIds); const receipt = await testContract.finalizePools.awaitTransactionSuccessAsync(poolIds);
@@ -412,7 +412,7 @@ blockchainTests.resets('finalizer unit tests', env => {
}); });
it('`rewardsPaid` <= `rewardsAvailable` <= contract balance at the end of the epoch', async () => { it('`rewardsPaid` <= `rewardsAvailable` <= contract balance at the end of the epoch', async () => {
const pools = await Promise.all(_.times(3, () => addActivePoolAsync())); const pools = await Promise.all(_.times(3, async () => addActivePoolAsync()));
const poolIds = pools.map(p => p.poolId); const poolIds = pools.map(p => p.poolId);
let receipt = await testContract.endEpoch.awaitTransactionSuccessAsync(); let receipt = await testContract.endEpoch.awaitTransactionSuccessAsync();
const { rewardsAvailable } = getEpochEndedEvents(receipt.logs)[0]; const { rewardsAvailable } = getEpochEndedEvents(receipt.logs)[0];
@@ -438,7 +438,7 @@ blockchainTests.resets('finalizer unit tests', env => {
for (const i of _.times(numTests)) { for (const i of _.times(numTests)) {
const numPools = _.random(1, 32); const numPools = _.random(1, 32);
it(`${i + 1}/${numTests} \`rewardsPaid\` <= \`rewardsAvailable\` (${numPools} pools)`, async () => { it(`${i + 1}/${numTests} \`rewardsPaid\` <= \`rewardsAvailable\` (${numPools} pools)`, async () => {
const pools = await Promise.all(_.times(numPools, () => addActivePoolAsync())); const pools = await Promise.all(_.times(numPools, async () => addActivePoolAsync()));
const poolIds = pools.map(p => p.poolId); const poolIds = pools.map(p => p.poolId);
let receipt = await testContract.endEpoch.awaitTransactionSuccessAsync(); let receipt = await testContract.endEpoch.awaitTransactionSuccessAsync();
const { rewardsAvailable } = getEpochEndedEvents(receipt.logs)[0]; const { rewardsAvailable } = getEpochEndedEvents(receipt.logs)[0];
@@ -477,7 +477,7 @@ blockchainTests.resets('finalizer unit tests', env => {
it('can finalize multiple pools over multiple transactions', async () => { it('can finalize multiple pools over multiple transactions', async () => {
const nextEpoch = INITIAL_EPOCH + 1; const nextEpoch = INITIAL_EPOCH + 1;
const pools = await Promise.all(_.times(2, () => addActivePoolAsync())); const pools = await Promise.all(_.times(2, async () => addActivePoolAsync()));
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
const receipts = await Promise.all( const receipts = await Promise.all(
pools.map(pool => testContract.internalFinalizePool.awaitTransactionSuccessAsync(pool.poolId)), pools.map(pool => testContract.internalFinalizePool.awaitTransactionSuccessAsync(pool.poolId)),
@@ -497,7 +497,7 @@ blockchainTests.resets('finalizer unit tests', env => {
}); });
it('ignores a finalized pool', async () => { it('ignores a finalized pool', async () => {
const pools = await Promise.all(_.times(3, () => addActivePoolAsync())); const pools = await Promise.all(_.times(3, async () => addActivePoolAsync()));
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
const [finalizedPool] = _.sampleSize(pools, 1); const [finalizedPool] = _.sampleSize(pools, 1);
await testContract.internalFinalizePool.awaitTransactionSuccessAsync(finalizedPool.poolId); await testContract.internalFinalizePool.awaitTransactionSuccessAsync(finalizedPool.poolId);
@@ -507,7 +507,7 @@ blockchainTests.resets('finalizer unit tests', env => {
}); });
it('resets pool state after finalizing it', async () => { it('resets pool state after finalizing it', async () => {
const pools = await Promise.all(_.times(3, () => addActivePoolAsync())); const pools = await Promise.all(_.times(3, async () => addActivePoolAsync()));
const pool = _.sample(pools) as ActivePoolOpts; const pool = _.sample(pools) as ActivePoolOpts;
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
await testContract.internalFinalizePool.awaitTransactionSuccessAsync(pool.poolId); await testContract.internalFinalizePool.awaitTransactionSuccessAsync(pool.poolId);
@@ -521,7 +521,7 @@ blockchainTests.resets('finalizer unit tests', env => {
}); });
it('`rewardsPaid` <= `rewardsAvailable` <= contract balance at the end of the epoch', async () => { it('`rewardsPaid` <= `rewardsAvailable` <= contract balance at the end of the epoch', async () => {
const pools = await Promise.all(_.times(3, () => addActivePoolAsync())); const pools = await Promise.all(_.times(3, async () => addActivePoolAsync()));
const receipt = await testContract.endEpoch.awaitTransactionSuccessAsync(); const receipt = await testContract.endEpoch.awaitTransactionSuccessAsync();
const { rewardsAvailable } = getEpochEndedEvents(receipt.logs)[0]; const { rewardsAvailable } = getEpochEndedEvents(receipt.logs)[0];
expect(rewardsAvailable).to.bignumber.lte(INITIAL_BALANCE); expect(rewardsAvailable).to.bignumber.lte(INITIAL_BALANCE);
@@ -551,7 +551,7 @@ blockchainTests.resets('finalizer unit tests', env => {
for (const i of _.times(numTests)) { for (const i of _.times(numTests)) {
const numPools = _.random(1, 32); const numPools = _.random(1, 32);
it(`${i + 1}/${numTests} \`rewardsPaid\` <= \`rewardsAvailable\` (${numPools} pools)`, async () => { it(`${i + 1}/${numTests} \`rewardsPaid\` <= \`rewardsAvailable\` (${numPools} pools)`, async () => {
const pools = await Promise.all(_.times(numPools, () => addActivePoolAsync())); const pools = await Promise.all(_.times(numPools, async () => addActivePoolAsync()));
const receipt = await testContract.endEpoch.awaitTransactionSuccessAsync(); const receipt = await testContract.endEpoch.awaitTransactionSuccessAsync();
const { rewardsAvailable } = getEpochEndedEvents(receipt.logs)[0]; const { rewardsAvailable } = getEpochEndedEvents(receipt.logs)[0];
const receipts = await Promise.all( const receipts = await Promise.all(
@@ -601,11 +601,11 @@ blockchainTests.resets('finalizer unit tests', env => {
it('rolls over leftover rewards into th next epoch', async () => { it('rolls over leftover rewards into th next epoch', async () => {
const poolIds = _.times(3, () => hexRandom()); const poolIds = _.times(3, () => hexRandom());
await Promise.all(poolIds.map(id => addActivePoolAsync({ poolId: id }))); await Promise.all(poolIds.map(async id => addActivePoolAsync({ poolId: id })));
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
let receipt = await testContract.finalizePools.awaitTransactionSuccessAsync(poolIds); let receipt = await testContract.finalizePools.awaitTransactionSuccessAsync(poolIds);
const { rewardsRemaining: rolledOverRewards } = getEpochFinalizedEvents(receipt.logs)[0]; const { rewardsRemaining: rolledOverRewards } = getEpochFinalizedEvents(receipt.logs)[0];
await Promise.all(poolIds.map(id => addActivePoolAsync({ poolId: id }))); await Promise.all(poolIds.map(async id => addActivePoolAsync({ poolId: id })));
receipt = await testContract.endEpoch.awaitTransactionSuccessAsync(); receipt = await testContract.endEpoch.awaitTransactionSuccessAsync();
const { rewardsAvailable } = getEpochEndedEvents(receipt.logs)[0]; const { rewardsAvailable } = getEpochEndedEvents(receipt.logs)[0];
expect(rewardsAvailable).to.bignumber.eq(rolledOverRewards); expect(rewardsAvailable).to.bignumber.eq(rolledOverRewards);
@@ -690,7 +690,7 @@ blockchainTests.resets('finalizer unit tests', env => {
}); });
it('returns empty if pool was already finalized', async () => { it('returns empty if pool was already finalized', async () => {
const pools = await Promise.all(_.times(3, () => addActivePoolAsync())); const pools = await Promise.all(_.times(3, async () => addActivePoolAsync()));
const [pool] = _.sampleSize(pools, 1); const [pool] = _.sampleSize(pools, 1);
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
await testContract.finalizePools.awaitTransactionSuccessAsync([pool.poolId]); await testContract.finalizePools.awaitTransactionSuccessAsync([pool.poolId]);
@@ -712,7 +712,7 @@ blockchainTests.resets('finalizer unit tests', env => {
}); });
it('computes one reward among multiple pools', async () => { it('computes one reward among multiple pools', async () => {
const pools = await Promise.all(_.times(3, () => addActivePoolAsync())); const pools = await Promise.all(_.times(3, async () => addActivePoolAsync()));
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
const [pool] = _.sampleSize(pools, 1); const [pool] = _.sampleSize(pools, 1);
const totalFeesCollected = BigNumber.sum(...pools.map(p => p.feesCollected)); const totalFeesCollected = BigNumber.sum(...pools.map(p => p.feesCollected));

View File

@@ -1,12 +1,4 @@
import { import { blockchainTests, constants, expect, hexConcat, hexRandom, hexSlice } from '@0x/contracts-test-utils';
blockchainTests,
constants,
expect,
hexConcat,
hexRandom,
hexSlice,
testCombinatoriallyWithReferenceFunc,
} from '@0x/contracts-test-utils';
import { StakingRevertErrors } from '@0x/order-utils'; import { StakingRevertErrors } from '@0x/order-utils';
import { cartesianProduct } from 'js-combinatorics'; import { cartesianProduct } from 'js-combinatorics';