Merge pull request #2129 from 0xProject/features/contracts-staking/rip-staking-wrapper
Remove StakingWrapper
This commit is contained in:
@@ -29,6 +29,7 @@ contract ReadOnlyProxy is
|
|||||||
|
|
||||||
/// @dev Executes a read-only call to the staking contract, via `revertDelegateCall`.
|
/// @dev Executes a read-only call to the staking contract, via `revertDelegateCall`.
|
||||||
/// By routing through `revertDelegateCall` any state changes are reverted.
|
/// By routing through `revertDelegateCall` any state changes are reverted.
|
||||||
|
// solhint-disable-next-line payable-fallback
|
||||||
function ()
|
function ()
|
||||||
external
|
external
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { StakingWrapper } from '../utils/staking_wrapper';
|
import { StakingApiWrapper } from '../utils/api_wrapper';
|
||||||
|
|
||||||
export class BaseActor {
|
export class BaseActor {
|
||||||
protected readonly _owner: string;
|
protected readonly _owner: string;
|
||||||
protected readonly _stakingWrapper: StakingWrapper;
|
protected readonly _stakingApiWrapper: StakingApiWrapper;
|
||||||
|
|
||||||
constructor(owner: string, stakingWrapper: StakingWrapper) {
|
constructor(owner: string, stakingApiWrapper: StakingApiWrapper) {
|
||||||
this._owner = owner;
|
this._owner = owner;
|
||||||
this._stakingWrapper = stakingWrapper;
|
this._stakingApiWrapper = stakingApiWrapper;
|
||||||
}
|
}
|
||||||
public getOwner(): string {
|
public getOwner(): string {
|
||||||
return this._owner;
|
return this._owner;
|
||||||
}
|
}
|
||||||
public getStakingWrapper(): StakingWrapper {
|
public getStakingApiWrapper(): StakingApiWrapper {
|
||||||
return this._stakingWrapper;
|
return this._stakingApiWrapper;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { expect } from '@0x/contracts-test-utils';
|
|||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { StakingWrapper } from '../utils/staking_wrapper';
|
import { StakingApiWrapper } from '../utils/api_wrapper';
|
||||||
import {
|
import {
|
||||||
MemberBalancesByPoolId,
|
MemberBalancesByPoolId,
|
||||||
MembersByPoolId,
|
MembersByPoolId,
|
||||||
@@ -28,12 +28,12 @@ export class FinalizerActor extends BaseActor {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
owner: string,
|
owner: string,
|
||||||
stakingWrapper: StakingWrapper,
|
stakingApiWrapper: StakingApiWrapper,
|
||||||
poolIds: string[],
|
poolIds: string[],
|
||||||
operatorByPoolId: OperatorByPoolId,
|
operatorByPoolId: OperatorByPoolId,
|
||||||
membersByPoolId: MembersByPoolId,
|
membersByPoolId: MembersByPoolId,
|
||||||
) {
|
) {
|
||||||
super(owner, stakingWrapper);
|
super(owner, stakingApiWrapper);
|
||||||
this._poolIds = _.cloneDeep(poolIds);
|
this._poolIds = _.cloneDeep(poolIds);
|
||||||
this._operatorByPoolId = _.cloneDeep(operatorByPoolId);
|
this._operatorByPoolId = _.cloneDeep(operatorByPoolId);
|
||||||
this._membersByPoolId = _.cloneDeep(membersByPoolId);
|
this._membersByPoolId = _.cloneDeep(membersByPoolId);
|
||||||
@@ -59,7 +59,7 @@ export class FinalizerActor extends BaseActor {
|
|||||||
memberRewardByPoolId,
|
memberRewardByPoolId,
|
||||||
);
|
);
|
||||||
// finalize
|
// finalize
|
||||||
await this._stakingWrapper.skipToNextEpochAsync();
|
await this._stakingApiWrapper.utils.skipToNextEpochAsync();
|
||||||
// assert reward vault changes
|
// 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(
|
expect(finalRewardVaultBalanceByPoolId, 'final pool balances in reward vault').to.be.deep.equal(
|
||||||
@@ -82,15 +82,16 @@ export class FinalizerActor extends BaseActor {
|
|||||||
if (rewardByPoolId[poolId] === undefined) {
|
if (rewardByPoolId[poolId] === undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const totalStakeDelegatedToPool = (await this._stakingWrapper.getTotalStakeDelegatedToPoolAsync(poolId))
|
const totalStakeDelegatedToPool = (await this._stakingApiWrapper.stakingContract.getTotalStakeDelegatedToPool.callAsync(
|
||||||
.currentEpochBalance;
|
poolId,
|
||||||
|
)).currentEpochBalance;
|
||||||
for (const member of membersByPoolId[poolId]) {
|
for (const member of membersByPoolId[poolId]) {
|
||||||
if (totalStakeDelegatedToPool.eq(0)) {
|
if (totalStakeDelegatedToPool.eq(0)) {
|
||||||
expectedMemberBalancesByPoolId[poolId][member] = new BigNumber(0);
|
expectedMemberBalancesByPoolId[poolId][member] = new BigNumber(0);
|
||||||
} else {
|
} else {
|
||||||
const stakeDelegatedToPoolByMember = (await this._stakingWrapper.getStakeDelegatedToPoolByOwnerAsync(
|
const stakeDelegatedToPoolByMember = (await this._stakingApiWrapper.stakingContract.getStakeDelegatedToPoolByOwner.callAsync(
|
||||||
poolId,
|
|
||||||
member,
|
member,
|
||||||
|
poolId,
|
||||||
)).currentEpochBalance;
|
)).currentEpochBalance;
|
||||||
const rewardThisEpoch = rewardByPoolId[poolId]
|
const rewardThisEpoch = rewardByPoolId[poolId]
|
||||||
.times(stakeDelegatedToPoolByMember)
|
.times(stakeDelegatedToPoolByMember)
|
||||||
@@ -113,7 +114,10 @@ export class FinalizerActor extends BaseActor {
|
|||||||
for (const member of members) {
|
for (const member of members) {
|
||||||
memberBalancesByPoolId[poolId][
|
memberBalancesByPoolId[poolId][
|
||||||
member
|
member
|
||||||
] = await this._stakingWrapper.computeRewardBalanceOfStakingPoolMemberAsync(poolId, member);
|
] = await this._stakingApiWrapper.stakingContract.computeRewardBalanceOfDelegator.callAsync(
|
||||||
|
poolId,
|
||||||
|
member,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return memberBalancesByPoolId;
|
return memberBalancesByPoolId;
|
||||||
@@ -143,8 +147,9 @@ export class FinalizerActor extends BaseActor {
|
|||||||
rewardVaultBalance: RewardVaultBalance,
|
rewardVaultBalance: RewardVaultBalance,
|
||||||
operatorShare: BigNumber,
|
operatorShare: BigNumber,
|
||||||
): Promise<RewardVaultBalance> {
|
): Promise<RewardVaultBalance> {
|
||||||
const totalStakeDelegatedToPool = (await this._stakingWrapper.getTotalStakeDelegatedToPoolAsync(poolId))
|
const totalStakeDelegatedToPool = (await this._stakingApiWrapper.stakingContract.getTotalStakeDelegatedToPool.callAsync(
|
||||||
.currentEpochBalance;
|
poolId,
|
||||||
|
)).currentEpochBalance;
|
||||||
const operatorPortion = totalStakeDelegatedToPool.eq(0)
|
const operatorPortion = totalStakeDelegatedToPool.eq(0)
|
||||||
? reward
|
? reward
|
||||||
: reward.times(operatorShare).dividedToIntegerBy(100);
|
: reward.times(operatorShare).dividedToIntegerBy(100);
|
||||||
@@ -159,9 +164,7 @@ export class FinalizerActor extends BaseActor {
|
|||||||
private async _getOperatorShareByPoolIdAsync(poolIds: string[]): Promise<OperatorShareByPoolId> {
|
private async _getOperatorShareByPoolIdAsync(poolIds: string[]): Promise<OperatorShareByPoolId> {
|
||||||
const operatorShareByPoolId: OperatorShareByPoolId = {};
|
const operatorShareByPoolId: OperatorShareByPoolId = {};
|
||||||
for (const poolId of poolIds) {
|
for (const poolId of poolIds) {
|
||||||
const operatorShare = await this._stakingWrapper
|
const operatorShare = await this._stakingApiWrapper.rewardVaultContract.getOperatorShare.callAsync(poolId);
|
||||||
.getStakingPoolRewardVaultContract()
|
|
||||||
.getOperatorShare.callAsync(poolId);
|
|
||||||
operatorShareByPoolId[poolId] = operatorShare;
|
operatorShareByPoolId[poolId] = operatorShare;
|
||||||
}
|
}
|
||||||
return operatorShareByPoolId;
|
return operatorShareByPoolId;
|
||||||
@@ -177,9 +180,9 @@ export class FinalizerActor extends BaseActor {
|
|||||||
|
|
||||||
private async _getRewardVaultBalanceAsync(poolId: string): Promise<RewardVaultBalance> {
|
private async _getRewardVaultBalanceAsync(poolId: string): Promise<RewardVaultBalance> {
|
||||||
const balances = await Promise.all([
|
const balances = await Promise.all([
|
||||||
this._stakingWrapper.rewardVaultBalanceOfAsync(poolId),
|
this._stakingApiWrapper.rewardVaultContract.balanceOf.callAsync(poolId),
|
||||||
this._stakingWrapper.rewardVaultBalanceOfOperatorAsync(poolId),
|
this._stakingApiWrapper.rewardVaultContract.balanceOfOperator.callAsync(poolId),
|
||||||
this._stakingWrapper.rewardVaultBalanceOfMembersAsync(poolId),
|
this._stakingApiWrapper.rewardVaultContract.balanceOfMembers.callAsync(poolId),
|
||||||
]);
|
]);
|
||||||
return {
|
return {
|
||||||
poolBalance: balances[0],
|
poolBalance: balances[0],
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import { BaseActor } from './base_actor';
|
|||||||
export class MakerActor extends BaseActor {
|
export class MakerActor extends BaseActor {
|
||||||
public async joinStakingPoolAsMakerAsync(poolId: string, revertError?: RevertError): Promise<void> {
|
public async joinStakingPoolAsMakerAsync(poolId: string, revertError?: RevertError): Promise<void> {
|
||||||
// Join pool
|
// Join pool
|
||||||
const txReceiptPromise = this._stakingWrapper.joinStakingPoolAsMakerAsync(poolId, this._owner);
|
const txReceiptPromise = this._stakingApiWrapper.stakingContract.joinStakingPoolAsMaker.awaitTransactionSuccessAsync(
|
||||||
|
poolId,
|
||||||
|
{ from: this._owner },
|
||||||
|
);
|
||||||
|
|
||||||
if (revertError !== undefined) {
|
if (revertError !== undefined) {
|
||||||
await expect(txReceiptPromise).to.revertWith(revertError);
|
await expect(txReceiptPromise).to.revertWith(revertError);
|
||||||
@@ -18,7 +21,9 @@ export class MakerActor extends BaseActor {
|
|||||||
await txReceiptPromise;
|
await txReceiptPromise;
|
||||||
|
|
||||||
// Pool id of the maker should be nil (join would've thrown otherwise)
|
// Pool id of the maker should be nil (join would've thrown otherwise)
|
||||||
const poolIdOfMaker = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(this._owner);
|
const poolIdOfMaker = await this._stakingApiWrapper.stakingContract.getStakingPoolIdOfMaker.callAsync(
|
||||||
|
this._owner,
|
||||||
|
);
|
||||||
expect(poolIdOfMaker, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
|
expect(poolIdOfMaker, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,10 +33,10 @@ export class MakerActor extends BaseActor {
|
|||||||
revertError?: RevertError,
|
revertError?: RevertError,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// remove maker (should fail if makerAddress !== this._owner)
|
// remove maker (should fail if makerAddress !== this._owner)
|
||||||
const txReceiptPromise = this._stakingWrapper.removeMakerFromStakingPoolAsync(
|
const txReceiptPromise = this._stakingApiWrapper.stakingContract.removeMakerFromStakingPool.awaitTransactionSuccessAsync(
|
||||||
poolId,
|
poolId,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
this._owner,
|
{ from: this._owner },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (revertError !== undefined) {
|
if (revertError !== undefined) {
|
||||||
@@ -41,7 +46,9 @@ export class MakerActor extends BaseActor {
|
|||||||
await txReceiptPromise;
|
await txReceiptPromise;
|
||||||
|
|
||||||
// check the pool id of the maker
|
// check the pool id of the maker
|
||||||
const poolIdOfMakerAfterRemoving = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(this._owner);
|
const poolIdOfMakerAfterRemoving = await this._stakingApiWrapper.stakingContract.getStakingPoolIdOfMaker.callAsync(
|
||||||
|
this._owner,
|
||||||
|
);
|
||||||
expect(poolIdOfMakerAfterRemoving, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
|
expect(poolIdOfMakerAfterRemoving, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ export class PoolOperatorActor extends BaseActor {
|
|||||||
revertError?: RevertError,
|
revertError?: RevertError,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// query next pool id
|
// query next pool id
|
||||||
const nextPoolId = await this._stakingWrapper.getNextStakingPoolIdAsync();
|
const nextPoolId = await this._stakingApiWrapper.stakingContract.getNextStakingPoolId.callAsync();
|
||||||
// create pool
|
// create pool
|
||||||
const poolIdPromise = this._stakingWrapper.createStakingPoolAsync(
|
const poolIdPromise = this._stakingApiWrapper.utils.createStakingPoolAsync(
|
||||||
this._owner,
|
this._owner,
|
||||||
operatorShare,
|
operatorShare,
|
||||||
addOperatorAsMaker,
|
addOperatorAsMaker,
|
||||||
@@ -30,10 +30,14 @@ export class PoolOperatorActor extends BaseActor {
|
|||||||
|
|
||||||
if (addOperatorAsMaker) {
|
if (addOperatorAsMaker) {
|
||||||
// check the pool id of the operator
|
// check the pool id of the operator
|
||||||
const poolIdOfMaker = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(this._owner);
|
const poolIdOfMaker = await this._stakingApiWrapper.stakingContract.getStakingPoolIdOfMaker.callAsync(
|
||||||
|
this._owner,
|
||||||
|
);
|
||||||
expect(poolIdOfMaker, 'pool id of maker').to.be.equal(poolId);
|
expect(poolIdOfMaker, 'pool id of maker').to.be.equal(poolId);
|
||||||
// check the number of makers in the pool
|
// check the number of makers in the pool
|
||||||
const numMakersAfterRemoving = await this._stakingWrapper.getNumberOfMakersInStakingPoolAsync(poolId);
|
const numMakersAfterRemoving = await this._stakingApiWrapper.stakingContract.getNumberOfMakersInStakingPool.callAsync(
|
||||||
|
poolId,
|
||||||
|
);
|
||||||
expect(numMakersAfterRemoving, 'number of makers in pool').to.be.bignumber.equal(1);
|
expect(numMakersAfterRemoving, 'number of makers in pool').to.be.bignumber.equal(1);
|
||||||
}
|
}
|
||||||
return poolId;
|
return poolId;
|
||||||
@@ -44,14 +48,20 @@ export class PoolOperatorActor extends BaseActor {
|
|||||||
revertError?: RevertError,
|
revertError?: RevertError,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// add maker
|
// add maker
|
||||||
const txReceiptPromise = this._stakingWrapper.addMakerToStakingPoolAsync(poolId, makerAddress, this._owner);
|
const txReceiptPromise = this._stakingApiWrapper.stakingContract.addMakerToStakingPool.awaitTransactionSuccessAsync(
|
||||||
|
poolId,
|
||||||
|
makerAddress,
|
||||||
|
{ from: this._owner },
|
||||||
|
);
|
||||||
if (revertError !== undefined) {
|
if (revertError !== undefined) {
|
||||||
await expect(txReceiptPromise).to.revertWith(revertError);
|
await expect(txReceiptPromise).to.revertWith(revertError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await txReceiptPromise;
|
await txReceiptPromise;
|
||||||
// check the pool id of the maker
|
// check the pool id of the maker
|
||||||
const poolIdOfMaker = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(makerAddress);
|
const poolIdOfMaker = await this._stakingApiWrapper.stakingContract.getStakingPoolIdOfMaker.callAsync(
|
||||||
|
makerAddress,
|
||||||
|
);
|
||||||
expect(poolIdOfMaker, 'pool id of maker').to.be.equal(poolId);
|
expect(poolIdOfMaker, 'pool id of maker').to.be.equal(poolId);
|
||||||
}
|
}
|
||||||
public async removeMakerFromStakingPoolAsync(
|
public async removeMakerFromStakingPoolAsync(
|
||||||
@@ -60,10 +70,10 @@ export class PoolOperatorActor extends BaseActor {
|
|||||||
revertError?: RevertError,
|
revertError?: RevertError,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// remove maker
|
// remove maker
|
||||||
const txReceiptPromise = this._stakingWrapper.removeMakerFromStakingPoolAsync(
|
const txReceiptPromise = this._stakingApiWrapper.stakingContract.removeMakerFromStakingPool.awaitTransactionSuccessAsync(
|
||||||
poolId,
|
poolId,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
this._owner,
|
{ from: this._owner },
|
||||||
);
|
);
|
||||||
if (revertError !== undefined) {
|
if (revertError !== undefined) {
|
||||||
await expect(txReceiptPromise).to.revertWith(revertError);
|
await expect(txReceiptPromise).to.revertWith(revertError);
|
||||||
@@ -71,7 +81,9 @@ export class PoolOperatorActor extends BaseActor {
|
|||||||
}
|
}
|
||||||
await txReceiptPromise;
|
await txReceiptPromise;
|
||||||
// check the pool id of the maker
|
// check the pool id of the maker
|
||||||
const poolIdOfMakerAfterRemoving = await this._stakingWrapper.getStakingPoolIdOfMakerAsync(makerAddress);
|
const poolIdOfMakerAfterRemoving = await this._stakingApiWrapper.stakingContract.getStakingPoolIdOfMaker.callAsync(
|
||||||
|
makerAddress,
|
||||||
|
);
|
||||||
expect(poolIdOfMakerAfterRemoving, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
|
expect(poolIdOfMakerAfterRemoving, 'pool id of maker').to.be.equal(stakingConstants.NIL_POOL_ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { expect } from '@0x/contracts-test-utils';
|
|||||||
import { BigNumber, RevertError } from '@0x/utils';
|
import { BigNumber, RevertError } from '@0x/utils';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { StakingWrapper } from '../utils/staking_wrapper';
|
import { StakingApiWrapper } from '../utils/api_wrapper';
|
||||||
import { StakeBalances, StakeInfo, StakeStatus } from '../utils/types';
|
import { StakeBalances, StakeInfo, StakeStatus } from '../utils/types';
|
||||||
|
|
||||||
import { BaseActor } from './base_actor';
|
import { BaseActor } from './base_actor';
|
||||||
@@ -10,16 +10,18 @@ import { BaseActor } from './base_actor';
|
|||||||
export class StakerActor extends BaseActor {
|
export class StakerActor extends BaseActor {
|
||||||
private readonly _poolIds: string[];
|
private readonly _poolIds: string[];
|
||||||
|
|
||||||
constructor(owner: string, stakingWrapper: StakingWrapper) {
|
constructor(owner: string, stakingApiWrapper: StakingApiWrapper) {
|
||||||
super(owner, stakingWrapper);
|
super(owner, stakingApiWrapper);
|
||||||
this._poolIds = [];
|
this._poolIds = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public async stakeAsync(amount: BigNumber, revertError?: RevertError): Promise<void> {
|
public async stakeAsync(amount: BigNumber, revertError?: RevertError): Promise<void> {
|
||||||
const initZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
const initZrxBalanceOfVault = await this._stakingApiWrapper.utils.getZrxTokenBalanceOfZrxVaultAsync();
|
||||||
const initStakerBalances = await this.getBalancesAsync();
|
const initStakerBalances = await this.getBalancesAsync();
|
||||||
// deposit stake
|
// deposit stake
|
||||||
const txReceiptPromise = this._stakingWrapper.stakeAsync(this._owner, amount);
|
const txReceiptPromise = this._stakingApiWrapper.stakingContract.stake.awaitTransactionSuccessAsync(amount, {
|
||||||
|
from: this._owner,
|
||||||
|
});
|
||||||
if (revertError !== undefined) {
|
if (revertError !== undefined) {
|
||||||
await expect(txReceiptPromise, 'expected revert error').to.revertWith(revertError);
|
await expect(txReceiptPromise, 'expected revert error').to.revertWith(revertError);
|
||||||
return;
|
return;
|
||||||
@@ -38,17 +40,19 @@ export class StakerActor extends BaseActor {
|
|||||||
);
|
);
|
||||||
await this.assertBalancesAsync(expectedStakerBalances);
|
await this.assertBalancesAsync(expectedStakerBalances);
|
||||||
// check zrx balance of vault
|
// check zrx balance of vault
|
||||||
const finalZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
const finalZrxBalanceOfVault = await this._stakingApiWrapper.utils.getZrxTokenBalanceOfZrxVaultAsync();
|
||||||
expect(finalZrxBalanceOfVault, 'final balance of zrx vault').to.be.bignumber.equal(
|
expect(finalZrxBalanceOfVault, 'final balance of zrx vault').to.be.bignumber.equal(
|
||||||
initZrxBalanceOfVault.plus(amount),
|
initZrxBalanceOfVault.plus(amount),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async unstakeAsync(amount: BigNumber, revertError?: RevertError): Promise<void> {
|
public async unstakeAsync(amount: BigNumber, revertError?: RevertError): Promise<void> {
|
||||||
const initZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
const initZrxBalanceOfVault = await this._stakingApiWrapper.utils.getZrxTokenBalanceOfZrxVaultAsync();
|
||||||
const initStakerBalances = await this.getBalancesAsync();
|
const initStakerBalances = await this.getBalancesAsync();
|
||||||
// deposit stake
|
// deposit stake
|
||||||
const txReceiptPromise = this._stakingWrapper.unstakeAsync(this._owner, amount);
|
const txReceiptPromise = this._stakingApiWrapper.stakingContract.unstake.awaitTransactionSuccessAsync(amount, {
|
||||||
|
from: this._owner,
|
||||||
|
});
|
||||||
if (revertError !== undefined) {
|
if (revertError !== undefined) {
|
||||||
await expect(txReceiptPromise, 'expected revert error').to.revertWith(revertError);
|
await expect(txReceiptPromise, 'expected revert error').to.revertWith(revertError);
|
||||||
return;
|
return;
|
||||||
@@ -68,7 +72,7 @@ export class StakerActor extends BaseActor {
|
|||||||
expectedStakerBalances.withdrawableStakeBalance = initStakerBalances.withdrawableStakeBalance.minus(amount);
|
expectedStakerBalances.withdrawableStakeBalance = initStakerBalances.withdrawableStakeBalance.minus(amount);
|
||||||
await this.assertBalancesAsync(expectedStakerBalances);
|
await this.assertBalancesAsync(expectedStakerBalances);
|
||||||
// check zrx balance of vault
|
// check zrx balance of vault
|
||||||
const finalZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
const finalZrxBalanceOfVault = await this._stakingApiWrapper.utils.getZrxTokenBalanceOfZrxVaultAsync();
|
||||||
expect(finalZrxBalanceOfVault, 'final balance of zrx vault').to.be.bignumber.equal(
|
expect(finalZrxBalanceOfVault, 'final balance of zrx vault').to.be.bignumber.equal(
|
||||||
initZrxBalanceOfVault.minus(amount),
|
initZrxBalanceOfVault.minus(amount),
|
||||||
);
|
);
|
||||||
@@ -85,7 +89,7 @@ export class StakerActor extends BaseActor {
|
|||||||
this._poolIds.push(to.poolId);
|
this._poolIds.push(to.poolId);
|
||||||
}
|
}
|
||||||
// cache balances
|
// cache balances
|
||||||
const initZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
const initZrxBalanceOfVault = await this._stakingApiWrapper.utils.getZrxTokenBalanceOfZrxVaultAsync();
|
||||||
const initStakerBalances = await this.getBalancesAsync();
|
const initStakerBalances = await this.getBalancesAsync();
|
||||||
// @TODO check receipt logs and return value via eth_call
|
// @TODO check receipt logs and return value via eth_call
|
||||||
// check balances
|
// check balances
|
||||||
@@ -141,7 +145,12 @@ export class StakerActor extends BaseActor {
|
|||||||
].nextEpochBalance = initStakerBalances.totalDelegatedStakeByPool[to.poolId].nextEpochBalance.plus(amount);
|
].nextEpochBalance = initStakerBalances.totalDelegatedStakeByPool[to.poolId].nextEpochBalance.plus(amount);
|
||||||
}
|
}
|
||||||
// move stake
|
// move stake
|
||||||
const txReceiptPromise = this._stakingWrapper.moveStakeAsync(this._owner, from, to, amount);
|
const txReceiptPromise = this._stakingApiWrapper.stakingContract.moveStake.awaitTransactionSuccessAsync(
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
amount,
|
||||||
|
{ from: this._owner },
|
||||||
|
);
|
||||||
if (revertError !== undefined) {
|
if (revertError !== undefined) {
|
||||||
await expect(txReceiptPromise).to.revertWith(revertError);
|
await expect(txReceiptPromise).to.revertWith(revertError);
|
||||||
return;
|
return;
|
||||||
@@ -150,21 +159,21 @@ export class StakerActor extends BaseActor {
|
|||||||
// check balances
|
// check balances
|
||||||
await this.assertBalancesAsync(expectedStakerBalances);
|
await this.assertBalancesAsync(expectedStakerBalances);
|
||||||
// check zrx balance of vault
|
// check zrx balance of vault
|
||||||
const finalZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
const finalZrxBalanceOfVault = await this._stakingApiWrapper.utils.getZrxTokenBalanceOfZrxVaultAsync();
|
||||||
expect(finalZrxBalanceOfVault, 'final balance of zrx vault').to.be.bignumber.equal(initZrxBalanceOfVault);
|
expect(finalZrxBalanceOfVault, 'final balance of zrx vault').to.be.bignumber.equal(initZrxBalanceOfVault);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async goToNextEpochAsync(): Promise<void> {
|
public async goToNextEpochAsync(): Promise<void> {
|
||||||
// cache balances
|
// cache balances
|
||||||
const initZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
const initZrxBalanceOfVault = await this._stakingApiWrapper.utils.getZrxTokenBalanceOfZrxVaultAsync();
|
||||||
const initStakerBalances = await this.getBalancesAsync();
|
const initStakerBalances = await this.getBalancesAsync();
|
||||||
// go to next epoch
|
// go to next epoch
|
||||||
await this._stakingWrapper.skipToNextEpochAsync();
|
await this._stakingApiWrapper.utils.skipToNextEpochAsync();
|
||||||
// check balances
|
// check balances
|
||||||
const expectedStakerBalances = this.getNextEpochBalances(initStakerBalances);
|
const expectedStakerBalances = this.getNextEpochBalances(initStakerBalances);
|
||||||
await this.assertBalancesAsync(expectedStakerBalances);
|
await this.assertBalancesAsync(expectedStakerBalances);
|
||||||
// check zrx balance of vault
|
// check zrx balance of vault
|
||||||
const finalZrxBalanceOfVault = await this._stakingWrapper.getZrxTokenBalanceOfZrxVaultAsync();
|
const finalZrxBalanceOfVault = await this._stakingApiWrapper.utils.getZrxTokenBalanceOfZrxVaultAsync();
|
||||||
expect(finalZrxBalanceOfVault, 'final balance of zrx vault').to.be.bignumber.equal(initZrxBalanceOfVault);
|
expect(finalZrxBalanceOfVault, 'final balance of zrx vault').to.be.bignumber.equal(initZrxBalanceOfVault);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,23 +197,27 @@ export class StakerActor extends BaseActor {
|
|||||||
}
|
}
|
||||||
public async getBalancesAsync(): Promise<StakeBalances> {
|
public async getBalancesAsync(): Promise<StakeBalances> {
|
||||||
const stakerBalances: StakeBalances = {
|
const stakerBalances: StakeBalances = {
|
||||||
zrxBalance: await this._stakingWrapper.getZrxTokenBalanceAsync(this._owner),
|
zrxBalance: await this._stakingApiWrapper.zrxTokenContract.balanceOf.callAsync(this._owner),
|
||||||
stakeBalance: await this._stakingWrapper.getTotalStakeAsync(this._owner),
|
stakeBalance: await this._stakingApiWrapper.stakingContract.getTotalStake.callAsync(this._owner),
|
||||||
stakeBalanceInVault: await this._stakingWrapper.getZrxVaultBalanceAsync(this._owner),
|
stakeBalanceInVault: await this._stakingApiWrapper.zrxVaultContract.balanceOf.callAsync(this._owner),
|
||||||
withdrawableStakeBalance: await this._stakingWrapper.getWithdrawableStakeAsync(this._owner),
|
withdrawableStakeBalance: await this._stakingApiWrapper.stakingContract.getWithdrawableStake.callAsync(
|
||||||
activeStakeBalance: await this._stakingWrapper.getActiveStakeAsync(this._owner),
|
this._owner,
|
||||||
inactiveStakeBalance: await this._stakingWrapper.getInactiveStakeAsync(this._owner),
|
),
|
||||||
delegatedStakeBalance: await this._stakingWrapper.getStakeDelegatedByOwnerAsync(this._owner),
|
activeStakeBalance: await this._stakingApiWrapper.stakingContract.getActiveStake.callAsync(this._owner),
|
||||||
|
inactiveStakeBalance: await this._stakingApiWrapper.stakingContract.getInactiveStake.callAsync(this._owner),
|
||||||
|
delegatedStakeBalance: await this._stakingApiWrapper.stakingContract.getStakeDelegatedByOwner.callAsync(
|
||||||
|
this._owner,
|
||||||
|
),
|
||||||
delegatedStakeByPool: {},
|
delegatedStakeByPool: {},
|
||||||
totalDelegatedStakeByPool: {},
|
totalDelegatedStakeByPool: {},
|
||||||
};
|
};
|
||||||
// lookup for each pool
|
// lookup for each pool
|
||||||
for (const poolId of this._poolIds) {
|
for (const poolId of this._poolIds) {
|
||||||
const delegatedStakeBalanceByPool = await this._stakingWrapper.getStakeDelegatedToPoolByOwnerAsync(
|
const delegatedStakeBalanceByPool = await this._stakingApiWrapper.stakingContract.getStakeDelegatedToPoolByOwner.callAsync(
|
||||||
poolId,
|
|
||||||
this._owner,
|
this._owner,
|
||||||
|
poolId,
|
||||||
);
|
);
|
||||||
const totalDelegatedStakeBalanceByPool = await this._stakingWrapper.getTotalStakeDelegatedToPoolAsync(
|
const totalDelegatedStakeBalanceByPool = await this._stakingApiWrapper.stakingContract.getTotalStakeDelegatedToPool.callAsync(
|
||||||
poolId,
|
poolId,
|
||||||
);
|
);
|
||||||
stakerBalances.delegatedStakeByPool[poolId] = delegatedStakeBalanceByPool;
|
stakerBalances.delegatedStakeByPool[poolId] = delegatedStakeBalanceByPool;
|
||||||
@@ -250,7 +263,9 @@ export class StakerActor extends BaseActor {
|
|||||||
}
|
}
|
||||||
public async forceBalanceSyncAsync(): Promise<void> {
|
public async forceBalanceSyncAsync(): Promise<void> {
|
||||||
const initBalances = await this.getBalancesAsync();
|
const initBalances = await this.getBalancesAsync();
|
||||||
await this._stakingWrapper.stakeAsync(this._owner, new BigNumber(0));
|
await this._stakingApiWrapper.stakingContract.stake.awaitTransactionSuccessAsync(new BigNumber(0), {
|
||||||
|
from: this._owner,
|
||||||
|
});
|
||||||
await this.assertBalancesAsync(initBalances);
|
await this.assertBalancesAsync(initBalances);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
|
||||||
import { blockchainTests, describe, expect } from '@0x/contracts-test-utils';
|
import { blockchainTests, describe, expect } from '@0x/contracts-test-utils';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import { LogWithDecodedArgs } from 'ethereum-types';
|
import { LogWithDecodedArgs } from 'ethereum-types';
|
||||||
@@ -7,21 +6,19 @@ import * as _ from 'lodash';
|
|||||||
|
|
||||||
import { StakingProxyReadOnlyModeSetEventArgs } from '../src';
|
import { StakingProxyReadOnlyModeSetEventArgs } from '../src';
|
||||||
|
|
||||||
import { StakingWrapper } from './utils/staking_wrapper';
|
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
|
||||||
|
import { toBaseUnitAmount } from './utils/number_utils';
|
||||||
|
|
||||||
// tslint:disable:no-unnecessary-type-assertion
|
// tslint:disable:no-unnecessary-type-assertion
|
||||||
blockchainTests.resets('Catastrophe Tests', env => {
|
blockchainTests.resets('Catastrophe Tests', env => {
|
||||||
// constants
|
// constants
|
||||||
const ZRX_TOKEN_DECIMALS = new BigNumber(18);
|
|
||||||
const ZERO = new BigNumber(0);
|
const ZERO = new BigNumber(0);
|
||||||
// tokens & addresses
|
// tokens & addresses
|
||||||
let accounts: string[];
|
let accounts: string[];
|
||||||
let owner: string;
|
let owner: string;
|
||||||
let actors: string[];
|
let actors: string[];
|
||||||
let zrxTokenContract: DummyERC20TokenContract;
|
|
||||||
let erc20ProxyContract: ERC20ProxyContract;
|
|
||||||
// wrappers
|
// wrappers
|
||||||
let stakingWrapper: StakingWrapper;
|
let stakingApiWrapper: StakingApiWrapper;
|
||||||
let erc20Wrapper: ERC20Wrapper;
|
let erc20Wrapper: ERC20Wrapper;
|
||||||
// tests
|
// tests
|
||||||
before(async () => {
|
before(async () => {
|
||||||
@@ -29,63 +26,64 @@ blockchainTests.resets('Catastrophe Tests', env => {
|
|||||||
accounts = await env.web3Wrapper.getAvailableAddressesAsync();
|
accounts = await env.web3Wrapper.getAvailableAddressesAsync();
|
||||||
owner = accounts[0];
|
owner = accounts[0];
|
||||||
actors = accounts.slice(2, 5);
|
actors = accounts.slice(2, 5);
|
||||||
// deploy erc20 proxy
|
// set up ERC20Wrapper
|
||||||
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
||||||
erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
|
|
||||||
// deploy zrx token
|
|
||||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
|
||||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
|
||||||
// deploy staking contracts
|
// deploy staking contracts
|
||||||
stakingWrapper = new StakingWrapper(
|
stakingApiWrapper = await deployAndConfigureContractsAsync(env, owner, erc20Wrapper);
|
||||||
env.provider,
|
|
||||||
owner,
|
|
||||||
erc20ProxyContract,
|
|
||||||
erc20ProxyContract,
|
|
||||||
zrxTokenContract,
|
|
||||||
);
|
|
||||||
await stakingWrapper.deployAndConfigureContractsAsync();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Read-Only Mode', () => {
|
describe('Read-Only Mode', () => {
|
||||||
it('should be able to change state by default', async () => {
|
it('should be able to change state by default', async () => {
|
||||||
// stake some zrx and assert the balance
|
// stake some zrx and assert the balance
|
||||||
const amountToStake = StakingWrapper.toBaseUnitAmount(10);
|
const amountToStake = toBaseUnitAmount(10);
|
||||||
await stakingWrapper.stakeAsync(actors[0], amountToStake);
|
await stakingApiWrapper.stakingContract.stake.awaitTransactionSuccessAsync(amountToStake, {
|
||||||
const activeStakeBalance = await stakingWrapper.getActiveStakeAsync(actors[0]);
|
from: actors[0],
|
||||||
|
});
|
||||||
|
const activeStakeBalance = await stakingApiWrapper.stakingContract.getActiveStake.callAsync(actors[0]);
|
||||||
expect(activeStakeBalance.currentEpochBalance).to.be.bignumber.equal(amountToStake);
|
expect(activeStakeBalance.currentEpochBalance).to.be.bignumber.equal(amountToStake);
|
||||||
});
|
});
|
||||||
it('should not change state when in read-only mode', async () => {
|
it('should not change state when in read-only mode', async () => {
|
||||||
// set to read-only mode
|
// set to read-only mode
|
||||||
await stakingWrapper.setReadOnlyModeAsync(true);
|
await stakingApiWrapper.stakingProxyContract.setReadOnlyMode.awaitTransactionSuccessAsync(true);
|
||||||
// try to stake
|
// try to stake
|
||||||
const amountToStake = StakingWrapper.toBaseUnitAmount(10);
|
const amountToStake = toBaseUnitAmount(10);
|
||||||
await stakingWrapper.stakeAsync(actors[0], amountToStake);
|
await stakingApiWrapper.stakingContract.stake.awaitTransactionSuccessAsync(amountToStake, {
|
||||||
const activeStakeBalance = await stakingWrapper.getActiveStakeAsync(actors[0]);
|
from: actors[0],
|
||||||
|
});
|
||||||
|
const activeStakeBalance = await stakingApiWrapper.stakingContract.getActiveStake.callAsync(actors[0]);
|
||||||
expect(activeStakeBalance.currentEpochBalance).to.be.bignumber.equal(ZERO);
|
expect(activeStakeBalance.currentEpochBalance).to.be.bignumber.equal(ZERO);
|
||||||
});
|
});
|
||||||
it('should read values correctly when in read-only mode', async () => {
|
it('should read values correctly when in read-only mode', async () => {
|
||||||
// stake some zrx
|
// stake some zrx
|
||||||
const amountToStake = StakingWrapper.toBaseUnitAmount(10);
|
const amountToStake = toBaseUnitAmount(10);
|
||||||
await stakingWrapper.stakeAsync(actors[0], amountToStake);
|
await stakingApiWrapper.stakingContract.stake.awaitTransactionSuccessAsync(amountToStake, {
|
||||||
|
from: actors[0],
|
||||||
|
});
|
||||||
// set to read-only mode
|
// set to read-only mode
|
||||||
await stakingWrapper.setReadOnlyModeAsync(true);
|
await stakingApiWrapper.stakingProxyContract.setReadOnlyMode.awaitTransactionSuccessAsync(true);
|
||||||
// read stake balance in read-only mode
|
// read stake balance in read-only mode
|
||||||
const activeStakeBalanceReadOnly = await stakingWrapper.getActiveStakeAsync(actors[0]);
|
const activeStakeBalanceReadOnly = await stakingApiWrapper.stakingContract.getActiveStake.callAsync(
|
||||||
|
actors[0],
|
||||||
|
);
|
||||||
expect(activeStakeBalanceReadOnly.currentEpochBalance).to.be.bignumber.equal(amountToStake);
|
expect(activeStakeBalanceReadOnly.currentEpochBalance).to.be.bignumber.equal(amountToStake);
|
||||||
});
|
});
|
||||||
it('should exit read-only mode', async () => {
|
it('should exit read-only mode', async () => {
|
||||||
// set to read-only mode
|
// set to read-only mode
|
||||||
await stakingWrapper.setReadOnlyModeAsync(true);
|
await stakingApiWrapper.stakingProxyContract.setReadOnlyMode.awaitTransactionSuccessAsync(true);
|
||||||
await stakingWrapper.setReadOnlyModeAsync(false);
|
await stakingApiWrapper.stakingProxyContract.setReadOnlyMode.awaitTransactionSuccessAsync(false);
|
||||||
// try to stake
|
// try to stake
|
||||||
const amountToStake = StakingWrapper.toBaseUnitAmount(10);
|
const amountToStake = toBaseUnitAmount(10);
|
||||||
await stakingWrapper.stakeAsync(actors[0], amountToStake);
|
await stakingApiWrapper.stakingContract.stake.awaitTransactionSuccessAsync(amountToStake, {
|
||||||
const activeStakeBalance = await stakingWrapper.getActiveStakeAsync(actors[0]);
|
from: actors[0],
|
||||||
|
});
|
||||||
|
const activeStakeBalance = await stakingApiWrapper.stakingContract.getActiveStake.callAsync(actors[0]);
|
||||||
expect(activeStakeBalance.currentEpochBalance).to.be.bignumber.equal(amountToStake);
|
expect(activeStakeBalance.currentEpochBalance).to.be.bignumber.equal(amountToStake);
|
||||||
});
|
});
|
||||||
it('should emit event when setting read-only mode', async () => {
|
it('should emit event when setting read-only mode', async () => {
|
||||||
// set to read-only mode
|
// set to read-only mode
|
||||||
const txReceipt = await stakingWrapper.setReadOnlyModeAsync(true);
|
const txReceipt = await stakingApiWrapper.stakingProxyContract.setReadOnlyMode.awaitTransactionSuccessAsync(
|
||||||
|
true,
|
||||||
|
);
|
||||||
expect(txReceipt.logs.length).to.be.equal(1);
|
expect(txReceipt.logs.length).to.be.equal(1);
|
||||||
const trueLog = txReceipt.logs[0] as LogWithDecodedArgs<StakingProxyReadOnlyModeSetEventArgs>;
|
const trueLog = txReceipt.logs[0] as LogWithDecodedArgs<StakingProxyReadOnlyModeSetEventArgs>;
|
||||||
expect(trueLog.args.readOnlyMode).to.be.true();
|
expect(trueLog.args.readOnlyMode).to.be.true();
|
||||||
|
|||||||
@@ -1,75 +1,47 @@
|
|||||||
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
import { blockchainTests, expect } from '@0x/contracts-test-utils';
|
||||||
import { chaiSetup, provider, web3Wrapper } from '@0x/contracts-test-utils';
|
|
||||||
import { BlockchainLifecycle } from '@0x/dev-utils';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
|
||||||
import * as chai from 'chai';
|
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { artifacts } from '../src';
|
import { artifacts } from '../src';
|
||||||
|
|
||||||
|
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
|
||||||
import { constants as stakingConstants } from './utils/constants';
|
import { constants as stakingConstants } from './utils/constants';
|
||||||
import { StakingWrapper } from './utils/staking_wrapper';
|
|
||||||
|
|
||||||
chaiSetup.configure();
|
|
||||||
const expect = chai.expect;
|
|
||||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
|
||||||
// tslint:disable:no-unnecessary-type-assertion
|
// tslint:disable:no-unnecessary-type-assertion
|
||||||
describe('Epochs', () => {
|
blockchainTests('Epochs', env => {
|
||||||
// constants
|
|
||||||
const ZRX_TOKEN_DECIMALS = new BigNumber(18);
|
|
||||||
// tokens & addresses
|
// tokens & addresses
|
||||||
let accounts: string[];
|
let accounts: string[];
|
||||||
let owner: string;
|
let owner: string;
|
||||||
let zrxTokenContract: DummyERC20TokenContract;
|
|
||||||
let erc20ProxyContract: ERC20ProxyContract;
|
|
||||||
// wrappers
|
// wrappers
|
||||||
let stakingWrapper: StakingWrapper;
|
let stakingApiWrapper: StakingApiWrapper;
|
||||||
let erc20Wrapper: ERC20Wrapper;
|
let erc20Wrapper: ERC20Wrapper;
|
||||||
// tests
|
// tests
|
||||||
before(async () => {
|
|
||||||
await blockchainLifecycle.startAsync();
|
|
||||||
});
|
|
||||||
after(async () => {
|
|
||||||
await blockchainLifecycle.revertAsync();
|
|
||||||
});
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
// create accounts
|
// create accounts
|
||||||
accounts = await web3Wrapper.getAvailableAddressesAsync();
|
accounts = await env.getAccountAddressesAsync();
|
||||||
owner = accounts[0];
|
owner = accounts[0];
|
||||||
// deploy erc20 proxy
|
// set up ERC20Wrapper
|
||||||
erc20Wrapper = new ERC20Wrapper(provider, accounts, owner);
|
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
||||||
erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
|
|
||||||
// deploy zrx token
|
|
||||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
|
||||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
|
||||||
// deploy staking contracts
|
// deploy staking contracts
|
||||||
stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, erc20ProxyContract, zrxTokenContract);
|
stakingApiWrapper = await deployAndConfigureContractsAsync(env, owner, erc20Wrapper, artifacts.TestStaking);
|
||||||
await stakingWrapper.deployAndConfigureContractsAsync(artifacts.TestStaking);
|
|
||||||
});
|
|
||||||
beforeEach(async () => {
|
|
||||||
await blockchainLifecycle.startAsync();
|
|
||||||
});
|
|
||||||
afterEach(async () => {
|
|
||||||
await blockchainLifecycle.revertAsync();
|
|
||||||
});
|
});
|
||||||
describe('Epochs & TimeLocks', () => {
|
describe('Epochs & TimeLocks', () => {
|
||||||
it('basic epochs & timeLock periods', async () => {
|
it('basic epochs & timeLock periods', async () => {
|
||||||
///// 1/3 Validate Assumptions /////
|
///// 1/3 Validate Assumptions /////
|
||||||
expect(await stakingWrapper.getEpochDurationInSecondsAsync()).to.be.bignumber.equal(
|
expect(await stakingApiWrapper.stakingContract.getEpochDurationInSeconds.callAsync()).to.be.bignumber.equal(
|
||||||
stakingConstants.EPOCH_DURATION_IN_SECONDS,
|
stakingConstants.EPOCH_DURATION_IN_SECONDS,
|
||||||
);
|
);
|
||||||
///// 2/3 Validate Initial Epoch & TimeLock Period /////
|
///// 2/3 Validate Initial Epoch & TimeLock Period /////
|
||||||
{
|
{
|
||||||
// epoch
|
// epoch
|
||||||
const currentEpoch = await stakingWrapper.getCurrentEpochAsync();
|
const currentEpoch = await stakingApiWrapper.stakingContract.getCurrentEpoch.callAsync();
|
||||||
expect(currentEpoch).to.be.bignumber.equal(stakingConstants.INITIAL_EPOCH);
|
expect(currentEpoch).to.be.bignumber.equal(stakingConstants.INITIAL_EPOCH);
|
||||||
}
|
}
|
||||||
///// 3/3 Increment Epoch (TimeLock Should Not Increment) /////
|
///// 3/3 Increment Epoch (TimeLock Should Not Increment) /////
|
||||||
await stakingWrapper.skipToNextEpochAsync();
|
await stakingApiWrapper.utils.skipToNextEpochAsync();
|
||||||
{
|
{
|
||||||
// epoch
|
// epoch
|
||||||
const currentEpoch = await stakingWrapper.getCurrentEpochAsync();
|
const currentEpoch = await stakingApiWrapper.stakingContract.getCurrentEpoch.callAsync();
|
||||||
expect(currentEpoch).to.be.bignumber.equal(stakingConstants.INITIAL_EPOCH.plus(1));
|
expect(currentEpoch).to.be.bignumber.equal(stakingConstants.INITIAL_EPOCH.plus(1));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,68 +1,56 @@
|
|||||||
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
|
||||||
import { blockchainTests, expect } from '@0x/contracts-test-utils';
|
import { blockchainTests, expect } from '@0x/contracts-test-utils';
|
||||||
import { StakingRevertErrors } from '@0x/order-utils';
|
import { StakingRevertErrors } from '@0x/order-utils';
|
||||||
import { BigNumber } from '@0x/utils';
|
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { StakingWrapper } from './utils/staking_wrapper';
|
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
|
||||||
|
|
||||||
// tslint:disable:no-unnecessary-type-assertion
|
// tslint:disable:no-unnecessary-type-assertion
|
||||||
blockchainTests('Exchange Integrations', env => {
|
blockchainTests('Exchange Integrations', env => {
|
||||||
// constants
|
|
||||||
const ZRX_TOKEN_DECIMALS = new BigNumber(18);
|
|
||||||
// tokens & addresses
|
// tokens & addresses
|
||||||
let accounts: string[];
|
let accounts: string[];
|
||||||
let owner: string;
|
let owner: string;
|
||||||
let exchange: string;
|
let exchange: string;
|
||||||
let zrxTokenContract: DummyERC20TokenContract;
|
|
||||||
let erc20ProxyContract: ERC20ProxyContract;
|
|
||||||
// wrappers
|
// wrappers
|
||||||
let stakingWrapper: StakingWrapper;
|
let stakingApiWrapper: StakingApiWrapper;
|
||||||
let erc20Wrapper: ERC20Wrapper;
|
let erc20Wrapper: ERC20Wrapper;
|
||||||
// tests
|
// tests
|
||||||
before(async () => {
|
before(async () => {
|
||||||
// create accounts
|
// create accounts
|
||||||
accounts = await env.web3Wrapper.getAvailableAddressesAsync();
|
accounts = await env.getAccountAddressesAsync();
|
||||||
owner = accounts[0];
|
owner = accounts[0];
|
||||||
exchange = accounts[1];
|
exchange = accounts[1];
|
||||||
// deploy erc20 proxy
|
// set up ERC20Wrapper
|
||||||
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
||||||
erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
|
|
||||||
// deploy zrx token
|
|
||||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
|
||||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
|
||||||
// deploy staking contracts
|
// deploy staking contracts
|
||||||
stakingWrapper = new StakingWrapper(
|
stakingApiWrapper = await deployAndConfigureContractsAsync(env, owner, erc20Wrapper);
|
||||||
env.provider,
|
|
||||||
owner,
|
|
||||||
erc20ProxyContract,
|
|
||||||
erc20ProxyContract,
|
|
||||||
zrxTokenContract,
|
|
||||||
);
|
|
||||||
await stakingWrapper.deployAndConfigureContractsAsync();
|
|
||||||
});
|
});
|
||||||
blockchainTests.resets('Exchange Tracking in Staking Contract', () => {
|
blockchainTests.resets('Exchange Tracking in Staking Contract', () => {
|
||||||
it('basic exchange tracking', async () => {
|
it('basic exchange tracking', async () => {
|
||||||
|
const {
|
||||||
|
isValidExchangeAddress,
|
||||||
|
addExchangeAddress,
|
||||||
|
removeExchangeAddress,
|
||||||
|
} = stakingApiWrapper.stakingContract;
|
||||||
// 1 try querying an invalid addresses
|
// 1 try querying an invalid addresses
|
||||||
const invalidAddress = '0x0000000000000000000000000000000000000001';
|
const invalidAddress = '0x0000000000000000000000000000000000000001';
|
||||||
const isInvalidAddressValid = await stakingWrapper.isValidExchangeAddressAsync(invalidAddress);
|
const isInvalidAddressValid = await isValidExchangeAddress.callAsync(invalidAddress);
|
||||||
expect(isInvalidAddressValid).to.be.false();
|
expect(isInvalidAddressValid).to.be.false();
|
||||||
// 2 add valid address
|
// 2 add valid address
|
||||||
await stakingWrapper.addExchangeAddressAsync(exchange);
|
await addExchangeAddress.awaitTransactionSuccessAsync(exchange);
|
||||||
const isValidAddressValid = await stakingWrapper.isValidExchangeAddressAsync(exchange);
|
const isValidAddressValid = await isValidExchangeAddress.callAsync(exchange);
|
||||||
expect(isValidAddressValid).to.be.true();
|
expect(isValidAddressValid).to.be.true();
|
||||||
// 3 try adding valid address again
|
// 3 try adding valid address again
|
||||||
let revertError = new StakingRevertErrors.ExchangeAddressAlreadyRegisteredError(exchange);
|
let revertError = new StakingRevertErrors.ExchangeAddressAlreadyRegisteredError(exchange);
|
||||||
let tx = stakingWrapper.addExchangeAddressAsync(exchange);
|
let tx = addExchangeAddress.awaitTransactionSuccessAsync(exchange);
|
||||||
await expect(tx).to.revertWith(revertError);
|
await expect(tx).to.revertWith(revertError);
|
||||||
// 4 remove valid address
|
// 4 remove valid address
|
||||||
await stakingWrapper.removeExchangeAddressAsync(exchange);
|
await removeExchangeAddress.awaitTransactionSuccessAsync(exchange);
|
||||||
const isValidAddressStillValid = await stakingWrapper.isValidExchangeAddressAsync(exchange);
|
const isValidAddressStillValid = await isValidExchangeAddress.callAsync(exchange);
|
||||||
expect(isValidAddressStillValid).to.be.false();
|
expect(isValidAddressStillValid).to.be.false();
|
||||||
// 5 try removing valid address again
|
// 5 try removing valid address again
|
||||||
revertError = new StakingRevertErrors.ExchangeAddressNotRegisteredError(exchange);
|
revertError = new StakingRevertErrors.ExchangeAddressNotRegisteredError(exchange);
|
||||||
tx = stakingWrapper.removeExchangeAddressAsync(exchange);
|
tx = removeExchangeAddress.awaitTransactionSuccessAsync(exchange);
|
||||||
await expect(tx).to.revertWith(revertError);
|
await expect(tx).to.revertWith(revertError);
|
||||||
// @todo should not be able to add / remove an exchange if not contract owner.
|
// @todo should not be able to add / remove an exchange if not contract owner.
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,72 +1,58 @@
|
|||||||
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
|
||||||
import { blockchainTests, constants, expect } from '@0x/contracts-test-utils';
|
import { blockchainTests, constants, expect } from '@0x/contracts-test-utils';
|
||||||
import { StakingRevertErrors } from '@0x/order-utils';
|
import { StakingRevertErrors } from '@0x/order-utils';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { MakerActor } from './actors/maker_actor';
|
import { MakerActor } from './actors/maker_actor';
|
||||||
import { PoolOperatorActor } from './actors/pool_operator_actor';
|
import { PoolOperatorActor } from './actors/pool_operator_actor';
|
||||||
|
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
|
||||||
import { constants as stakingConstants } from './utils/constants';
|
import { constants as stakingConstants } from './utils/constants';
|
||||||
import { StakingWrapper } from './utils/staking_wrapper';
|
|
||||||
|
|
||||||
// tslint:disable:no-unnecessary-type-assertion
|
// tslint:disable:no-unnecessary-type-assertion
|
||||||
blockchainTests('Staking Pool Management', env => {
|
blockchainTests('Staking Pool Management', env => {
|
||||||
// constants
|
// constants
|
||||||
const { DUMMY_TOKEN_DECIMALS, PPM_100_PERCENT, PPM_DENOMINATOR } = constants;
|
const { PPM_100_PERCENT, PPM_DENOMINATOR } = constants;
|
||||||
// tokens & addresses
|
// tokens & addresses
|
||||||
let accounts: string[];
|
let accounts: string[];
|
||||||
let owner: string;
|
let owner: string;
|
||||||
let users: string[];
|
let users: string[];
|
||||||
let zrxTokenContract: DummyERC20TokenContract;
|
|
||||||
let erc20ProxyContract: ERC20ProxyContract;
|
|
||||||
// wrappers
|
// wrappers
|
||||||
let stakingWrapper: StakingWrapper;
|
let stakingApiWrapper: StakingApiWrapper;
|
||||||
let erc20Wrapper: ERC20Wrapper;
|
let erc20Wrapper: ERC20Wrapper;
|
||||||
// tests
|
// tests
|
||||||
before(async () => {
|
before(async () => {
|
||||||
// create accounts
|
// create accounts
|
||||||
accounts = await env.web3Wrapper.getAvailableAddressesAsync();
|
accounts = await env.getAccountAddressesAsync();
|
||||||
owner = accounts[0];
|
owner = accounts[0];
|
||||||
users = accounts.slice(1);
|
users = accounts.slice(1);
|
||||||
// deploy erc20 proxy
|
// set up ERC20Wrapper
|
||||||
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
||||||
erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
|
|
||||||
// deploy zrx token
|
|
||||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, DUMMY_TOKEN_DECIMALS);
|
|
||||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
|
||||||
// deploy staking contracts
|
// deploy staking contracts
|
||||||
stakingWrapper = new StakingWrapper(
|
stakingApiWrapper = await deployAndConfigureContractsAsync(env, owner, erc20Wrapper);
|
||||||
env.provider,
|
|
||||||
owner,
|
|
||||||
erc20ProxyContract,
|
|
||||||
erc20ProxyContract,
|
|
||||||
zrxTokenContract,
|
|
||||||
);
|
|
||||||
await stakingWrapper.deployAndConfigureContractsAsync();
|
|
||||||
});
|
});
|
||||||
blockchainTests.resets('Staking Pool Management', () => {
|
blockchainTests.resets('Staking Pool Management', () => {
|
||||||
it('Should successfully create a pool', async () => {
|
it('Should successfully create a pool', async () => {
|
||||||
// test parameters
|
// test parameters
|
||||||
const operatorAddress = users[0];
|
const operatorAddress = users[0];
|
||||||
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
|
const operatorShare = (39 / 100) * PPM_DENOMINATOR;
|
||||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
|
||||||
// create pool
|
// create pool
|
||||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
|
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
|
||||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||||
// check that the next pool id was incremented
|
// check that the next pool id was incremented
|
||||||
const nextPoolId = await stakingWrapper.getNextStakingPoolIdAsync();
|
const nextPoolId = await stakingApiWrapper.stakingContract.getNextStakingPoolId.callAsync();
|
||||||
expect(nextPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
|
expect(nextPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
|
||||||
});
|
});
|
||||||
it('Should successfully create a pool and add owner as a maker', async () => {
|
it('Should successfully create a pool and add owner as a maker', async () => {
|
||||||
// test parameters
|
// test parameters
|
||||||
const operatorAddress = users[0];
|
const operatorAddress = users[0];
|
||||||
const operatorShare = 39;
|
const operatorShare = 39;
|
||||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
|
||||||
// create pool
|
// create pool
|
||||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||||
// check that the next pool id was incremented
|
// check that the next pool id was incremented
|
||||||
const nextPoolId = await stakingWrapper.getNextStakingPoolIdAsync();
|
const nextPoolId = await stakingApiWrapper.stakingContract.getNextStakingPoolId.callAsync();
|
||||||
expect(nextPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
|
expect(nextPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
|
||||||
});
|
});
|
||||||
it('Should throw if poolOperatorShare is > PPM_DENOMINATOR', async () => {
|
it('Should throw if poolOperatorShare is > PPM_DENOMINATOR', async () => {
|
||||||
@@ -74,7 +60,7 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
const operatorAddress = users[0];
|
const operatorAddress = users[0];
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
const operatorShare = PPM_100_PERCENT + 1;
|
const operatorShare = PPM_100_PERCENT + 1;
|
||||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
|
||||||
// create pool
|
// create pool
|
||||||
const tx = poolOperator.createStakingPoolAsync(operatorShare, true);
|
const tx = poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||||
const expectedPoolId = stakingConstants.INITIAL_POOL_ID;
|
const expectedPoolId = stakingConstants.INITIAL_POOL_ID;
|
||||||
@@ -85,9 +71,9 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
// test parameters
|
// test parameters
|
||||||
const operatorAddress = users[0];
|
const operatorAddress = users[0];
|
||||||
const operatorShare = 39;
|
const operatorShare = 39;
|
||||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
|
||||||
const makerAddress = users[1];
|
const makerAddress = users[1];
|
||||||
const maker = new MakerActor(makerAddress, stakingWrapper);
|
const maker = new MakerActor(makerAddress, stakingApiWrapper);
|
||||||
// create pool
|
// create pool
|
||||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||||
@@ -102,9 +88,9 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
// test parameters
|
// test parameters
|
||||||
const operatorAddress = users[0];
|
const operatorAddress = users[0];
|
||||||
const operatorShare = 39;
|
const operatorShare = 39;
|
||||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
|
||||||
const makerAddress = users[1];
|
const makerAddress = users[1];
|
||||||
const maker = new MakerActor(makerAddress, stakingWrapper);
|
const maker = new MakerActor(makerAddress, stakingApiWrapper);
|
||||||
// create pool
|
// create pool
|
||||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||||
@@ -119,9 +105,9 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
// test parameters
|
// test parameters
|
||||||
const operatorAddress = users[0];
|
const operatorAddress = users[0];
|
||||||
const operatorShare = 39;
|
const operatorShare = 39;
|
||||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
|
||||||
const makerAddresses = users.slice(1, 4);
|
const makerAddresses = users.slice(1, 4);
|
||||||
const makers = makerAddresses.map(makerAddress => new MakerActor(makerAddress, stakingWrapper));
|
const makers = makerAddresses.map(makerAddress => new MakerActor(makerAddress, stakingApiWrapper));
|
||||||
|
|
||||||
// create pool
|
// create pool
|
||||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
|
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
|
||||||
@@ -134,7 +120,7 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// check the number of makers in the pool
|
// check the number of makers in the pool
|
||||||
let numMakers = await stakingWrapper.getNumberOfMakersInStakingPoolAsync(poolId);
|
let numMakers = await stakingApiWrapper.stakingContract.getNumberOfMakersInStakingPool.callAsync(poolId);
|
||||||
expect(numMakers, 'number of makers in pool after adding').to.be.bignumber.equal(3);
|
expect(numMakers, 'number of makers in pool after adding').to.be.bignumber.equal(3);
|
||||||
|
|
||||||
// remove maker from pool
|
// remove maker from pool
|
||||||
@@ -145,17 +131,17 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// check the number of makers in the pool
|
// check the number of makers in the pool
|
||||||
numMakers = await stakingWrapper.getNumberOfMakersInStakingPoolAsync(poolId);
|
numMakers = await stakingApiWrapper.stakingContract.getNumberOfMakersInStakingPool.callAsync(poolId);
|
||||||
expect(numMakers, 'number of makers in pool after removing').to.be.bignumber.equal(0);
|
expect(numMakers, 'number of makers in pool after removing').to.be.bignumber.equal(0);
|
||||||
});
|
});
|
||||||
it('Should fail if maker already assigned to another pool tries to join', async () => {
|
it('Should fail if maker already assigned to another pool tries to join', async () => {
|
||||||
// test parameters
|
// test parameters
|
||||||
const operatorShare = 39;
|
const operatorShare = 39;
|
||||||
const assignedPoolOperator = new PoolOperatorActor(users[0], stakingWrapper);
|
const assignedPoolOperator = new PoolOperatorActor(users[0], stakingApiWrapper);
|
||||||
const otherPoolOperator = new PoolOperatorActor(users[1], stakingWrapper);
|
const otherPoolOperator = new PoolOperatorActor(users[1], stakingApiWrapper);
|
||||||
|
|
||||||
const makerAddress = users[2];
|
const makerAddress = users[2];
|
||||||
const maker = new MakerActor(makerAddress, stakingWrapper);
|
const maker = new MakerActor(makerAddress, stakingApiWrapper);
|
||||||
|
|
||||||
// create pools
|
// create pools
|
||||||
const assignedPoolId = await assignedPoolOperator.createStakingPoolAsync(operatorShare, true);
|
const assignedPoolId = await assignedPoolOperator.createStakingPoolAsync(operatorShare, true);
|
||||||
@@ -180,8 +166,7 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
// test parameters
|
// test parameters
|
||||||
const operatorAddress = users[0];
|
const operatorAddress = users[0];
|
||||||
const operatorShare = 39;
|
const operatorShare = 39;
|
||||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
|
||||||
|
|
||||||
const makerAddress = users[1];
|
const makerAddress = users[1];
|
||||||
|
|
||||||
// create pool
|
// create pool
|
||||||
@@ -199,11 +184,11 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
it('Should fail to add maker to pool if the maker joined a different pool', async () => {
|
it('Should fail to add maker to pool if the maker joined a different pool', async () => {
|
||||||
// test parameters
|
// test parameters
|
||||||
const operatorShare = 39;
|
const operatorShare = 39;
|
||||||
const assignedPoolOperator = new PoolOperatorActor(users[0], stakingWrapper);
|
const assignedPoolOperator = new PoolOperatorActor(users[0], stakingApiWrapper);
|
||||||
const otherPoolOperator = new PoolOperatorActor(users[1], stakingWrapper);
|
const otherPoolOperator = new PoolOperatorActor(users[1], stakingApiWrapper);
|
||||||
|
|
||||||
const makerAddress = users[2];
|
const makerAddress = users[2];
|
||||||
const maker = new MakerActor(makerAddress, stakingWrapper);
|
const maker = new MakerActor(makerAddress, stakingApiWrapper);
|
||||||
|
|
||||||
// create pools
|
// create pools
|
||||||
const joinedPoolId = await assignedPoolOperator.createStakingPoolAsync(operatorShare, true);
|
const joinedPoolId = await assignedPoolOperator.createStakingPoolAsync(operatorShare, true);
|
||||||
@@ -226,9 +211,9 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
// test parameters
|
// test parameters
|
||||||
const operatorAddress = users[0];
|
const operatorAddress = users[0];
|
||||||
const operatorShare = 39;
|
const operatorShare = 39;
|
||||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
|
||||||
const makerAddress = users[1];
|
const makerAddress = users[1];
|
||||||
const maker = new MakerActor(makerAddress, stakingWrapper);
|
const maker = new MakerActor(makerAddress, stakingApiWrapper);
|
||||||
// create pool
|
// create pool
|
||||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||||
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
|
||||||
@@ -247,7 +232,7 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
// test parameters
|
// test parameters
|
||||||
const operatorAddress = users[0];
|
const operatorAddress = users[0];
|
||||||
const operatorShare = 39;
|
const operatorShare = 39;
|
||||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
|
||||||
const makerAddress = users[1];
|
const makerAddress = users[1];
|
||||||
// create pool
|
// create pool
|
||||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||||
@@ -264,9 +249,9 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
// test parameters
|
// test parameters
|
||||||
const operatorAddress = users[0];
|
const operatorAddress = users[0];
|
||||||
const operatorShare = 39;
|
const operatorShare = 39;
|
||||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
|
||||||
const makerAddress = users[1];
|
const makerAddress = users[1];
|
||||||
const maker = new MakerActor(makerAddress, stakingWrapper);
|
const maker = new MakerActor(makerAddress, stakingApiWrapper);
|
||||||
const notOperatorAddress = users[2];
|
const notOperatorAddress = users[2];
|
||||||
// create pool
|
// create pool
|
||||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||||
@@ -277,16 +262,20 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
notOperatorAddress,
|
notOperatorAddress,
|
||||||
operatorAddress,
|
operatorAddress,
|
||||||
);
|
);
|
||||||
const tx = stakingWrapper.addMakerToStakingPoolAsync(poolId, makerAddress, notOperatorAddress);
|
const tx = stakingApiWrapper.stakingContract.addMakerToStakingPool.awaitTransactionSuccessAsync(
|
||||||
|
poolId,
|
||||||
|
makerAddress,
|
||||||
|
{ from: notOperatorAddress },
|
||||||
|
);
|
||||||
await expect(tx).to.revertWith(revertError);
|
await expect(tx).to.revertWith(revertError);
|
||||||
});
|
});
|
||||||
it('Should fail to remove a maker when called by someone other than the pool operator or maker', async () => {
|
it('Should fail to remove a maker when called by someone other than the pool operator or maker', async () => {
|
||||||
// test parameters
|
// test parameters
|
||||||
const operatorAddress = users[0];
|
const operatorAddress = users[0];
|
||||||
const operatorShare = 39;
|
const operatorShare = 39;
|
||||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
|
||||||
const makerAddress = users[1];
|
const makerAddress = users[1];
|
||||||
const maker = new MakerActor(makerAddress, stakingWrapper);
|
const maker = new MakerActor(makerAddress, stakingApiWrapper);
|
||||||
const neitherOperatorNorMakerAddress = users[2];
|
const neitherOperatorNorMakerAddress = users[2];
|
||||||
// create pool
|
// create pool
|
||||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
|
||||||
@@ -300,10 +289,10 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
operatorAddress,
|
operatorAddress,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
);
|
);
|
||||||
const tx = stakingWrapper.removeMakerFromStakingPoolAsync(
|
const tx = stakingApiWrapper.stakingContract.removeMakerFromStakingPool.awaitTransactionSuccessAsync(
|
||||||
poolId,
|
poolId,
|
||||||
makerAddress,
|
makerAddress,
|
||||||
neitherOperatorNorMakerAddress,
|
{ from: neitherOperatorNorMakerAddress },
|
||||||
);
|
);
|
||||||
await expect(tx).to.revertWith(revertError);
|
await expect(tx).to.revertWith(revertError);
|
||||||
});
|
});
|
||||||
@@ -311,10 +300,10 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
// test parameters
|
// test parameters
|
||||||
const operatorAddress = users[0];
|
const operatorAddress = users[0];
|
||||||
const operatorShare = 39;
|
const operatorShare = 39;
|
||||||
const poolOperator = new PoolOperatorActor(operatorAddress, stakingWrapper);
|
const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
|
||||||
|
|
||||||
const makerAddresses = users.slice(1, stakingConstants.MAX_MAKERS_IN_POOL + 2);
|
const makerAddresses = users.slice(1, stakingConstants.MAX_MAKERS_IN_POOL + 2);
|
||||||
const makers = makerAddresses.map(makerAddress => new MakerActor(makerAddress, stakingWrapper));
|
const makers = makerAddresses.map(makerAddress => new MakerActor(makerAddress, stakingApiWrapper));
|
||||||
|
|
||||||
// create pool
|
// create pool
|
||||||
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
|
const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
|
||||||
@@ -329,7 +318,7 @@ blockchainTests('Staking Pool Management', env => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// check the number of makers in the pool
|
// check the number of makers in the pool
|
||||||
const numMakers = await stakingWrapper.getNumberOfMakersInStakingPoolAsync(poolId);
|
const numMakers = await stakingApiWrapper.stakingContract.getNumberOfMakersInStakingPool.callAsync(poolId);
|
||||||
expect(numMakers, 'number of makers in pool').to.be.bignumber.equal(stakingConstants.MAX_MAKERS_IN_POOL);
|
expect(numMakers, 'number of makers in pool').to.be.bignumber.equal(stakingConstants.MAX_MAKERS_IN_POOL);
|
||||||
|
|
||||||
const lastMakerAddress = _.last(makerAddresses) as string;
|
const lastMakerAddress = _.last(makerAddresses) as string;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
import { blockchainTests, describe, expect } from '@0x/contracts-test-utils';
|
||||||
import { blockchainTests, describe, expect, provider, web3Wrapper } from '@0x/contracts-test-utils';
|
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
@@ -8,24 +7,21 @@ import { artifacts } from '../src';
|
|||||||
|
|
||||||
import { FinalizerActor } from './actors/finalizer_actor';
|
import { FinalizerActor } from './actors/finalizer_actor';
|
||||||
import { StakerActor } from './actors/staker_actor';
|
import { StakerActor } from './actors/staker_actor';
|
||||||
import { StakingWrapper } from './utils/staking_wrapper';
|
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
|
||||||
import { MembersByPoolId, OperatorByPoolId, StakeStatus } from './utils/types';
|
import { toBaseUnitAmount } from './utils/number_utils';
|
||||||
|
import { MembersByPoolId, OperatorByPoolId, StakeInfo, StakeStatus } from './utils/types';
|
||||||
|
|
||||||
// tslint:disable:no-unnecessary-type-assertion
|
// tslint:disable:no-unnecessary-type-assertion
|
||||||
// tslint:disable:max-file-line-count
|
// tslint:disable:max-file-line-count
|
||||||
blockchainTests.resets('Testing Rewards', () => {
|
blockchainTests.resets('Testing Rewards', env => {
|
||||||
// constants
|
|
||||||
const ZRX_TOKEN_DECIMALS = new BigNumber(18);
|
|
||||||
// tokens & addresses
|
// tokens & addresses
|
||||||
let accounts: string[];
|
let accounts: string[];
|
||||||
let owner: string;
|
let owner: string;
|
||||||
let actors: string[];
|
let actors: string[];
|
||||||
let exchangeAddress: string;
|
let exchangeAddress: string;
|
||||||
let takerAddress: string;
|
let takerAddress: string;
|
||||||
let zrxTokenContract: DummyERC20TokenContract;
|
|
||||||
let erc20ProxyContract: ERC20ProxyContract;
|
|
||||||
// wrappers
|
// wrappers
|
||||||
let stakingWrapper: StakingWrapper;
|
let stakingApiWrapper: StakingApiWrapper;
|
||||||
// let testWrapper: TestRewardBalancesContract;
|
// let testWrapper: TestRewardBalancesContract;
|
||||||
let erc20Wrapper: ERC20Wrapper;
|
let erc20Wrapper: ERC20Wrapper;
|
||||||
// test parameters
|
// test parameters
|
||||||
@@ -36,27 +32,23 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
// tests
|
// tests
|
||||||
before(async () => {
|
before(async () => {
|
||||||
// create accounts
|
// create accounts
|
||||||
accounts = await web3Wrapper.getAvailableAddressesAsync();
|
accounts = await env.getAccountAddressesAsync();
|
||||||
owner = accounts[0];
|
owner = accounts[0];
|
||||||
exchangeAddress = accounts[1];
|
exchangeAddress = accounts[1];
|
||||||
takerAddress = accounts[2];
|
takerAddress = accounts[2];
|
||||||
actors = accounts.slice(3);
|
actors = accounts.slice(3);
|
||||||
// deploy erc20 proxy
|
// set up ERC20Wrapper
|
||||||
erc20Wrapper = new ERC20Wrapper(provider, accounts, owner);
|
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
||||||
erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
|
|
||||||
// deploy zrx token
|
|
||||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
|
||||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
|
||||||
// deploy staking contracts
|
// deploy staking contracts
|
||||||
stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, erc20ProxyContract, zrxTokenContract);
|
stakingApiWrapper = await deployAndConfigureContractsAsync(env, owner, erc20Wrapper, artifacts.TestStaking);
|
||||||
await stakingWrapper.deployAndConfigureContractsAsync(artifacts.TestStaking);
|
|
||||||
// setup stakers
|
// setup stakers
|
||||||
stakers = [new StakerActor(actors[0], stakingWrapper), new StakerActor(actors[1], stakingWrapper)];
|
stakers = [new StakerActor(actors[0], stakingApiWrapper), new StakerActor(actors[1], stakingApiWrapper)];
|
||||||
// setup pools
|
// setup pools
|
||||||
poolOperator = actors[2];
|
poolOperator = actors[2];
|
||||||
poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, 0, true);
|
poolId = await stakingApiWrapper.utils.createStakingPoolAsync(poolOperator, 0, true); // add operator as maker
|
||||||
// set exchange address
|
// set exchange address
|
||||||
await stakingWrapper.addExchangeAddressAsync(exchangeAddress);
|
await stakingApiWrapper.stakingContract.addExchangeAddress.awaitTransactionSuccessAsync(exchangeAddress);
|
||||||
// associate operators for tracking in Finalizer
|
// associate operators for tracking in Finalizer
|
||||||
const operatorByPoolId: OperatorByPoolId = {};
|
const operatorByPoolId: OperatorByPoolId = {};
|
||||||
operatorByPoolId[poolId] = poolOperator;
|
operatorByPoolId[poolId] = poolOperator;
|
||||||
@@ -66,7 +58,7 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
membersByPoolId[poolId] = [actors[0], actors[1]];
|
membersByPoolId[poolId] = [actors[0], actors[1]];
|
||||||
membersByPoolId[poolId] = [actors[0], actors[1]];
|
membersByPoolId[poolId] = [actors[0], actors[1]];
|
||||||
// create Finalizer actor
|
// create Finalizer actor
|
||||||
finalizer = new FinalizerActor(actors[3], stakingWrapper, [poolId], operatorByPoolId, membersByPoolId);
|
finalizer = new FinalizerActor(actors[3], stakingApiWrapper, [poolId], operatorByPoolId, membersByPoolId);
|
||||||
});
|
});
|
||||||
describe('Reward Simulation', () => {
|
describe('Reward Simulation', () => {
|
||||||
interface EndBalances {
|
interface EndBalances {
|
||||||
@@ -124,17 +116,23 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
};
|
};
|
||||||
const finalEndBalancesAsArray = await Promise.all([
|
const finalEndBalancesAsArray = await Promise.all([
|
||||||
// staker 1
|
// staker 1
|
||||||
stakingWrapper.computeRewardBalanceOfStakingPoolMemberAsync(poolId, stakers[0].getOwner()),
|
stakingApiWrapper.stakingContract.computeRewardBalanceOfDelegator.callAsync(
|
||||||
stakingWrapper.getEthVaultContract().balanceOf.callAsync(stakers[0].getOwner()),
|
poolId,
|
||||||
|
stakers[0].getOwner(),
|
||||||
|
),
|
||||||
|
stakingApiWrapper.ethVaultContract.balanceOf.callAsync(stakers[0].getOwner()),
|
||||||
// staker 2
|
// staker 2
|
||||||
stakingWrapper.computeRewardBalanceOfStakingPoolMemberAsync(poolId, stakers[1].getOwner()),
|
stakingApiWrapper.stakingContract.computeRewardBalanceOfDelegator.callAsync(
|
||||||
stakingWrapper.getEthVaultContract().balanceOf.callAsync(stakers[1].getOwner()),
|
poolId,
|
||||||
|
stakers[1].getOwner(),
|
||||||
|
),
|
||||||
|
stakingApiWrapper.ethVaultContract.balanceOf.callAsync(stakers[1].getOwner()),
|
||||||
// operator
|
// operator
|
||||||
stakingWrapper.rewardVaultBalanceOfOperatorAsync(poolId),
|
stakingApiWrapper.rewardVaultContract.balanceOfOperator.callAsync(poolId),
|
||||||
stakingWrapper.getEthVaultContract().balanceOf.callAsync(poolOperator),
|
stakingApiWrapper.ethVaultContract.balanceOf.callAsync(poolOperator),
|
||||||
// undivided balance in reward pool
|
// undivided balance in reward pool
|
||||||
stakingWrapper.rewardVaultBalanceOfAsync(poolId),
|
stakingApiWrapper.rewardVaultContract.balanceOf.callAsync(poolId),
|
||||||
stakingWrapper.rewardVaultBalanceOfMembersAsync(poolId),
|
stakingApiWrapper.rewardVaultContract.balanceOfMembers.callAsync(poolId),
|
||||||
]);
|
]);
|
||||||
expect(finalEndBalancesAsArray[0], 'stakerRewardVaultBalance_1').to.be.bignumber.equal(
|
expect(finalEndBalancesAsArray[0], 'stakerRewardVaultBalance_1').to.be.bignumber.equal(
|
||||||
expectedEndBalances.stakerRewardVaultBalance_1,
|
expectedEndBalances.stakerRewardVaultBalance_1,
|
||||||
@@ -164,17 +162,22 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
const payProtocolFeeAndFinalize = async (_fee?: BigNumber) => {
|
const payProtocolFeeAndFinalize = async (_fee?: BigNumber) => {
|
||||||
const fee = _fee !== undefined ? _fee : ZERO;
|
const fee = _fee !== undefined ? _fee : ZERO;
|
||||||
if (!fee.eq(ZERO)) {
|
if (!fee.eq(ZERO)) {
|
||||||
await stakingWrapper.payProtocolFeeAsync(poolOperator, takerAddress, fee, fee, exchangeAddress);
|
await stakingApiWrapper.stakingContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
|
poolOperator,
|
||||||
|
takerAddress,
|
||||||
|
fee,
|
||||||
|
{ from: exchangeAddress, value: fee },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
await finalizer.finalizeAsync([{ reward: fee, poolId }]);
|
await finalizer.finalizeAsync([{ reward: fee, poolId }]);
|
||||||
};
|
};
|
||||||
const ZERO = new BigNumber(0);
|
const ZERO = new BigNumber(0);
|
||||||
it('Reward balance should be zero in same epoch as delegation', async () => {
|
it('Reward balance should be zero in same epoch as delegation', async () => {
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(4);
|
const amount = toBaseUnitAmount(4);
|
||||||
await stakers[0].stakeAsync(amount);
|
await stakers[0].stakeAsync(amount);
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
amount,
|
amount,
|
||||||
);
|
);
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
@@ -182,7 +185,7 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
await validateEndBalances({});
|
await validateEndBalances({});
|
||||||
});
|
});
|
||||||
it('Operator should receive entire reward if no delegators in their pool', async () => {
|
it('Operator should receive entire reward if no delegators in their pool', async () => {
|
||||||
const reward = StakingWrapper.toBaseUnitAmount(10);
|
const reward = toBaseUnitAmount(10);
|
||||||
await payProtocolFeeAndFinalize(reward);
|
await payProtocolFeeAndFinalize(reward);
|
||||||
// sanity check final balances - all zero
|
// sanity check final balances - all zero
|
||||||
await validateEndBalances({
|
await validateEndBalances({
|
||||||
@@ -192,15 +195,15 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
});
|
});
|
||||||
it('Operator should receive entire reward if no delegators in their pool (staker joins this epoch but is active next epoch)', async () => {
|
it('Operator should receive entire reward if no delegators in their pool (staker joins this epoch but is active next epoch)', async () => {
|
||||||
// delegate
|
// delegate
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(4);
|
const amount = toBaseUnitAmount(4);
|
||||||
await stakers[0].stakeAsync(amount);
|
await stakers[0].stakeAsync(amount);
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
amount,
|
amount,
|
||||||
);
|
);
|
||||||
// finalize
|
// finalize
|
||||||
const reward = StakingWrapper.toBaseUnitAmount(10);
|
const reward = toBaseUnitAmount(10);
|
||||||
await payProtocolFeeAndFinalize(reward);
|
await payProtocolFeeAndFinalize(reward);
|
||||||
// sanity check final balances
|
// sanity check final balances
|
||||||
await validateEndBalances({
|
await validateEndBalances({
|
||||||
@@ -210,17 +213,17 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
});
|
});
|
||||||
it('Should give pool reward to delegator', async () => {
|
it('Should give pool reward to delegator', async () => {
|
||||||
// delegate
|
// delegate
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(4);
|
const amount = toBaseUnitAmount(4);
|
||||||
await stakers[0].stakeAsync(amount);
|
await stakers[0].stakeAsync(amount);
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
amount,
|
amount,
|
||||||
);
|
);
|
||||||
// skip epoch, so staker can start earning rewards
|
// skip epoch, so staker can start earning rewards
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
// finalize
|
// finalize
|
||||||
const reward = StakingWrapper.toBaseUnitAmount(10);
|
const reward = toBaseUnitAmount(10);
|
||||||
await payProtocolFeeAndFinalize(reward);
|
await payProtocolFeeAndFinalize(reward);
|
||||||
// sanity check final balances
|
// sanity check final balances
|
||||||
await validateEndBalances({
|
await validateEndBalances({
|
||||||
@@ -231,25 +234,25 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
});
|
});
|
||||||
it('Should split pool reward between delegators', async () => {
|
it('Should split pool reward between delegators', async () => {
|
||||||
// first staker delegates
|
// first staker delegates
|
||||||
const stakeAmounts = [StakingWrapper.toBaseUnitAmount(4), StakingWrapper.toBaseUnitAmount(6)];
|
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
||||||
const totalStakeAmount = StakingWrapper.toBaseUnitAmount(10);
|
const totalStakeAmount = toBaseUnitAmount(10);
|
||||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
await stakers[0].stakeAsync(stakeAmounts[0]);
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmounts[0],
|
stakeAmounts[0],
|
||||||
);
|
);
|
||||||
// second staker delegates
|
// second staker delegates
|
||||||
await stakers[1].stakeAsync(stakeAmounts[1]);
|
await stakers[1].stakeAsync(stakeAmounts[1]);
|
||||||
await stakers[1].moveStakeAsync(
|
await stakers[1].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmounts[1],
|
stakeAmounts[1],
|
||||||
);
|
);
|
||||||
// skip epoch, so staker can start earning rewards
|
// skip epoch, so staker can start earning rewards
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
// finalize
|
// finalize
|
||||||
const reward = StakingWrapper.toBaseUnitAmount(10);
|
const reward = toBaseUnitAmount(10);
|
||||||
await payProtocolFeeAndFinalize(reward);
|
await payProtocolFeeAndFinalize(reward);
|
||||||
// sanity check final balances
|
// sanity check final balances
|
||||||
await validateEndBalances({
|
await validateEndBalances({
|
||||||
@@ -261,12 +264,12 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
});
|
});
|
||||||
it('Should split pool reward between delegators, when they join in different epochs', async () => {
|
it('Should split pool reward between delegators, when they join in different epochs', async () => {
|
||||||
// first staker delegates (epoch 0)
|
// first staker delegates (epoch 0)
|
||||||
const stakeAmounts = [StakingWrapper.toBaseUnitAmount(4), StakingWrapper.toBaseUnitAmount(6)];
|
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
||||||
const totalStakeAmount = StakingWrapper.toBaseUnitAmount(10);
|
const totalStakeAmount = toBaseUnitAmount(10);
|
||||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
await stakers[0].stakeAsync(stakeAmounts[0]);
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmounts[0],
|
stakeAmounts[0],
|
||||||
);
|
);
|
||||||
// skip epoch, so staker can start earning rewards
|
// skip epoch, so staker can start earning rewards
|
||||||
@@ -274,14 +277,14 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
// second staker delegates (epoch 1)
|
// second staker delegates (epoch 1)
|
||||||
await stakers[1].stakeAsync(stakeAmounts[1]);
|
await stakers[1].stakeAsync(stakeAmounts[1]);
|
||||||
await stakers[1].moveStakeAsync(
|
await stakers[1].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmounts[1],
|
stakeAmounts[1],
|
||||||
);
|
);
|
||||||
// skip epoch, so staker can start earning rewards
|
// skip epoch, so staker can start earning rewards
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
// finalize
|
// finalize
|
||||||
const reward = StakingWrapper.toBaseUnitAmount(10);
|
const reward = toBaseUnitAmount(10);
|
||||||
await payProtocolFeeAndFinalize(reward);
|
await payProtocolFeeAndFinalize(reward);
|
||||||
// sanity check final balances
|
// sanity check final balances
|
||||||
await validateEndBalances({
|
await validateEndBalances({
|
||||||
@@ -293,12 +296,12 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
});
|
});
|
||||||
it('Should give pool reward to delegators only for the epoch during which they delegated', async () => {
|
it('Should give pool reward to delegators only for the epoch during which they delegated', async () => {
|
||||||
// first staker delegates (epoch 0)
|
// first staker delegates (epoch 0)
|
||||||
const stakeAmounts = [StakingWrapper.toBaseUnitAmount(4), StakingWrapper.toBaseUnitAmount(6)];
|
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
||||||
const totalStakeAmount = StakingWrapper.toBaseUnitAmount(10);
|
const totalStakeAmount = toBaseUnitAmount(10);
|
||||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
await stakers[0].stakeAsync(stakeAmounts[0]);
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmounts[0],
|
stakeAmounts[0],
|
||||||
);
|
);
|
||||||
// skip epoch, so first staker can start earning rewards
|
// skip epoch, so first staker can start earning rewards
|
||||||
@@ -306,15 +309,15 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
// second staker delegates (epoch 1)
|
// second staker delegates (epoch 1)
|
||||||
await stakers[1].stakeAsync(stakeAmounts[1]);
|
await stakers[1].stakeAsync(stakeAmounts[1]);
|
||||||
await stakers[1].moveStakeAsync(
|
await stakers[1].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmounts[1],
|
stakeAmounts[1],
|
||||||
);
|
);
|
||||||
// only the first staker will get this reward
|
// only the first staker will get this reward
|
||||||
const rewardForOnlyFirstDelegator = StakingWrapper.toBaseUnitAmount(10);
|
const rewardForOnlyFirstDelegator = toBaseUnitAmount(10);
|
||||||
await payProtocolFeeAndFinalize(rewardForOnlyFirstDelegator);
|
await payProtocolFeeAndFinalize(rewardForOnlyFirstDelegator);
|
||||||
// finalize
|
// finalize
|
||||||
const rewardForBothDelegators = StakingWrapper.toBaseUnitAmount(20);
|
const rewardForBothDelegators = toBaseUnitAmount(20);
|
||||||
await payProtocolFeeAndFinalize(rewardForBothDelegators);
|
await payProtocolFeeAndFinalize(rewardForBothDelegators);
|
||||||
// sanity check final balances
|
// sanity check final balances
|
||||||
await validateEndBalances({
|
await validateEndBalances({
|
||||||
@@ -329,26 +332,26 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('Should split pool reward between delegators, over several consecutive epochs', async () => {
|
it('Should split pool reward between delegators, over several consecutive epochs', async () => {
|
||||||
const rewardForOnlyFirstDelegator = StakingWrapper.toBaseUnitAmount(10);
|
const rewardForOnlyFirstDelegator = toBaseUnitAmount(10);
|
||||||
const sharedRewards = [
|
const sharedRewards = [
|
||||||
StakingWrapper.toBaseUnitAmount(20),
|
toBaseUnitAmount(20),
|
||||||
StakingWrapper.toBaseUnitAmount(16),
|
toBaseUnitAmount(16),
|
||||||
StakingWrapper.toBaseUnitAmount(24),
|
toBaseUnitAmount(24),
|
||||||
StakingWrapper.toBaseUnitAmount(5),
|
toBaseUnitAmount(5),
|
||||||
StakingWrapper.toBaseUnitAmount(0),
|
toBaseUnitAmount(0),
|
||||||
StakingWrapper.toBaseUnitAmount(17),
|
toBaseUnitAmount(17),
|
||||||
];
|
];
|
||||||
const totalSharedRewardsAsNumber = _.sumBy(sharedRewards, v => {
|
const totalSharedRewardsAsNumber = _.sumBy(sharedRewards, v => {
|
||||||
return v.toNumber();
|
return v.toNumber();
|
||||||
});
|
});
|
||||||
const totalSharedRewards = new BigNumber(totalSharedRewardsAsNumber);
|
const totalSharedRewards = new BigNumber(totalSharedRewardsAsNumber);
|
||||||
// first staker delegates (epoch 0)
|
// first staker delegates (epoch 0)
|
||||||
const stakeAmounts = [StakingWrapper.toBaseUnitAmount(4), StakingWrapper.toBaseUnitAmount(6)];
|
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
||||||
const totalStakeAmount = StakingWrapper.toBaseUnitAmount(10);
|
const totalStakeAmount = toBaseUnitAmount(10);
|
||||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
await stakers[0].stakeAsync(stakeAmounts[0]);
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmounts[0],
|
stakeAmounts[0],
|
||||||
);
|
);
|
||||||
// skip epoch, so first staker can start earning rewards
|
// skip epoch, so first staker can start earning rewards
|
||||||
@@ -356,8 +359,8 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
// second staker delegates (epoch 1)
|
// second staker delegates (epoch 1)
|
||||||
await stakers[1].stakeAsync(stakeAmounts[1]);
|
await stakers[1].stakeAsync(stakeAmounts[1]);
|
||||||
await stakers[1].moveStakeAsync(
|
await stakers[1].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmounts[1],
|
stakeAmounts[1],
|
||||||
);
|
);
|
||||||
// only the first staker will get this reward
|
// only the first staker will get this reward
|
||||||
@@ -380,22 +383,22 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
});
|
});
|
||||||
it('Should send existing rewards from reward vault to eth vault correctly when undelegating stake', async () => {
|
it('Should send existing rewards from reward vault to eth vault correctly when undelegating stake', async () => {
|
||||||
// first staker delegates (epoch 0)
|
// first staker delegates (epoch 0)
|
||||||
const stakeAmount = StakingWrapper.toBaseUnitAmount(4);
|
const stakeAmount = toBaseUnitAmount(4);
|
||||||
await stakers[0].stakeAsync(stakeAmount);
|
await stakers[0].stakeAsync(stakeAmount);
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmount,
|
stakeAmount,
|
||||||
);
|
);
|
||||||
// skip epoch, so first staker can start earning rewards
|
// skip epoch, so first staker can start earning rewards
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
// earn reward
|
// earn reward
|
||||||
const reward = StakingWrapper.toBaseUnitAmount(10);
|
const reward = toBaseUnitAmount(10);
|
||||||
await payProtocolFeeAndFinalize(reward);
|
await payProtocolFeeAndFinalize(reward);
|
||||||
// undelegate (moves delegator's from the transient reward vault into the eth vault)
|
// undelegate (moves delegator's from the transient reward vault into the eth vault)
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
stakeAmount,
|
stakeAmount,
|
||||||
);
|
);
|
||||||
// sanity check final balances
|
// sanity check final balances
|
||||||
@@ -406,23 +409,23 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
});
|
});
|
||||||
it('Should send existing rewards from reward vault to eth vault correctly when delegating more stake', async () => {
|
it('Should send existing rewards from reward vault to eth vault correctly when delegating more stake', async () => {
|
||||||
// first staker delegates (epoch 0)
|
// first staker delegates (epoch 0)
|
||||||
const stakeAmount = StakingWrapper.toBaseUnitAmount(4);
|
const stakeAmount = toBaseUnitAmount(4);
|
||||||
await stakers[0].stakeAsync(stakeAmount);
|
await stakers[0].stakeAsync(stakeAmount);
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmount,
|
stakeAmount,
|
||||||
);
|
);
|
||||||
// skip epoch, so first staker can start earning rewards
|
// skip epoch, so first staker can start earning rewards
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
// earn reward
|
// earn reward
|
||||||
const reward = StakingWrapper.toBaseUnitAmount(10);
|
const reward = toBaseUnitAmount(10);
|
||||||
await payProtocolFeeAndFinalize(reward);
|
await payProtocolFeeAndFinalize(reward);
|
||||||
// add more stake
|
// add more stake
|
||||||
await stakers[0].stakeAsync(stakeAmount);
|
await stakers[0].stakeAsync(stakeAmount);
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmount,
|
stakeAmount,
|
||||||
);
|
);
|
||||||
// sanity check final balances
|
// sanity check final balances
|
||||||
@@ -432,14 +435,14 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('Should continue earning rewards after adding more stake and progressing several epochs', async () => {
|
it('Should continue earning rewards after adding more stake and progressing several epochs', async () => {
|
||||||
const rewardBeforeAddingMoreStake = StakingWrapper.toBaseUnitAmount(10);
|
const rewardBeforeAddingMoreStake = toBaseUnitAmount(10);
|
||||||
const rewardsAfterAddingMoreStake = [
|
const rewardsAfterAddingMoreStake = [
|
||||||
StakingWrapper.toBaseUnitAmount(20),
|
toBaseUnitAmount(20),
|
||||||
StakingWrapper.toBaseUnitAmount(16),
|
toBaseUnitAmount(16),
|
||||||
StakingWrapper.toBaseUnitAmount(24),
|
toBaseUnitAmount(24),
|
||||||
StakingWrapper.toBaseUnitAmount(5),
|
toBaseUnitAmount(5),
|
||||||
StakingWrapper.toBaseUnitAmount(0),
|
toBaseUnitAmount(0),
|
||||||
StakingWrapper.toBaseUnitAmount(17),
|
toBaseUnitAmount(17),
|
||||||
];
|
];
|
||||||
const totalRewardsAfterAddingMoreStake = new BigNumber(
|
const totalRewardsAfterAddingMoreStake = new BigNumber(
|
||||||
_.sumBy(rewardsAfterAddingMoreStake, v => {
|
_.sumBy(rewardsAfterAddingMoreStake, v => {
|
||||||
@@ -447,11 +450,11 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
// first staker delegates (epoch 0)
|
// first staker delegates (epoch 0)
|
||||||
const stakeAmounts = [StakingWrapper.toBaseUnitAmount(4), StakingWrapper.toBaseUnitAmount(6)];
|
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
||||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
await stakers[0].stakeAsync(stakeAmounts[0]);
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmounts[0],
|
stakeAmounts[0],
|
||||||
);
|
);
|
||||||
// skip epoch, so first staker can start earning rewards
|
// skip epoch, so first staker can start earning rewards
|
||||||
@@ -459,8 +462,8 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
// second staker delegates (epoch 1)
|
// second staker delegates (epoch 1)
|
||||||
await stakers[0].stakeAsync(stakeAmounts[1]);
|
await stakers[0].stakeAsync(stakeAmounts[1]);
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmounts[1],
|
stakeAmounts[1],
|
||||||
);
|
);
|
||||||
// only the first staker will get this reward
|
// only the first staker will get this reward
|
||||||
@@ -478,13 +481,13 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
});
|
});
|
||||||
it('Should stop collecting rewards after undelegating', async () => {
|
it('Should stop collecting rewards after undelegating', async () => {
|
||||||
// first staker delegates (epoch 0)
|
// first staker delegates (epoch 0)
|
||||||
const rewardForDelegator = StakingWrapper.toBaseUnitAmount(10);
|
const rewardForDelegator = toBaseUnitAmount(10);
|
||||||
const rewardNotForDelegator = StakingWrapper.toBaseUnitAmount(7);
|
const rewardNotForDelegator = toBaseUnitAmount(7);
|
||||||
const stakeAmount = StakingWrapper.toBaseUnitAmount(4);
|
const stakeAmount = toBaseUnitAmount(4);
|
||||||
await stakers[0].stakeAsync(stakeAmount);
|
await stakers[0].stakeAsync(stakeAmount);
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmount,
|
stakeAmount,
|
||||||
);
|
);
|
||||||
// skip epoch, so first staker can start earning rewards
|
// skip epoch, so first staker can start earning rewards
|
||||||
@@ -493,8 +496,8 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
await payProtocolFeeAndFinalize(rewardForDelegator);
|
await payProtocolFeeAndFinalize(rewardForDelegator);
|
||||||
// undelegate stake and finalize epoch
|
// undelegate stake and finalize epoch
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
stakeAmount,
|
stakeAmount,
|
||||||
);
|
);
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
@@ -509,25 +512,25 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
});
|
});
|
||||||
it('Should stop collecting rewards after undelegating, after several epochs', async () => {
|
it('Should stop collecting rewards after undelegating, after several epochs', async () => {
|
||||||
// first staker delegates (epoch 0)
|
// first staker delegates (epoch 0)
|
||||||
const rewardForDelegator = StakingWrapper.toBaseUnitAmount(10);
|
const rewardForDelegator = toBaseUnitAmount(10);
|
||||||
const rewardsNotForDelegator = [
|
const rewardsNotForDelegator = [
|
||||||
StakingWrapper.toBaseUnitAmount(20),
|
toBaseUnitAmount(20),
|
||||||
StakingWrapper.toBaseUnitAmount(16),
|
toBaseUnitAmount(16),
|
||||||
StakingWrapper.toBaseUnitAmount(24),
|
toBaseUnitAmount(24),
|
||||||
StakingWrapper.toBaseUnitAmount(5),
|
toBaseUnitAmount(5),
|
||||||
StakingWrapper.toBaseUnitAmount(0),
|
toBaseUnitAmount(0),
|
||||||
StakingWrapper.toBaseUnitAmount(17),
|
toBaseUnitAmount(17),
|
||||||
];
|
];
|
||||||
const totalRewardsNotForDelegator = new BigNumber(
|
const totalRewardsNotForDelegator = new BigNumber(
|
||||||
_.sumBy(rewardsNotForDelegator, v => {
|
_.sumBy(rewardsNotForDelegator, v => {
|
||||||
return v.toNumber();
|
return v.toNumber();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const stakeAmount = StakingWrapper.toBaseUnitAmount(4);
|
const stakeAmount = toBaseUnitAmount(4);
|
||||||
await stakers[0].stakeAsync(stakeAmount);
|
await stakers[0].stakeAsync(stakeAmount);
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmount,
|
stakeAmount,
|
||||||
);
|
);
|
||||||
// skip epoch, so first staker can start earning rewards
|
// skip epoch, so first staker can start earning rewards
|
||||||
@@ -536,8 +539,8 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
await payProtocolFeeAndFinalize(rewardForDelegator);
|
await payProtocolFeeAndFinalize(rewardForDelegator);
|
||||||
// undelegate stake and finalize epoch
|
// undelegate stake and finalize epoch
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
stakeAmount,
|
stakeAmount,
|
||||||
);
|
);
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
@@ -554,13 +557,13 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
});
|
});
|
||||||
it('Should collect fees correctly when leaving and returning to a pool', async () => {
|
it('Should collect fees correctly when leaving and returning to a pool', async () => {
|
||||||
// first staker delegates (epoch 0)
|
// first staker delegates (epoch 0)
|
||||||
const rewardsForDelegator = [StakingWrapper.toBaseUnitAmount(10), StakingWrapper.toBaseUnitAmount(15)];
|
const rewardsForDelegator = [toBaseUnitAmount(10), toBaseUnitAmount(15)];
|
||||||
const rewardNotForDelegator = StakingWrapper.toBaseUnitAmount(7);
|
const rewardNotForDelegator = toBaseUnitAmount(7);
|
||||||
const stakeAmount = StakingWrapper.toBaseUnitAmount(4);
|
const stakeAmount = toBaseUnitAmount(4);
|
||||||
await stakers[0].stakeAsync(stakeAmount);
|
await stakers[0].stakeAsync(stakeAmount);
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmount,
|
stakeAmount,
|
||||||
);
|
);
|
||||||
// skip epoch, so first staker can start earning rewards
|
// skip epoch, so first staker can start earning rewards
|
||||||
@@ -569,8 +572,8 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
await payProtocolFeeAndFinalize(rewardsForDelegator[0]);
|
await payProtocolFeeAndFinalize(rewardsForDelegator[0]);
|
||||||
// undelegate stake and finalize epoch
|
// undelegate stake and finalize epoch
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
stakeAmount,
|
stakeAmount,
|
||||||
);
|
);
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
@@ -578,8 +581,8 @@ blockchainTests.resets('Testing Rewards', () => {
|
|||||||
await payProtocolFeeAndFinalize(rewardNotForDelegator);
|
await payProtocolFeeAndFinalize(rewardNotForDelegator);
|
||||||
// delegate stake and go to next epoch
|
// delegate stake and go to next epoch
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId },
|
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||||
stakeAmount,
|
stakeAmount,
|
||||||
);
|
);
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
import { blockchainTests, describe } from '@0x/contracts-test-utils';
|
||||||
import { blockchainTests, describe, web3Wrapper } from '@0x/contracts-test-utils';
|
|
||||||
import { StakingRevertErrors } from '@0x/order-utils';
|
import { StakingRevertErrors } from '@0x/order-utils';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
@@ -8,22 +7,20 @@ import * as _ from 'lodash';
|
|||||||
import { artifacts } from '../src';
|
import { artifacts } from '../src';
|
||||||
|
|
||||||
import { StakerActor } from './actors/staker_actor';
|
import { StakerActor } from './actors/staker_actor';
|
||||||
import { StakingWrapper } from './utils/staking_wrapper';
|
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
|
||||||
|
import { toBaseUnitAmount } from './utils/number_utils';
|
||||||
import { StakeInfo, StakeStatus } from './utils/types';
|
import { StakeInfo, StakeStatus } from './utils/types';
|
||||||
|
|
||||||
// tslint:disable:no-unnecessary-type-assertion
|
// tslint:disable:no-unnecessary-type-assertion
|
||||||
blockchainTests.resets('Stake Statuses', env => {
|
blockchainTests.resets('Stake Statuses', env => {
|
||||||
// constants
|
// constants
|
||||||
const ZRX_TOKEN_DECIMALS = new BigNumber(18);
|
|
||||||
const ZERO = new BigNumber(0);
|
const ZERO = new BigNumber(0);
|
||||||
// tokens & addresses
|
// tokens & addresses
|
||||||
let accounts: string[];
|
let accounts: string[];
|
||||||
let owner: string;
|
let owner: string;
|
||||||
let actors: string[];
|
let actors: string[];
|
||||||
let zrxTokenContract: DummyERC20TokenContract;
|
|
||||||
let erc20ProxyContract: ERC20ProxyContract;
|
|
||||||
// wrappers
|
// wrappers
|
||||||
let stakingWrapper: StakingWrapper;
|
let stakingApiWrapper: StakingApiWrapper;
|
||||||
let erc20Wrapper: ERC20Wrapper;
|
let erc20Wrapper: ERC20Wrapper;
|
||||||
// stake actor
|
// stake actor
|
||||||
let staker: StakerActor;
|
let staker: StakerActor;
|
||||||
@@ -32,49 +29,39 @@ blockchainTests.resets('Stake Statuses', env => {
|
|||||||
// tests
|
// tests
|
||||||
before(async () => {
|
before(async () => {
|
||||||
// create accounts
|
// create accounts
|
||||||
accounts = await web3Wrapper.getAvailableAddressesAsync();
|
accounts = await env.getAccountAddressesAsync();
|
||||||
owner = accounts[0];
|
owner = accounts[0];
|
||||||
actors = accounts.slice(2, 5);
|
actors = accounts.slice(2, 5);
|
||||||
// deploy erc20 proxy
|
// set up ERC20Wrapper
|
||||||
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
||||||
erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
|
|
||||||
// deploy zrx token
|
|
||||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
|
||||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
|
||||||
// deploy staking contracts
|
// deploy staking contracts
|
||||||
stakingWrapper = new StakingWrapper(
|
stakingApiWrapper = await deployAndConfigureContractsAsync(env, owner, erc20Wrapper, artifacts.TestStaking);
|
||||||
env.provider,
|
|
||||||
owner,
|
|
||||||
erc20ProxyContract,
|
|
||||||
erc20ProxyContract,
|
|
||||||
zrxTokenContract,
|
|
||||||
);
|
|
||||||
await stakingWrapper.deployAndConfigureContractsAsync(artifacts.TestStaking);
|
|
||||||
// setup new staker
|
// setup new staker
|
||||||
staker = new StakerActor(actors[0], stakingWrapper);
|
staker = new StakerActor(actors[0], stakingApiWrapper);
|
||||||
// setup pools
|
// setup pools
|
||||||
poolOperator = actors[1];
|
poolOperator = actors[1];
|
||||||
poolIds = await Promise.all([
|
poolIds = await Promise.all([
|
||||||
await stakingWrapper.createStakingPoolAsync(poolOperator, 4, false),
|
await stakingApiWrapper.utils.createStakingPoolAsync(poolOperator, 4, false),
|
||||||
await stakingWrapper.createStakingPoolAsync(poolOperator, 5, false),
|
await stakingApiWrapper.utils.createStakingPoolAsync(poolOperator, 5, false),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
describe('Stake', () => {
|
describe('Stake', () => {
|
||||||
it('should successfully stake zero ZRX', async () => {
|
it('should successfully stake zero ZRX', async () => {
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(0);
|
const amount = toBaseUnitAmount(0);
|
||||||
await staker.stakeAsync(amount);
|
await staker.stakeAsync(amount);
|
||||||
});
|
});
|
||||||
it('should successfully stake non-zero ZRX', async () => {
|
it('should successfully stake non-zero ZRX', async () => {
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
const amount = toBaseUnitAmount(10);
|
||||||
await staker.stakeAsync(amount);
|
await staker.stakeAsync(amount);
|
||||||
});
|
});
|
||||||
it('should retain stake balance across 1 epoch', async () => {
|
it('should retain stake balance across 1 epoch', async () => {
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
const amount = toBaseUnitAmount(10);
|
||||||
await staker.stakeAsync(amount);
|
await staker.stakeAsync(amount);
|
||||||
await staker.goToNextEpochAsync();
|
await staker.goToNextEpochAsync();
|
||||||
});
|
});
|
||||||
it('should retain stake balance across 2 epochs', async () => {
|
it('should retain stake balance across 2 epochs', async () => {
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
const amount = toBaseUnitAmount(10);
|
||||||
await staker.stakeAsync(amount);
|
await staker.stakeAsync(amount);
|
||||||
await staker.goToNextEpochAsync();
|
await staker.goToNextEpochAsync();
|
||||||
await staker.goToNextEpochAsync();
|
await staker.goToNextEpochAsync();
|
||||||
@@ -83,44 +70,44 @@ blockchainTests.resets('Stake Statuses', env => {
|
|||||||
describe('Move Stake', () => {
|
describe('Move Stake', () => {
|
||||||
it("should be able to rebalance next epoch's stake", async () => {
|
it("should be able to rebalance next epoch's stake", async () => {
|
||||||
// epoch 1
|
// epoch 1
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
const amount = toBaseUnitAmount(10);
|
||||||
await staker.stakeAsync(amount);
|
await staker.stakeAsync(amount);
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Active }, { status: StakeStatus.Inactive }, amount);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||||
// still epoch 1 ~ should be able to move stake again
|
// still epoch 1 ~ should be able to move stake again
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Inactive },
|
new StakeInfo(StakeStatus.Inactive),
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
amount,
|
amount,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('should fail to move the same stake more than once', async () => {
|
it('should fail to move the same stake more than once', async () => {
|
||||||
// epoch 1
|
// epoch 1
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
const amount = toBaseUnitAmount(10);
|
||||||
await staker.stakeAsync(amount);
|
await staker.stakeAsync(amount);
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Active }, { status: StakeStatus.Inactive }, amount);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||||
// stake is now inactive, should not be able to move it out of active status again
|
// stake is now inactive, should not be able to move it out of active status again
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Inactive },
|
new StakeInfo(StakeStatus.Inactive),
|
||||||
amount,
|
amount,
|
||||||
new StakingRevertErrors.InsufficientBalanceError(amount, ZERO),
|
new StakingRevertErrors.InsufficientBalanceError(amount, ZERO),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('should fail to reassign stake', async () => {
|
it('should fail to reassign stake', async () => {
|
||||||
// epoch 1
|
// epoch 1
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
const amount = toBaseUnitAmount(10);
|
||||||
await staker.stakeAsync(amount);
|
await staker.stakeAsync(amount);
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Active }, { status: StakeStatus.Inactive }, amount);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||||
// still epoch 1 ~ should be able to move stake again
|
// still epoch 1 ~ should be able to move stake again
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Inactive },
|
new StakeInfo(StakeStatus.Inactive),
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
amount,
|
amount,
|
||||||
);
|
);
|
||||||
// stake is now delegated; should fail to re-assign it from inactive back to active
|
// stake is now delegated; should fail to re-assign it from inactive back to active
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Inactive },
|
new StakeInfo(StakeStatus.Inactive),
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
amount,
|
amount,
|
||||||
new StakingRevertErrors.InsufficientBalanceError(amount, ZERO),
|
new StakingRevertErrors.InsufficientBalanceError(amount, ZERO),
|
||||||
);
|
);
|
||||||
@@ -128,56 +115,56 @@ blockchainTests.resets('Stake Statuses', env => {
|
|||||||
});
|
});
|
||||||
describe('Move Zero Stake', () => {
|
describe('Move Zero Stake', () => {
|
||||||
it('active -> active', async () => {
|
it('active -> active', async () => {
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Active }, { status: StakeStatus.Active }, ZERO);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Active), ZERO);
|
||||||
});
|
});
|
||||||
it('active -> inactive', async () => {
|
it('active -> inactive', async () => {
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Active }, { status: StakeStatus.Inactive }, ZERO);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), ZERO);
|
||||||
});
|
});
|
||||||
it('active -> delegated', async () => {
|
it('active -> delegated', async () => {
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
ZERO,
|
ZERO,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('inactive -> active', async () => {
|
it('inactive -> active', async () => {
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Inactive }, { status: StakeStatus.Active }, ZERO);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Inactive), new StakeInfo(StakeStatus.Active), ZERO);
|
||||||
});
|
});
|
||||||
it('inactive -> inactive', async () => {
|
it('inactive -> inactive', async () => {
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Inactive }, { status: StakeStatus.Inactive }, ZERO);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Inactive), new StakeInfo(StakeStatus.Inactive), ZERO);
|
||||||
});
|
});
|
||||||
it('inactive -> delegated', async () => {
|
it('inactive -> delegated', async () => {
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Inactive },
|
new StakeInfo(StakeStatus.Inactive),
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
ZERO,
|
ZERO,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('delegated -> active', async () => {
|
it('delegated -> active', async () => {
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
ZERO,
|
ZERO,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('delegated -> inactive', async () => {
|
it('delegated -> inactive', async () => {
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
{ status: StakeStatus.Inactive },
|
new StakeInfo(StakeStatus.Inactive),
|
||||||
ZERO,
|
ZERO,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('delegated -> delegated (same pool)', async () => {
|
it('delegated -> delegated (same pool)', async () => {
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
ZERO,
|
ZERO,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('delegated -> delegated (other pool)', async () => {
|
it('delegated -> delegated (other pool)', async () => {
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[1] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[1]),
|
||||||
ZERO,
|
ZERO,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -185,10 +172,10 @@ blockchainTests.resets('Stake Statuses', env => {
|
|||||||
describe('Move Non-Zero Stake', () => {
|
describe('Move Non-Zero Stake', () => {
|
||||||
const testMovePartialStake = async (from: StakeInfo, to: StakeInfo) => {
|
const testMovePartialStake = async (from: StakeInfo, to: StakeInfo) => {
|
||||||
// setup
|
// setup
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
const amount = toBaseUnitAmount(10);
|
||||||
await staker.stakeAsync(amount);
|
await staker.stakeAsync(amount);
|
||||||
if (from.status !== StakeStatus.Active) {
|
if (from.status !== StakeStatus.Active) {
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Active }, from, amount);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), from, amount);
|
||||||
}
|
}
|
||||||
// run test, checking balances in epochs [n .. n + 2]
|
// run test, checking balances in epochs [n .. n + 2]
|
||||||
// in epoch `n` - `next` is set
|
// in epoch `n` - `next` is set
|
||||||
@@ -199,111 +186,111 @@ blockchainTests.resets('Stake Statuses', env => {
|
|||||||
await staker.goToNextEpochAsync();
|
await staker.goToNextEpochAsync();
|
||||||
};
|
};
|
||||||
it('active -> active', async () => {
|
it('active -> active', async () => {
|
||||||
await testMovePartialStake({ status: StakeStatus.Active }, { status: StakeStatus.Active });
|
await testMovePartialStake(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Active));
|
||||||
});
|
});
|
||||||
it('active -> inactive', async () => {
|
it('active -> inactive', async () => {
|
||||||
await testMovePartialStake({ status: StakeStatus.Active }, { status: StakeStatus.Inactive });
|
await testMovePartialStake(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive));
|
||||||
});
|
});
|
||||||
it('active -> delegated', async () => {
|
it('active -> delegated', async () => {
|
||||||
await testMovePartialStake(
|
await testMovePartialStake(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('inactive -> active', async () => {
|
it('inactive -> active', async () => {
|
||||||
await testMovePartialStake({ status: StakeStatus.Inactive }, { status: StakeStatus.Active });
|
await testMovePartialStake(new StakeInfo(StakeStatus.Inactive), new StakeInfo(StakeStatus.Active));
|
||||||
});
|
});
|
||||||
it('inactive -> inactive', async () => {
|
it('inactive -> inactive', async () => {
|
||||||
await testMovePartialStake({ status: StakeStatus.Inactive }, { status: StakeStatus.Inactive });
|
await testMovePartialStake(new StakeInfo(StakeStatus.Inactive), new StakeInfo(StakeStatus.Inactive));
|
||||||
});
|
});
|
||||||
it('inactive -> delegated', async () => {
|
it('inactive -> delegated', async () => {
|
||||||
await testMovePartialStake(
|
await testMovePartialStake(
|
||||||
{ status: StakeStatus.Inactive },
|
new StakeInfo(StakeStatus.Inactive),
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('delegated -> active', async () => {
|
it('delegated -> active', async () => {
|
||||||
await testMovePartialStake(
|
await testMovePartialStake(
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('delegated -> inactive', async () => {
|
it('delegated -> inactive', async () => {
|
||||||
await testMovePartialStake(
|
await testMovePartialStake(
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
{ status: StakeStatus.Inactive },
|
new StakeInfo(StakeStatus.Inactive),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('delegated -> delegated (same pool)', async () => {
|
it('delegated -> delegated (same pool)', async () => {
|
||||||
await testMovePartialStake(
|
await testMovePartialStake(
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('delegated -> delegated (other pool)', async () => {
|
it('delegated -> delegated (other pool)', async () => {
|
||||||
await testMovePartialStake(
|
await testMovePartialStake(
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[1] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[1]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('Unstake', () => {
|
describe('Unstake', () => {
|
||||||
it('should successfully unstake zero ZRX', async () => {
|
it('should successfully unstake zero ZRX', async () => {
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(0);
|
const amount = toBaseUnitAmount(0);
|
||||||
await staker.unstakeAsync(amount);
|
await staker.unstakeAsync(amount);
|
||||||
});
|
});
|
||||||
it('should successfully unstake after being inactive for 1 epoch', async () => {
|
it('should successfully unstake after being inactive for 1 epoch', async () => {
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
const amount = toBaseUnitAmount(10);
|
||||||
await staker.stakeAsync(amount);
|
await staker.stakeAsync(amount);
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Active }, { status: StakeStatus.Inactive }, amount);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||||
await staker.goToNextEpochAsync(); // stake is now inactive
|
await staker.goToNextEpochAsync(); // stake is now inactive
|
||||||
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
||||||
await staker.unstakeAsync(amount);
|
await staker.unstakeAsync(amount);
|
||||||
});
|
});
|
||||||
it('should fail to unstake with insufficient balance', async () => {
|
it('should fail to unstake with insufficient balance', async () => {
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
const amount = toBaseUnitAmount(10);
|
||||||
await staker.stakeAsync(amount);
|
await staker.stakeAsync(amount);
|
||||||
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
||||||
});
|
});
|
||||||
it('should fail to unstake in the same epoch as stake was set to inactive', async () => {
|
it('should fail to unstake in the same epoch as stake was set to inactive', async () => {
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
const amount = toBaseUnitAmount(10);
|
||||||
await staker.stakeAsync(amount);
|
await staker.stakeAsync(amount);
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Active }, { status: StakeStatus.Inactive }, amount);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||||
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
||||||
});
|
});
|
||||||
it('should fail to unstake after being inactive for <1 epoch', async () => {
|
it('should fail to unstake after being inactive for <1 epoch', async () => {
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
const amount = toBaseUnitAmount(10);
|
||||||
await staker.stakeAsync(amount);
|
await staker.stakeAsync(amount);
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Active }, { status: StakeStatus.Inactive }, amount);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||||
await staker.goToNextEpochAsync();
|
await staker.goToNextEpochAsync();
|
||||||
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
||||||
});
|
});
|
||||||
it('should fail to unstake in same epoch that inactive/withdrawable stake has been reactivated', async () => {
|
it('should fail to unstake in same epoch that inactive/withdrawable stake has been reactivated', async () => {
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
const amount = toBaseUnitAmount(10);
|
||||||
await staker.stakeAsync(amount);
|
await staker.stakeAsync(amount);
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Active }, { status: StakeStatus.Inactive }, amount);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||||
await staker.goToNextEpochAsync(); // stake is now inactive
|
await staker.goToNextEpochAsync(); // stake is now inactive
|
||||||
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Inactive }, { status: StakeStatus.Active }, amount);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Inactive), new StakeInfo(StakeStatus.Active), amount);
|
||||||
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
||||||
});
|
});
|
||||||
it('should fail to unstake one epoch after inactive/withdrawable stake has been reactivated', async () => {
|
it('should fail to unstake one epoch after inactive/withdrawable stake has been reactivated', async () => {
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
const amount = toBaseUnitAmount(10);
|
||||||
await staker.stakeAsync(amount);
|
await staker.stakeAsync(amount);
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Active }, { status: StakeStatus.Inactive }, amount);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||||
await staker.goToNextEpochAsync(); // stake is now inactive
|
await staker.goToNextEpochAsync(); // stake is now inactive
|
||||||
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Inactive }, { status: StakeStatus.Active }, amount);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Inactive), new StakeInfo(StakeStatus.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 StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
||||||
});
|
});
|
||||||
it('should fail to unstake >1 epoch after inactive/withdrawable stake has been reactivated', async () => {
|
it('should fail to unstake >1 epoch after inactive/withdrawable stake has been reactivated', async () => {
|
||||||
const amount = StakingWrapper.toBaseUnitAmount(10);
|
const amount = toBaseUnitAmount(10);
|
||||||
await staker.stakeAsync(amount);
|
await staker.stakeAsync(amount);
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Active }, { status: StakeStatus.Inactive }, amount);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Active), new StakeInfo(StakeStatus.Inactive), amount);
|
||||||
await staker.goToNextEpochAsync(); // stake is now inactive
|
await staker.goToNextEpochAsync(); // stake is now inactive
|
||||||
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
await staker.goToNextEpochAsync(); // stake is now withdrawable
|
||||||
await staker.moveStakeAsync({ status: StakeStatus.Inactive }, { status: StakeStatus.Active }, amount);
|
await staker.moveStakeAsync(new StakeInfo(StakeStatus.Inactive), new StakeInfo(StakeStatus.Active), amount);
|
||||||
await staker.goToNextEpochAsync(); // stake is active and not withdrawable
|
await staker.goToNextEpochAsync(); // stake is active and not withdrawable
|
||||||
await staker.goToNextEpochAsync(); // stake is active and not withdrawable
|
await staker.goToNextEpochAsync(); // stake is active and not withdrawable
|
||||||
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
await staker.unstakeAsync(amount, new StakingRevertErrors.InsufficientBalanceError(amount, ZERO));
|
||||||
@@ -312,17 +299,17 @@ blockchainTests.resets('Stake Statuses', env => {
|
|||||||
describe('Simulations', () => {
|
describe('Simulations', () => {
|
||||||
it('Simulation from Staking Spec', async () => {
|
it('Simulation from Staking Spec', async () => {
|
||||||
// Epoch 1: Stake some ZRX
|
// Epoch 1: Stake some ZRX
|
||||||
await staker.stakeAsync(StakingWrapper.toBaseUnitAmount(4));
|
await staker.stakeAsync(toBaseUnitAmount(4));
|
||||||
// Later in Epoch 1: User delegates and deactivates some stake
|
// Later in Epoch 1: User delegates and deactivates some stake
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Inactive },
|
new StakeInfo(StakeStatus.Inactive),
|
||||||
StakingWrapper.toBaseUnitAmount(1),
|
toBaseUnitAmount(1),
|
||||||
);
|
);
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
StakingWrapper.toBaseUnitAmount(2),
|
toBaseUnitAmount(2),
|
||||||
);
|
);
|
||||||
// Epoch 2: Status updates (no user intervention required)
|
// Epoch 2: Status updates (no user intervention required)
|
||||||
await staker.goToNextEpochAsync();
|
await staker.goToNextEpochAsync();
|
||||||
@@ -330,33 +317,33 @@ blockchainTests.resets('Stake Statuses', env => {
|
|||||||
await staker.goToNextEpochAsync();
|
await staker.goToNextEpochAsync();
|
||||||
// Later in Epoch 3: User reactivates half of their inactive stake; this becomes Active next epoch
|
// Later in Epoch 3: User reactivates half of their inactive stake; this becomes Active next epoch
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Inactive },
|
new StakeInfo(StakeStatus.Inactive),
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
StakingWrapper.toBaseUnitAmount(0.5),
|
toBaseUnitAmount(0.5),
|
||||||
);
|
);
|
||||||
// Later in Epoch 3: User re-delegates half of their stake from Pool 1 to Pool 2
|
// Later in Epoch 3: User re-delegates half of their stake from Pool 1 to Pool 2
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[0] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[0]),
|
||||||
{ status: StakeStatus.Delegated, poolId: poolIds[1] },
|
new StakeInfo(StakeStatus.Delegated, poolIds[1]),
|
||||||
StakingWrapper.toBaseUnitAmount(1),
|
toBaseUnitAmount(1),
|
||||||
);
|
);
|
||||||
// Epoch 4: Status updates (no user intervention required)
|
// Epoch 4: Status updates (no user intervention required)
|
||||||
await staker.goToNextEpochAsync();
|
await staker.goToNextEpochAsync();
|
||||||
// Later in Epoch 4: User deactivates all active stake
|
// Later in Epoch 4: User deactivates all active stake
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
{ status: StakeStatus.Inactive },
|
new StakeInfo(StakeStatus.Inactive),
|
||||||
StakingWrapper.toBaseUnitAmount(1.5),
|
toBaseUnitAmount(1.5),
|
||||||
);
|
);
|
||||||
// Later in Epoch 4: User withdraws all available inactive stake
|
// Later in Epoch 4: User withdraws all available inactive stake
|
||||||
await staker.unstakeAsync(StakingWrapper.toBaseUnitAmount(0.5));
|
await staker.unstakeAsync(toBaseUnitAmount(0.5));
|
||||||
// Epoch 5: Status updates (no user intervention required)
|
// Epoch 5: Status updates (no user intervention required)
|
||||||
await staker.goToNextEpochAsync();
|
await staker.goToNextEpochAsync();
|
||||||
// Later in Epoch 5: User reactivates a portion of their stake
|
// Later in Epoch 5: User reactivates a portion of their stake
|
||||||
await staker.moveStakeAsync(
|
await staker.moveStakeAsync(
|
||||||
{ status: StakeStatus.Inactive },
|
new StakeInfo(StakeStatus.Inactive),
|
||||||
{ status: StakeStatus.Active },
|
new StakeInfo(StakeStatus.Active),
|
||||||
StakingWrapper.toBaseUnitAmount(1),
|
toBaseUnitAmount(1),
|
||||||
);
|
);
|
||||||
// Epoch 6: Status updates (no user intervention required)
|
// Epoch 6: Status updates (no user intervention required)
|
||||||
await staker.goToNextEpochAsync();
|
await staker.goToNextEpochAsync();
|
||||||
|
|||||||
208
contracts/staking/test/utils/api_wrapper.ts
Normal file
208
contracts/staking/test/utils/api_wrapper.ts
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||||
|
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||||
|
import { BlockchainTestsEnvironment, constants, txDefaults } from '@0x/contracts-test-utils';
|
||||||
|
import { BigNumber, logUtils } from '@0x/utils';
|
||||||
|
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||||
|
import { ContractArtifact, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
import {
|
||||||
|
artifacts,
|
||||||
|
EthVaultContract,
|
||||||
|
ReadOnlyProxyContract,
|
||||||
|
StakingContract,
|
||||||
|
StakingPoolRewardVaultContract,
|
||||||
|
StakingProxyContract,
|
||||||
|
ZrxVaultContract,
|
||||||
|
} from '../../src';
|
||||||
|
|
||||||
|
export class StakingApiWrapper {
|
||||||
|
public stakingContractAddress: string; // The address of the real Staking.sol contract
|
||||||
|
public stakingContract: StakingContract; // The StakingProxy.sol contract wrapped as a StakingContract to borrow API
|
||||||
|
public stakingProxyContract: StakingProxyContract; // The StakingProxy.sol contract as a StakingProxyContract
|
||||||
|
public zrxVaultContract: ZrxVaultContract;
|
||||||
|
public ethVaultContract: EthVaultContract;
|
||||||
|
public rewardVaultContract: StakingPoolRewardVaultContract;
|
||||||
|
public zrxTokenContract: DummyERC20TokenContract;
|
||||||
|
public utils = {
|
||||||
|
// Epoch Utils
|
||||||
|
fastForwardToNextEpochAsync: async (): Promise<void> => {
|
||||||
|
// increase timestamp of next block
|
||||||
|
const epochDurationInSeconds = await this.stakingContract.getEpochDurationInSeconds.callAsync();
|
||||||
|
await this._web3Wrapper.increaseTimeAsync(epochDurationInSeconds.toNumber());
|
||||||
|
// mine next block
|
||||||
|
await this._web3Wrapper.mineBlockAsync();
|
||||||
|
},
|
||||||
|
|
||||||
|
skipToNextEpochAsync: async (): Promise<TransactionReceiptWithDecodedLogs> => {
|
||||||
|
await this.utils.fastForwardToNextEpochAsync();
|
||||||
|
// increment epoch in contracts
|
||||||
|
const txReceipt = await this.stakingContract.finalizeFees.awaitTransactionSuccessAsync();
|
||||||
|
logUtils.log(`Finalization costed ${txReceipt.gasUsed} gas`);
|
||||||
|
// mine next block
|
||||||
|
await this._web3Wrapper.mineBlockAsync();
|
||||||
|
return txReceipt;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Other Utils
|
||||||
|
createStakingPoolAsync: async (
|
||||||
|
operatorAddress: string,
|
||||||
|
operatorShare: number,
|
||||||
|
addOperatorAsMaker: boolean,
|
||||||
|
): Promise<string> => {
|
||||||
|
const txReceipt = await this.stakingContract.createStakingPool.awaitTransactionSuccessAsync(
|
||||||
|
operatorShare,
|
||||||
|
addOperatorAsMaker,
|
||||||
|
{ from: operatorAddress },
|
||||||
|
);
|
||||||
|
const createStakingPoolLog = txReceipt.logs[0];
|
||||||
|
const poolId = (createStakingPoolLog as any).args.poolId;
|
||||||
|
return poolId;
|
||||||
|
},
|
||||||
|
|
||||||
|
getZrxTokenBalanceOfZrxVaultAsync: async (): Promise<BigNumber> => {
|
||||||
|
return this.zrxTokenContract.balanceOf.callAsync(this.zrxVaultContract.address);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly _web3Wrapper: Web3Wrapper;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
env: BlockchainTestsEnvironment,
|
||||||
|
ownerAddress: string,
|
||||||
|
stakingProxyContract: StakingProxyContract,
|
||||||
|
stakingContract: StakingContract,
|
||||||
|
zrxVaultContract: ZrxVaultContract,
|
||||||
|
ethVaultContract: EthVaultContract,
|
||||||
|
rewardVaultContract: StakingPoolRewardVaultContract,
|
||||||
|
zrxTokenContract: DummyERC20TokenContract,
|
||||||
|
) {
|
||||||
|
this._web3Wrapper = env.web3Wrapper;
|
||||||
|
this.zrxVaultContract = zrxVaultContract;
|
||||||
|
this.ethVaultContract = ethVaultContract;
|
||||||
|
this.rewardVaultContract = rewardVaultContract;
|
||||||
|
this.zrxTokenContract = zrxTokenContract;
|
||||||
|
|
||||||
|
this.stakingContractAddress = stakingContract.address;
|
||||||
|
this.stakingProxyContract = stakingProxyContract;
|
||||||
|
// disguise the staking proxy as a StakingContract
|
||||||
|
const logDecoderDependencies = _.mapValues({ ...artifacts, ...erc20Artifacts }, v => v.compilerOutput.abi);
|
||||||
|
this.stakingContract = new StakingContract(
|
||||||
|
stakingProxyContract.address,
|
||||||
|
env.provider,
|
||||||
|
{
|
||||||
|
...txDefaults,
|
||||||
|
from: ownerAddress,
|
||||||
|
to: stakingProxyContract.address,
|
||||||
|
gas: 3e6,
|
||||||
|
gasPrice: 0,
|
||||||
|
},
|
||||||
|
logDecoderDependencies,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deploys and configures all staking contracts and returns a StakingApiWrapper instance, which
|
||||||
|
* holds the deployed contracts and serves as the entry point for their public functions.
|
||||||
|
*/
|
||||||
|
export async function deployAndConfigureContractsAsync(
|
||||||
|
env: BlockchainTestsEnvironment,
|
||||||
|
ownerAddress: string,
|
||||||
|
erc20Wrapper: ERC20Wrapper,
|
||||||
|
customStakingArtifact?: ContractArtifact,
|
||||||
|
): Promise<StakingApiWrapper> {
|
||||||
|
// deploy erc20 proxy
|
||||||
|
const erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
|
||||||
|
// deploy zrx token
|
||||||
|
const [zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, constants.DUMMY_TOKEN_DECIMALS);
|
||||||
|
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||||
|
|
||||||
|
// deploy staking contract
|
||||||
|
const stakingContract = await StakingContract.deployFrom0xArtifactAsync(
|
||||||
|
customStakingArtifact !== undefined ? customStakingArtifact : artifacts.Staking,
|
||||||
|
env.provider,
|
||||||
|
txDefaults,
|
||||||
|
artifacts,
|
||||||
|
);
|
||||||
|
// deploy read-only proxy
|
||||||
|
const readOnlyProxyContract = await ReadOnlyProxyContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.ReadOnlyProxy,
|
||||||
|
env.provider,
|
||||||
|
txDefaults,
|
||||||
|
artifacts,
|
||||||
|
);
|
||||||
|
// deploy staking proxy
|
||||||
|
const stakingProxyContract = await StakingProxyContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.StakingProxy,
|
||||||
|
env.provider,
|
||||||
|
txDefaults,
|
||||||
|
artifacts,
|
||||||
|
stakingContract.address,
|
||||||
|
readOnlyProxyContract.address,
|
||||||
|
erc20ProxyContract.address,
|
||||||
|
);
|
||||||
|
|
||||||
|
// deploy zrx vault
|
||||||
|
const zrxVaultContract = await ZrxVaultContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.ZrxVault,
|
||||||
|
env.provider,
|
||||||
|
txDefaults,
|
||||||
|
artifacts,
|
||||||
|
erc20ProxyContract.address,
|
||||||
|
zrxTokenContract.address,
|
||||||
|
);
|
||||||
|
// configure erc20 proxy to accept calls from zrx vault
|
||||||
|
await erc20ProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync(zrxVaultContract.address);
|
||||||
|
// set staking proxy contract in zrx vault
|
||||||
|
await zrxVaultContract.setStakingContract.awaitTransactionSuccessAsync(stakingProxyContract.address);
|
||||||
|
// set zrx vault in staking contract
|
||||||
|
const setZrxVaultCalldata = stakingContract.setZrxVault.getABIEncodedTransactionData(zrxVaultContract.address);
|
||||||
|
const setZrxVaultTxData = {
|
||||||
|
from: ownerAddress,
|
||||||
|
to: stakingProxyContract.address,
|
||||||
|
data: setZrxVaultCalldata,
|
||||||
|
};
|
||||||
|
await env.web3Wrapper.awaitTransactionSuccessAsync(await env.web3Wrapper.sendTransactionAsync(setZrxVaultTxData));
|
||||||
|
// deploy eth vault
|
||||||
|
const ethVaultContract = await EthVaultContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.EthVault,
|
||||||
|
env.provider,
|
||||||
|
txDefaults,
|
||||||
|
artifacts,
|
||||||
|
);
|
||||||
|
// deploy reward vault
|
||||||
|
const rewardVaultContract = await StakingPoolRewardVaultContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.StakingPoolRewardVault,
|
||||||
|
env.provider,
|
||||||
|
txDefaults,
|
||||||
|
artifacts,
|
||||||
|
);
|
||||||
|
// set eth vault in reward vault
|
||||||
|
await rewardVaultContract.setEthVault.sendTransactionAsync(ethVaultContract.address);
|
||||||
|
// set staking proxy contract in reward vault
|
||||||
|
await rewardVaultContract.setStakingContract.awaitTransactionSuccessAsync(stakingProxyContract.address);
|
||||||
|
// set reward vault in staking contract
|
||||||
|
const setStakingPoolRewardVaultCalldata = stakingContract.setStakingPoolRewardVault.getABIEncodedTransactionData(
|
||||||
|
rewardVaultContract.address,
|
||||||
|
);
|
||||||
|
const setStakingPoolRewardVaultTxData = {
|
||||||
|
from: ownerAddress,
|
||||||
|
to: stakingProxyContract.address,
|
||||||
|
data: setStakingPoolRewardVaultCalldata,
|
||||||
|
};
|
||||||
|
await env.web3Wrapper.awaitTransactionSuccessAsync(
|
||||||
|
await env.web3Wrapper.sendTransactionAsync(setStakingPoolRewardVaultTxData),
|
||||||
|
);
|
||||||
|
|
||||||
|
return new StakingApiWrapper(
|
||||||
|
env,
|
||||||
|
ownerAddress,
|
||||||
|
stakingProxyContract,
|
||||||
|
stakingContract,
|
||||||
|
zrxVaultContract,
|
||||||
|
ethVaultContract,
|
||||||
|
rewardVaultContract,
|
||||||
|
zrxTokenContract,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { expect } from '@0x/contracts-test-utils';
|
import { expect } from '@0x/contracts-test-utils';
|
||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import { Decimal } from 'decimal.js';
|
import { Decimal } from 'decimal.js';
|
||||||
|
|
||||||
@@ -90,3 +91,13 @@ export function assertRoughlyEquals(actual: Numberish, expected: Numberish, prec
|
|||||||
}
|
}
|
||||||
expect(actual).to.bignumber.eq(expected);
|
expect(actual).to.bignumber.eq(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts `amount` into a base unit amount with 18 digits.
|
||||||
|
*/
|
||||||
|
export function toBaseUnitAmount(amount: Numberish): BigNumber {
|
||||||
|
const decimals = 18;
|
||||||
|
const amountAsBigNumber = new BigNumber(amount);
|
||||||
|
const baseUnitAmount = Web3Wrapper.toBaseUnitAmount(amountAsBigNumber, decimals);
|
||||||
|
return baseUnitAmount;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,537 +0,0 @@
|
|||||||
import { ERC20ProxyContract } from '@0x/contracts-asset-proxy';
|
|
||||||
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
|
|
||||||
import { LogDecoder, txDefaults } from '@0x/contracts-test-utils';
|
|
||||||
import { BigNumber, logUtils } from '@0x/utils';
|
|
||||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
|
||||||
import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
|
||||||
import * as _ from 'lodash';
|
|
||||||
|
|
||||||
import {
|
|
||||||
artifacts,
|
|
||||||
EthVaultContract,
|
|
||||||
ReadOnlyProxyContract,
|
|
||||||
StakingContract,
|
|
||||||
StakingPoolRewardVaultContract,
|
|
||||||
StakingProxyContract,
|
|
||||||
ZrxVaultContract,
|
|
||||||
} from '../../src';
|
|
||||||
|
|
||||||
import { constants } from './constants';
|
|
||||||
import { StakeBalance } from './types';
|
|
||||||
|
|
||||||
export class StakingWrapper {
|
|
||||||
private readonly _web3Wrapper: Web3Wrapper;
|
|
||||||
private readonly _provider: Provider;
|
|
||||||
private readonly _logDecoder: LogDecoder;
|
|
||||||
private readonly _ownerAddress: string;
|
|
||||||
private readonly _wethProxyContract: ERC20ProxyContract;
|
|
||||||
private readonly _erc20ProxyContract: ERC20ProxyContract;
|
|
||||||
private readonly _zrxTokenContract: DummyERC20TokenContract;
|
|
||||||
private _stakingContractIfExists?: StakingContract;
|
|
||||||
private _stakingProxyContractIfExists?: StakingProxyContract;
|
|
||||||
private _zrxVaultContractIfExists?: ZrxVaultContract;
|
|
||||||
private _ethVaultContractIfExists?: EthVaultContract;
|
|
||||||
private _rewardVaultContractIfExists?: StakingPoolRewardVaultContract;
|
|
||||||
private _readOnlyProxyContractIfExists?: ReadOnlyProxyContract;
|
|
||||||
public static toBaseUnitAmount(amount: BigNumber | number): BigNumber {
|
|
||||||
const decimals = 18;
|
|
||||||
const amountAsBigNumber = typeof amount === 'number' ? new BigNumber(amount) : amount;
|
|
||||||
const baseUnitAmount = Web3Wrapper.toBaseUnitAmount(amountAsBigNumber, decimals);
|
|
||||||
return baseUnitAmount;
|
|
||||||
}
|
|
||||||
public static toFixedPoint(amount: BigNumber | number, decimals: number): BigNumber {
|
|
||||||
const amountAsBigNumber = typeof amount === 'number' ? new BigNumber(amount) : amount;
|
|
||||||
const scalar = Math.pow(10, decimals);
|
|
||||||
const amountAsFixedPoint = amountAsBigNumber.times(scalar);
|
|
||||||
return amountAsFixedPoint;
|
|
||||||
}
|
|
||||||
public static toFloatingPoint(amount: BigNumber | number, decimals: number): BigNumber {
|
|
||||||
const amountAsBigNumber = typeof amount === 'number' ? new BigNumber(amount) : amount;
|
|
||||||
const scalar = Math.pow(10, decimals);
|
|
||||||
const amountAsFloatingPoint = amountAsBigNumber.dividedBy(scalar);
|
|
||||||
return amountAsFloatingPoint;
|
|
||||||
}
|
|
||||||
public static trimFloat(amount: BigNumber | number, decimals: number): BigNumber {
|
|
||||||
const amountAsBigNumber = typeof amount === 'number' ? new BigNumber(amount) : amount;
|
|
||||||
const scalar = Math.pow(10, decimals);
|
|
||||||
const amountAsFloatingPoint = amountAsBigNumber
|
|
||||||
.multipliedBy(scalar)
|
|
||||||
.dividedToIntegerBy(1)
|
|
||||||
.dividedBy(scalar);
|
|
||||||
return amountAsFloatingPoint;
|
|
||||||
}
|
|
||||||
constructor(
|
|
||||||
provider: Provider,
|
|
||||||
ownerAddres: string,
|
|
||||||
erc20ProxyContract: any, // This needs to be the `any` type so that other types of proxies can be used
|
|
||||||
wethProxyContract: any, // This needs to be the `any` type so that other types of proxies can be used
|
|
||||||
zrxTokenContract: DummyERC20TokenContract,
|
|
||||||
) {
|
|
||||||
this._web3Wrapper = new Web3Wrapper(provider);
|
|
||||||
this._provider = provider;
|
|
||||||
const decoderArtifacts = _.merge(artifacts, erc20Artifacts);
|
|
||||||
this._logDecoder = new LogDecoder(this._web3Wrapper, decoderArtifacts);
|
|
||||||
this._ownerAddress = ownerAddres;
|
|
||||||
this._erc20ProxyContract = erc20ProxyContract;
|
|
||||||
this._wethProxyContract = wethProxyContract;
|
|
||||||
this._zrxTokenContract = zrxTokenContract;
|
|
||||||
}
|
|
||||||
public getStakingContract(): StakingContract {
|
|
||||||
this._validateDeployedOrThrow();
|
|
||||||
return this._stakingContractIfExists as StakingContract;
|
|
||||||
}
|
|
||||||
public getStakingProxyContract(): StakingProxyContract {
|
|
||||||
this._validateDeployedOrThrow();
|
|
||||||
return this._stakingProxyContractIfExists as StakingProxyContract;
|
|
||||||
}
|
|
||||||
public getZrxVaultContract(): ZrxVaultContract {
|
|
||||||
this._validateDeployedOrThrow();
|
|
||||||
return this._zrxVaultContractIfExists as ZrxVaultContract;
|
|
||||||
}
|
|
||||||
public getEthVaultContract(): EthVaultContract {
|
|
||||||
this._validateDeployedOrThrow();
|
|
||||||
return this._ethVaultContractIfExists as EthVaultContract;
|
|
||||||
}
|
|
||||||
public getStakingPoolRewardVaultContract(): StakingPoolRewardVaultContract {
|
|
||||||
this._validateDeployedOrThrow();
|
|
||||||
return this._rewardVaultContractIfExists as StakingPoolRewardVaultContract;
|
|
||||||
}
|
|
||||||
public async deployAndConfigureContractsAsync(customStakingArtifact?: any): Promise<void> {
|
|
||||||
// deploy read-only proxy
|
|
||||||
this._readOnlyProxyContractIfExists = await ReadOnlyProxyContract.deployFrom0xArtifactAsync(
|
|
||||||
artifacts.ReadOnlyProxy,
|
|
||||||
this._provider,
|
|
||||||
txDefaults,
|
|
||||||
artifacts,
|
|
||||||
);
|
|
||||||
// deploy zrx vault
|
|
||||||
this._zrxVaultContractIfExists = await ZrxVaultContract.deployFrom0xArtifactAsync(
|
|
||||||
artifacts.ZrxVault,
|
|
||||||
this._provider,
|
|
||||||
txDefaults,
|
|
||||||
artifacts,
|
|
||||||
this._erc20ProxyContract.address,
|
|
||||||
this._zrxTokenContract.address,
|
|
||||||
);
|
|
||||||
// deploy eth vault
|
|
||||||
this._ethVaultContractIfExists = await EthVaultContract.deployFrom0xArtifactAsync(
|
|
||||||
artifacts.EthVault,
|
|
||||||
this._provider,
|
|
||||||
txDefaults,
|
|
||||||
artifacts,
|
|
||||||
);
|
|
||||||
// deploy reward vault
|
|
||||||
this._rewardVaultContractIfExists = await StakingPoolRewardVaultContract.deployFrom0xArtifactAsync(
|
|
||||||
artifacts.StakingPoolRewardVault,
|
|
||||||
this._provider,
|
|
||||||
txDefaults,
|
|
||||||
artifacts,
|
|
||||||
);
|
|
||||||
// set eth vault in reward vault
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
// deploy staking contract
|
|
||||||
this._stakingContractIfExists = await StakingContract.deployFrom0xArtifactAsync(
|
|
||||||
customStakingArtifact === undefined ? artifacts.Staking : customStakingArtifact,
|
|
||||||
this._provider,
|
|
||||||
txDefaults,
|
|
||||||
artifacts,
|
|
||||||
);
|
|
||||||
// deploy staking proxy
|
|
||||||
this._stakingProxyContractIfExists = await StakingProxyContract.deployFrom0xArtifactAsync(
|
|
||||||
artifacts.StakingProxy,
|
|
||||||
this._provider,
|
|
||||||
txDefaults,
|
|
||||||
artifacts,
|
|
||||||
this._stakingContractIfExists.address,
|
|
||||||
this._readOnlyProxyContractIfExists.address,
|
|
||||||
this._wethProxyContract.address,
|
|
||||||
);
|
|
||||||
// configure weth proxy to accept calls from staking.
|
|
||||||
await this._wethProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
|
||||||
(this._stakingProxyContractIfExists as StakingProxyContract).address, // tslint:disable-line:no-unnecessary-type-assertion
|
|
||||||
);
|
|
||||||
// set staking proxy contract in zrx vault
|
|
||||||
await this._zrxVaultContractIfExists.setStakingContract.awaitTransactionSuccessAsync(
|
|
||||||
this._stakingProxyContractIfExists.address,
|
|
||||||
);
|
|
||||||
// set zrx vault in staking contract
|
|
||||||
const setZrxVaultCalldata = this._stakingContractIfExists.setZrxVault.getABIEncodedTransactionData(
|
|
||||||
this._zrxVaultContractIfExists.address,
|
|
||||||
);
|
|
||||||
const setZrxVaultTxData = {
|
|
||||||
from: this._ownerAddress,
|
|
||||||
to: this._stakingProxyContractIfExists.address,
|
|
||||||
data: setZrxVaultCalldata,
|
|
||||||
};
|
|
||||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
|
||||||
await this._web3Wrapper.sendTransactionAsync(setZrxVaultTxData),
|
|
||||||
);
|
|
||||||
// set staking proxy contract in reward vault
|
|
||||||
await this._rewardVaultContractIfExists.setStakingContract.awaitTransactionSuccessAsync(
|
|
||||||
this._stakingProxyContractIfExists.address,
|
|
||||||
);
|
|
||||||
// set reward vault in staking contract
|
|
||||||
const setStakingPoolRewardVaultCalldata = this._stakingContractIfExists.setStakingPoolRewardVault.getABIEncodedTransactionData(
|
|
||||||
this._rewardVaultContractIfExists.address,
|
|
||||||
);
|
|
||||||
const setStakingPoolRewardVaultTxData = {
|
|
||||||
from: this._ownerAddress,
|
|
||||||
to: this._stakingProxyContractIfExists.address,
|
|
||||||
data: setStakingPoolRewardVaultCalldata,
|
|
||||||
};
|
|
||||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
|
||||||
await this._web3Wrapper.sendTransactionAsync(setStakingPoolRewardVaultTxData),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
public async setReadOnlyModeAsync(readOnlyMode: boolean): Promise<TransactionReceiptWithDecodedLogs> {
|
|
||||||
const txReceipt = await this.getStakingProxyContract().setReadOnlyMode.awaitTransactionSuccessAsync(
|
|
||||||
readOnlyMode,
|
|
||||||
);
|
|
||||||
return txReceipt;
|
|
||||||
}
|
|
||||||
public async getEthBalanceAsync(owner: string): Promise<BigNumber> {
|
|
||||||
const balance = this._web3Wrapper.getBalanceInWeiAsync(owner);
|
|
||||||
return balance;
|
|
||||||
}
|
|
||||||
///// STAKE /////
|
|
||||||
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);
|
|
||||||
const txReceipt = await this._executeTransactionAsync(calldata, owner);
|
|
||||||
return txReceipt;
|
|
||||||
}
|
|
||||||
public async moveStakeAsync(
|
|
||||||
owner: string,
|
|
||||||
_fromStatus: {
|
|
||||||
status: number;
|
|
||||||
poolId?: string;
|
|
||||||
},
|
|
||||||
_toStatus: {
|
|
||||||
status: number;
|
|
||||||
poolId?: string;
|
|
||||||
},
|
|
||||||
amount: BigNumber,
|
|
||||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
|
||||||
const fromStatus = {
|
|
||||||
status: _fromStatus.status,
|
|
||||||
poolId: _fromStatus.poolId !== undefined ? _fromStatus.poolId : constants.NIL_POOL_ID,
|
|
||||||
};
|
|
||||||
const toStatus = {
|
|
||||||
status: _toStatus.status,
|
|
||||||
poolId: _toStatus.poolId !== undefined ? _toStatus.poolId : constants.NIL_POOL_ID,
|
|
||||||
};
|
|
||||||
const calldata = this.getStakingContract().moveStake.getABIEncodedTransactionData(fromStatus, toStatus, amount);
|
|
||||||
const txReceipt = await this._executeTransactionAsync(calldata, owner);
|
|
||||||
return txReceipt;
|
|
||||||
}
|
|
||||||
///// STAKE BALANCES /////
|
|
||||||
public async getTotalStakeAsync(owner: string): Promise<BigNumber> {
|
|
||||||
const calldata = this.getStakingContract().getTotalStake.getABIEncodedTransactionData(owner);
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const value = this.getStakingContract().getTotalStake.getABIDecodedReturnData(returnData);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
public async getActiveStakeAsync(owner: string): Promise<StakeBalance> {
|
|
||||||
const calldata = this.getStakingContract().getActiveStake.getABIEncodedTransactionData(owner);
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const value = this.getStakingContract().getActiveStake.getABIDecodedReturnData(returnData);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
public async getInactiveStakeAsync(owner: string): Promise<StakeBalance> {
|
|
||||||
const calldata = this.getStakingContract().getInactiveStake.getABIEncodedTransactionData(owner);
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const value = this.getStakingContract().getInactiveStake.getABIDecodedReturnData(returnData);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
public async getWithdrawableStakeAsync(owner: string): Promise<BigNumber> {
|
|
||||||
const calldata = this.getStakingContract().getWithdrawableStake.getABIEncodedTransactionData(owner);
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const value = this.getStakingContract().getWithdrawableStake.getABIDecodedReturnData(returnData);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
public async getStakeDelegatedByOwnerAsync(owner: string): Promise<StakeBalance> {
|
|
||||||
const calldata = this.getStakingContract().getStakeDelegatedByOwner.getABIEncodedTransactionData(owner);
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const value = this.getStakingContract().getStakeDelegatedByOwner.getABIDecodedReturnData(returnData);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
public async getStakeDelegatedToPoolByOwnerAsync(poolId: string, owner: string): Promise<StakeBalance> {
|
|
||||||
const calldata = this.getStakingContract().getStakeDelegatedToPoolByOwner.getABIEncodedTransactionData(
|
|
||||||
owner,
|
|
||||||
poolId,
|
|
||||||
);
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const value = this.getStakingContract().getStakeDelegatedToPoolByOwner.getABIDecodedReturnData(returnData);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
public async getTotalStakeDelegatedToPoolAsync(poolId: string): Promise<StakeBalance> {
|
|
||||||
const calldata = this.getStakingContract().getTotalStakeDelegatedToPool.getABIEncodedTransactionData(poolId);
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const value = this.getStakingContract().getTotalStakeDelegatedToPool.getABIDecodedReturnData(returnData);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
///// POOLS /////
|
|
||||||
public async getNextStakingPoolIdAsync(): Promise<string> {
|
|
||||||
const calldata = this.getStakingContract().getNextStakingPoolId.getABIEncodedTransactionData();
|
|
||||||
const nextPoolId = await this._callAsync(calldata);
|
|
||||||
return nextPoolId;
|
|
||||||
}
|
|
||||||
public async createStakingPoolAsync(
|
|
||||||
operatorAddress: string,
|
|
||||||
operatorShare: number,
|
|
||||||
addOperatorAsMaker: boolean,
|
|
||||||
): Promise<string> {
|
|
||||||
const calldata = this.getStakingContract().createStakingPool.getABIEncodedTransactionData(
|
|
||||||
operatorShare,
|
|
||||||
addOperatorAsMaker,
|
|
||||||
);
|
|
||||||
const txReceipt = await this._executeTransactionAsync(calldata, operatorAddress);
|
|
||||||
const createStakingPoolLog = this._logDecoder.decodeLogOrThrow(txReceipt.logs[0]);
|
|
||||||
const poolId = (createStakingPoolLog as any).args.poolId;
|
|
||||||
return poolId;
|
|
||||||
}
|
|
||||||
public async joinStakingPoolAsMakerAsync(
|
|
||||||
poolId: string,
|
|
||||||
makerAddress: string,
|
|
||||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
|
||||||
const calldata = this.getStakingContract().joinStakingPoolAsMaker.getABIEncodedTransactionData(poolId);
|
|
||||||
const txReceipt = await this._executeTransactionAsync(calldata, makerAddress);
|
|
||||||
return txReceipt;
|
|
||||||
}
|
|
||||||
public async addMakerToStakingPoolAsync(
|
|
||||||
poolId: string,
|
|
||||||
makerAddress: string,
|
|
||||||
operatorAddress: string,
|
|
||||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
|
||||||
const calldata = this.getStakingContract().addMakerToStakingPool.getABIEncodedTransactionData(
|
|
||||||
poolId,
|
|
||||||
makerAddress,
|
|
||||||
);
|
|
||||||
const txReceipt = await this._executeTransactionAsync(calldata, operatorAddress);
|
|
||||||
return txReceipt;
|
|
||||||
}
|
|
||||||
public async removeMakerFromStakingPoolAsync(
|
|
||||||
poolId: string,
|
|
||||||
makerAddress: string,
|
|
||||||
operatorAddress: string,
|
|
||||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
|
||||||
const calldata = this.getStakingContract().removeMakerFromStakingPool.getABIEncodedTransactionData(
|
|
||||||
poolId,
|
|
||||||
makerAddress,
|
|
||||||
);
|
|
||||||
const txReceipt = await this._executeTransactionAsync(calldata, operatorAddress);
|
|
||||||
return txReceipt;
|
|
||||||
}
|
|
||||||
public async getStakingPoolIdOfMakerAsync(makerAddress: string): Promise<string> {
|
|
||||||
const calldata = this.getStakingContract().getStakingPoolIdOfMaker.getABIEncodedTransactionData(makerAddress);
|
|
||||||
const poolId = await this._callAsync(calldata);
|
|
||||||
return poolId;
|
|
||||||
}
|
|
||||||
public async getNumberOfMakersInStakingPoolAsync(poolId: string): Promise<BigNumber> {
|
|
||||||
const calldata = this.getStakingContract().getNumberOfMakersInStakingPool.getABIEncodedTransactionData(poolId);
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const value = this.getStakingContract().getNumberOfMakersInStakingPool.getABIDecodedReturnData(returnData);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
///// EPOCHS /////
|
|
||||||
public async goToNextEpochAsync(): Promise<TransactionReceiptWithDecodedLogs> {
|
|
||||||
const calldata = this.getStakingContract().finalizeFees.getABIEncodedTransactionData();
|
|
||||||
const txReceipt = await this._executeTransactionAsync(calldata, undefined, new BigNumber(0), true);
|
|
||||||
logUtils.log(`Finalization costed ${txReceipt.gasUsed} gas`);
|
|
||||||
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
|
|
||||||
await this._web3Wrapper.mineBlockAsync();
|
|
||||||
}
|
|
||||||
public async skipToNextEpochAsync(): Promise<TransactionReceiptWithDecodedLogs> {
|
|
||||||
await this.fastForwardToNextEpochAsync();
|
|
||||||
// increment epoch in contracts
|
|
||||||
const txReceipt = await this.goToNextEpochAsync();
|
|
||||||
await this._web3Wrapper.mineBlockAsync();
|
|
||||||
return txReceipt;
|
|
||||||
}
|
|
||||||
public async getEpochDurationInSecondsAsync(): Promise<BigNumber> {
|
|
||||||
const calldata = this.getStakingContract().getEpochDurationInSeconds.getABIEncodedTransactionData();
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const value = this.getStakingContract().getEpochDurationInSeconds.getABIDecodedReturnData(returnData);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
public async getCurrentEpochStartTimeInSecondsAsync(): Promise<BigNumber> {
|
|
||||||
const calldata = this.getStakingContract().getCurrentEpochStartTimeInSeconds.getABIEncodedTransactionData();
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const value = this.getStakingContract().getCurrentEpochStartTimeInSeconds.getABIDecodedReturnData(returnData);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
public async getCurrentEpochEarliestEndTimeInSecondsAsync(): Promise<BigNumber> {
|
|
||||||
const calldata = this.getStakingContract().getCurrentEpochEarliestEndTimeInSeconds.getABIEncodedTransactionData();
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const value = this.getStakingContract().getCurrentEpochEarliestEndTimeInSeconds.getABIDecodedReturnData(
|
|
||||||
returnData,
|
|
||||||
);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
public async getCurrentEpochAsync(): Promise<BigNumber> {
|
|
||||||
const calldata = this.getStakingContract().getCurrentEpoch.getABIEncodedTransactionData();
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const value = this.getStakingContract().getCurrentEpoch.getABIDecodedReturnData(returnData);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
///// PROTOCOL FEES /////
|
|
||||||
public async payProtocolFeeAsync(
|
|
||||||
makerAddress: string,
|
|
||||||
payerAddress: string,
|
|
||||||
protocolFeePaid: BigNumber,
|
|
||||||
amount: BigNumber,
|
|
||||||
exchangeAddress: string,
|
|
||||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
|
||||||
const calldata = this.getStakingContract().payProtocolFee.getABIEncodedTransactionData(
|
|
||||||
makerAddress,
|
|
||||||
payerAddress,
|
|
||||||
protocolFeePaid,
|
|
||||||
);
|
|
||||||
const txReceipt = await this._executeTransactionAsync(calldata, exchangeAddress, amount);
|
|
||||||
return txReceipt;
|
|
||||||
}
|
|
||||||
public async getProtocolFeesThisEpochByPoolAsync(poolId: string): Promise<BigNumber> {
|
|
||||||
const calldata = this.getStakingContract().getProtocolFeesThisEpochByPool.getABIEncodedTransactionData(poolId);
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const value = this.getStakingContract().getProtocolFeesThisEpochByPool.getABIDecodedReturnData(returnData);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
public async getTotalProtocolFeesThisEpochAsync(): Promise<BigNumber> {
|
|
||||||
const calldata = this.getStakingContract().getTotalProtocolFeesThisEpoch.getABIEncodedTransactionData();
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const value = this.getStakingContract().getTotalProtocolFeesThisEpoch.getABIDecodedReturnData(returnData);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
///// EXCHANGES /////
|
|
||||||
public async isValidExchangeAddressAsync(exchangeAddress: string): Promise<boolean> {
|
|
||||||
const calldata = this.getStakingContract().isValidExchangeAddress.getABIEncodedTransactionData(exchangeAddress);
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const isValid = this.getStakingContract().isValidExchangeAddress.getABIDecodedReturnData(returnData);
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
public async addExchangeAddressAsync(
|
|
||||||
exchangeAddress: string,
|
|
||||||
ownerAddressIfExists?: string,
|
|
||||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
|
||||||
const calldata = this.getStakingContract().addExchangeAddress.getABIEncodedTransactionData(exchangeAddress);
|
|
||||||
const ownerAddress = ownerAddressIfExists !== undefined ? ownerAddressIfExists : this._ownerAddress;
|
|
||||||
const txReceipt = await this._executeTransactionAsync(calldata, ownerAddress);
|
|
||||||
return txReceipt;
|
|
||||||
}
|
|
||||||
public async removeExchangeAddressAsync(
|
|
||||||
exchangeAddress: string,
|
|
||||||
ownerAddressIfExists?: string,
|
|
||||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
|
||||||
const calldata = this.getStakingContract().removeExchangeAddress.getABIEncodedTransactionData(exchangeAddress);
|
|
||||||
const ownerAddress = ownerAddressIfExists !== undefined ? ownerAddressIfExists : this._ownerAddress;
|
|
||||||
const txReceipt = await this._executeTransactionAsync(calldata, ownerAddress);
|
|
||||||
return txReceipt;
|
|
||||||
}
|
|
||||||
///// REWARDS /////
|
|
||||||
public async computeRewardBalanceOfStakingPoolMemberAsync(poolId: string, owner: string): Promise<BigNumber> {
|
|
||||||
const calldata = this.getStakingContract().computeRewardBalanceOfDelegator.getABIEncodedTransactionData(
|
|
||||||
poolId,
|
|
||||||
owner,
|
|
||||||
);
|
|
||||||
const returnData = await this._callAsync(calldata);
|
|
||||||
const value = this.getStakingContract().computeRewardBalanceOfDelegator.getABIDecodedReturnData(returnData);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
///// REWARD VAULT /////
|
|
||||||
public async rewardVaultEnterCatastrophicFailureModeAsync(
|
|
||||||
zeroExMultisigAddress: string,
|
|
||||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
|
||||||
const calldata = this.getStakingPoolRewardVaultContract().enterCatastrophicFailure.getABIEncodedTransactionData();
|
|
||||||
const txReceipt = await this._executeTransactionAsync(calldata, zeroExMultisigAddress);
|
|
||||||
return txReceipt;
|
|
||||||
}
|
|
||||||
public async rewardVaultBalanceOfAsync(poolId: string): Promise<BigNumber> {
|
|
||||||
const balance = await this.getStakingPoolRewardVaultContract().balanceOf.callAsync(poolId);
|
|
||||||
return balance;
|
|
||||||
}
|
|
||||||
public async rewardVaultBalanceOfOperatorAsync(poolId: string): Promise<BigNumber> {
|
|
||||||
const balance = await this.getStakingPoolRewardVaultContract().balanceOfOperator.callAsync(poolId);
|
|
||||||
return balance;
|
|
||||||
}
|
|
||||||
public async rewardVaultBalanceOfMembersAsync(poolId: string): Promise<BigNumber> {
|
|
||||||
const balance = await this.getStakingPoolRewardVaultContract().balanceOfMembers.callAsync(poolId);
|
|
||||||
return balance;
|
|
||||||
}
|
|
||||||
public async rewardVaultRegisterPoolAsync(
|
|
||||||
poolId: string,
|
|
||||||
poolOperatorShare: number,
|
|
||||||
stakingContractAddress: string,
|
|
||||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
|
||||||
const calldata = this.getStakingPoolRewardVaultContract().registerStakingPool.getABIEncodedTransactionData(
|
|
||||||
poolId,
|
|
||||||
poolOperatorShare,
|
|
||||||
);
|
|
||||||
const txReceipt = await this._executeTransactionAsync(calldata, stakingContractAddress);
|
|
||||||
return txReceipt;
|
|
||||||
}
|
|
||||||
///// ZRX VAULT /////
|
|
||||||
public async getZrxVaultBalanceAsync(holder: string): Promise<BigNumber> {
|
|
||||||
const balance = await this.getZrxVaultContract().balanceOf.callAsync(holder);
|
|
||||||
return balance;
|
|
||||||
}
|
|
||||||
public async getZrxTokenBalanceAsync(holder: string): Promise<BigNumber> {
|
|
||||||
const balance = await this._zrxTokenContract.balanceOf.callAsync(holder);
|
|
||||||
return balance;
|
|
||||||
}
|
|
||||||
public async getZrxTokenBalanceOfZrxVaultAsync(): Promise<BigNumber> {
|
|
||||||
const balance = await this._zrxTokenContract.balanceOf.callAsync(this.getZrxVaultContract().address);
|
|
||||||
return balance;
|
|
||||||
}
|
|
||||||
private async _executeTransactionAsync(
|
|
||||||
calldata: string,
|
|
||||||
from?: string,
|
|
||||||
value?: BigNumber,
|
|
||||||
includeLogs?: boolean,
|
|
||||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
|
||||||
const txData = {
|
|
||||||
from: from ? from : this._ownerAddress,
|
|
||||||
to: this.getStakingProxyContract().address,
|
|
||||||
data: calldata,
|
|
||||||
gas: 3000000,
|
|
||||||
gasPrice: 0,
|
|
||||||
value,
|
|
||||||
};
|
|
||||||
const txHash = await this._web3Wrapper.sendTransactionAsync(txData);
|
|
||||||
const txReceipt = await (includeLogs
|
|
||||||
? this._logDecoder.getTxWithDecodedLogsAsync(txHash)
|
|
||||||
: this._web3Wrapper.awaitTransactionSuccessAsync(txHash));
|
|
||||||
return txReceipt;
|
|
||||||
}
|
|
||||||
private async _callAsync(calldata: string, from?: string): Promise<any> {
|
|
||||||
const txData = {
|
|
||||||
from: from ? from : this._ownerAddress,
|
|
||||||
to: this.getStakingProxyContract().address,
|
|
||||||
data: calldata,
|
|
||||||
gas: 3000000,
|
|
||||||
};
|
|
||||||
const returnValue = await this._web3Wrapper.callAsync(txData);
|
|
||||||
return returnValue;
|
|
||||||
}
|
|
||||||
private _validateDeployedOrThrow(): void {
|
|
||||||
if (this._stakingContractIfExists === undefined) {
|
|
||||||
throw new Error('Staking contracts are not deployed. Call `deployStakingContracts`');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// tslint:disable-line:max-file-line-count
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { BigNumber } from '@0x/utils';
|
import { BigNumber } from '@0x/utils';
|
||||||
|
|
||||||
|
import { constants } from './constants';
|
||||||
|
|
||||||
export interface StakerBalances {
|
export interface StakerBalances {
|
||||||
zrxBalance: BigNumber;
|
zrxBalance: BigNumber;
|
||||||
stakeBalance: BigNumber;
|
stakeBalance: BigNumber;
|
||||||
@@ -53,9 +55,14 @@ export enum StakeStatus {
|
|||||||
Delegated,
|
Delegated,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StakeInfo {
|
export class StakeInfo {
|
||||||
status: StakeStatus;
|
public status: StakeStatus;
|
||||||
poolId?: string;
|
public poolId: string;
|
||||||
|
|
||||||
|
constructor(status: StakeStatus, poolId?: string) {
|
||||||
|
this.status = status;
|
||||||
|
this.poolId = poolId !== undefined ? poolId : constants.NIL_POOL_ID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StakeBalances {
|
export interface StakeBalances {
|
||||||
|
|||||||
@@ -1,64 +1,53 @@
|
|||||||
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
|
||||||
import { blockchainTests, expect } from '@0x/contracts-test-utils';
|
import { blockchainTests, expect } from '@0x/contracts-test-utils';
|
||||||
import { StakingRevertErrors } from '@0x/order-utils';
|
import { StakingRevertErrors } from '@0x/order-utils';
|
||||||
import { BigNumber } from '@0x/utils';
|
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import { StakingWrapper } from './utils/staking_wrapper';
|
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
|
||||||
|
|
||||||
// tslint:disable:no-unnecessary-type-assertion
|
// tslint:disable:no-unnecessary-type-assertion
|
||||||
blockchainTests('Staking Vaults', env => {
|
blockchainTests('Staking Vaults', env => {
|
||||||
// constants
|
|
||||||
const ZRX_TOKEN_DECIMALS = new BigNumber(18);
|
|
||||||
// tokens & addresses
|
// tokens & addresses
|
||||||
let accounts: string[];
|
let accounts: string[];
|
||||||
let owner: string;
|
let owner: string;
|
||||||
let users: string[];
|
let users: string[];
|
||||||
let zrxTokenContract: DummyERC20TokenContract;
|
|
||||||
let erc20ProxyContract: ERC20ProxyContract;
|
|
||||||
// wrappers
|
// wrappers
|
||||||
let stakingWrapper: StakingWrapper;
|
let stakingApiWrapper: StakingApiWrapper;
|
||||||
let erc20Wrapper: ERC20Wrapper;
|
let erc20Wrapper: ERC20Wrapper;
|
||||||
// tests
|
// tests
|
||||||
before(async () => {
|
before(async () => {
|
||||||
// create accounts
|
// create accounts
|
||||||
accounts = await env.web3Wrapper.getAvailableAddressesAsync();
|
accounts = await env.getAccountAddressesAsync();
|
||||||
owner = accounts[0];
|
owner = accounts[0];
|
||||||
users = accounts.slice(1);
|
users = accounts.slice(1);
|
||||||
// deploy erc20 proxy
|
// set up ERC20Wrapper
|
||||||
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
|
||||||
erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
|
|
||||||
// deploy zrx token
|
|
||||||
[zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
|
|
||||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
|
||||||
// deploy staking contracts
|
// deploy staking contracts
|
||||||
stakingWrapper = new StakingWrapper(
|
stakingApiWrapper = await deployAndConfigureContractsAsync(env, owner, erc20Wrapper);
|
||||||
env.provider,
|
|
||||||
owner,
|
|
||||||
erc20ProxyContract,
|
|
||||||
erc20ProxyContract,
|
|
||||||
zrxTokenContract,
|
|
||||||
);
|
|
||||||
await stakingWrapper.deployAndConfigureContractsAsync();
|
|
||||||
});
|
});
|
||||||
blockchainTests.resets('Reward Vault', () => {
|
blockchainTests.resets('Reward Vault', () => {
|
||||||
|
// @TODO (hysz): Resolve non-EOA transaction issue so that this test can be unskipped
|
||||||
it.skip('basic management', async () => {
|
it.skip('basic management', async () => {
|
||||||
// 1 setup test parameters
|
// 1 setup test parameters
|
||||||
const poolOperator = users[0];
|
const poolOperator = users[0];
|
||||||
const operatorShare = 39;
|
const operatorShare = 39;
|
||||||
const poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, operatorShare, true);
|
const poolId = await stakingApiWrapper.utils.createStakingPoolAsync(poolOperator, operatorShare, true);
|
||||||
const stakingContractAddress = stakingWrapper.getStakingContract().address;
|
|
||||||
const notStakingContractAddress = poolOperator;
|
const notStakingContractAddress = poolOperator;
|
||||||
// create pool in vault
|
|
||||||
await stakingWrapper.rewardVaultRegisterPoolAsync(poolId, operatorShare, stakingContractAddress);
|
|
||||||
// should fail to create pool if it already exists
|
// should fail to create pool if it already exists
|
||||||
let revertError = new StakingRevertErrors.PoolAlreadyExistsError(poolId);
|
let revertError = new StakingRevertErrors.PoolAlreadyExistsError(poolId);
|
||||||
let tx = stakingWrapper.rewardVaultRegisterPoolAsync(poolId, operatorShare, stakingContractAddress);
|
let tx = stakingApiWrapper.rewardVaultContract.registerStakingPool.awaitTransactionSuccessAsync(
|
||||||
|
poolId,
|
||||||
|
operatorShare,
|
||||||
|
{ from: stakingApiWrapper.stakingContractAddress },
|
||||||
|
);
|
||||||
await expect(tx).to.revertWith(revertError);
|
await expect(tx).to.revertWith(revertError);
|
||||||
// should fail to create a pool from an address other than the staking contract
|
// should fail to create a pool from an address other than the staking contract
|
||||||
revertError = new StakingRevertErrors.OnlyCallableByStakingContractError(notStakingContractAddress);
|
revertError = new StakingRevertErrors.OnlyCallableByStakingContractError(notStakingContractAddress);
|
||||||
tx = stakingWrapper.rewardVaultRegisterPoolAsync(poolId, operatorShare, notStakingContractAddress);
|
tx = stakingApiWrapper.rewardVaultContract.registerStakingPool.awaitTransactionSuccessAsync(
|
||||||
|
poolId,
|
||||||
|
operatorShare,
|
||||||
|
{ from: notStakingContractAddress },
|
||||||
|
);
|
||||||
await expect(tx).to.revertWith(revertError);
|
await expect(tx).to.revertWith(revertError);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user