@0x/contracts-staking: Well, it almost worked.

This commit is contained in:
Lawrence Forman 2019-09-16 10:55:50 -04:00 committed by Lawrence Forman
parent 38b94ec5f8
commit 7ef3c12722
11 changed files with 912 additions and 127 deletions

View File

@ -42,6 +42,7 @@ library LibStakingRichErrors {
InvalidCobbDouglasAlpha,
InvalidRewardDelegatedStakeWeight,
InvalidMaximumMakersInPool,
InvalidMinimumPoolStake,
InvalidWethProxyAddress,
InvalidEthVaultAddress,
InvalidRewardVaultAddress,

View File

@ -187,11 +187,15 @@ contract MixinStakingPoolRewards is
amountOfDelegatedStake
);
// Normalize fraction components by dividing by the min token value
// (10^18)
// Normalize fraction components by dividing by the minimum denominator.
uint256 minDenominator =
mostRecentCumulativeRewards.denominator <= amountOfDelegatedStake ?
mostRecentCumulativeRewards.denominator :
amountOfDelegatedStake;
minDenominator = minDenominator == 0 ? 1 : minDenominator;
(uint256 numeratorNormalized, uint256 denominatorNormalized) = (
numerator.safeDiv(MIN_TOKEN_VALUE),
denominator.safeDiv(MIN_TOKEN_VALUE)
numerator.safeDiv(minDenominator),
denominator.safeDiv(minDenominator)
);
// store cumulative rewards and set most recent

View File

@ -19,11 +19,173 @@
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;
import "../src/Staking.sol";
import "../src/interfaces/IStructs.sol";
import "./TestStaking.sol";
contract TestDelegatorRewards is
Staking
TestStaking
{
// TODO
event Deposit(
bytes32 poolId,
address member,
uint256 balance
);
event FinalizePool(
bytes32 poolId,
uint256 reward,
uint256 stake
);
struct UnfinalizedMembersReward {
uint256 reward;
uint256 stake;
}
constructor() public {
init();
}
mapping (uint256 => mapping (bytes32 => UnfinalizedMembersReward)) private
unfinalizedMembersRewardByPoolByEpoch;
/// @dev Expose _finalizePool
function internalFinalizePool(bytes32 poolId) external {
_finalizePool(poolId);
}
/// @dev Set unfinalized members reward for a pool in the current epoch.
function setUnfinalizedMembersRewards(
bytes32 poolId,
uint256 membersReward,
uint256 membersStake
)
external
{
unfinalizedMembersRewardByPoolByEpoch[currentEpoch][poolId] =
UnfinalizedMembersReward({
reward: membersReward,
stake: membersStake
});
}
/// @dev Advance the epoch.
function advanceEpoch() external {
currentEpoch += 1;
}
/// @dev Create and delegate stake that is active in the current epoch.
/// Only used to test purportedly unreachable states.
/// Also withdraws pending rewards to the eth vault.
function delegateStakeNow(
address delegator,
bytes32 poolId,
uint256 stake
)
external
{
_transferDelegatorsAccumulatedRewardsToEthVault(poolId, delegator);
_syncCumulativeRewardsNeededByDelegator(poolId, currentEpoch);
IStructs.StoredBalance storage _stake =
delegatedStakeToPoolByOwner[delegator][poolId];
_stake.currentEpochBalance += uint96(stake);
_stake.nextEpochBalance += uint96(stake);
_stake.currentEpoch = uint64(currentEpoch);
}
/// @dev Create and delegate stake that will occur in the next epoch
/// (normal behavior).
/// Also withdraws pending rewards to the eth vault.
function delegateStake(
address delegator,
bytes32 poolId,
uint256 stake
)
external
{
_transferDelegatorsAccumulatedRewardsToEthVault(poolId, delegator);
_syncCumulativeRewardsNeededByDelegator(poolId, currentEpoch);
IStructs.StoredBalance storage _stake =
delegatedStakeToPoolByOwner[delegator][poolId];
if (_stake.currentEpoch < currentEpoch) {
_stake.currentEpochBalance = _stake.nextEpochBalance;
}
_stake.nextEpochBalance += uint96(stake);
_stake.currentEpoch = uint64(currentEpoch);
}
/// @dev Clear stake that will occur in the next epoch
/// (normal behavior).
/// Also withdraws pending rewards to the eth vault.
function undelegateStake(
address delegator,
bytes32 poolId,
uint256 stake
)
external
{
_transferDelegatorsAccumulatedRewardsToEthVault(poolId, delegator);
_syncCumulativeRewardsNeededByDelegator(poolId, currentEpoch);
IStructs.StoredBalance storage _stake =
delegatedStakeToPoolByOwner[delegator][poolId];
if (_stake.currentEpoch < currentEpoch) {
_stake.currentEpochBalance = _stake.nextEpochBalance;
}
_stake.nextEpochBalance -= uint96(stake);
_stake.currentEpoch = uint64(currentEpoch);
}
/// @dev Expose `_recordDepositInRewardVaultFor`.
function recordRewardForDelegators(
bytes32 poolId,
uint256 reward,
uint256 amountOfDelegatedStake
)
external
{
_recordRewardForDelegators(poolId, reward, amountOfDelegatedStake);
}
/// @dev Overridden to just emit events.
function _transferMemberBalanceToEthVault(
bytes32 poolId,
address member,
uint256 balance
)
internal
{
emit Deposit(
poolId,
member,
balance
);
}
/// @dev Overridden to realize unfinalizedMembersRewardByPoolByEpoch in
/// the current epoch and eit a event,
function _finalizePool(bytes32 poolId)
internal
returns (IStructs.PoolRewards memory rewards)
{
UnfinalizedMembersReward memory reward =
unfinalizedMembersRewardByPoolByEpoch[currentEpoch][poolId];
delete unfinalizedMembersRewardByPoolByEpoch[currentEpoch][poolId];
rewards.membersReward = reward.reward;
rewards.membersStake = reward.stake;
_recordRewardForDelegators(poolId, reward.reward, reward.stake);
emit FinalizePool(poolId, reward.reward, reward.stake);
}
/// @dev Overridden to use unfinalizedMembersRewardByPoolByEpoch.
function _getUnfinalizedPoolRewards(bytes32 poolId)
internal
view
returns (IStructs.PoolRewards memory rewards)
{
UnfinalizedMembersReward storage reward =
unfinalizedMembersRewardByPoolByEpoch[currentEpoch][poolId];
rewards.membersReward = reward.reward;
rewards.membersStake = reward.stake;
}
}

View File

@ -21,11 +21,11 @@ pragma experimental ABIEncoderV2;
import "../src/interfaces/IStructs.sol";
import "../src/libs/LibCobbDouglas.sol";
import "../src/Staking.sol";
import "./TestStaking.sol";
contract TestFinalizer is
Staking
TestStaking
{
event RecordRewardForDelegatorsCall(
bytes32 poolId,

View File

@ -1,14 +1,15 @@
import { expect } from '@0x/contracts-test-utils';
import { constants, expect } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { StakingApiWrapper } from '../utils/api_wrapper';
import {
MemberBalancesByPoolId,
MembersByPoolId,
OperatorBalanceByPoolId,
DelegatorBalancesByPoolId,
DelegatorsByPoolId,
OperatorByPoolId,
OperatorShareByPoolId,
RewardByPoolId,
RewardVaultBalance,
RewardVaultBalanceByPoolId,
} from '../utils/types';
@ -19,59 +20,67 @@ interface Reward {
poolId: string;
}
const { PPM_100_PERCENT } = constants;
// tslint:disable: prefer-conditional-expression
export class FinalizerActor extends BaseActor {
private readonly _poolIds: string[];
private readonly _operatorByPoolId: OperatorByPoolId;
private readonly _membersByPoolId: MembersByPoolId;
private readonly _delegatorsByPoolId: DelegatorsByPoolId;
constructor(
owner: string,
stakingApiWrapper: StakingApiWrapper,
poolIds: string[],
operatorByPoolId: OperatorByPoolId,
membersByPoolId: MembersByPoolId,
delegatorsByPoolId: DelegatorsByPoolId,
) {
super(owner, stakingApiWrapper);
this._poolIds = _.cloneDeep(poolIds);
this._operatorByPoolId = _.cloneDeep(operatorByPoolId);
this._membersByPoolId = _.cloneDeep(membersByPoolId);
this._delegatorsByPoolId = _.cloneDeep(delegatorsByPoolId);
}
public async finalizeAsync(rewards: Reward[] = []): Promise<void> {
// cache initial info and balances
const operatorShareByPoolId = await this._getOperatorShareByPoolIdAsync(this._poolIds);
const rewardVaultBalanceByPoolId = await this._getRewardVaultBalanceByPoolIdAsync(this._poolIds);
const memberBalancesByPoolId = await this._getMemberBalancesByPoolIdAsync(this._membersByPoolId);
const operatorBalanceByPoolId = await this._getOperatorBalanceByPoolIdAsync(this._operatorByPoolId);
const operatorShareByPoolId =
await this._getOperatorShareByPoolIdAsync(this._poolIds);
const rewardVaultBalanceByPoolId =
await this._getRewardVaultBalanceByPoolIdAsync(this._poolIds);
const delegatorBalancesByPoolId =
await this._getDelegatorBalancesByPoolIdAsync(this._delegatorsByPoolId);
const delegatorStakesByPoolId =
await this._getDelegatorStakesByPoolIdAsync(this._delegatorsByPoolId);
// compute expected changes
const [
expectedOperatorBalanceByPoolId,
expectedRewardVaultBalanceByPoolId,
] = await this._computeExpectedRewardVaultBalanceAsyncByPoolIdAsync(
const expectedRewardVaultBalanceByPoolId =
await this._computeExpectedRewardVaultBalanceAsyncByPoolIdAsync(
rewards,
operatorBalanceByPoolId,
rewardVaultBalanceByPoolId,
operatorShareByPoolId,
);
const memberRewardByPoolId = _.mapValues(_.keyBy(rewards, 'poolId'), r => {
return r.reward.minus(r.reward.times(operatorShareByPoolId[r.poolId]).dividedToIntegerBy(100));
});
const expectedMemberBalancesByPoolId = await this._computeExpectedMemberBalancesByPoolIdAsync(
this._membersByPoolId,
memberBalancesByPoolId,
memberRewardByPoolId,
const totalRewardsByPoolId =
_.zipObject(_.map(rewards, 'poolId'), _.map(rewards, 'reward'));
const expectedDelegatorBalancesByPoolId =
await this._computeExpectedDelegatorBalancesByPoolIdAsync(
this._delegatorsByPoolId,
delegatorBalancesByPoolId,
delegatorStakesByPoolId,
operatorShareByPoolId,
totalRewardsByPoolId,
);
// finalize
await this._stakingApiWrapper.utils.skipToNextEpochAndFinalizeAsync();
// assert reward vault changes
const finalRewardVaultBalanceByPoolId = await this._getRewardVaultBalanceByPoolIdAsync(this._poolIds);
const finalRewardVaultBalanceByPoolId =
await this._getRewardVaultBalanceByPoolIdAsync(this._poolIds);
expect(finalRewardVaultBalanceByPoolId, 'final pool balances in reward vault').to.be.deep.equal(
expectedRewardVaultBalanceByPoolId,
);
// assert member balances
const finalMemberBalancesByPoolId = await this._getMemberBalancesByPoolIdAsync(this._membersByPoolId);
expect(finalMemberBalancesByPoolId, 'final delegator balances in reward vault').to.be.deep.equal(
expectedMemberBalancesByPoolId,
// assert delegator balances
const finalDelegatorBalancesByPoolId =
await this._getDelegatorBalancesByPoolIdAsync(this._delegatorsByPoolId);
expect(finalDelegatorBalancesByPoolId, 'final delegator balances in reward vault').to.be.deep.equal(
expectedDelegatorBalancesByPoolId,
);
// assert operator balances
const finalOperatorBalanceByPoolId = await this._getOperatorBalanceByPoolIdAsync(this._operatorByPoolId);
@ -80,55 +89,100 @@ export class FinalizerActor extends BaseActor {
);
}
private async _computeExpectedMemberBalancesByPoolIdAsync(
membersByPoolId: MembersByPoolId,
memberBalancesByPoolId: MemberBalancesByPoolId,
rewardByPoolId: { [key: string]: BigNumber },
): Promise<MemberBalancesByPoolId> {
const expectedMemberBalancesByPoolId = _.cloneDeep(memberBalancesByPoolId);
for (const poolId of Object.keys(membersByPoolId)) {
if (rewardByPoolId[poolId] === undefined) {
private async _computeExpectedDelegatorBalancesByPoolIdAsync(
delegatorsByPoolId: DelegatorsByPoolId,
delegatorBalancesByPoolId: DelegatorBalancesByPoolId,
delegatorStakesByPoolId: DelegatorBalancesByPoolId,
operatorShareByPoolId: OperatorShareByPoolId,
totalRewardByPoolId: RewardByPoolId,
): Promise<DelegatorBalancesByPoolId> {
const expectedDelegatorBalancesByPoolId = _.cloneDeep(delegatorBalancesByPoolId);
for (const poolId of Object.keys(delegatorsByPoolId)) {
if (totalRewardByPoolId[poolId] === undefined) {
continue;
}
const totalStakeDelegatedToPool = (await this._stakingApiWrapper.stakingContract.getTotalStakeDelegatedToPool.callAsync(
poolId,
)).currentEpochBalance;
for (const member of membersByPoolId[poolId]) {
if (totalStakeDelegatedToPool.eq(0)) {
expectedMemberBalancesByPoolId[poolId][member] = new BigNumber(0);
} else {
const stakeDelegatedToPoolByMember = (await this._stakingApiWrapper.stakingContract.getStakeDelegatedToPoolByOwner.callAsync(
member,
poolId,
)).currentEpochBalance;
const rewardThisEpoch = rewardByPoolId[poolId]
.times(stakeDelegatedToPoolByMember)
.dividedToIntegerBy(totalStakeDelegatedToPool);
expectedMemberBalancesByPoolId[poolId][member] =
memberBalancesByPoolId[poolId][member] === undefined
? rewardThisEpoch
: memberBalancesByPoolId[poolId][member].plus(rewardThisEpoch);
const operator = this._operatorByPoolId[poolId];
const [, membersStakeInPool] =
await this._getOperatorAndDelegatorsStakeInPoolAsync(poolId);
const operatorShare = operatorShareByPoolId[poolId].dividedBy(PPM_100_PERCENT);
const totalReward = totalRewardByPoolId[poolId];
const operatorReward = membersStakeInPool.eq(0) ?
totalReward :
totalReward.times(operatorShare).integerValue(BigNumber.ROUND_DOWN);
const membersTotalReward = totalReward.minus(operatorReward);
for (const delegator of delegatorsByPoolId[poolId]) {
let delegatorReward = new BigNumber(0);
if (delegator === operator) {
delegatorReward = operatorReward;
} else if (membersStakeInPool.gt(0)) {
const delegatorStake = delegatorStakesByPoolId[poolId][delegator];
delegatorReward = delegatorStake
.times(membersTotalReward)
.dividedBy(membersStakeInPool)
.integerValue(BigNumber.ROUND_DOWN);
}
const currentBalance = expectedDelegatorBalancesByPoolId[poolId][delegator] || 0;
expectedDelegatorBalancesByPoolId[poolId][delegator] = delegatorReward.plus(currentBalance);
}
}
}
return expectedMemberBalancesByPoolId;
return expectedDelegatorBalancesByPoolId;
}
private async _getMemberBalancesByPoolIdAsync(membersByPoolId: MembersByPoolId): Promise<MemberBalancesByPoolId> {
const memberBalancesByPoolId: MemberBalancesByPoolId = {};
for (const poolId of Object.keys(membersByPoolId)) {
const members = membersByPoolId[poolId];
memberBalancesByPoolId[poolId] = {};
for (const member of members) {
memberBalancesByPoolId[poolId][
member
] = await this._stakingApiWrapper.stakingContract.computeRewardBalanceOfDelegator.callAsync(
private async _getDelegatorBalancesByPoolIdAsync(
delegatorsByPoolId: DelegatorsByPoolId,
): Promise<DelegatorBalancesByPoolId> {
const computeRewardBalanceOfDelegator =
this._stakingApiWrapper.stakingContract.computeRewardBalanceOfDelegator;
const rewardVaultBalanceOfOperator =
this._stakingApiWrapper.rewardVaultContract.balanceOfOperator;
const delegatorBalancesByPoolId: DelegatorBalancesByPoolId = {};
for (const poolId of Object.keys(delegatorsByPoolId)) {
const operator = this._operatorByPoolId[poolId];
const delegators = delegatorsByPoolId[poolId];
delegatorBalancesByPoolId[poolId] = {};
for (const delegator of delegators) {
let balance =
new BigNumber(delegatorBalancesByPoolId[poolId][delegator] || 0);
if (delegator === operator) {
balance = balance.plus(
await rewardVaultBalanceOfOperator.callAsync(poolId),
);
} else {
balance = balance.plus(
await computeRewardBalanceOfDelegator.callAsync(
poolId,
member,
delegator,
),
);
}
delegatorBalancesByPoolId[poolId][delegator] = balance;
}
return memberBalancesByPoolId;
}
return delegatorBalancesByPoolId;
}
private async _getDelegatorStakesByPoolIdAsync(
delegatorsByPoolId: DelegatorsByPoolId,
): Promise<DelegatorBalancesByPoolId> {
const getStakeDelegatedToPoolByOwner =
this._stakingApiWrapper.stakingContract.getStakeDelegatedToPoolByOwner;
const delegatorBalancesByPoolId: DelegatorBalancesByPoolId = {};
for (const poolId of Object.keys(delegatorsByPoolId)) {
const delegators = delegatorsByPoolId[poolId];
delegatorBalancesByPoolId[poolId] = {};
for (const delegator of delegators) {
delegatorBalancesByPoolId[poolId][
delegator
] = (await getStakeDelegatedToPoolByOwner.callAsync(
delegator,
poolId,
)).currentEpochBalance;
}
}
return delegatorBalancesByPoolId;
}
private async _computeExpectedRewardVaultBalanceAsyncByPoolIdAsync(
@ -141,13 +195,10 @@ export class FinalizerActor extends BaseActor {
const expectedRewardVaultBalanceByPoolId = _.cloneDeep(rewardVaultBalanceByPoolId);
for (const reward of rewards) {
const operatorShare = operatorShareByPoolId[reward.poolId];
[
expectedOperatorBalanceByPoolId[reward.poolId],
expectedRewardVaultBalanceByPoolId[reward.poolId],
] = await this._computeExpectedRewardVaultBalanceAsync(
expectedRewardVaultBalanceByPoolId[reward.poolId] =
await this._computeExpectedRewardVaultBalanceAsync(
reward.poolId,
reward.reward,
expectedOperatorBalanceByPoolId[reward.poolId],
expectedRewardVaultBalanceByPoolId[reward.poolId],
operatorShare,
);
@ -161,13 +212,11 @@ export class FinalizerActor extends BaseActor {
operatorBalance: BigNumber,
rewardVaultBalance: BigNumber,
operatorShare: BigNumber,
): Promise<[BigNumber, BigNumber]> {
const totalStakeDelegatedToPool = (await this._stakingApiWrapper.stakingContract.getTotalStakeDelegatedToPool.callAsync(
poolId,
)).currentEpochBalance;
const operatorPortion = totalStakeDelegatedToPool.eq(0)
): Promise<RewardVaultBalance> {
const [, membersStakeInPool] = await this._getOperatorAndDelegatorsStakeInPoolAsync(poolId);
const operatorPortion = membersStakeInPool.eq(0)
? reward
: reward.times(operatorShare).dividedToIntegerBy(100);
: reward.times(operatorShare).dividedToIntegerBy(PPM_100_PERCENT);
const membersPortion = reward.minus(operatorPortion);
return [operatorBalance.plus(operatorPortion), rewardVaultBalance.plus(membersPortion)];
}
@ -184,6 +233,22 @@ export class FinalizerActor extends BaseActor {
return operatorBalanceByPoolId;
}
private async _getOperatorAndDelegatorsStakeInPoolAsync(
poolId: string,
): Promise<[BigNumber, BigNumber]> {
const stakingContract = this._stakingApiWrapper.stakingContract;
const operator = await stakingContract.getPoolOperator.callAsync(poolId);
const totalStakeInPool = (await stakingContract.getTotalStakeDelegatedToPool.callAsync(
poolId,
)).currentEpochBalance;
const operatorStakeInPool = (await stakingContract.getStakeDelegatedToPoolByOwner.callAsync(
operator,
poolId,
)).currentEpochBalance;
const membersStakeInPool = totalStakeInPool.minus(operatorStakeInPool);
return [operatorStakeInPool, membersStakeInPool];
}
private async _getOperatorShareByPoolIdAsync(poolIds: string[]): Promise<OperatorShareByPoolId> {
const operatorShareByPoolId: OperatorShareByPoolId = {};
for (const poolId of poolIds) {

View File

@ -6,7 +6,7 @@ import { artifacts, IStakingEventsParamsSetEventArgs, MixinParamsContract } from
import { constants as stakingConstants } from './utils/constants';
import { StakingParams } from './utils/types';
blockchainTests('Configurable Parameters', env => {
blockchainTests('Configurable Parameters unit tests', env => {
let testContract: MixinParamsContract;
let authorizedAddress: string;
let notAuthorizedAddress: string;

View File

@ -10,7 +10,7 @@ import { PoolOperatorActor } from './actors/pool_operator_actor';
import { StakerActor } from './actors/staker_actor';
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
import { toBaseUnitAmount } from './utils/number_utils';
import { MembersByPoolId, OperatorByPoolId, StakeInfo, StakeStatus } from './utils/types';
import { DelegatorsByPoolId, OperatorByPoolId, StakeInfo, StakeStatus } from './utils/types';
// tslint:disable:no-unnecessary-type-assertion
// tslint:disable:max-file-line-count
@ -67,14 +67,14 @@ blockchainTests.resets('Testing Rewards', env => {
const operatorByPoolId: OperatorByPoolId = {};
operatorByPoolId[poolId] = poolOperator.getOwner();
// associate actors with pools for tracking in Finalizer
const membersByPoolId: MembersByPoolId = {};
membersByPoolId[poolId] = [actors[0], actors[1]];
const stakersByPoolId: DelegatorsByPoolId = {};
stakersByPoolId[poolId] = actors.slice(0, 3);
// create Finalizer actor
finalizer = new FinalizerActor(actors[3], stakingApiWrapper, [poolId], operatorByPoolId, membersByPoolId);
finalizer = new FinalizerActor(actors[3], stakingApiWrapper, [poolId], operatorByPoolId, stakersByPoolId);
// Skip to next epoch so operator stake is realized.
await stakingApiWrapper.utils.skipToNextEpochAndFinalizeAsync();
});
describe('Reward Simulation', () => {
describe.skip('Reward Simulation', () => {
interface EndBalances {
// staker 1
stakerRewardVaultBalance_1?: BigNumber;
@ -399,12 +399,9 @@ blockchainTests.resets('Testing Rewards', env => {
toBaseUnitAmount(0),
toBaseUnitAmount(17),
];
const totalRewardsAfterAddingMoreStake = new BigNumber(
_.sumBy(rewardsAfterAddingMoreStake, v => {
return v.toNumber();
}),
);
const totalRewardsAfterAddingMoreStake = BigNumber.sum(...rewardsAfterAddingMoreStake);
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
const totalStake = BigNumber.sum(...stakeAmounts);
// first staker delegates (epoch 0)
await stakers[0].stakeWithPoolAsync(poolId, stakeAmounts[0]);
// skip epoch, so first staker can start earning rewards
@ -419,7 +416,16 @@ blockchainTests.resets('Testing Rewards', env => {
}
// sanity check final balances
await validateEndBalances({
stakerRewardVaultBalance_1: rewardBeforeAddingMoreStake.plus(totalRewardsAfterAddingMoreStake),
stakerRewardVaultBalance_1: rewardBeforeAddingMoreStake.plus(
totalRewardsAfterAddingMoreStake
.times(stakeAmounts[0])
.dividedBy(totalStake)
.integerValue(BigNumber.ROUND_DOWN),
),
stakerRewardVaultBalance_2: totalRewardsAfterAddingMoreStake
.times(stakeAmounts[1])
.dividedBy(totalStake)
.integerValue(BigNumber.ROUND_DOWN),
poolRewardVaultBalance: rewardBeforeAddingMoreStake.plus(totalRewardsAfterAddingMoreStake),
membersRewardVaultBalance: rewardBeforeAddingMoreStake.plus(totalRewardsAfterAddingMoreStake),
});
@ -464,11 +470,7 @@ blockchainTests.resets('Testing Rewards', env => {
toBaseUnitAmount(0),
toBaseUnitAmount(17),
];
const totalRewardsNotForDelegator = new BigNumber(
_.sumBy(rewardsNotForDelegator, v => {
return v.toNumber();
}),
);
const totalRewardsNotForDelegator = BigNumber.sum(...rewardsNotForDelegator);
const stakeAmount = toBaseUnitAmount(4);
await stakers[0].stakeWithPoolAsync(poolId, stakeAmount);
// skip epoch, so first staker can start earning rewards
@ -492,7 +494,7 @@ blockchainTests.resets('Testing Rewards', env => {
operatorEthVaultBalance: totalRewardsNotForDelegator,
});
});
it('Should collect fees correctly when leaving and returning to a pool', async () => {
it.only('Should collect fees correctly when leaving and returning to a pool', async () => {
// first staker delegates (epoch 0)
const rewardsForDelegator = [toBaseUnitAmount(10), toBaseUnitAmount(15)];
const rewardNotForDelegator = toBaseUnitAmount(7);

View File

@ -1,8 +1,24 @@
import { blockchainTests, expect, Numberish } from '@0x/contracts-test-utils';
import {
blockchainTests,
constants,
expect,
filterLogsToArguments,
hexRandom,
Numberish,
} from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';
import { LogEntry } from 'ethereum-types';
import { artifacts, TestDelegatorRewardsContract } from '../../src';
import {
artifacts,
TestDelegatorRewardsContract,
TestDelegatorRewardsDepositEventArgs,
TestDelegatorRewardsEvents,
} from '../../src';
blockchainTests('delegator rewards', env => {
import { assertRoughlyEquals, getRandomInteger, toBaseUnitAmount } from '../utils/number_utils';
blockchainTests.resets('delegator unit rewards', env => {
let testContract: TestDelegatorRewardsContract;
before(async () => {
@ -14,9 +30,543 @@ blockchainTests('delegator rewards', env => {
);
});
interface RewardPoolMembersOpts {
poolId: string;
reward: Numberish;
stake: Numberish;
}
async function rewardPoolMembersAsync(
opts?: Partial<RewardPoolMembersOpts>,
): Promise<RewardPoolMembersOpts> {
const _opts = {
poolId: hexRandom(),
reward: getRandomInteger(1, toBaseUnitAmount(100)),
stake: getRandomInteger(1, toBaseUnitAmount(10)),
...opts,
};
await testContract.recordRewardForDelegators.awaitTransactionSuccessAsync(
_opts.poolId,
new BigNumber(_opts.reward),
new BigNumber(_opts.stake),
);
return _opts;
}
interface SetUnfinalizedMembersRewardsOpts {
poolId: string;
reward: Numberish;
stake: Numberish;
}
async function setUnfinalizedMembersRewardsAsync(
opts?: Partial<SetUnfinalizedMembersRewardsOpts>,
): Promise<SetUnfinalizedMembersRewardsOpts> {
const _opts = {
poolId: hexRandom(),
reward: getRandomInteger(1, toBaseUnitAmount(100)),
stake: getRandomInteger(1, toBaseUnitAmount(10)),
...opts,
};
await testContract.setUnfinalizedMembersRewards.awaitTransactionSuccessAsync(
_opts.poolId,
new BigNumber(_opts.reward),
new BigNumber(_opts.stake),
);
return _opts;
}
type ResultWithDeposit<T extends {}> = T & {
deposit: BigNumber;
};
interface DelegateStakeOpts {
delegator: string;
stake: Numberish;
}
async function delegateStakeNowAsync(
poolId: string,
opts?: Partial<DelegateStakeOpts>,
): Promise<ResultWithDeposit<DelegateStakeOpts>> {
return delegateStakeAsync(poolId, opts, true);
}
async function delegateStakeAsync(
poolId: string,
opts?: Partial<DelegateStakeOpts>,
now?: boolean,
): Promise<ResultWithDeposit<DelegateStakeOpts>> {
const _opts = {
delegator: randomAddress(),
stake: getRandomInteger(1, toBaseUnitAmount(10)),
...opts,
};
const fn = now ? testContract.delegateStakeNow : testContract.delegateStake;
const receipt = await fn.awaitTransactionSuccessAsync(
_opts.delegator,
poolId,
new BigNumber(_opts.stake),
);
return {
..._opts,
deposit: getDepositFromLogs(receipt.logs, poolId, _opts.delegator),
};
}
async function undelegateStakeAsync(
poolId: string,
delegator: string,
stake?: Numberish,
): Promise<ResultWithDeposit<{ stake: BigNumber }>> {
const _stake = new BigNumber(
stake || (await
testContract
.getStakeDelegatedToPoolByOwner
.callAsync(delegator, poolId)
).currentEpochBalance,
);
const receipt = await testContract.undelegateStake.awaitTransactionSuccessAsync(
delegator,
poolId,
_stake,
);
return {
stake: _stake,
deposit: getDepositFromLogs(receipt.logs, poolId, delegator),
};
}
function getDepositFromLogs(logs: LogEntry[], poolId: string, delegator?: string): BigNumber {
const events =
filterLogsToArguments<TestDelegatorRewardsDepositEventArgs>(
logs,
TestDelegatorRewardsEvents.Deposit,
);
if (events.length > 0) {
expect(events.length).to.eq(1);
expect(events[0].poolId).to.eq(poolId);
if (delegator !== undefined) {
expect(events[0].member).to.eq(delegator);
}
return events[0].balance;
}
return constants.ZERO_AMOUNT;
}
async function advanceEpochAsync(): Promise<number> {
await testContract.advanceEpoch.awaitTransactionSuccessAsync();
const epoch = await testContract.getCurrentEpoch.callAsync();
return epoch.toNumber();
}
async function getDelegatorRewardAsync(poolId: string, delegator: string): Promise<BigNumber> {
return testContract.computeRewardBalanceOfDelegator.callAsync(
poolId,
delegator,
);
}
async function touchStakeAsync(poolId: string, delegator: string): Promise<ResultWithDeposit<{}>> {
return undelegateStakeAsync(poolId, delegator, 0);
}
async function finalizePoolAsync(poolId: string): Promise<ResultWithDeposit<{}>> {
const receipt = await testContract.internalFinalizePool.awaitTransactionSuccessAsync(poolId);
return {
deposit: getDepositFromLogs(receipt.logs, poolId),
};
}
function randomAddress(): string {
return hexRandom(constants.ADDRESS_LENGTH);
}
function computeDelegatorRewards(
totalRewards: Numberish,
delegatorStake: Numberish,
totalDelegatorStake: Numberish,
): BigNumber {
return new BigNumber(totalRewards)
.times(delegatorStake)
.dividedBy(new BigNumber(totalDelegatorStake))
.integerValue(BigNumber.ROUND_DOWN);
}
describe('computeRewardBalanceOfDelegator()', () => {
it('does stuff', () => {
// TODO
it('nothing in epoch 0 for delegator with no stake', async () => {
const { poolId } = await rewardPoolMembersAsync();
const delegator = randomAddress();
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(0);
});
it('nothing in epoch 1 for delegator with no stake', async () => {
await advanceEpochAsync(); // epoch 1
const { poolId } = await rewardPoolMembersAsync();
const delegator = randomAddress();
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(0);
});
it('nothing in epoch 0 for delegator staked in epoch 0', async () => {
const { poolId } = await rewardPoolMembersAsync();
// Assign active stake to pool in epoch 0, which is usuaslly not
// possible due to delegating delays.
const { delegator } = await delegateStakeNowAsync(poolId);
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(0);
});
it('nothing in epoch 1 for delegator delegating in epoch 1', async () => {
await advanceEpochAsync(); // epoch 1
const { poolId } = await rewardPoolMembersAsync();
const { delegator } = await delegateStakeAsync(poolId);
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(0);
});
it('nothing in epoch 1 for delegator delegating in epoch 0', async () => {
const poolId = hexRandom();
const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 (stake now active)
// rewards paid for stake in epoch 0.
await rewardPoolMembersAsync({ poolId, stake });
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(0);
});
it('all rewards from epoch 2 for delegator delegating in epoch 0', async () => {
const poolId = hexRandom();
const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 (stake now active)
await advanceEpochAsync(); // epoch 2
// rewards paid for stake in epoch 1.
const { reward } = await rewardPoolMembersAsync({ poolId, stake });
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(reward);
});
it('all rewards from epoch 2 and 3 for delegator delegating in epoch 0', async () => {
const poolId = hexRandom();
const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 (stake now active)
await advanceEpochAsync(); // epoch 2
const { reward: reward1 } = await rewardPoolMembersAsync({ poolId, stake });
await advanceEpochAsync(); // epoch 3
const { reward: reward2 } = await rewardPoolMembersAsync({ poolId, stake });
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(BigNumber.sum(reward1, reward2));
});
it('partial rewards from epoch 2 and 3 for delegator partially delegating in epoch 0', async () => {
const poolId = hexRandom();
const { delegator, stake: delegatorStake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 (stake now active)
await advanceEpochAsync(); // epoch 2
// rewards paid for stake in epoch 1.
const { reward, stake: rewardStake } = await rewardPoolMembersAsync(
{ poolId, stake: new BigNumber(delegatorStake).times(2) },
);
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator);
const expectedDelegatorRewards = computeDelegatorRewards(reward, delegatorStake, rewardStake);
assertRoughlyEquals(delegatorReward, expectedDelegatorRewards);
});
it.only('has correct reward immediately after unstaking', async () => {
const poolId = hexRandom();
const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 (stake now active)
await advanceEpochAsync(); // epoch 2
// rewards paid for stake in epoch 1.
const { reward } = await rewardPoolMembersAsync(
{ poolId, stake },
);
await undelegateStakeAsync(poolId, delegator);
await advanceEpochAsync(); // epoch 3
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator);
expect(delegatorReward).to.bignumber.eq(reward);
});
it('computes correct rewards for 2 staggered delegators', async () => {
const poolId = hexRandom();
const { delegator: delegatorA, stake: stakeA } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 (stake A now active)
const { delegator: delegatorB, stake: stakeB } = await delegateStakeAsync(poolId);
const totalStake = BigNumber.sum(stakeA, stakeB);
await advanceEpochAsync(); // epoch 2 (stake B now active)
// rewards paid for stake in epoch 1 (delegator A only)
const { reward: reward1 } = await rewardPoolMembersAsync(
{ poolId, stake: stakeA },
);
await advanceEpochAsync(); // epoch 3
// rewards paid for stake in epoch 2 (delegator A and B)
const { reward: reward2 } = await rewardPoolMembersAsync(
{ poolId, stake: totalStake },
);
const delegatorRewardA = await getDelegatorRewardAsync(poolId, delegatorA);
const expectedDelegatorRewardA = BigNumber.sum(
computeDelegatorRewards(reward1, stakeA, stakeA),
computeDelegatorRewards(reward2, stakeA, totalStake),
);
assertRoughlyEquals(delegatorRewardA, expectedDelegatorRewardA);
const delegatorRewardB = await getDelegatorRewardAsync(poolId, delegatorB);
const expectedDelegatorRewardB = BigNumber.sum(
computeDelegatorRewards(reward2, stakeB, totalStake),
);
assertRoughlyEquals(delegatorRewardB, expectedDelegatorRewardB);
});
it('computes correct rewards for 2 staggered delegators with a 2 epoch gap between payments', async () => {
const poolId = hexRandom();
const { delegator: delegatorA, stake: stakeA } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 (stake A now active)
const { delegator: delegatorB, stake: stakeB } = await delegateStakeAsync(poolId);
const totalStake = BigNumber.sum(stakeA, stakeB);
await advanceEpochAsync(); // epoch 2 (stake B now active)
// rewards paid for stake in epoch 1 (delegator A only)
const { reward: reward1 } = await rewardPoolMembersAsync(
{ poolId, stake: stakeA },
);
await advanceEpochAsync(); // epoch 3
await advanceEpochAsync(); // epoch 4
// rewards paid for stake in epoch 3 (delegator A and B)
const { reward: reward2 } = await rewardPoolMembersAsync(
{ poolId, stake: totalStake },
);
const delegatorRewardA = await getDelegatorRewardAsync(poolId, delegatorA);
const expectedDelegatorRewardA = BigNumber.sum(
computeDelegatorRewards(reward1, stakeA, stakeA),
computeDelegatorRewards(reward2, stakeA, totalStake),
);
assertRoughlyEquals(delegatorRewardA, expectedDelegatorRewardA);
const delegatorRewardB = await getDelegatorRewardAsync(poolId, delegatorB);
const expectedDelegatorRewardB = BigNumber.sum(
computeDelegatorRewards(reward2, stakeB, totalStake),
);
assertRoughlyEquals(delegatorRewardB, expectedDelegatorRewardB);
});
it('correct rewards for rewards with different stakes', async () => {
const poolId = hexRandom();
const { delegator, stake: delegatorStake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 (stake now active)
await advanceEpochAsync(); // epoch 2
// rewards paid for stake in epoch 1.
const { reward: reward1, stake: rewardStake1 } = await rewardPoolMembersAsync(
{ poolId, stake: new BigNumber(delegatorStake).times(2) },
);
await advanceEpochAsync(); // epoch 3
// rewards paid for stake in epoch 2
const { reward: reward2, stake: rewardStake2 } = await rewardPoolMembersAsync(
{ poolId, stake: new BigNumber(delegatorStake).times(3) },
);
const delegatorReward = await getDelegatorRewardAsync(poolId, delegator);
const expectedDelegatorReward = BigNumber.sum(
computeDelegatorRewards(reward1, delegatorStake, rewardStake1),
computeDelegatorRewards(reward2, delegatorStake, rewardStake2),
);
assertRoughlyEquals(delegatorReward, expectedDelegatorReward);
});
describe('with unfinalized rewards', async () => {
it('nothing with only unfinalized rewards from epoch 1 for deleator with nothing delegated', async () => {
const poolId = hexRandom();
const { delegator, stake } = await delegateStakeAsync(poolId, { stake: 0 });
await advanceEpochAsync(); // epoch 1
await setUnfinalizedMembersRewardsAsync({ poolId, stake });
const reward = await getDelegatorRewardAsync(poolId, delegator);
expect(reward).to.bignumber.eq(0);
});
it('nothing with only unfinalized rewards from epoch 1 for deleator delegating in epoch 0', async () => {
const poolId = hexRandom();
const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1
await setUnfinalizedMembersRewardsAsync({ poolId, stake });
const reward = await getDelegatorRewardAsync(poolId, delegator);
expect(reward).to.bignumber.eq(0);
});
it('returns only unfinalized rewards from epoch 2 for delegator delegating in epoch 1', async () => {
const poolId = hexRandom();
const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1
await advanceEpochAsync(); // epoch 2
const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake });
const reward = await getDelegatorRewardAsync(poolId, delegator);
expect(reward).to.bignumber.eq(unfinalizedReward);
});
it('returns only unfinalized rewards from epoch 3 for delegator delegating in epoch 1', async () => {
const poolId = hexRandom();
const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1
await advanceEpochAsync(); // epoch 2
await advanceEpochAsync(); // epoch 3
const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake });
const reward = await getDelegatorRewardAsync(poolId, delegator);
expect(reward).to.bignumber.eq(unfinalizedReward);
});
it('returns unfinalized rewards from epoch 3 + rewards from epoch 2 for delegator delegating in epoch 1', async () => {
const poolId = hexRandom();
const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1
await advanceEpochAsync(); // epoch 2
const { reward: prevReward } = await rewardPoolMembersAsync({ poolId, stake });
await advanceEpochAsync(); // epoch 3
const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake });
const reward = await getDelegatorRewardAsync(poolId, delegator);
const expectedReward = BigNumber.sum(prevReward, unfinalizedReward);
expect(reward).to.bignumber.eq(expectedReward);
});
it('returns unfinalized rewards from epoch 4 + rewards from epoch 2 for delegator delegating in epoch 1', async () => {
const poolId = hexRandom();
const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1
await advanceEpochAsync(); // epoch 2
const { reward: prevReward } = await rewardPoolMembersAsync({ poolId, stake });
await advanceEpochAsync(); // epoch 3
await advanceEpochAsync(); // epoch 4
const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake });
const reward = await getDelegatorRewardAsync(poolId, delegator);
const expectedReward = BigNumber.sum(prevReward, unfinalizedReward);
expect(reward).to.bignumber.eq(expectedReward);
});
it('returns correct rewards if unfinalized stake is different from previous rewards', async () => {
const poolId = hexRandom();
const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1
await advanceEpochAsync(); // epoch 2
const { reward: prevReward, stake: prevStake } = await rewardPoolMembersAsync(
{ poolId, stake: new BigNumber(stake).times(2) },
);
await advanceEpochAsync(); // epoch 3
await advanceEpochAsync(); // epoch 4
const { reward: unfinalizedReward, stake: unfinalizedStake } =
await setUnfinalizedMembersRewardsAsync(
{ poolId, stake: new BigNumber(stake).times(5) },
);
const reward = await getDelegatorRewardAsync(poolId, delegator);
const expectedReward = BigNumber.sum(
computeDelegatorRewards(prevReward, stake, prevStake),
computeDelegatorRewards(unfinalizedReward, stake, unfinalizedStake),
);
assertRoughlyEquals(reward, expectedReward);
});
});
});
describe('reward transfers', async () => {
it('transfers all rewards to eth vault when touching stake', async () => {
const poolId = hexRandom();
const { delegator, stake } = await delegateStakeAsync(poolId);
await advanceEpochAsync(); // epoch 1 (stake now active)
await advanceEpochAsync(); // epoch 2
// rewards paid for stake in epoch 1
const { reward } = await rewardPoolMembersAsync({ poolId, stake });
const { deposit } = await touchStakeAsync(poolId, delegator);
expect(deposit).to.bignumber.eq(reward);
});
it('does not collect extra rewards from delegating more stake in the reward epoch', async () => {
const poolId = hexRandom();
const stakeResults = [];
// stake
stakeResults.push(await delegateStakeAsync(poolId));
const { delegator, stake } = stakeResults[0];
const totalStake = new BigNumber(stake).times(2);
await advanceEpochAsync(); // epoch 1 (stake now active)
// add more stake.
stakeResults.push(await delegateStakeAsync(poolId, { delegator, stake }));
await advanceEpochAsync(); // epoch 1 (2 * stake now active)
// reward for epoch 1, using 2 * stake so delegator should
// only be entitled to a fraction of the rewards.
const { reward } = await rewardPoolMembersAsync({ poolId, stake: totalStake });
await advanceEpochAsync(); // epoch 2
// touch the stake one last time
stakeResults.push(await touchStakeAsync(poolId, delegator));
// Should only see deposits for epoch 2.
const expectedDeposit = computeDelegatorRewards(reward, stake, totalStake);
const allDeposits = stakeResults.map(r => r.deposit);
assertRoughlyEquals(BigNumber.sum(...allDeposits), expectedDeposit);
});
it('only collects rewards from staked epochs', async () => {
const poolId = hexRandom();
const stakeResults = [];
// stake
stakeResults.push(await delegateStakeAsync(poolId));
const { delegator, stake } = stakeResults[0];
await advanceEpochAsync(); // epoch 1 (stake now active)
// unstake before and after reward payout, to be extra sneaky.
const unstake1 = new BigNumber(stake).dividedToIntegerBy(2);
stakeResults.push(await undelegateStakeAsync(poolId, delegator, unstake1));
// reward for epoch 0
await rewardPoolMembersAsync({ poolId, stake });
const unstake2 = new BigNumber(stake).minus(unstake1);
stakeResults.push(await undelegateStakeAsync(poolId, delegator, unstake2));
await advanceEpochAsync(); // epoch 2 (no active stake)
// reward for epoch 1
const { reward } = await rewardPoolMembersAsync({ poolId, stake });
// re-stake
stakeResults.push(await delegateStakeAsync(poolId, { delegator, stake }));
await advanceEpochAsync(); // epoch 3 (stake now active)
// reward for epoch 2
await rewardPoolMembersAsync({ poolId, stake });
// touch the stake one last time
stakeResults.push(await touchStakeAsync(poolId, delegator));
// Should only see deposits for epoch 2.
const allDeposits = stakeResults.map(r => r.deposit);
assertRoughlyEquals(BigNumber.sum(...allDeposits), reward);
});
it('delegator B collects correct rewards after delegator A finalizes', 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: prevReward } = await rewardPoolMembersAsync({ poolId, stake: totalStake });
await advanceEpochAsync(); // epoch 3
// unfinalized rewards for stake in epoch 2
const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake: totalStake });
const totalRewards = BigNumber.sum(prevReward, unfinalizedReward);
// delegator A will finalize and collect rewards by touching stake.
const { deposit: depositA } = await touchStakeAsync(poolId, delegatorA);
assertRoughlyEquals(depositA, computeDelegatorRewards(totalRewards, stakeA, totalStake));
// delegator B will collect rewards by touching stake
const { deposit: depositB } = await touchStakeAsync(poolId, delegatorB);
assertRoughlyEquals(depositB, computeDelegatorRewards(totalRewards, stakeB, totalStake));
});
it('delegator A and B collect correct rewards after external finalization', 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: prevReward } = await rewardPoolMembersAsync({ poolId, stake: totalStake });
await advanceEpochAsync(); // epoch 3
// unfinalized rewards for stake in epoch 2
const { reward: unfinalizedReward } = await setUnfinalizedMembersRewardsAsync({ poolId, stake: totalStake });
const totalRewards = BigNumber.sum(prevReward, unfinalizedReward);
// finalize
await finalizePoolAsync(poolId);
// delegator A will collect rewards by touching stake.
const { deposit: depositA } = await touchStakeAsync(poolId, delegatorA);
assertRoughlyEquals(depositA, computeDelegatorRewards(totalRewards, stakeA, totalStake));
// delegator B will collect rewards by touching stake
const { deposit: depositB } = await touchStakeAsync(poolId, delegatorB);
assertRoughlyEquals(depositB, computeDelegatorRewards(totalRewards, stakeB, totalStake));
});
});
});
// tslint:disable: max-file-line-count

View File

@ -23,16 +23,14 @@ import {
} from '../../src';
import { getRandomInteger, toBaseUnitAmount } from '../utils/number_utils';
blockchainTests.resets('finalizer tests', env => {
blockchainTests.resets('finalizer unit tests', env => {
const { ZERO_AMOUNT } = constants;
const INITIAL_EPOCH = 0;
const INITIAL_BALANCE = toBaseUnitAmount(32);
let senderAddress: string;
let rewardReceiverAddress: string;
let testContract: TestFinalizerContract;
before(async () => {
[senderAddress] = await env.getAccountAddressesAsync();
rewardReceiverAddress = hexRandom(constants.ADDRESS_LENGTH);
testContract = await TestFinalizerContract.deployFrom0xArtifactAsync(
artifacts.TestFinalizer,
@ -48,7 +46,7 @@ blockchainTests.resets('finalizer tests', env => {
async function sendEtherAsync(to: string, amount: Numberish): Promise<void> {
await env.web3Wrapper.awaitTransactionSuccessAsync(
await env.web3Wrapper.sendTransactionAsync({
from: senderAddress,
from: (await env.getAccountAddressesAsync())[0],
to,
value: new BigNumber(amount),
}),

View File

@ -121,7 +121,7 @@ export interface RewardByPoolId {
[key: string]: BigNumber;
}
export interface MemberBalancesByPoolId {
export interface DelegatorBalancesByPoolId {
[key: string]: BalanceByOwner;
}
@ -129,6 +129,6 @@ export interface OperatorByPoolId {
[key: string]: string;
}
export interface MembersByPoolId {
export interface DelegatorsByPoolId {
[key: string]: string[];
}

View File

@ -57,6 +57,9 @@ library LibFractions {
pure
returns (uint256 result)
{
if (s == 0) {
return 0;
}
if (n2 == 0) {
return result = s
.safeMul(n1)