Tests for new staking mechanics
This commit is contained in:
@@ -229,7 +229,7 @@ contract MixinExchangeFees is
|
||||
|
||||
// sanity check - this is a gas optimization that can be used because we assume a non-zero
|
||||
// split between stake and fees generated in the cobb-douglas computation (see below).
|
||||
if (totalFeesCollected == 0 || totalWeightedStake == 0) {
|
||||
if (totalFeesCollected == 0) {
|
||||
return (
|
||||
totalActivePools,
|
||||
totalFeesCollected,
|
||||
@@ -247,8 +247,8 @@ contract MixinExchangeFees is
|
||||
initialContractBalance,
|
||||
activePools[i].feesCollected,
|
||||
totalFeesCollected,
|
||||
activePools[i].weightedStake,
|
||||
totalWeightedStake,
|
||||
activePools[i].weightedStake != 0 ? activePools[i].weightedStake : 1,
|
||||
totalWeightedStake != 0 ? totalWeightedStake : 1,
|
||||
cobbDouglasAlphaNumerator,
|
||||
cobbDouglasAlphaDenomintor
|
||||
);
|
||||
|
@@ -165,4 +165,12 @@ interface IStakingPoolRewardVault {
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
|
||||
/// @dev Returns the operator share of a pool's balance.
|
||||
/// @param poolId Unique Id of pool.
|
||||
/// @return Operator share (integer out of 100)
|
||||
function getOperatorShare(bytes32 poolId)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
}
|
||||
|
@@ -237,6 +237,17 @@ contract StakingPoolRewardVault is
|
||||
return balanceByPoolId[poolId].membersBalance;
|
||||
}
|
||||
|
||||
/// @dev Returns the operator share of a pool's balance.
|
||||
/// @param poolId Unique Id of pool.
|
||||
/// @return Operator share (integer out of 100)
|
||||
function getOperatorShare(bytes32 poolId)
|
||||
external
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return balanceByPoolId[poolId].operatorShare;
|
||||
}
|
||||
|
||||
/// @dev Increments a balance struct, splitting the input amount between the
|
||||
/// pool operator and members of the pool based on the pool operator's share.
|
||||
/// @param balance Balance struct to increment.
|
||||
|
@@ -1,160 +0,0 @@
|
||||
import { expect } from '@0x/contracts-test-utils';
|
||||
import { BigNumber, RevertError } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { StakingWrapper } from '../utils/staking_wrapper';
|
||||
import { DelegatorBalances, StakerBalances } from '../utils/types';
|
||||
|
||||
import { StakerActor } from './staker_actor';
|
||||
|
||||
export class DelegatorActor extends StakerActor {
|
||||
/**
|
||||
constructor(owner: string, stakingWrapper: StakingWrapper) {
|
||||
super(owner, stakingWrapper);
|
||||
}
|
||||
public async depositZrxAndDelegateToStakingPoolAsync(
|
||||
poolId: string,
|
||||
amount: BigNumber,
|
||||
revertError?: RevertError,
|
||||
): Promise<void> {
|
||||
// query init balances
|
||||
const initZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
||||
const initDelegatorBalances = await this.getBalancesAsync([poolId]);
|
||||
// deposit stake
|
||||
const txReceiptPromise = this._stakingWrapper.depositZrxAndDelegateToStakingPoolAsync(
|
||||
this._owner,
|
||||
poolId,
|
||||
amount,
|
||||
);
|
||||
if (revertError !== undefined) {
|
||||
await expect(txReceiptPromise).to.revertWith(revertError);
|
||||
return;
|
||||
}
|
||||
await txReceiptPromise;
|
||||
// @TODO check receipt logs and return value via eth_call
|
||||
// check balances
|
||||
const expectedDelegatorBalances = initDelegatorBalances;
|
||||
expectedDelegatorBalances.zrxBalance = initDelegatorBalances.zrxBalance.minus(amount);
|
||||
expectedDelegatorBalances.stakeBalance = initDelegatorBalances.stakeBalance.plus(amount);
|
||||
expectedDelegatorBalances.stakeBalanceInVault = initDelegatorBalances.stakeBalanceInVault.plus(amount);
|
||||
expectedDelegatorBalances.activatedStakeBalance = initDelegatorBalances.activatedStakeBalance.plus(amount);
|
||||
expectedDelegatorBalances.delegatedStakeBalance = initDelegatorBalances.delegatedStakeBalance.plus(amount);
|
||||
expectedDelegatorBalances.stakeDelegatedToPoolByOwner[0] = initDelegatorBalances.stakeDelegatedToPoolByOwner[0].plus(
|
||||
amount,
|
||||
);
|
||||
expectedDelegatorBalances.stakeDelegatedToPool[0] = initDelegatorBalances.stakeDelegatedToPool[0].plus(amount);
|
||||
await this.assertBalancesAsync(expectedDelegatorBalances, [poolId]);
|
||||
// check zrx balance of vault
|
||||
const finalZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
||||
expect(finalZrxBalanceOfVault).to.be.bignumber.equal(initZrxBalanceOfVault.plus(amount));
|
||||
}
|
||||
public async activateAndDelegateStakeAsync(
|
||||
poolId: string,
|
||||
amount: BigNumber,
|
||||
revertError?: RevertError,
|
||||
): Promise<void> {
|
||||
// query init balances
|
||||
const initDelegatorBalances = await this.getBalancesAsync([poolId]);
|
||||
// activate and delegate
|
||||
const txReceiptPromise = this._stakingWrapper.activateAndDelegateStakeAsync(this._owner, poolId, amount);
|
||||
if (revertError !== undefined) {
|
||||
await expect(txReceiptPromise).to.revertWith(revertError);
|
||||
return;
|
||||
}
|
||||
await txReceiptPromise;
|
||||
// @TODO check receipt logs and return value via eth_call
|
||||
// check balances
|
||||
// check balances
|
||||
const expectedDelegatorBalances = initDelegatorBalances;
|
||||
expectedDelegatorBalances.activatedStakeBalance = initDelegatorBalances.activatedStakeBalance.plus(amount);
|
||||
expectedDelegatorBalances.withdrawableStakeBalance = expectedDelegatorBalances.withdrawableStakeBalance.minus(
|
||||
amount,
|
||||
);
|
||||
expectedDelegatorBalances.activatableStakeBalance = expectedDelegatorBalances.activatableStakeBalance.minus(
|
||||
amount,
|
||||
);
|
||||
expectedDelegatorBalances.deactivatedStakeBalance = expectedDelegatorBalances.deactivatedStakeBalance.minus(
|
||||
amount,
|
||||
);
|
||||
expectedDelegatorBalances.delegatedStakeBalance = initDelegatorBalances.delegatedStakeBalance.plus(amount);
|
||||
expectedDelegatorBalances.stakeDelegatedToPoolByOwner[0] = initDelegatorBalances.stakeDelegatedToPoolByOwner[0].plus(
|
||||
amount,
|
||||
);
|
||||
expectedDelegatorBalances.stakeDelegatedToPool[0] = initDelegatorBalances.stakeDelegatedToPool[0].plus(amount);
|
||||
await this.assertBalancesAsync(expectedDelegatorBalances, [poolId]);
|
||||
}
|
||||
public async deactivateAndTimeLockDelegatedStakeAsync(
|
||||
poolId: string,
|
||||
amount: BigNumber,
|
||||
revertError?: RevertError,
|
||||
): Promise<void> {
|
||||
// query init balances
|
||||
const initDelegatorBalances = await this.getBalancesAsync([poolId]);
|
||||
// deactivate and timeLock
|
||||
const txReceiptPromise = this._stakingWrapper.deactivateAndTimeLockDelegatedStakeAsync(
|
||||
this._owner,
|
||||
poolId,
|
||||
amount,
|
||||
);
|
||||
if (revertError !== undefined) {
|
||||
await expect(txReceiptPromise).to.revertWith(revertError);
|
||||
return;
|
||||
}
|
||||
await txReceiptPromise;
|
||||
// @TODO check receipt logs and return value via eth_call
|
||||
// check balances
|
||||
const expectedDelegatorBalances = initDelegatorBalances;
|
||||
expectedDelegatorBalances.activatedStakeBalance = initDelegatorBalances.activatedStakeBalance.minus(amount);
|
||||
expectedDelegatorBalances.timeLockedStakeBalance = expectedDelegatorBalances.timeLockedStakeBalance.plus(
|
||||
amount,
|
||||
);
|
||||
expectedDelegatorBalances.deactivatedStakeBalance = expectedDelegatorBalances.deactivatedStakeBalance.plus(
|
||||
amount,
|
||||
);
|
||||
expectedDelegatorBalances.delegatedStakeBalance = initDelegatorBalances.delegatedStakeBalance.minus(amount);
|
||||
expectedDelegatorBalances.stakeDelegatedToPoolByOwner[0] = initDelegatorBalances.stakeDelegatedToPoolByOwner[0].minus(
|
||||
amount,
|
||||
);
|
||||
expectedDelegatorBalances.stakeDelegatedToPool[0] = initDelegatorBalances.stakeDelegatedToPool[0].minus(amount);
|
||||
await this.assertBalancesAsync(expectedDelegatorBalances, [poolId]);
|
||||
}
|
||||
public async getBalancesAsync(maybePoolIds?: string[]): Promise<DelegatorBalances> {
|
||||
const stakerBalances = await super.getBalancesAsync();
|
||||
const delegatorBalances = {
|
||||
...stakerBalances,
|
||||
delegatedStakeBalance: await this._stakingWrapper.getStakeDelegatedByOwnerAsync(this._owner),
|
||||
stakeDelegatedToPoolByOwner: Array(),
|
||||
stakeDelegatedToPool: Array(),
|
||||
};
|
||||
const poolIds = maybePoolIds !== undefined ? maybePoolIds : [];
|
||||
for (const poolId of poolIds) {
|
||||
const stakeDelegatedToPoolByOwner = await this._stakingWrapper.getStakeDelegatedToPoolByOwnerAsync(
|
||||
poolId,
|
||||
this._owner,
|
||||
);
|
||||
delegatorBalances.stakeDelegatedToPoolByOwner.push(stakeDelegatedToPoolByOwner);
|
||||
const stakeDelegatedToPool = await this._stakingWrapper.getTotalStakeDelegatedToPoolAsync(poolId);
|
||||
delegatorBalances.stakeDelegatedToPool.push(stakeDelegatedToPool);
|
||||
}
|
||||
return delegatorBalances;
|
||||
}
|
||||
public async assertBalancesAsync(expectedBalances: DelegatorBalances, maybePoolIds?: string[]): Promise<void> {
|
||||
await super.assertBalancesAsync(expectedBalances);
|
||||
const balances = await this.getBalancesAsync(maybePoolIds);
|
||||
expect(balances.delegatedStakeBalance, 'delegated stake balance').to.be.bignumber.equal(
|
||||
expectedBalances.delegatedStakeBalance,
|
||||
);
|
||||
const poolIds = maybePoolIds !== undefined ? maybePoolIds : [];
|
||||
for (let i = 0; i < poolIds.length; i++) {
|
||||
expect(
|
||||
balances.stakeDelegatedToPoolByOwner[i],
|
||||
`stake delegated to pool ${poolIds[i]} by owner`,
|
||||
).to.be.bignumber.equal(expectedBalances.stakeDelegatedToPoolByOwner[i]);
|
||||
expect(
|
||||
balances.stakeDelegatedToPool[i],
|
||||
`total stake delegated to pool ${poolIds[i]}`,
|
||||
).to.be.bignumber.equal(expectedBalances.stakeDelegatedToPool[i]);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
186
contracts/staking/test/actors/finalizer_actor.ts
Normal file
186
contracts/staking/test/actors/finalizer_actor.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import { expect } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { StakingWrapper } from '../utils/staking_wrapper';
|
||||
import {
|
||||
MemberBalancesByPoolId,
|
||||
MembersByPoolId,
|
||||
OperatorByPoolId,
|
||||
OperatorShareByPoolId,
|
||||
RewardVaultBalance,
|
||||
RewardVaultBalanceByPoolId,
|
||||
} from '../utils/types';
|
||||
|
||||
import { BaseActor } from './base_actor';
|
||||
|
||||
interface Reward {
|
||||
reward: BigNumber;
|
||||
poolId: string;
|
||||
}
|
||||
|
||||
export class FinalizerActor extends BaseActor {
|
||||
private readonly _poolIds: string[];
|
||||
// @TODO (hysz): this will be used later to liquidate the reward vault.
|
||||
// tslint:disable-next-line no-unused-variable
|
||||
private readonly _operatorByPoolId: OperatorByPoolId;
|
||||
private readonly _membersByPoolId: MembersByPoolId;
|
||||
|
||||
constructor(
|
||||
owner: string,
|
||||
stakingWrapper: StakingWrapper,
|
||||
poolIds: string[],
|
||||
operatorByPoolId: OperatorByPoolId,
|
||||
membersByPoolId: MembersByPoolId,
|
||||
) {
|
||||
super(owner, stakingWrapper);
|
||||
this._poolIds = _.cloneDeep(poolIds);
|
||||
this._operatorByPoolId = _.cloneDeep(operatorByPoolId);
|
||||
this._membersByPoolId = _.cloneDeep(membersByPoolId);
|
||||
}
|
||||
|
||||
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);
|
||||
// compute expecnted changes
|
||||
const expectedRewardVaultBalanceByPoolId = await this._computeExpectedRewardVaultBalanceAsyncByPoolIdAsync(
|
||||
rewards,
|
||||
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,
|
||||
);
|
||||
// finalize
|
||||
await this._stakingWrapper.skipToNextEpochAsync();
|
||||
// assert reward vault changes
|
||||
const finalRewardVaultBalanceByPoolId = await this._getRewardVaultBalanceByPoolIdAsync(this._poolIds);
|
||||
expect(finalRewardVaultBalanceByPoolId).to.be.deep.equal(expectedRewardVaultBalanceByPoolId);
|
||||
// assert member balances
|
||||
const finalMemberBalancesByPoolId = await this._getMemberBalancesByPoolIdAsync(this._membersByPoolId);
|
||||
expect(finalMemberBalancesByPoolId).to.be.deep.equal(expectedMemberBalancesByPoolId);
|
||||
}
|
||||
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
const totalStakeDelegatedToPool = (await this._stakingWrapper.getTotalStakeDelegatedToPoolAsync(poolId))
|
||||
.current;
|
||||
for (const member of membersByPoolId[poolId]) {
|
||||
if (totalStakeDelegatedToPool.eq(0)) {
|
||||
expectedMemberBalancesByPoolId[poolId][member] = new BigNumber(0);
|
||||
} else {
|
||||
const stakeDelegatedToPoolByMember = (await this._stakingWrapper.getStakeDelegatedToPoolByOwnerAsync(
|
||||
poolId,
|
||||
member,
|
||||
)).current;
|
||||
const rewardThisEpoch = rewardByPoolId[poolId]
|
||||
.times(stakeDelegatedToPoolByMember)
|
||||
.dividedToIntegerBy(totalStakeDelegatedToPool);
|
||||
expectedMemberBalancesByPoolId[poolId][member] =
|
||||
memberBalancesByPoolId[poolId][member] === undefined
|
||||
? rewardThisEpoch
|
||||
: memberBalancesByPoolId[poolId][member].plus(rewardThisEpoch);
|
||||
}
|
||||
}
|
||||
}
|
||||
return expectedMemberBalancesByPoolId;
|
||||
}
|
||||
|
||||
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._stakingWrapper.computeRewardBalanceOfStakingPoolMemberAsync(poolId, member);
|
||||
}
|
||||
}
|
||||
return memberBalancesByPoolId;
|
||||
}
|
||||
|
||||
private async _computeExpectedRewardVaultBalanceAsyncByPoolIdAsync(
|
||||
rewards: Reward[],
|
||||
rewardVaultBalanceByPoolId: RewardVaultBalanceByPoolId,
|
||||
operatorShareByPoolId: OperatorShareByPoolId,
|
||||
): Promise<RewardVaultBalanceByPoolId> {
|
||||
const expectedRewardVaultBalanceByPoolId = _.cloneDeep(rewardVaultBalanceByPoolId);
|
||||
for (const reward of rewards) {
|
||||
const operatorShare = operatorShareByPoolId[reward.poolId];
|
||||
expectedRewardVaultBalanceByPoolId[reward.poolId] = await this._computeExpectedRewardVaultBalanceAsync(
|
||||
reward.poolId,
|
||||
reward.reward,
|
||||
expectedRewardVaultBalanceByPoolId[reward.poolId],
|
||||
operatorShare,
|
||||
);
|
||||
}
|
||||
return expectedRewardVaultBalanceByPoolId;
|
||||
}
|
||||
|
||||
private async _computeExpectedRewardVaultBalanceAsync(
|
||||
poolId: string,
|
||||
reward: BigNumber,
|
||||
rewardVaultBalance: RewardVaultBalance,
|
||||
operatorShare: BigNumber,
|
||||
): Promise<RewardVaultBalance> {
|
||||
const totalStakeDelegatedToPool = (await this._stakingWrapper.getTotalStakeDelegatedToPoolAsync(poolId))
|
||||
.current;
|
||||
const operatorPortion = totalStakeDelegatedToPool.eq(0)
|
||||
? reward
|
||||
: reward.times(operatorShare).dividedToIntegerBy(100);
|
||||
const membersPortion = reward.minus(operatorPortion);
|
||||
return {
|
||||
poolBalance: rewardVaultBalance.poolBalance.plus(reward),
|
||||
operatorBalance: rewardVaultBalance.operatorBalance.plus(operatorPortion),
|
||||
membersBalance: rewardVaultBalance.membersBalance.plus(membersPortion),
|
||||
};
|
||||
}
|
||||
|
||||
private async _getOperatorShareByPoolIdAsync(poolIds: string[]): Promise<OperatorShareByPoolId> {
|
||||
const operatorShareByPoolId: OperatorShareByPoolId = {};
|
||||
for (const poolId of poolIds) {
|
||||
const operatorShare = await this._stakingWrapper
|
||||
.getStakingPoolRewardVaultContract()
|
||||
.getOperatorShare.callAsync(poolId);
|
||||
operatorShareByPoolId[poolId] = operatorShare;
|
||||
}
|
||||
return operatorShareByPoolId;
|
||||
}
|
||||
|
||||
private async _getRewardVaultBalanceByPoolIdAsync(poolIds: string[]): Promise<RewardVaultBalanceByPoolId> {
|
||||
const rewardVaultBalanceByPoolId: RewardVaultBalanceByPoolId = {};
|
||||
for (const poolId of poolIds) {
|
||||
rewardVaultBalanceByPoolId[poolId] = await this._getRewardVaultBalanceAsync(poolId);
|
||||
}
|
||||
return rewardVaultBalanceByPoolId;
|
||||
}
|
||||
|
||||
private async _getRewardVaultBalanceAsync(poolId: string): Promise<RewardVaultBalance> {
|
||||
const balances = await Promise.all([
|
||||
this._stakingWrapper.rewardVaultBalanceOfAsync(poolId),
|
||||
this._stakingWrapper.rewardVaultBalanceOfOperatorAsync(poolId),
|
||||
this._stakingWrapper.rewardVaultBalanceOfMembersAsync(poolId),
|
||||
]);
|
||||
return {
|
||||
poolBalance: balances[0],
|
||||
operatorBalance: balances[1],
|
||||
membersBalance: balances[2],
|
||||
};
|
||||
}
|
||||
}
|
@@ -3,25 +3,23 @@ import { BigNumber, RevertError } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { StakingWrapper } from '../utils/staking_wrapper';
|
||||
import { StakerBalances } from '../utils/types';
|
||||
import { StakeBalances, StakeState, StakeStateInfo } from '../utils/types';
|
||||
|
||||
import { BaseActor } from './base_actor';
|
||||
|
||||
export class StakerActor extends BaseActor {
|
||||
/*
|
||||
private readonly _poolIds: string[];
|
||||
|
||||
constructor(owner: string, stakingWrapper: StakingWrapper) {
|
||||
super(owner, stakingWrapper);
|
||||
this._poolIds = [];
|
||||
}
|
||||
public async depositZrxAndMintDeactivatedStakeAsync(amount: BigNumber, revertError?: RevertError): Promise<void> {
|
||||
await this._stakingWrapper.depositZrxAndMintDeactivatedStakeAsync(this._owner, amount);
|
||||
throw new Error('Checks Unimplemented');
|
||||
}
|
||||
public async depositZrxAndMintActivatedStakeAsync(amount: BigNumber, revertError?: RevertError): Promise<void> {
|
||||
// query init balances
|
||||
|
||||
public async stakeAsync(amount: BigNumber, revertError?: RevertError): Promise<void> {
|
||||
const initZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
||||
const initStakerBalances = await this.getBalancesAsync();
|
||||
// deposit stake
|
||||
const txReceiptPromise = this._stakingWrapper.depositZrxAndMintActivatedStakeAsync(this._owner, amount);
|
||||
const txReceiptPromise = this._stakingWrapper.stakeAsync(this._owner, amount);
|
||||
if (revertError !== undefined) {
|
||||
await expect(txReceiptPromise).to.revertWith(revertError);
|
||||
return;
|
||||
@@ -31,57 +29,20 @@ export class StakerActor extends BaseActor {
|
||||
// check balances
|
||||
const expectedStakerBalances = initStakerBalances;
|
||||
expectedStakerBalances.zrxBalance = initStakerBalances.zrxBalance.minus(amount);
|
||||
expectedStakerBalances.stakeBalance = initStakerBalances.stakeBalance.plus(amount);
|
||||
expectedStakerBalances.stakeBalanceInVault = initStakerBalances.stakeBalanceInVault.plus(amount);
|
||||
expectedStakerBalances.activatedStakeBalance = initStakerBalances.activatedStakeBalance.plus(amount);
|
||||
expectedStakerBalances.activeStakeBalance.current = initStakerBalances.activeStakeBalance.current.plus(amount);
|
||||
expectedStakerBalances.activeStakeBalance.next = initStakerBalances.activeStakeBalance.next.plus(amount);
|
||||
await this.assertBalancesAsync(expectedStakerBalances);
|
||||
// check zrx balance of vault
|
||||
const finalZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
||||
expect(finalZrxBalanceOfVault).to.be.bignumber.equal(initZrxBalanceOfVault.plus(amount));
|
||||
}
|
||||
public async activateStakeAsync(amount: BigNumber, revertError?: RevertError): Promise<void> {
|
||||
// query init balances
|
||||
const initStakerBalances = await this.getBalancesAsync();
|
||||
// activate stake
|
||||
const txReceiptPromise = this._stakingWrapper.activateStakeAsync(this._owner, amount);
|
||||
if (revertError !== undefined) {
|
||||
await expect(txReceiptPromise).to.revertWith(revertError);
|
||||
return;
|
||||
}
|
||||
await txReceiptPromise;
|
||||
// @TODO check receipt logs and return value via eth_call
|
||||
// check balances
|
||||
const expectedStakerBalances = initStakerBalances;
|
||||
expectedStakerBalances.withdrawableStakeBalance = initStakerBalances.withdrawableStakeBalance.minus(amount);
|
||||
expectedStakerBalances.activatableStakeBalance = initStakerBalances.activatableStakeBalance.minus(amount);
|
||||
expectedStakerBalances.activatedStakeBalance = initStakerBalances.activatedStakeBalance.plus(amount);
|
||||
expectedStakerBalances.deactivatedStakeBalance = initStakerBalances.deactivatedStakeBalance.minus(amount);
|
||||
await this.assertBalancesAsync(expectedStakerBalances);
|
||||
}
|
||||
public async deactivateAndTimeLockStakeAsync(amount: BigNumber, revertError?: RevertError): Promise<void> {
|
||||
// query init balances
|
||||
const initStakerBalances = await this.getBalancesAsync();
|
||||
// deactivate and timeLock stake
|
||||
const txReceiptPromise = this._stakingWrapper.deactivateAndTimeLockStakeAsync(this._owner, amount);
|
||||
if (revertError !== undefined) {
|
||||
await expect(txReceiptPromise).to.revertWith(revertError);
|
||||
return;
|
||||
}
|
||||
await txReceiptPromise;
|
||||
// @TODO check receipt logs and return value via eth_call
|
||||
// check balances
|
||||
const expectedStakerBalances = initStakerBalances;
|
||||
expectedStakerBalances.activatedStakeBalance = initStakerBalances.activatedStakeBalance.minus(amount);
|
||||
expectedStakerBalances.timeLockedStakeBalance = initStakerBalances.timeLockedStakeBalance.plus(amount);
|
||||
expectedStakerBalances.deactivatedStakeBalance = initStakerBalances.deactivatedStakeBalance.plus(amount);
|
||||
await this.assertBalancesAsync(expectedStakerBalances);
|
||||
}
|
||||
public async burnDeactivatedStakeAndWithdrawZrxAsync(amount: BigNumber, revertError?: RevertError): Promise<void> {
|
||||
// query init balances
|
||||
|
||||
public async unstakeAsync(amount: BigNumber, revertError?: RevertError): Promise<void> {
|
||||
const initZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
||||
const initStakerBalances = await this.getBalancesAsync();
|
||||
// withdraw stake
|
||||
const txReceiptPromise = this._stakingWrapper.burnDeactivatedStakeAndWithdrawZrxAsync(this._owner, amount);
|
||||
// deposit stake
|
||||
const txReceiptPromise = this._stakingWrapper.unstakeAsync(this._owner, amount);
|
||||
if (revertError !== undefined) {
|
||||
await expect(txReceiptPromise).to.revertWith(revertError);
|
||||
return;
|
||||
@@ -91,50 +52,180 @@ export class StakerActor extends BaseActor {
|
||||
// check balances
|
||||
const expectedStakerBalances = initStakerBalances;
|
||||
expectedStakerBalances.zrxBalance = initStakerBalances.zrxBalance.plus(amount);
|
||||
expectedStakerBalances.stakeBalance = initStakerBalances.stakeBalance.minus(amount);
|
||||
expectedStakerBalances.stakeBalanceInVault = initStakerBalances.stakeBalanceInVault.minus(amount);
|
||||
expectedStakerBalances.inactiveStakeBalance.next = initStakerBalances.inactiveStakeBalance.next.minus(amount);
|
||||
expectedStakerBalances.inactiveStakeBalance.current = initStakerBalances.inactiveStakeBalance.current.minus(
|
||||
amount,
|
||||
);
|
||||
expectedStakerBalances.withdrawableStakeBalance = initStakerBalances.withdrawableStakeBalance.minus(amount);
|
||||
expectedStakerBalances.activatableStakeBalance = initStakerBalances.activatableStakeBalance.minus(amount);
|
||||
expectedStakerBalances.deactivatedStakeBalance = initStakerBalances.deactivatedStakeBalance.minus(amount);
|
||||
await this.assertBalancesAsync(expectedStakerBalances);
|
||||
// check zrx balance of vault
|
||||
const finalZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
||||
expect(finalZrxBalanceOfVault).to.be.bignumber.equal(initZrxBalanceOfVault.minus(amount));
|
||||
}
|
||||
public async getBalancesAsync(): Promise<StakerBalances> {
|
||||
const stakerBalances = {
|
||||
|
||||
public async moveStakeAsync(
|
||||
from: StakeStateInfo,
|
||||
to: StakeStateInfo,
|
||||
amount: BigNumber,
|
||||
revertError?: RevertError,
|
||||
): Promise<void> {
|
||||
// check if we're moving stake into a new pool
|
||||
if (to.state === StakeState.Delegated && to.poolId !== undefined && !_.includes(this._poolIds, to.poolId)) {
|
||||
this._poolIds.push(to.poolId);
|
||||
}
|
||||
// cache balances
|
||||
const initZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
||||
const initStakerBalances = await this.getBalancesAsync();
|
||||
// @TODO check receipt logs and return value via eth_call
|
||||
// check balances
|
||||
const expectedStakerBalances = initStakerBalances;
|
||||
// from
|
||||
if (from.state === StakeState.Active) {
|
||||
expectedStakerBalances.activeStakeBalance.next = initStakerBalances.activeStakeBalance.next.minus(amount);
|
||||
} else if (from.state === StakeState.Inactive) {
|
||||
expectedStakerBalances.inactiveStakeBalance.next = initStakerBalances.inactiveStakeBalance.next.minus(
|
||||
amount,
|
||||
);
|
||||
if (
|
||||
expectedStakerBalances.inactiveStakeBalance.next.isLessThan(
|
||||
expectedStakerBalances.withdrawableStakeBalance,
|
||||
)
|
||||
) {
|
||||
expectedStakerBalances.withdrawableStakeBalance = expectedStakerBalances.inactiveStakeBalance.next;
|
||||
}
|
||||
} else if (from.state === StakeState.Delegated && from.poolId !== undefined) {
|
||||
expectedStakerBalances.delegatedStakeBalance.next = initStakerBalances.delegatedStakeBalance.next.minus(
|
||||
amount,
|
||||
);
|
||||
expectedStakerBalances.delegatedStakeByPool[from.poolId].next = initStakerBalances.delegatedStakeByPool[
|
||||
from.poolId
|
||||
].next.minus(amount);
|
||||
expectedStakerBalances.totalDelegatedStakeByPool[
|
||||
from.poolId
|
||||
].next = initStakerBalances.totalDelegatedStakeByPool[from.poolId].next.minus(amount);
|
||||
}
|
||||
// to
|
||||
if (to.state === StakeState.Active) {
|
||||
expectedStakerBalances.activeStakeBalance.next = initStakerBalances.activeStakeBalance.next.plus(amount);
|
||||
} else if (to.state === StakeState.Inactive) {
|
||||
expectedStakerBalances.inactiveStakeBalance.next = initStakerBalances.inactiveStakeBalance.next.plus(
|
||||
amount,
|
||||
);
|
||||
} else if (to.state === StakeState.Delegated && to.poolId !== undefined) {
|
||||
expectedStakerBalances.delegatedStakeBalance.next = initStakerBalances.delegatedStakeBalance.next.plus(
|
||||
amount,
|
||||
);
|
||||
expectedStakerBalances.delegatedStakeByPool[to.poolId].next = initStakerBalances.delegatedStakeByPool[
|
||||
to.poolId
|
||||
].next.plus(amount);
|
||||
expectedStakerBalances.totalDelegatedStakeByPool[
|
||||
to.poolId
|
||||
].next = initStakerBalances.totalDelegatedStakeByPool[to.poolId].next.plus(amount);
|
||||
}
|
||||
// move stake
|
||||
const txReceiptPromise = this._stakingWrapper.moveStakeAsync(this._owner, from, to, amount);
|
||||
if (revertError !== undefined) {
|
||||
await expect(txReceiptPromise).to.revertWith(revertError);
|
||||
return;
|
||||
}
|
||||
await txReceiptPromise;
|
||||
// check balances
|
||||
await this.assertBalancesAsync(expectedStakerBalances);
|
||||
// check zrx balance of vault
|
||||
const finalZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
||||
expect(finalZrxBalanceOfVault).to.be.bignumber.equal(initZrxBalanceOfVault);
|
||||
}
|
||||
|
||||
public async goToNextEpochAsync(): Promise<void> {
|
||||
// cache balances
|
||||
const initZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
||||
const initStakerBalances = await this.getBalancesAsync();
|
||||
// go to next epoch
|
||||
await this._stakingWrapper.skipToNextEpochAsync();
|
||||
// check balances
|
||||
const expectedStakerBalances = this.getNextEpochBalances(initStakerBalances);
|
||||
await this.assertBalancesAsync(expectedStakerBalances);
|
||||
// check zrx balance of vault
|
||||
const finalZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
||||
expect(finalZrxBalanceOfVault).to.be.bignumber.equal(initZrxBalanceOfVault);
|
||||
}
|
||||
|
||||
public getNextEpochBalances(balances: StakeBalances): StakeBalances {
|
||||
const nextBalances = _.cloneDeep(balances);
|
||||
nextBalances.withdrawableStakeBalance = nextBalances.inactiveStakeBalance.next.isLessThan(
|
||||
nextBalances.inactiveStakeBalance.current,
|
||||
)
|
||||
? nextBalances.inactiveStakeBalance.next
|
||||
: nextBalances.inactiveStakeBalance.current;
|
||||
nextBalances.activeStakeBalance.current = nextBalances.activeStakeBalance.next;
|
||||
nextBalances.inactiveStakeBalance.current = nextBalances.inactiveStakeBalance.next;
|
||||
nextBalances.delegatedStakeBalance.current = nextBalances.delegatedStakeBalance.next;
|
||||
for (const poolId of this._poolIds) {
|
||||
nextBalances.delegatedStakeByPool[poolId].current = nextBalances.delegatedStakeByPool[poolId].next;
|
||||
nextBalances.totalDelegatedStakeByPool[poolId].current =
|
||||
nextBalances.totalDelegatedStakeByPool[poolId].next;
|
||||
}
|
||||
return nextBalances;
|
||||
}
|
||||
public async getBalancesAsync(): Promise<StakeBalances> {
|
||||
const stakerBalances: StakeBalances = {
|
||||
zrxBalance: await this._stakingWrapper.getZrxTokenBalanceAsync(this._owner),
|
||||
stakeBalance: await this._stakingWrapper.getTotalStakeAsync(this._owner),
|
||||
stakeBalanceInVault: await this._stakingWrapper.getZrxVaultBalanceAsync(this._owner),
|
||||
withdrawableStakeBalance: await this._stakingWrapper.getWithdrawableStakeAsync(this._owner),
|
||||
activatableStakeBalance: await this._stakingWrapper.getActivatableStakeAsync(this._owner),
|
||||
activatedStakeBalance: await this._stakingWrapper.getActivatedStakeAsync(this._owner),
|
||||
timeLockedStakeBalance: await this._stakingWrapper.getTimeLockedStakeAsync(this._owner),
|
||||
deactivatedStakeBalance: await this._stakingWrapper.getDeactivatedStakeAsync(this._owner),
|
||||
activeStakeBalance: await this._stakingWrapper.getActiveStakeAsync(this._owner),
|
||||
inactiveStakeBalance: await this._stakingWrapper.getInactiveStakeAsync(this._owner),
|
||||
delegatedStakeBalance: await this._stakingWrapper.getStakeDelegatedByOwnerAsync(this._owner),
|
||||
delegatedStakeByPool: {},
|
||||
totalDelegatedStakeByPool: {},
|
||||
};
|
||||
// lookup for each pool
|
||||
for (const poolId of this._poolIds) {
|
||||
const delegatedStakeBalanceByPool = await this._stakingWrapper.getStakeDelegatedToPoolByOwnerAsync(
|
||||
poolId,
|
||||
this._owner,
|
||||
);
|
||||
const totalDelegatedStakeBalanceByPool = await this._stakingWrapper.getTotalStakeDelegatedToPoolAsync(
|
||||
poolId,
|
||||
);
|
||||
stakerBalances.delegatedStakeByPool[poolId] = delegatedStakeBalanceByPool;
|
||||
stakerBalances.totalDelegatedStakeByPool[poolId] = totalDelegatedStakeBalanceByPool;
|
||||
}
|
||||
return stakerBalances;
|
||||
}
|
||||
public async assertBalancesAsync(expectedBalances: StakerBalances): Promise<void> {
|
||||
public async assertBalancesAsync(expectedBalances: StakeBalances): Promise<void> {
|
||||
const balances = await this.getBalancesAsync();
|
||||
expect(balances.zrxBalance, 'zrx balance').to.be.bignumber.equal(expectedBalances.zrxBalance);
|
||||
expect(balances.stakeBalance, 'stake balance').to.be.bignumber.equal(expectedBalances.stakeBalance);
|
||||
expect(balances.stakeBalanceInVault, 'stake balance, recorded in vault').to.be.bignumber.equal(
|
||||
expectedBalances.stakeBalanceInVault,
|
||||
);
|
||||
expect(balances.withdrawableStakeBalance, 'withdrawable stake balance').to.be.bignumber.equal(
|
||||
expectedBalances.withdrawableStakeBalance,
|
||||
);
|
||||
expect(balances.activatableStakeBalance, 'activatable stake balance').to.be.bignumber.equal(
|
||||
expectedBalances.activatableStakeBalance,
|
||||
expect(balances.activeStakeBalance.current, 'active stake balance (current)').to.be.bignumber.equal(
|
||||
expectedBalances.activeStakeBalance.current,
|
||||
);
|
||||
expect(balances.activatedStakeBalance, 'activated stake balance').to.be.bignumber.equal(
|
||||
expectedBalances.activatedStakeBalance,
|
||||
expect(balances.activeStakeBalance.next, 'active stake balance (next)').to.be.bignumber.equal(
|
||||
expectedBalances.activeStakeBalance.next,
|
||||
);
|
||||
expect(balances.timeLockedStakeBalance, 'timeLocked stake balance').to.be.bignumber.equal(
|
||||
expectedBalances.timeLockedStakeBalance,
|
||||
expect(balances.inactiveStakeBalance.current, 'inactive stake balance (current)').to.be.bignumber.equal(
|
||||
expectedBalances.inactiveStakeBalance.current,
|
||||
);
|
||||
expect(balances.deactivatedStakeBalance, 'deactivated stake balance').to.be.bignumber.equal(
|
||||
expectedBalances.deactivatedStakeBalance,
|
||||
expect(balances.inactiveStakeBalance.next, 'inactive stake balance (next)').to.be.bignumber.equal(
|
||||
expectedBalances.inactiveStakeBalance.next,
|
||||
);
|
||||
expect(balances.delegatedStakeBalance.current, 'delegated stake balance (current)').to.be.bignumber.equal(
|
||||
expectedBalances.delegatedStakeBalance.current,
|
||||
);
|
||||
expect(balances.delegatedStakeBalance.next, 'delegated stake balance (next)').to.be.bignumber.equal(
|
||||
expectedBalances.delegatedStakeBalance.next,
|
||||
);
|
||||
expect(balances.delegatedStakeByPool, 'delegated stake by pool').to.be.deep.equal(
|
||||
expectedBalances.delegatedStakeByPool,
|
||||
);
|
||||
expect(balances.totalDelegatedStakeByPool, 'total delegated stake by pool').to.be.deep.equal(
|
||||
expectedBalances.totalDelegatedStakeByPool,
|
||||
);
|
||||
}
|
||||
public async forceBalanceSyncAsync(): Promise<void> {
|
||||
@@ -142,5 +233,4 @@ export class StakerActor extends BaseActor {
|
||||
await this._stakingWrapper.stakeAsync(this._owner, new BigNumber(0));
|
||||
await this.assertBalancesAsync(initBalances);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
600
contracts/staking/test/rewards_test.ts
Normal file
600
contracts/staking/test/rewards_test.ts
Normal file
@@ -0,0 +1,600 @@
|
||||
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { blockchainTests, describe, expect, provider, web3Wrapper } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { FinalizerActor } from './actors/finalizer_actor';
|
||||
import { StakerActor } from './actors/staker_actor';
|
||||
import { StakingWrapper } from './utils/staking_wrapper';
|
||||
import { MembersByPoolId, OperatorByPoolId, StakeState } from './utils/types';
|
||||
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
// tslint:disable:max-file-line-count
|
||||
blockchainTests.resets.only('Testing Rewards', () => {
|
||||
// constants
|
||||
const ZRX_TOKEN_DECIMALS = new BigNumber(18);
|
||||
// tokens & addresses
|
||||
let accounts: string[];
|
||||
let owner: string;
|
||||
let actors: string[];
|
||||
let exchangeAddress: string;
|
||||
let takerAddress: string;
|
||||
let zrxTokenContract: DummyERC20TokenContract;
|
||||
let erc20ProxyContract: ERC20ProxyContract;
|
||||
// wrappers
|
||||
let stakingWrapper: StakingWrapper;
|
||||
// let testWrapper: TestRewardBalancesContract;
|
||||
let erc20Wrapper: ERC20Wrapper;
|
||||
// test parameters
|
||||
let stakers: StakerActor[];
|
||||
let poolId: string;
|
||||
let poolOperator: string;
|
||||
let finalizer: FinalizerActor;
|
||||
// tests
|
||||
before(async () => {
|
||||
// create accounts
|
||||
accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
owner = accounts[0];
|
||||
exchangeAddress = accounts[1];
|
||||
takerAddress = accounts[2];
|
||||
actors = accounts.slice(3);
|
||||
// deploy erƒsc20 proxy
|
||||
erc20Wrapper = new ERC20Wrapper(provider, accounts, owner);
|
||||
erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
|
||||
// deploy zrx token
|
||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
// deploy staking contracts
|
||||
stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
|
||||
await stakingWrapper.deployAndConfigureContractsAsync();
|
||||
// setup stakers
|
||||
stakers = [new StakerActor(actors[0], stakingWrapper), new StakerActor(actors[1], stakingWrapper)];
|
||||
// setup pools
|
||||
poolOperator = actors[2];
|
||||
poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, 0);
|
||||
// add operator as maker
|
||||
const approvalMessage = stakingWrapper.signApprovalForStakingPool(poolId, poolOperator);
|
||||
await stakingWrapper.addMakerToStakingPoolAsync(poolId, poolOperator, approvalMessage.signature, poolOperator);
|
||||
// set exchange address
|
||||
await stakingWrapper.addExchangeAddressAsync(exchangeAddress);
|
||||
// associate operators for tracking in Finalizer
|
||||
const operatorByPoolId: OperatorByPoolId = {};
|
||||
operatorByPoolId[poolId] = poolOperator;
|
||||
operatorByPoolId[poolId] = poolOperator;
|
||||
// associate actors with pools for tracking in Finalizer
|
||||
const membersByPoolId: MembersByPoolId = {};
|
||||
membersByPoolId[poolId] = [actors[0], actors[1]];
|
||||
membersByPoolId[poolId] = [actors[0], actors[1]];
|
||||
// create Finalizer actor
|
||||
finalizer = new FinalizerActor(actors[3], stakingWrapper, [poolId], operatorByPoolId, membersByPoolId);
|
||||
});
|
||||
describe('Reward Simulation', () => {
|
||||
interface EndBalances {
|
||||
// staker 1
|
||||
stakerRewardVaultBalance_1?: BigNumber;
|
||||
stakerEthVaultBalance_1?: BigNumber;
|
||||
// staker 2
|
||||
stakerRewardVaultBalance_2?: BigNumber;
|
||||
stakerEthVaultBalance_2?: BigNumber;
|
||||
// operator
|
||||
operatorRewardVaultBalance?: BigNumber;
|
||||
operatorEthVaultBalance?: BigNumber;
|
||||
// undivided balance in reward pool
|
||||
poolRewardVaultBalance?: BigNumber;
|
||||
membersRewardVaultBalance?: BigNumber;
|
||||
}
|
||||
const validateEndBalances = async (_expectedEndBalances: EndBalances): Promise<void> => {
|
||||
const expectedEndBalances = {
|
||||
// staker 1
|
||||
stakerRewardVaultBalance_1:
|
||||
_expectedEndBalances.stakerRewardVaultBalance_1 !== undefined
|
||||
? _expectedEndBalances.stakerRewardVaultBalance_1
|
||||
: ZERO,
|
||||
stakerEthVaultBalance_1:
|
||||
_expectedEndBalances.stakerEthVaultBalance_1 !== undefined
|
||||
? _expectedEndBalances.stakerEthVaultBalance_1
|
||||
: ZERO,
|
||||
// staker 2
|
||||
stakerRewardVaultBalance_2:
|
||||
_expectedEndBalances.stakerRewardVaultBalance_2 !== undefined
|
||||
? _expectedEndBalances.stakerRewardVaultBalance_2
|
||||
: ZERO,
|
||||
stakerEthVaultBalance_2:
|
||||
_expectedEndBalances.stakerEthVaultBalance_2 !== undefined
|
||||
? _expectedEndBalances.stakerEthVaultBalance_2
|
||||
: ZERO,
|
||||
// operator
|
||||
operatorRewardVaultBalance:
|
||||
_expectedEndBalances.operatorRewardVaultBalance !== undefined
|
||||
? _expectedEndBalances.operatorRewardVaultBalance
|
||||
: ZERO,
|
||||
operatorEthVaultBalance:
|
||||
_expectedEndBalances.operatorEthVaultBalance !== undefined
|
||||
? _expectedEndBalances.operatorEthVaultBalance
|
||||
: ZERO,
|
||||
// undivided balance in reward pool
|
||||
poolRewardVaultBalance:
|
||||
_expectedEndBalances.poolRewardVaultBalance !== undefined
|
||||
? _expectedEndBalances.poolRewardVaultBalance
|
||||
: ZERO,
|
||||
membersRewardVaultBalance:
|
||||
_expectedEndBalances.membersRewardVaultBalance !== undefined
|
||||
? _expectedEndBalances.membersRewardVaultBalance
|
||||
: ZERO,
|
||||
};
|
||||
const finalEndBalancesAsArray = await Promise.all([
|
||||
// staker 1
|
||||
stakingWrapper.computeRewardBalanceOfStakingPoolMemberAsync(poolId, stakers[0].getOwner()),
|
||||
stakingWrapper.getEthVaultContract().balanceOf.callAsync(stakers[0].getOwner()),
|
||||
// staker 2
|
||||
stakingWrapper.computeRewardBalanceOfStakingPoolMemberAsync(poolId, stakers[1].getOwner()),
|
||||
stakingWrapper.getEthVaultContract().balanceOf.callAsync(stakers[1].getOwner()),
|
||||
// operator
|
||||
stakingWrapper.rewardVaultBalanceOfOperatorAsync(poolId),
|
||||
stakingWrapper.getEthVaultContract().balanceOf.callAsync(poolOperator),
|
||||
// undivided balance in reward pool
|
||||
stakingWrapper.rewardVaultBalanceOfAsync(poolId),
|
||||
stakingWrapper.rewardVaultBalanceOfMembersAsync(poolId),
|
||||
]);
|
||||
expect(finalEndBalancesAsArray[0], 'stakerRewardVaultBalance_1').to.be.bignumber.equal(
|
||||
expectedEndBalances.stakerRewardVaultBalance_1,
|
||||
);
|
||||
expect(finalEndBalancesAsArray[1], 'stakerEthVaultBalance_1').to.be.bignumber.equal(
|
||||
expectedEndBalances.stakerEthVaultBalance_1,
|
||||
);
|
||||
expect(finalEndBalancesAsArray[2], 'stakerRewardVaultBalance_2').to.be.bignumber.equal(
|
||||
expectedEndBalances.stakerRewardVaultBalance_2,
|
||||
);
|
||||
expect(finalEndBalancesAsArray[3], 'stakerEthVaultBalance_2').to.be.bignumber.equal(
|
||||
expectedEndBalances.stakerEthVaultBalance_2,
|
||||
);
|
||||
expect(finalEndBalancesAsArray[4], 'operatorRewardVaultBalance').to.be.bignumber.equal(
|
||||
expectedEndBalances.operatorRewardVaultBalance,
|
||||
);
|
||||
expect(finalEndBalancesAsArray[5], 'operatorEthVaultBalance').to.be.bignumber.equal(
|
||||
expectedEndBalances.operatorEthVaultBalance,
|
||||
);
|
||||
expect(finalEndBalancesAsArray[6], 'poolRewardVaultBalance').to.be.bignumber.equal(
|
||||
expectedEndBalances.poolRewardVaultBalance,
|
||||
);
|
||||
expect(finalEndBalancesAsArray[7], 'membersRewardVaultBalance').to.be.bignumber.equal(
|
||||
expectedEndBalances.membersRewardVaultBalance,
|
||||
);
|
||||
};
|
||||
const payProtocolFeeAndFinalize = async (_fee?: BigNumber) => {
|
||||
const fee = _fee !== undefined ? _fee : ZERO;
|
||||
if (!fee.eq(ZERO)) {
|
||||
await stakingWrapper.payProtocolFeeAsync(poolOperator, takerAddress, fee, fee, exchangeAddress);
|
||||
}
|
||||
await finalizer.finalizeAsync([{ reward: fee, poolId }]);
|
||||
};
|
||||
const ZERO = new BigNumber(0);
|
||||
it('Reward balance should be zero in same epoch as delegation', async () => {
|
||||
const amount = StakingWrapper.toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(amount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
amount,
|
||||
);
|
||||
await payProtocolFeeAndFinalize();
|
||||
// sanit check final balances - all zero
|
||||
await validateEndBalances({});
|
||||
});
|
||||
it('Operator should receive entire reward if no delegators in their pool', async () => {
|
||||
const reward = StakingWrapper.toBaseUnitAmount(10);
|
||||
await payProtocolFeeAndFinalize(reward);
|
||||
// sanity check final balances - all zero
|
||||
await validateEndBalances({
|
||||
operatorRewardVaultBalance: reward,
|
||||
poolRewardVaultBalance: reward,
|
||||
});
|
||||
});
|
||||
it('Operator should receive entire reward if no delegators in their pool (staker joins this epoch but is active next epoch)', async () => {
|
||||
// delegate
|
||||
const amount = StakingWrapper.toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(amount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
amount,
|
||||
);
|
||||
// finalize
|
||||
const reward = StakingWrapper.toBaseUnitAmount(10);
|
||||
await payProtocolFeeAndFinalize(reward);
|
||||
// sanity check final balances
|
||||
await validateEndBalances({
|
||||
operatorRewardVaultBalance: reward,
|
||||
poolRewardVaultBalance: reward,
|
||||
});
|
||||
});
|
||||
it('Should give pool reward to delegator', async () => {
|
||||
// delegate
|
||||
const amount = StakingWrapper.toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(amount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
amount,
|
||||
);
|
||||
// skip epoch, so staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// finalize
|
||||
const reward = StakingWrapper.toBaseUnitAmount(10);
|
||||
await payProtocolFeeAndFinalize(reward);
|
||||
// sanity check final balances
|
||||
await validateEndBalances({
|
||||
stakerRewardVaultBalance_1: reward,
|
||||
poolRewardVaultBalance: reward,
|
||||
membersRewardVaultBalance: reward,
|
||||
});
|
||||
});
|
||||
it('Should split pool reward between delegators', async () => {
|
||||
// first staker delegates
|
||||
const stakeAmounts = [StakingWrapper.toBaseUnitAmount(4), StakingWrapper.toBaseUnitAmount(6)];
|
||||
const totalStakeAmount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmounts[0],
|
||||
);
|
||||
// second staker delegates
|
||||
await stakers[1].stakeAsync(stakeAmounts[1]);
|
||||
await stakers[1].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmounts[1],
|
||||
);
|
||||
// skip epoch, so staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// finalize
|
||||
const reward = StakingWrapper.toBaseUnitAmount(10);
|
||||
await payProtocolFeeAndFinalize(reward);
|
||||
// sanity check final balances
|
||||
await validateEndBalances({
|
||||
stakerRewardVaultBalance_1: reward.times(stakeAmounts[0]).dividedToIntegerBy(totalStakeAmount),
|
||||
stakerRewardVaultBalance_2: reward.times(stakeAmounts[1]).dividedToIntegerBy(totalStakeAmount),
|
||||
poolRewardVaultBalance: reward,
|
||||
membersRewardVaultBalance: reward,
|
||||
});
|
||||
});
|
||||
it('Should split pool reward between delegators, when they join in different epochs', async () => {
|
||||
// first staker delegates (epoch 0)
|
||||
const stakeAmounts = [StakingWrapper.toBaseUnitAmount(4), StakingWrapper.toBaseUnitAmount(6)];
|
||||
const totalStakeAmount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmounts[0],
|
||||
);
|
||||
// skip epoch, so staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// second staker delegates (epoch 1)
|
||||
await stakers[1].stakeAsync(stakeAmounts[1]);
|
||||
await stakers[1].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmounts[1],
|
||||
);
|
||||
// skip epoch, so staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// finalize
|
||||
const reward = StakingWrapper.toBaseUnitAmount(10);
|
||||
await payProtocolFeeAndFinalize(reward);
|
||||
// sanity check final balances
|
||||
await validateEndBalances({
|
||||
stakerRewardVaultBalance_1: reward.times(stakeAmounts[0]).dividedToIntegerBy(totalStakeAmount),
|
||||
stakerRewardVaultBalance_2: reward.times(stakeAmounts[1]).dividedToIntegerBy(totalStakeAmount),
|
||||
poolRewardVaultBalance: reward,
|
||||
membersRewardVaultBalance: reward,
|
||||
});
|
||||
});
|
||||
it('Should give pool reward to delegators only for the epoch during which they delegated', async () => {
|
||||
// first staker delegates (epoch 0)
|
||||
const stakeAmounts = [StakingWrapper.toBaseUnitAmount(4), StakingWrapper.toBaseUnitAmount(6)];
|
||||
const totalStakeAmount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmounts[0],
|
||||
);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// second staker delegates (epoch 1)
|
||||
await stakers[1].stakeAsync(stakeAmounts[1]);
|
||||
await stakers[1].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmounts[1],
|
||||
);
|
||||
// only the first staker will get this reward
|
||||
const rewardForOnlyFirstDelegator = StakingWrapper.toBaseUnitAmount(10);
|
||||
await payProtocolFeeAndFinalize(rewardForOnlyFirstDelegator);
|
||||
// finalize
|
||||
const rewardForBothDelegators = StakingWrapper.toBaseUnitAmount(20);
|
||||
await payProtocolFeeAndFinalize(rewardForBothDelegators);
|
||||
// sanity check final balances
|
||||
await validateEndBalances({
|
||||
stakerRewardVaultBalance_1: rewardForOnlyFirstDelegator.plus(
|
||||
rewardForBothDelegators.times(stakeAmounts[0]).dividedToIntegerBy(totalStakeAmount),
|
||||
),
|
||||
stakerRewardVaultBalance_2: rewardForBothDelegators
|
||||
.times(stakeAmounts[1])
|
||||
.dividedToIntegerBy(totalStakeAmount),
|
||||
poolRewardVaultBalance: rewardForOnlyFirstDelegator.plus(rewardForBothDelegators),
|
||||
membersRewardVaultBalance: rewardForOnlyFirstDelegator.plus(rewardForBothDelegators),
|
||||
});
|
||||
});
|
||||
it('Should split pool reward between delegators, over several consecutive epochs', async () => {
|
||||
const rewardForOnlyFirstDelegator = StakingWrapper.toBaseUnitAmount(10);
|
||||
const sharedRewards = [
|
||||
StakingWrapper.toBaseUnitAmount(20),
|
||||
StakingWrapper.toBaseUnitAmount(16),
|
||||
StakingWrapper.toBaseUnitAmount(24),
|
||||
StakingWrapper.toBaseUnitAmount(5),
|
||||
StakingWrapper.toBaseUnitAmount(0),
|
||||
StakingWrapper.toBaseUnitAmount(17),
|
||||
];
|
||||
const totalSharedRewardsAsNumber = _.sumBy(sharedRewards, v => {
|
||||
return v.toNumber();
|
||||
});
|
||||
const totalSharedRewards = new BigNumber(totalSharedRewardsAsNumber);
|
||||
// first staker delegates (epoch 0)
|
||||
const stakeAmounts = [StakingWrapper.toBaseUnitAmount(4), StakingWrapper.toBaseUnitAmount(6)];
|
||||
const totalStakeAmount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmounts[0],
|
||||
);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// second staker delegates (epoch 1)
|
||||
await stakers[1].stakeAsync(stakeAmounts[1]);
|
||||
await stakers[1].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmounts[1],
|
||||
);
|
||||
// only the first staker will get this reward
|
||||
await payProtocolFeeAndFinalize(rewardForOnlyFirstDelegator);
|
||||
// earn a bunch of rewards
|
||||
for (const reward of sharedRewards) {
|
||||
await payProtocolFeeAndFinalize(reward);
|
||||
}
|
||||
// sanity check final balances
|
||||
await validateEndBalances({
|
||||
stakerRewardVaultBalance_1: rewardForOnlyFirstDelegator.plus(
|
||||
totalSharedRewards.times(stakeAmounts[0]).dividedToIntegerBy(totalStakeAmount),
|
||||
),
|
||||
stakerRewardVaultBalance_2: totalSharedRewards
|
||||
.times(stakeAmounts[1])
|
||||
.dividedToIntegerBy(totalStakeAmount),
|
||||
poolRewardVaultBalance: rewardForOnlyFirstDelegator.plus(totalSharedRewards),
|
||||
membersRewardVaultBalance: rewardForOnlyFirstDelegator.plus(totalSharedRewards),
|
||||
});
|
||||
});
|
||||
it('Should send existing rewards from reward vault to eth vault correctly when undelegating stake', async () => {
|
||||
// first staker delegates (epoch 0)
|
||||
const stakeAmount = StakingWrapper.toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(stakeAmount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmount,
|
||||
);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// earn reward
|
||||
const reward = StakingWrapper.toBaseUnitAmount(10);
|
||||
await payProtocolFeeAndFinalize(reward);
|
||||
// undelegate (moves delegator's from the transient reward vault into the eth vault)
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
{ state: StakeState.Active },
|
||||
stakeAmount,
|
||||
);
|
||||
// sanity check final balances
|
||||
await validateEndBalances({
|
||||
stakerRewardVaultBalance_1: ZERO,
|
||||
stakerEthVaultBalance_1: reward,
|
||||
});
|
||||
});
|
||||
it('Should send existing rewards from reward vault to eth vault correctly when delegating more stake', async () => {
|
||||
// first staker delegates (epoch 0)
|
||||
const stakeAmount = StakingWrapper.toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(stakeAmount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmount,
|
||||
);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// earn reward
|
||||
const reward = StakingWrapper.toBaseUnitAmount(10);
|
||||
await payProtocolFeeAndFinalize(reward);
|
||||
// add more stake
|
||||
await stakers[0].stakeAsync(stakeAmount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmount,
|
||||
);
|
||||
// sanity check final balances
|
||||
await validateEndBalances({
|
||||
stakerRewardVaultBalance_1: ZERO,
|
||||
stakerEthVaultBalance_1: reward,
|
||||
});
|
||||
});
|
||||
it('Should continue earning rewards after adding more stake and progressing several epochs', async () => {
|
||||
const rewardBeforeAddingMoreStake = StakingWrapper.toBaseUnitAmount(10);
|
||||
const rewardsAfterAddingMoreStake = [
|
||||
StakingWrapper.toBaseUnitAmount(20),
|
||||
StakingWrapper.toBaseUnitAmount(16),
|
||||
StakingWrapper.toBaseUnitAmount(24),
|
||||
StakingWrapper.toBaseUnitAmount(5),
|
||||
StakingWrapper.toBaseUnitAmount(0),
|
||||
StakingWrapper.toBaseUnitAmount(17),
|
||||
];
|
||||
const totalRewardsAfterAddingMoreStake = new BigNumber(
|
||||
_.sumBy(rewardsAfterAddingMoreStake, v => {
|
||||
return v.toNumber();
|
||||
}),
|
||||
);
|
||||
// first staker delegates (epoch 0)
|
||||
const stakeAmounts = [StakingWrapper.toBaseUnitAmount(4), StakingWrapper.toBaseUnitAmount(6)];
|
||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmounts[0],
|
||||
);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// second staker delegates (epoch 1)
|
||||
await stakers[0].stakeAsync(stakeAmounts[1]);
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmounts[1],
|
||||
);
|
||||
// only the first staker will get this reward
|
||||
await payProtocolFeeAndFinalize(rewardBeforeAddingMoreStake);
|
||||
// earn a bunch of rewards
|
||||
for (const reward of rewardsAfterAddingMoreStake) {
|
||||
await payProtocolFeeAndFinalize(reward);
|
||||
}
|
||||
// sanity check final balances
|
||||
await validateEndBalances({
|
||||
stakerRewardVaultBalance_1: rewardBeforeAddingMoreStake.plus(totalRewardsAfterAddingMoreStake),
|
||||
poolRewardVaultBalance: rewardBeforeAddingMoreStake.plus(totalRewardsAfterAddingMoreStake),
|
||||
membersRewardVaultBalance: rewardBeforeAddingMoreStake.plus(totalRewardsAfterAddingMoreStake),
|
||||
});
|
||||
});
|
||||
it('Should stop collecting rewards after undelegating', async () => {
|
||||
// first staker delegates (epoch 0)
|
||||
const rewardForDelegator = StakingWrapper.toBaseUnitAmount(10);
|
||||
const rewardNotForDelegator = StakingWrapper.toBaseUnitAmount(7);
|
||||
const stakeAmount = StakingWrapper.toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(stakeAmount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmount,
|
||||
);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// earn reward
|
||||
await payProtocolFeeAndFinalize(rewardForDelegator);
|
||||
// undelegate stake and finalize epoch
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
{ state: StakeState.Active },
|
||||
stakeAmount,
|
||||
);
|
||||
await payProtocolFeeAndFinalize();
|
||||
// this should not go do the delegator
|
||||
await payProtocolFeeAndFinalize(rewardNotForDelegator);
|
||||
// sanity check final balances
|
||||
await validateEndBalances({
|
||||
stakerEthVaultBalance_1: rewardForDelegator,
|
||||
poolRewardVaultBalance: rewardNotForDelegator,
|
||||
operatorRewardVaultBalance: rewardNotForDelegator,
|
||||
});
|
||||
});
|
||||
it('Should stop collecting rewards after undelegating, after several epochs', async () => {
|
||||
// first staker delegates (epoch 0)
|
||||
const rewardForDelegator = StakingWrapper.toBaseUnitAmount(10);
|
||||
const rewardsNotForDelegator = [
|
||||
StakingWrapper.toBaseUnitAmount(20),
|
||||
StakingWrapper.toBaseUnitAmount(16),
|
||||
StakingWrapper.toBaseUnitAmount(24),
|
||||
StakingWrapper.toBaseUnitAmount(5),
|
||||
StakingWrapper.toBaseUnitAmount(0),
|
||||
StakingWrapper.toBaseUnitAmount(17),
|
||||
];
|
||||
const totalRewardsNotForDelegator = new BigNumber(
|
||||
_.sumBy(rewardsNotForDelegator, v => {
|
||||
return v.toNumber();
|
||||
}),
|
||||
);
|
||||
const stakeAmount = StakingWrapper.toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(stakeAmount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmount,
|
||||
);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// earn reward
|
||||
await payProtocolFeeAndFinalize(rewardForDelegator);
|
||||
// undelegate stake and finalize epoch
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
{ state: StakeState.Active },
|
||||
stakeAmount,
|
||||
);
|
||||
await payProtocolFeeAndFinalize();
|
||||
// this should not go do the delegator
|
||||
for (const reward of rewardsNotForDelegator) {
|
||||
await payProtocolFeeAndFinalize(reward);
|
||||
}
|
||||
// sanity check final balances
|
||||
await validateEndBalances({
|
||||
stakerEthVaultBalance_1: rewardForDelegator,
|
||||
poolRewardVaultBalance: totalRewardsNotForDelegator,
|
||||
operatorRewardVaultBalance: totalRewardsNotForDelegator,
|
||||
});
|
||||
});
|
||||
it('Should collect fees correctly when leaving and returning to a pool', async () => {
|
||||
// first staker delegates (epoch 0)
|
||||
const rewardsForDelegator = [StakingWrapper.toBaseUnitAmount(10), StakingWrapper.toBaseUnitAmount(15)];
|
||||
const rewardNotForDelegator = StakingWrapper.toBaseUnitAmount(7);
|
||||
const stakeAmount = StakingWrapper.toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(stakeAmount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmount,
|
||||
);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// earn reward
|
||||
await payProtocolFeeAndFinalize(rewardsForDelegator[0]);
|
||||
// undelegate stake and finalize epoch
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
{ state: StakeState.Active },
|
||||
stakeAmount,
|
||||
);
|
||||
await payProtocolFeeAndFinalize();
|
||||
// this should not go do the delegator
|
||||
await payProtocolFeeAndFinalize(rewardNotForDelegator);
|
||||
// delegate stake and go to next epoch
|
||||
await stakers[0].moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId },
|
||||
stakeAmount,
|
||||
);
|
||||
await payProtocolFeeAndFinalize();
|
||||
// this reward should go to delegator
|
||||
await payProtocolFeeAndFinalize(rewardsForDelegator[1]);
|
||||
// sanity check final balances
|
||||
await validateEndBalances({
|
||||
stakerRewardVaultBalance_1: rewardsForDelegator[1],
|
||||
stakerEthVaultBalance_1: rewardsForDelegator[0],
|
||||
operatorRewardVaultBalance: rewardNotForDelegator,
|
||||
poolRewardVaultBalance: rewardNotForDelegator.plus(rewardsForDelegator[1]),
|
||||
membersRewardVaultBalance: rewardsForDelegator[1],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:enable:no-unnecessary-type-assertion
|
@@ -1,3 +1,6 @@
|
||||
/*
|
||||
@TODO (hysz) - update once new staking mechanics are merged
|
||||
|
||||
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { blockchainTests, expect } from '@0x/contracts-test-utils';
|
||||
@@ -9,7 +12,6 @@ import { Simulation } from './utils/Simulation';
|
||||
import { StakingWrapper } from './utils/staking_wrapper';
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
blockchainTests('End-To-End Simulations', env => {
|
||||
/*
|
||||
// constants
|
||||
const ZRX_TOKEN_DECIMALS = new BigNumber(18);
|
||||
const PPM_ONE = 1e6;
|
||||
@@ -333,6 +335,6 @@ blockchainTests('End-To-End Simulations', env => {
|
||||
await expect(tx).to.revertWith(revertError);
|
||||
});
|
||||
});
|
||||
*/
|
||||
});
|
||||
// tslint:enable:no-unnecessary-type-assertion
|
||||
*/
|
||||
|
@@ -1,47 +1,357 @@
|
||||
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { blockchainTests } from '@0x/contracts-test-utils';
|
||||
import { StakingRevertErrors } from '@0x/order-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { blockchainTests, describe, provider, web3Wrapper } from '@0x/contracts-test-utils';
|
||||
import { BigNumber, StringRevertError } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DelegatorActor } from './actors/delegator_actor';
|
||||
import { StakerActor } from './actors/staker_actor';
|
||||
import { StakingWrapper } from './utils/staking_wrapper';
|
||||
import { StakeState, StakeStateInfo } from './utils/types';
|
||||
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
blockchainTests('Staking & Delegating', env => {
|
||||
blockchainTests.resets.only('Stake States', () => {
|
||||
// constants
|
||||
const ZRX_TOKEN_DECIMALS = new BigNumber(18);
|
||||
const ZERO = new BigNumber(0);
|
||||
// tokens & addresses
|
||||
let accounts: string[];
|
||||
let owner: string;
|
||||
let stakers: string[];
|
||||
let actors: string[];
|
||||
let zrxTokenContract: DummyERC20TokenContract;
|
||||
let erc20ProxyContract: ERC20ProxyContract;
|
||||
// wrappers
|
||||
let stakingWrapper: StakingWrapper;
|
||||
let erc20Wrapper: ERC20Wrapper;
|
||||
// stake actor
|
||||
let staker: StakerActor;
|
||||
let poolIds: string[];
|
||||
let poolOperator: string;
|
||||
// tests
|
||||
before(async () => {
|
||||
// create accounts
|
||||
accounts = await env.web3Wrapper.getAvailableAddressesAsync();
|
||||
accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
owner = accounts[0];
|
||||
stakers = accounts.slice(2, 5);
|
||||
actors = accounts.slice(2, 5);
|
||||
// deploy erc20 proxy
|
||||
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
||||
erc20Wrapper = new ERC20Wrapper(provider, accounts, owner);
|
||||
erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
|
||||
// deploy zrx token
|
||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
// deploy staking contracts
|
||||
stakingWrapper = new StakingWrapper(env.provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
|
||||
stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, zrxTokenContract, accounts);
|
||||
await stakingWrapper.deployAndConfigureContractsAsync();
|
||||
// setup new staker
|
||||
staker = new StakerActor(actors[0], stakingWrapper);
|
||||
// setup pools
|
||||
poolOperator = actors[1];
|
||||
poolIds = await Promise.all([
|
||||
await stakingWrapper.createStakingPoolAsync(poolOperator, 4),
|
||||
await stakingWrapper.createStakingPoolAsync(poolOperator, 5),
|
||||
]);
|
||||
});
|
||||
blockchainTests.resets('Staking', () => {
|
||||
describe('Stake', () => {
|
||||
it('should successfully stake zero ZRX', async () => {
|
||||
const amount = StakingWrapper.toBaseUnitAmount(0);
|
||||
await staker.stakeAsync(amount);
|
||||
});
|
||||
it('should successfully stake non-zero ZRX', async () => {
|
||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
});
|
||||
it('should retain stake balance across 1 epoch', async () => {
|
||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.goToNextEpochAsync();
|
||||
});
|
||||
it('should retain stake balance across 2 epochs', async () => {
|
||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.goToNextEpochAsync();
|
||||
await staker.goToNextEpochAsync();
|
||||
});
|
||||
});
|
||||
|
||||
blockchainTests.resets('Delegating', () => {
|
||||
describe('Move Stake', () => {
|
||||
it("should be able to rebalance next epoch's stake", async () => {
|
||||
// epoch 1
|
||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync({ state: StakeState.Active }, { state: StakeState.Inactive }, amount);
|
||||
// still epoch 1 ~ should be able to move stake again
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Inactive },
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
amount,
|
||||
);
|
||||
});
|
||||
it('should fail to move the same stake more than once', async () => {
|
||||
// epoch 1
|
||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync({ state: StakeState.Active }, { state: StakeState.Inactive }, amount);
|
||||
// stake is now inactive, should not be able to move it out of active state again
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Inactive },
|
||||
amount,
|
||||
new StringRevertError('Insufficient Balance'),
|
||||
);
|
||||
});
|
||||
it('should fail to reassign stake', async () => {
|
||||
// epoch 1
|
||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync({ state: StakeState.Active }, { state: StakeState.Inactive }, amount);
|
||||
// still epoch 1 ~ should be able to move stake again
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Inactive },
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
amount,
|
||||
);
|
||||
// stake is now delegated; should fail to re-assign it from inactive back to active
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Inactive },
|
||||
{ state: StakeState.Active },
|
||||
amount,
|
||||
new StringRevertError('Insufficient Balance'),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('Move Zero Stake', () => {
|
||||
it('active -> active', async () => {
|
||||
await staker.moveStakeAsync({ state: StakeState.Active }, { state: StakeState.Active }, ZERO);
|
||||
});
|
||||
it('active -> inactive', async () => {
|
||||
await staker.moveStakeAsync({ state: StakeState.Active }, { state: StakeState.Inactive }, ZERO);
|
||||
});
|
||||
it('active -> delegated', async () => {
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
ZERO,
|
||||
);
|
||||
});
|
||||
it('inactive -> active', async () => {
|
||||
await staker.moveStakeAsync({ state: StakeState.Inactive }, { state: StakeState.Active }, ZERO);
|
||||
});
|
||||
it('inactive -> inactive', async () => {
|
||||
await staker.moveStakeAsync({ state: StakeState.Inactive }, { state: StakeState.Inactive }, ZERO);
|
||||
});
|
||||
it('inactive -> delegated', async () => {
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Inactive },
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
ZERO,
|
||||
);
|
||||
});
|
||||
it('delegated -> active', async () => {
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
{ state: StakeState.Active },
|
||||
ZERO,
|
||||
);
|
||||
});
|
||||
it('delegated -> inactive', async () => {
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
{ state: StakeState.Inactive },
|
||||
ZERO,
|
||||
);
|
||||
});
|
||||
it('delegated -> delegated (same pool)', async () => {
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
ZERO,
|
||||
);
|
||||
});
|
||||
it('delegated -> delegated (other pool)', async () => {
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
{ state: StakeState.Delegated, poolId: poolIds[1] },
|
||||
ZERO,
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('Move Non-Zero Stake', () => {
|
||||
const testMovePartialStake = async (from: StakeStateInfo, to: StakeStateInfo) => {
|
||||
// setup
|
||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
if (from.state !== StakeState.Active) {
|
||||
await staker.moveStakeAsync({ state: StakeState.Active }, from, amount);
|
||||
}
|
||||
// run test, checking balances in epochs [n .. n + 2]
|
||||
// in epoch `n` - `next` is set
|
||||
// in epoch `n+1` - `current` is set
|
||||
// in epoch `n+2` - only withdrawable balance should change.
|
||||
await staker.moveStakeAsync(from, to, amount.div(2));
|
||||
await staker.goToNextEpochAsync();
|
||||
await staker.goToNextEpochAsync();
|
||||
};
|
||||
it('active -> active', async () => {
|
||||
await testMovePartialStake({ state: StakeState.Active }, { state: StakeState.Active });
|
||||
});
|
||||
it('active -> inactive', async () => {
|
||||
await testMovePartialStake({ state: StakeState.Active }, { state: StakeState.Inactive });
|
||||
});
|
||||
it('active -> delegated', async () => {
|
||||
await testMovePartialStake(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
);
|
||||
});
|
||||
it('inactive -> active', async () => {
|
||||
await testMovePartialStake({ state: StakeState.Inactive }, { state: StakeState.Active });
|
||||
});
|
||||
it('inactive -> inactive', async () => {
|
||||
await testMovePartialStake({ state: StakeState.Inactive }, { state: StakeState.Inactive });
|
||||
});
|
||||
it('inactive -> delegated', async () => {
|
||||
await testMovePartialStake(
|
||||
{ state: StakeState.Inactive },
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
);
|
||||
});
|
||||
it('delegated -> active', async () => {
|
||||
await testMovePartialStake(
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
{ state: StakeState.Active },
|
||||
);
|
||||
});
|
||||
it('delegated -> inactive', async () => {
|
||||
await testMovePartialStake(
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
{ state: StakeState.Inactive },
|
||||
);
|
||||
});
|
||||
it('delegated -> delegated (same pool)', async () => {
|
||||
await testMovePartialStake(
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
);
|
||||
});
|
||||
it('delegated -> delegated (other pool)', async () => {
|
||||
await testMovePartialStake(
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
{ state: StakeState.Delegated, poolId: poolIds[1] },
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('Unstake', () => {
|
||||
it('should successfully unstake zero ZRX', async () => {
|
||||
const amount = StakingWrapper.toBaseUnitAmount(0);
|
||||
await staker.unstakeAsync(amount);
|
||||
});
|
||||
it('should successfully unstake after being inactive for 1 epoch', async () => {
|
||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync({ state: StakeState.Active }, { state: StakeState.Inactive }, amount);
|
||||
await staker.goToNextEpochAsync(); // stake is now inactive
|
||||
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
||||
await staker.unstakeAsync(amount);
|
||||
});
|
||||
it('should fail to unstake with insufficient balance', async () => {
|
||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.unstakeAsync(amount, new StringRevertError('INSUFFICIENT_FUNDS'));
|
||||
});
|
||||
it('should fail to unstake in the same epoch as stake was set to inactive', async () => {
|
||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync({ state: StakeState.Active }, { state: StakeState.Inactive }, amount);
|
||||
await staker.unstakeAsync(amount, new StringRevertError('INSUFFICIENT_FUNDS'));
|
||||
});
|
||||
it('should fail to unstake after being inactive for <1 epoch', async () => {
|
||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync({ state: StakeState.Active }, { state: StakeState.Inactive }, amount);
|
||||
await staker.goToNextEpochAsync();
|
||||
await staker.unstakeAsync(amount, new StringRevertError('INSUFFICIENT_FUNDS'));
|
||||
});
|
||||
it('should fail to unstake in same epoch that inactive/withdrawable stake has been reactivated', async () => {
|
||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync({ state: StakeState.Active }, { state: StakeState.Inactive }, amount);
|
||||
await staker.goToNextEpochAsync(); // stake is now inactive
|
||||
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
||||
await staker.moveStakeAsync({ state: StakeState.Inactive }, { state: StakeState.Active }, amount);
|
||||
await staker.unstakeAsync(amount, new StringRevertError('INSUFFICIENT_FUNDS'));
|
||||
});
|
||||
it('should fail to unstake one epoch after inactive/withdrawable stake has been reactivated', async () => {
|
||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync({ state: StakeState.Active }, { state: StakeState.Inactive }, amount);
|
||||
await staker.goToNextEpochAsync(); // stake is now inactive
|
||||
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
||||
await staker.moveStakeAsync({ state: StakeState.Inactive }, { state: StakeState.Active }, amount);
|
||||
await staker.goToNextEpochAsync(); // stake is active and not withdrawable
|
||||
await staker.unstakeAsync(amount, new StringRevertError('INSUFFICIENT_FUNDS'));
|
||||
});
|
||||
it('should fail to unstake >1 epoch after inactive/withdrawable stake has been reactivated', async () => {
|
||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
||||
await staker.stakeAsync(amount);
|
||||
await staker.moveStakeAsync({ state: StakeState.Active }, { state: StakeState.Inactive }, amount);
|
||||
await staker.goToNextEpochAsync(); // stake is now inactive
|
||||
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
||||
await staker.moveStakeAsync({ state: StakeState.Inactive }, { state: StakeState.Active }, amount);
|
||||
await staker.goToNextEpochAsync(); // stake is active and not withdrawable
|
||||
await staker.goToNextEpochAsync(); // stake is active and not withdrawable
|
||||
await staker.unstakeAsync(amount, new StringRevertError('INSUFFICIENT_FUNDS'));
|
||||
});
|
||||
});
|
||||
describe('Simulations', () => {
|
||||
it('Simulation from Staking Spec', async () => {
|
||||
// Epoch 1: Stake some ZRX
|
||||
await staker.stakeAsync(StakingWrapper.toBaseUnitAmount(4));
|
||||
// Later in Epoch 1: User delegates and deactivates some stake
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Inactive },
|
||||
StakingWrapper.toBaseUnitAmount(1),
|
||||
);
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
StakingWrapper.toBaseUnitAmount(2),
|
||||
);
|
||||
// Epoch 2: State updates (no user intervention required)
|
||||
await staker.goToNextEpochAsync();
|
||||
// Epoch 3: Stake that has been inactive for an epoch can be withdrawn (no user intervention required)
|
||||
await staker.goToNextEpochAsync();
|
||||
// Later in Epoch 3: User reactivates half of their inactive stake; this becomes Active next epoch
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Inactive },
|
||||
{ state: StakeState.Active },
|
||||
StakingWrapper.toBaseUnitAmount(0.5),
|
||||
);
|
||||
// Later in Epoch 3: User re-delegates half of their stake from Pool 1 to Pool 2
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Delegated, poolId: poolIds[0] },
|
||||
{ state: StakeState.Delegated, poolId: poolIds[1] },
|
||||
StakingWrapper.toBaseUnitAmount(1),
|
||||
);
|
||||
// Epoch 4: State updates (no user intervention required)
|
||||
await staker.goToNextEpochAsync();
|
||||
// Later in Epoch 4: User deactivates all active stake
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Active },
|
||||
{ state: StakeState.Inactive },
|
||||
StakingWrapper.toBaseUnitAmount(1.5),
|
||||
);
|
||||
// Later in Epoch 4: User withdraws all available inactive stake
|
||||
await staker.unstakeAsync(StakingWrapper.toBaseUnitAmount(0.5));
|
||||
// Epoch 5: State updates (no user intervention required)
|
||||
await staker.goToNextEpochAsync();
|
||||
// Later in Epoch 5: User reactivates a portion of their stake
|
||||
await staker.moveStakeAsync(
|
||||
{ state: StakeState.Inactive },
|
||||
{ state: StakeState.Active },
|
||||
StakingWrapper.toBaseUnitAmount(1),
|
||||
);
|
||||
// Epoch 6: State updates (no user intervention required)
|
||||
await staker.goToNextEpochAsync();
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:enable:no-unnecessary-type-assertion
|
||||
|
@@ -1,8 +1,11 @@
|
||||
/*
|
||||
@TODO (hysz) - update once new staking mechanics are merged
|
||||
|
||||
import { expect } from '@0x/contracts-test-utils';
|
||||
import { chaiSetup } from '@0x/contracts-test-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DelegatorActor } from '../actors/delegator_actor';
|
||||
import { MakerActor } from '../actors/maker_actor';
|
||||
import { PoolOperatorActor } from '../actors/pool_operator_actor';
|
||||
|
||||
@@ -13,9 +16,6 @@ import { SimulationParams } from './types';
|
||||
const REWARD_PRECISION = 12;
|
||||
|
||||
export class Simulation {
|
||||
/*
|
||||
|
||||
|
||||
private readonly _stakingWrapper: StakingWrapper;
|
||||
private readonly _p: SimulationParams;
|
||||
private _userQueue: Queue<string>;
|
||||
@@ -275,5 +275,5 @@ export class Simulation {
|
||||
Simulation._assertRewardsEqual(reward, expectedReward, `reward withdrawn from pool ${poolId} for operator`);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
*/
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { BaseContract } from '@0x/base-contract';
|
||||
import { ERC20ProxyContract } from '@0x/contracts-asset-proxy';
|
||||
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { constants as testUtilsConstants, LogDecoder, txDefaults } from '@0x/contracts-test-utils';
|
||||
@@ -10,11 +9,11 @@ import * as _ from 'lodash';
|
||||
|
||||
import {
|
||||
artifacts,
|
||||
EthVaultContract,
|
||||
StakingContract,
|
||||
StakingPoolRewardVaultContract,
|
||||
StakingProxyContract,
|
||||
ZrxVaultContract,
|
||||
EthVaultContract,
|
||||
} from '../../src';
|
||||
|
||||
import { ApprovalFactory } from './approval_factory';
|
||||
@@ -61,7 +60,6 @@ export class StakingWrapper {
|
||||
.dividedBy(scalar);
|
||||
return amountAsFloatingPoint;
|
||||
}
|
||||
|
||||
constructor(
|
||||
provider: Provider,
|
||||
ownerAddres: string,
|
||||
@@ -123,7 +121,9 @@ export class StakingWrapper {
|
||||
artifacts,
|
||||
);
|
||||
// set eth vault in reward vault
|
||||
await this._rewardVaultContractIfExists.setEthVault.sendTransactionAsync(this._ethVaultContractIfExists.address);
|
||||
await this._rewardVaultContractIfExists.setEthVault.sendTransactionAsync(
|
||||
this._ethVaultContractIfExists.address,
|
||||
);
|
||||
// configure erc20 proxy to accept calls from zrx vault
|
||||
await this._erc20ProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
this._zrxVaultContractIfExists.address,
|
||||
@@ -181,23 +181,13 @@ export class StakingWrapper {
|
||||
return balance;
|
||||
}
|
||||
///// STAKE /////
|
||||
public async stakeAsync(
|
||||
owner: string,
|
||||
amount: BigNumber,
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const calldata = this.getStakingContract().stake.getABIEncodedTransactionData(
|
||||
amount,
|
||||
);
|
||||
public async stakeAsync(owner: string, amount: BigNumber): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const calldata = this.getStakingContract().stake.getABIEncodedTransactionData(amount);
|
||||
const txReceipt = await this._executeTransactionAsync(calldata, owner);
|
||||
return txReceipt;
|
||||
}
|
||||
public async unstakeAsync(
|
||||
owner: string,
|
||||
amount: BigNumber,
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const calldata = this.getStakingContract().unstake.getABIEncodedTransactionData(
|
||||
amount,
|
||||
);
|
||||
public async unstakeAsync(owner: string, amount: BigNumber): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const calldata = this.getStakingContract().unstake.getABIEncodedTransactionData(amount);
|
||||
const txReceipt = await this._executeTransactionAsync(calldata, owner);
|
||||
return txReceipt;
|
||||
}
|
||||
@@ -377,10 +367,10 @@ export class StakingWrapper {
|
||||
return txReceipt;
|
||||
}
|
||||
public async fastForwardToNextEpochAsync(): Promise<void> {
|
||||
// increase timestamp of next block
|
||||
const epochDurationInSeconds = await this.getEpochDurationInSecondsAsync();
|
||||
await this._web3Wrapper.increaseTimeAsync(epochDurationInSeconds.toNumber());
|
||||
// mine next block
|
||||
// increase timestamp of next block
|
||||
const epochDurationInSeconds = await this.getEpochDurationInSecondsAsync();
|
||||
await this._web3Wrapper.increaseTimeAsync(epochDurationInSeconds.toNumber());
|
||||
// mine next block
|
||||
await this._web3Wrapper.mineBlockAsync();
|
||||
}
|
||||
public async skipToNextEpochAsync(): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
@@ -476,9 +466,7 @@ export class StakingWrapper {
|
||||
owner,
|
||||
);
|
||||
const returnData = await this._callAsync(calldata);
|
||||
const value = this.getStakingContract().computeRewardBalanceOfDelegator.getABIDecodedReturnData(
|
||||
returnData,
|
||||
);
|
||||
const value = this.getStakingContract().computeRewardBalanceOfDelegator.getABIDecodedReturnData(returnData);
|
||||
return value;
|
||||
}
|
||||
///// REWARD VAULT /////
|
||||
|
@@ -54,8 +54,64 @@ export interface StakeBalance {
|
||||
nextEpochBalance: BigNumber,
|
||||
}
|
||||
|
||||
export interface StakeBalanceByPool {
|
||||
[key: string]: StakeBalance;
|
||||
}
|
||||
|
||||
|
||||
export enum StakeStatus {
|
||||
ACTIVE,
|
||||
INACTIVE,
|
||||
DELEGATED
|
||||
};
|
||||
Active,
|
||||
Inactive,
|
||||
Delegated,
|
||||
}
|
||||
|
||||
export interface StakeInfo {
|
||||
Status: StakeStatus;
|
||||
poolId?: string;
|
||||
}
|
||||
|
||||
export interface StakeBalances {
|
||||
zrxBalance: BigNumber;
|
||||
stakeBalance: BigNumber;
|
||||
stakeBalanceInVault: BigNumber;
|
||||
withdrawableStakeBalance: BigNumber;
|
||||
activeStakeBalance: StakeBalance;
|
||||
inactiveStakeBalance: StakeBalance;
|
||||
delegatedStakeBalance: StakeBalance;
|
||||
delegatedStakeByPool: StakeBalanceByPool;
|
||||
totalDelegatedStakeByPool: StakeBalanceByPool;
|
||||
}
|
||||
|
||||
export interface RewardVaultBalance {
|
||||
poolBalance: BigNumber;
|
||||
operatorBalance: BigNumber;
|
||||
membersBalance: BigNumber;
|
||||
}
|
||||
|
||||
export interface RewardVaultBalanceByPoolId {
|
||||
[key: string]: RewardVaultBalance;
|
||||
}
|
||||
|
||||
export interface OperatorShareByPoolId {
|
||||
[key: string]: BigNumber;
|
||||
}
|
||||
|
||||
export interface BalanceByOwner {
|
||||
[key: string]: BigNumber;
|
||||
}
|
||||
|
||||
export interface RewardByPoolId {
|
||||
[key: string]: BigNumber;
|
||||
}
|
||||
|
||||
export interface MemberBalancesByPoolId {
|
||||
[key: string]: BalanceByOwner;
|
||||
}
|
||||
|
||||
export interface OperatorByPoolId {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface MembersByPoolId {
|
||||
[key: string]: string[];
|
||||
}
|
||||
|
Reference in New Issue
Block a user