@0x/contracts-exchange
: Fixing tests and writing new ones.
This commit is contained in:
parent
d548ddac0d
commit
a1aad2e55e
@ -19,6 +19,7 @@
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||
import "../libs/LibStakingRichErrors.sol";
|
||||
@ -71,7 +72,8 @@ contract MixinExchangeFees is
|
||||
{
|
||||
_assertValidProtocolFee(protocolFeePaid);
|
||||
|
||||
// Transfer the protocol fee to this address if it should be paid in WETH.
|
||||
// Transfer the protocol fee to this address if it should be paid in
|
||||
// WETH.
|
||||
if (msg.value == 0) {
|
||||
wethAssetProxy.transferFrom(
|
||||
WETH_ASSET_DATA,
|
||||
@ -89,7 +91,8 @@ contract MixinExchangeFees is
|
||||
return;
|
||||
}
|
||||
|
||||
uint256 poolStake = getTotalStakeDelegatedToPool(poolId).currentEpochBalance;
|
||||
uint256 poolStake =
|
||||
getTotalStakeDelegatedToPool(poolId).currentEpochBalance;
|
||||
// Ignore pools with dust stake.
|
||||
if (poolStake < minimumPoolStake) {
|
||||
return;
|
||||
@ -103,35 +106,24 @@ contract MixinExchangeFees is
|
||||
|
||||
// If the pool was previously inactive in this epoch, initialize it.
|
||||
if (pool.feesCollected == 0) {
|
||||
// Compute weighted stake.
|
||||
uint256 operatorStake = getStakeDelegatedToPoolByOwner(
|
||||
rewardVault.operatorOf(poolId),
|
||||
poolId
|
||||
).currentEpochBalance;
|
||||
pool.weightedStake = operatorStake.safeAdd(
|
||||
poolStake
|
||||
.safeSub(operatorStake)
|
||||
.safeMul(rewardDelegatedStakeWeight)
|
||||
.safeDiv(PPM_DENOMINATOR)
|
||||
);
|
||||
|
||||
// Compute delegated (non-operator) stake.
|
||||
pool.delegatedStake = poolStake.safeSub(operatorStake);
|
||||
// Compute member and total weighted stake.
|
||||
(pool.membersStake, pool.weightedStake) =
|
||||
_computeMembersAndWeightedStake(poolId, poolStake);
|
||||
|
||||
// Increase the total weighted stake.
|
||||
totalWeightedStakeThisEpoch = totalWeightedStakeThisEpoch.safeAdd(
|
||||
pool.weightedStake
|
||||
);
|
||||
totalWeightedStakeThisEpoch =
|
||||
totalWeightedStakeThisEpoch.safeAdd(pool.weightedStake);
|
||||
|
||||
// Increase the numberof active pools.
|
||||
numActivePoolsThisEpoch += 1;
|
||||
|
||||
// Emit an event so keepers know what pools to pass into `finalize()`.
|
||||
// Emit an event so keepers know what pools to pass into
|
||||
// `finalize()`.
|
||||
emit StakingPoolActivated(currentEpoch, poolId);
|
||||
}
|
||||
|
||||
// Credit the fees to the pool.
|
||||
pool.feesCollected = protocolFeePaid;
|
||||
pool.feesCollected = pool.feesCollected.safeAdd(protocolFeePaid);
|
||||
|
||||
// Increase the total fees collected this epoch.
|
||||
totalFeesCollectedThisEpoch = totalFeesCollectedThisEpoch.safeAdd(
|
||||
@ -142,7 +134,8 @@ contract MixinExchangeFees is
|
||||
activePoolsThisEpoch[poolId] = pool;
|
||||
}
|
||||
|
||||
/// @dev Returns the total amount of fees collected thus far, in the current epoch.
|
||||
/// @dev Returns the total amount of fees collected thus far, in the current
|
||||
/// epoch.
|
||||
/// @return _totalFeesCollectedThisEpoch Total fees collected this epoch.
|
||||
function getTotalProtocolFeesThisEpoch()
|
||||
external
|
||||
@ -152,9 +145,21 @@ contract MixinExchangeFees is
|
||||
_totalFeesCollectedThisEpoch = totalFeesCollectedThisEpoch;
|
||||
}
|
||||
|
||||
/// @dev Returns the total balance of this contract, including WETH.
|
||||
/// @return totalBalance Total balance.
|
||||
function getTotalBalance()
|
||||
external
|
||||
view
|
||||
returns (uint256 totalBalance)
|
||||
{
|
||||
totalBalance = address(this).balance +
|
||||
IEtherToken(WETH_ADDRESS).balanceOf(address(this));
|
||||
}
|
||||
|
||||
/// @dev Returns the amount of fees attributed to the input pool this epoch.
|
||||
/// @param poolId Pool Id to query.
|
||||
/// @return feesCollectedByPool Amount of fees collected by the pool this epoch.
|
||||
/// @return feesCollectedByPool Amount of fees collected by the pool this
|
||||
/// epoch.
|
||||
function getProtocolFeesThisEpochByPool(bytes32 poolId)
|
||||
external
|
||||
view
|
||||
@ -168,20 +173,55 @@ contract MixinExchangeFees is
|
||||
feesCollected = pool.feesCollected;
|
||||
}
|
||||
|
||||
/// @dev Checks that the protocol fee passed into `payProtocolFee()` is valid.
|
||||
/// @param protocolFeePaid The `protocolFeePaid` parameter to `payProtocolFee.`
|
||||
/// @dev Computes the members and weighted stake for a pool at the current
|
||||
/// epoch.
|
||||
/// @param poolId ID of the pool.
|
||||
/// @param totalStake Total (unweighted) stake in the pool.
|
||||
/// @return membersStake Non-operator stake in the pool.
|
||||
/// @return weightedStake Weighted stake of the pool.
|
||||
function _computeMembersAndWeightedStake(
|
||||
bytes32 poolId,
|
||||
uint256 totalStake
|
||||
)
|
||||
private
|
||||
view
|
||||
returns (uint256 membersStake, uint256 weightedStake)
|
||||
{
|
||||
uint256 operatorStake = getStakeDelegatedToPoolByOwner(
|
||||
getPoolOperator(poolId),
|
||||
poolId
|
||||
).currentEpochBalance;
|
||||
membersStake = totalStake.safeSub(operatorStake);
|
||||
weightedStake = operatorStake.safeAdd(
|
||||
membersStake
|
||||
.safeMul(rewardDelegatedStakeWeight)
|
||||
.safeDiv(PPM_DENOMINATOR)
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Checks that the protocol fee passed into `payProtocolFee()` is
|
||||
/// valid.
|
||||
/// @param protocolFeePaid The `protocolFeePaid` parameter to
|
||||
/// `payProtocolFee.`
|
||||
function _assertValidProtocolFee(uint256 protocolFeePaid)
|
||||
private
|
||||
view
|
||||
{
|
||||
if (protocolFeePaid == 0 || (msg.value != protocolFeePaid && msg.value != 0)) {
|
||||
LibRichErrors.rrevert(LibStakingRichErrors.InvalidProtocolFeePaymentError(
|
||||
if (protocolFeePaid == 0 ||
|
||||
(msg.value != protocolFeePaid && msg.value != 0)) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.InvalidProtocolFeePaymentError(
|
||||
protocolFeePaid == 0 ?
|
||||
LibStakingRichErrors.ProtocolFeePaymentErrorCodes.ZeroProtocolFeePaid :
|
||||
LibStakingRichErrors.ProtocolFeePaymentErrorCodes.MismatchedFeeAndPayment,
|
||||
LibStakingRichErrors
|
||||
.ProtocolFeePaymentErrorCodes
|
||||
.ZeroProtocolFeePaid :
|
||||
LibStakingRichErrors
|
||||
.ProtocolFeePaymentErrorCodes
|
||||
.MismatchedFeeAndPayment,
|
||||
protocolFeePaid,
|
||||
msg.value
|
||||
));
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,11 +25,11 @@ interface IStructs {
|
||||
/// (see MixinExchangeFees).
|
||||
/// @param feesCollected Fees collected in ETH by this pool.
|
||||
/// @param weightedStake Amount of weighted stake in the pool.
|
||||
/// @param delegatedStake Amount of delegated, non-operator stake in the pool.
|
||||
/// @param membersStake Amount of non-operator stake in the pool.
|
||||
struct ActivePool {
|
||||
uint256 feesCollected;
|
||||
uint256 weightedStake;
|
||||
uint256 delegatedStake;
|
||||
uint256 membersStake;
|
||||
}
|
||||
|
||||
/// @dev Rewards credited to a pool during finalization.
|
||||
|
@ -113,6 +113,17 @@ contract MixinStakingPool is
|
||||
return poolById[poolId];
|
||||
}
|
||||
|
||||
/// @dev Look up the operator of a pool.
|
||||
/// @param poolId The ID of the pool.
|
||||
/// @return operatorAddress The pool operator.
|
||||
function getPoolOperator(bytes32 poolId)
|
||||
public
|
||||
view
|
||||
returns (address operatorAddress)
|
||||
{
|
||||
return rewardVault.operatorOf(poolId);
|
||||
}
|
||||
|
||||
/// @dev Computes the unique id that comes after the input pool id.
|
||||
/// @param poolId Unique id of pool.
|
||||
/// @return Next pool id after input pool.
|
||||
|
@ -93,7 +93,7 @@ contract MixinFinalizer is
|
||||
// Emit an event.
|
||||
emit EpochEnded(
|
||||
closingEpoch,
|
||||
numActivePoolsThisEpoch,
|
||||
unfinalizedPoolsRemaining,
|
||||
unfinalizedRewardsAvailable,
|
||||
unfinalizedTotalFeesCollected,
|
||||
unfinalizedTotalWeightedStake
|
||||
@ -179,7 +179,9 @@ contract MixinFinalizer is
|
||||
}
|
||||
|
||||
// Deposit all the rewards at once into the RewardVault.
|
||||
if (rewardsPaid != 0) {
|
||||
_depositIntoStakingPoolRewardVault(rewardsPaid);
|
||||
}
|
||||
|
||||
// Update finalization states.
|
||||
totalRewardsPaidLastEpoch =
|
||||
@ -310,6 +312,11 @@ contract MixinFinalizer is
|
||||
|
||||
IStructs.ActivePool memory pool =
|
||||
_getActivePoolFromEpoch(epoch - 1, poolId);
|
||||
// There can't be any rewards if the pool was active or if it has
|
||||
// no stake.
|
||||
if (pool.feesCollected == 0 || pool.weightedStake == 0) {
|
||||
return rewards;
|
||||
}
|
||||
|
||||
// Use the cobb-douglas function to compute the total reward.
|
||||
uint256 totalReward = LibCobbDouglas._cobbDouglas(
|
||||
@ -323,16 +330,13 @@ contract MixinFinalizer is
|
||||
);
|
||||
|
||||
// Split the reward between the operator and delegators.
|
||||
if (pool.delegatedStake == 0) {
|
||||
if (pool.membersStake == 0) {
|
||||
rewards.operatorReward = totalReward;
|
||||
} else {
|
||||
(rewards.operatorReward, rewards.membersReward) =
|
||||
rewardVault.splitAmountBetweenOperatorAndMembers(
|
||||
poolId,
|
||||
totalReward
|
||||
);
|
||||
_splitAmountBetweenOperatorAndMembers(poolId, totalReward);
|
||||
}
|
||||
rewards.membersStake = pool.delegatedStake;
|
||||
rewards.membersStake = pool.membersStake;
|
||||
}
|
||||
|
||||
/// @dev Converts the entire WETH balance of the contract into ETH.
|
||||
@ -344,6 +348,48 @@ contract MixinFinalizer is
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Splits an amount between the pool operator and members of the
|
||||
/// pool based on the pool operator's share.
|
||||
/// @param poolId The ID of the pool.
|
||||
/// @param amount Amount to to split.
|
||||
/// @return operatorPortion Portion of `amount` attributed to the operator.
|
||||
/// @return membersPortion Portion of `amount` attributed to the pool.
|
||||
function _splitAmountBetweenOperatorAndMembers(
|
||||
bytes32 poolId,
|
||||
uint256 amount
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (uint256 operatorReward, uint256 membersReward)
|
||||
{
|
||||
(operatorReward, membersReward) =
|
||||
rewardVault.splitAmountBetweenOperatorAndMembers(poolId, amount);
|
||||
}
|
||||
|
||||
/// @dev Record a deposit for a pool in the RewardVault.
|
||||
/// @param poolId ID of the pool.
|
||||
/// @param amount Amount in ETH to record.
|
||||
/// @param operatorOnly Only attribute amount to operator.
|
||||
/// @return operatorPortion Portion of `amount` attributed to the operator.
|
||||
/// @return membersPortion Portion of `amount` attributed to the pool.
|
||||
function _recordDepositInRewardVaultFor(
|
||||
bytes32 poolId,
|
||||
uint256 amount,
|
||||
bool operatorOnly
|
||||
)
|
||||
internal
|
||||
returns (
|
||||
uint256 operatorPortion,
|
||||
uint256 membersPortion
|
||||
)
|
||||
{
|
||||
(operatorPortion, membersPortion) = rewardVault.recordDepositFor(
|
||||
poolId,
|
||||
amount,
|
||||
operatorOnly
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Computes the reward owed to a pool during finalization and
|
||||
/// credits it to that pool for the CURRENT epoch.
|
||||
/// @param poolId The pool's ID.
|
||||
@ -356,6 +402,12 @@ contract MixinFinalizer is
|
||||
private
|
||||
returns (IStructs.PoolRewards memory rewards)
|
||||
{
|
||||
// There can't be any rewards if the pool was active or if it has
|
||||
// no stake.
|
||||
if (pool.feesCollected == 0 || pool.weightedStake == 0) {
|
||||
return rewards;
|
||||
}
|
||||
|
||||
// Use the cobb-douglas function to compute the total reward.
|
||||
uint256 totalReward = LibCobbDouglas._cobbDouglas(
|
||||
unfinalizedRewardsAvailable,
|
||||
@ -369,20 +421,20 @@ contract MixinFinalizer is
|
||||
|
||||
// Credit the pool the reward in the RewardVault.
|
||||
(rewards.operatorReward, rewards.membersReward) =
|
||||
rewardVault.recordDepositFor(
|
||||
_recordDepositInRewardVaultFor(
|
||||
poolId,
|
||||
totalReward,
|
||||
// If no delegated stake, all rewards go to the operator.
|
||||
pool.delegatedStake == 0
|
||||
pool.membersStake == 0
|
||||
);
|
||||
rewards.membersStake = pool.delegatedStake;
|
||||
rewards.membersStake = pool.membersStake;
|
||||
|
||||
// Sync delegator rewards.
|
||||
if (rewards.membersReward != 0) {
|
||||
_recordRewardForDelegators(
|
||||
poolId,
|
||||
rewards.membersReward,
|
||||
pool.delegatedStake
|
||||
pool.membersStake
|
||||
);
|
||||
}
|
||||
}
|
||||
|
145
contracts/staking/contracts/test/TestFinalizer.sol
Normal file
145
contracts/staking/contracts/test/TestFinalizer.sol
Normal file
@ -0,0 +1,145 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "../src/interfaces/IStructs.sol";
|
||||
import "../src/Staking.sol";
|
||||
|
||||
|
||||
contract TestFinalizer is
|
||||
Staking
|
||||
{
|
||||
struct RecordedReward {
|
||||
uint256 membersReward;
|
||||
uint256 membersStake;
|
||||
}
|
||||
|
||||
struct DepositedReward {
|
||||
uint256 totalReward;
|
||||
bool operatorOnly;
|
||||
}
|
||||
mapping (bytes32 => uint32) internal _operatorSharesByPool;
|
||||
mapping (bytes32 => RecordedReward) internal _recordedRewardsByPool;
|
||||
mapping (bytes32 => DepositedReward) internal _depositedRewardsByPool;
|
||||
|
||||
function getFinalizationState()
|
||||
external
|
||||
view
|
||||
returns (
|
||||
uint256 _closingEpoch,
|
||||
uint256 _unfinalizedPoolsRemaining,
|
||||
uint256 _unfinalizedRewardsAvailable,
|
||||
uint256 _unfinalizedTotalFeesCollected,
|
||||
uint256 _unfinalizedTotalWeightedStake
|
||||
)
|
||||
{
|
||||
_closingEpoch = currentEpoch - 1;
|
||||
_unfinalizedPoolsRemaining = unfinalizedPoolsRemaining;
|
||||
_unfinalizedRewardsAvailable = unfinalizedRewardsAvailable;
|
||||
_unfinalizedTotalFeesCollected = unfinalizedTotalFeesCollected;
|
||||
_unfinalizedTotalWeightedStake = unfinalizedTotalWeightedStake;
|
||||
}
|
||||
|
||||
function addActivePool(
|
||||
bytes32 poolId,
|
||||
uint32 operatorShare,
|
||||
uint256 feesCollected,
|
||||
uint256 membersStake,
|
||||
uint256 weightedStake
|
||||
)
|
||||
external
|
||||
{
|
||||
mapping (bytes32 => IStructs.ActivePool) storage activePools =
|
||||
_getActivePoolsFromEpoch(currentEpoch);
|
||||
assert(activePools[poolId].feesCollected == 0);
|
||||
_operatorSharesByPool[poolId] = operatorShare;
|
||||
activePools[poolId] = IStructs.ActivePool({
|
||||
feesCollected: feesCollected,
|
||||
membersStake: membersStake,
|
||||
weightedStake: weightedStake
|
||||
});
|
||||
totalFeesCollectedThisEpoch += feesCollected;
|
||||
totalWeightedStakeThisEpoch += weightedStake;
|
||||
numActivePoolsThisEpoch += 1;
|
||||
}
|
||||
|
||||
/// @dev Overridden to just store inputs.
|
||||
function _recordRewardForDelegators(
|
||||
bytes32 poolId,
|
||||
uint256 membersReward,
|
||||
uint256 membersStake
|
||||
)
|
||||
internal
|
||||
{
|
||||
_recordedRewardsByPool[poolId] = RecordedReward({
|
||||
membersReward: membersReward,
|
||||
membersStake: membersStake
|
||||
});
|
||||
}
|
||||
|
||||
/// @dev Overridden to store inputs and do some really basic math.
|
||||
function _recordDepositInRewardVaultFor(
|
||||
bytes32 poolId,
|
||||
uint256 totalReward,
|
||||
bool operatorOnly
|
||||
)
|
||||
internal
|
||||
returns (
|
||||
uint256 operatorPortion,
|
||||
uint256 membersPortion
|
||||
)
|
||||
{
|
||||
_depositedRewardsByPool[poolId] = DepositedReward({
|
||||
totalReward: totalReward,
|
||||
operatorOnly: operatorOnly
|
||||
});
|
||||
|
||||
if (operatorOnly) {
|
||||
operatorPortion = totalReward;
|
||||
} else {
|
||||
(operatorPortion, membersPortion) =
|
||||
_splitAmountBetweenOperatorAndMembers(poolId, totalReward);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Overridden to do some really basic math.
|
||||
function _splitAmountBetweenOperatorAndMembers(
|
||||
bytes32 poolId,
|
||||
uint256 amount
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (uint256 operatorPortion, uint256 membersPortion)
|
||||
{
|
||||
uint32 operatorShare = _operatorSharesByPool[poolId];
|
||||
operatorPortion = operatorShare * amount / PPM_DENOMINATOR;
|
||||
membersPortion = amount - operatorPortion;
|
||||
}
|
||||
|
||||
/// @dev Overriden to always succeed.
|
||||
function _goToNextEpoch() internal {
|
||||
currentEpoch += 1;
|
||||
}
|
||||
|
||||
/// @dev Overridden to do nothing.
|
||||
function _unwrapWETH() internal {
|
||||
// NOOP
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ pragma solidity ^0.5.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetProxy.sol";
|
||||
import "../src/interfaces/IStructs.sol";
|
||||
import "../src/Staking.sol";
|
||||
|
||||
|
||||
@ -27,7 +28,8 @@ contract TestProtocolFees is
|
||||
Staking
|
||||
{
|
||||
struct TestPool {
|
||||
uint256 stake;
|
||||
uint256 operatorStake;
|
||||
uint256 membersStake;
|
||||
mapping(address => bool) isMaker;
|
||||
}
|
||||
|
||||
@ -58,13 +60,15 @@ contract TestProtocolFees is
|
||||
/// @dev Create a test pool.
|
||||
function createTestPool(
|
||||
bytes32 poolId,
|
||||
uint256 stake,
|
||||
uint256 operatorStake,
|
||||
uint256 membersStake,
|
||||
address[] memory makerAddresses
|
||||
)
|
||||
public
|
||||
{
|
||||
TestPool storage pool = _testPools[poolId];
|
||||
pool.stake = stake;
|
||||
pool.operatorStake = operatorStake;
|
||||
pool.membersStake = membersStake;
|
||||
for (uint256 i = 0; i < makerAddresses.length; ++i) {
|
||||
pool.isMaker[makerAddresses[i]] = true;
|
||||
_makersToTestPoolIds[makerAddresses[i]] = poolId;
|
||||
@ -86,10 +90,34 @@ contract TestProtocolFees is
|
||||
view
|
||||
returns (IStructs.StakeBalance memory balance)
|
||||
{
|
||||
uint256 stake = _testPools[poolId].stake;
|
||||
TestPool memory pool = _testPools[poolId];
|
||||
uint256 stake = pool.operatorStake + pool.membersStake;
|
||||
return IStructs.StakeBalance({
|
||||
currentEpochBalance: stake,
|
||||
nextEpochBalance: stake
|
||||
});
|
||||
}
|
||||
|
||||
/// @dev Overridden to use test pools.
|
||||
function getStakeDelegatedToPoolByOwner(address, bytes32 poolId)
|
||||
public
|
||||
view
|
||||
returns (IStructs.StakeBalance memory balance)
|
||||
{
|
||||
TestPool memory pool = _testPools[poolId];
|
||||
return IStructs.StakeBalance({
|
||||
currentEpochBalance: pool.operatorStake,
|
||||
nextEpochBalance: pool.operatorStake
|
||||
});
|
||||
}
|
||||
|
||||
/// @dev Overridden to use test pools.
|
||||
function getPoolOperator(bytes32)
|
||||
public
|
||||
view
|
||||
returns (address operatorAddress)
|
||||
{
|
||||
// Just return nil, we won't use it.
|
||||
return address(0);
|
||||
}
|
||||
}
|
||||
|
@ -146,6 +146,15 @@ export class StakerActor extends BaseActor {
|
||||
expect(finalZrxBalanceOfVault, 'final balance of zrx vault').to.be.bignumber.equal(initZrxBalanceOfVault);
|
||||
}
|
||||
|
||||
public async stakeWithPoolAsync(poolId: string, amount: BigNumber): Promise<void> {
|
||||
await this.stakeAsync(amount);
|
||||
await this.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
amount,
|
||||
);
|
||||
}
|
||||
|
||||
public async goToNextEpochAsync(): Promise<void> {
|
||||
// cache balances
|
||||
const initZrxBalanceOfVault = await this._stakingApiWrapper.utils.getZrxTokenBalanceOfZrxVaultAsync();
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
expect,
|
||||
filterLogsToArguments,
|
||||
hexRandom,
|
||||
Numberish,
|
||||
randomAddress,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { StakingRevertErrors } from '@0x/order-utils';
|
||||
@ -54,9 +55,26 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
wethAssetData = await testContract.getWethAssetData.callAsync();
|
||||
});
|
||||
|
||||
async function createTestPoolAsync(stake: BigNumber, makers: string[]): Promise<string> {
|
||||
interface CreatePoolOpts {
|
||||
operatorStake: Numberish;
|
||||
membersStake: Numberish;
|
||||
makers: string[];
|
||||
}
|
||||
|
||||
async function createTestPoolAsync(opts: Partial<CreatePoolOpts>): Promise<string> {
|
||||
const _opts = {
|
||||
operatorStake: 0,
|
||||
membersStake: 0,
|
||||
makers: [],
|
||||
...opts,
|
||||
};
|
||||
const poolId = hexRandom();
|
||||
await testContract.createTestPool.awaitTransactionSuccessAsync(poolId, stake, makers);
|
||||
await testContract.createTestPool.awaitTransactionSuccessAsync(
|
||||
poolId,
|
||||
new BigNumber(_opts.operatorStake),
|
||||
new BigNumber(_opts.membersStake),
|
||||
_opts.makers,
|
||||
);
|
||||
return poolId;
|
||||
}
|
||||
|
||||
@ -154,7 +172,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
}
|
||||
|
||||
it('should not transfer WETH if value is sent', async () => {
|
||||
await createTestPoolAsync(minimumStake, []);
|
||||
await createTestPoolAsync({ operatorStake: minimumStake });
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
@ -164,8 +182,8 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
assertNoWETHTransferLogs(receipt.logs);
|
||||
});
|
||||
|
||||
it('should update `protocolFeesThisEpochByPool` if the maker is in a pool', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
||||
it('should credit pool if the maker is in a pool', async () => {
|
||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
@ -177,8 +195,8 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
expect(poolFees).to.bignumber.eq(DEFAULT_PROTOCOL_FEE_PAID);
|
||||
});
|
||||
|
||||
it('should not update `protocolFeesThisEpochByPool` if maker is not in a pool', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, []);
|
||||
it('should not credit the pool if maker is not in a pool', async () => {
|
||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake });
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
@ -191,7 +209,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
});
|
||||
|
||||
it('fees paid to the same maker should go to the same pool', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
||||
const payAsync = async () => {
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
@ -225,7 +243,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
}
|
||||
|
||||
it('should transfer WETH if no value is sent and the maker is not in a pool', async () => {
|
||||
await createTestPoolAsync(minimumStake, []);
|
||||
await createTestPoolAsync({ operatorStake: minimumStake });
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
@ -236,7 +254,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
});
|
||||
|
||||
it('should update `protocolFeesThisEpochByPool` if the maker is in a pool', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
@ -249,7 +267,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
});
|
||||
|
||||
it('should not update `protocolFeesThisEpochByPool` if maker is not in a pool', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, []);
|
||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake });
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
payerAddress,
|
||||
@ -262,7 +280,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
});
|
||||
|
||||
it('fees paid to the same maker should go to the same pool', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
||||
const payAsync = async () => {
|
||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
@ -280,7 +298,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
});
|
||||
|
||||
it('fees paid to the same maker in WETH then ETH should go to the same pool', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
||||
const payAsync = async (inWETH: boolean) => {
|
||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
@ -303,7 +321,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
describe('Multiple makers', () => {
|
||||
it('fees paid to different makers in the same pool go to that pool', async () => {
|
||||
const otherMakerAddress = randomAddress();
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress, otherMakerAddress]);
|
||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress, otherMakerAddress] });
|
||||
const payAsync = async (_makerAddress: string) => {
|
||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
_makerAddress,
|
||||
@ -322,8 +340,8 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
it('fees paid to makers in different pools go to their respective pools', async () => {
|
||||
const [fee, otherFee] = _.times(2, () => getRandomPortion(DEFAULT_PROTOCOL_FEE_PAID));
|
||||
const otherMakerAddress = randomAddress();
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
||||
const otherPoolId = await createTestPoolAsync(minimumStake, [otherMakerAddress]);
|
||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
||||
const otherPoolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [otherMakerAddress]});
|
||||
const payAsync = async (_poolId: string, _makerAddress: string, _fee: BigNumber) => {
|
||||
// prettier-ignore
|
||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
@ -346,7 +364,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
|
||||
describe('Dust stake', () => {
|
||||
it('credits pools with stake > minimum', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake.plus(1), [makerAddress]);
|
||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake.plus(1), makers: [makerAddress] });
|
||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
constants.NULL_ADDRESS,
|
||||
@ -358,7 +376,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
});
|
||||
|
||||
it('credits pools with stake == minimum', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
constants.NULL_ADDRESS,
|
||||
@ -370,7 +388,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
});
|
||||
|
||||
it('does not credit pools with stake < minimum', async () => {
|
||||
const poolId = await createTestPoolAsync(minimumStake.minus(1), [makerAddress]);
|
||||
const poolId = await createTestPoolAsync({ operatorStake: minimumStake.minus(1), makers: [makerAddress] });
|
||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
constants.NULL_ADDRESS,
|
||||
|
@ -6,6 +6,7 @@ import * as _ from 'lodash';
|
||||
import { artifacts } from '../src';
|
||||
|
||||
import { FinalizerActor } from './actors/finalizer_actor';
|
||||
import { PoolOperatorActor } from './actors/pool_operator_actor';
|
||||
import { StakerActor } from './actors/staker_actor';
|
||||
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
|
||||
import { toBaseUnitAmount } from './utils/number_utils';
|
||||
@ -26,8 +27,9 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
let erc20Wrapper: ERC20Wrapper;
|
||||
// test parameters
|
||||
let stakers: StakerActor[];
|
||||
let poolOperatorStaker: StakerActor;
|
||||
let poolId: string;
|
||||
let poolOperator: string;
|
||||
let poolOperator: PoolOperatorActor;
|
||||
let finalizer: FinalizerActor;
|
||||
// tests
|
||||
before(async () => {
|
||||
@ -43,7 +45,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
stakingApiWrapper = await deployAndConfigureContractsAsync(env, owner, erc20Wrapper, artifacts.TestStaking);
|
||||
// set up staking parameters
|
||||
await stakingApiWrapper.utils.setParamsAsync({
|
||||
minimumPoolStake: new BigNumber(0),
|
||||
minimumPoolStake: new BigNumber(1),
|
||||
cobbDouglasAlphaNumerator: new BigNumber(1),
|
||||
cobbDouglasAlphaDenominator: new BigNumber(6),
|
||||
rewardVaultAddress: stakingApiWrapper.rewardVaultContract.address,
|
||||
@ -51,22 +53,26 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
zrxVaultAddress: stakingApiWrapper.zrxVaultContract.address,
|
||||
});
|
||||
// setup stakers
|
||||
stakers = [new StakerActor(actors[0], stakingApiWrapper), new StakerActor(actors[1], stakingApiWrapper)];
|
||||
stakers = actors.slice(0, 2).map(a => new StakerActor(a, stakingApiWrapper));
|
||||
// setup pools
|
||||
poolOperator = actors[2];
|
||||
poolId = await stakingApiWrapper.utils.createStakingPoolAsync(poolOperator, 0, true); // add operator as maker
|
||||
poolOperator = new PoolOperatorActor(actors[2], stakingApiWrapper);
|
||||
// Create a pool where all rewards go to members.
|
||||
poolId = await poolOperator.createStakingPoolAsync(0, true);
|
||||
// Stake something in the pool or else it won't get any rewards.
|
||||
poolOperatorStaker = new StakerActor(poolOperator.getOwner(), stakingApiWrapper);
|
||||
await poolOperatorStaker.stakeWithPoolAsync(poolId, new BigNumber(1));
|
||||
// set exchange address
|
||||
await stakingApiWrapper.stakingContract.addExchangeAddress.awaitTransactionSuccessAsync(exchangeAddress);
|
||||
// associate operators for tracking in Finalizer
|
||||
const operatorByPoolId: OperatorByPoolId = {};
|
||||
operatorByPoolId[poolId] = poolOperator;
|
||||
operatorByPoolId[poolId] = poolOperator;
|
||||
operatorByPoolId[poolId] = poolOperator.getOwner();
|
||||
// associate actors with pools for tracking in Finalizer
|
||||
const membersByPoolId: MembersByPoolId = {};
|
||||
membersByPoolId[poolId] = [actors[0], actors[1]];
|
||||
membersByPoolId[poolId] = [actors[0], actors[1]];
|
||||
// create Finalizer actor
|
||||
finalizer = new FinalizerActor(actors[3], stakingApiWrapper, [poolId], operatorByPoolId, membersByPoolId);
|
||||
// Skip to next epoch so operator stake is realized.
|
||||
await stakingApiWrapper.utils.skipToNextEpochAndFinalizeAsync();
|
||||
});
|
||||
describe('Reward Simulation', () => {
|
||||
interface EndBalances {
|
||||
@ -154,7 +160,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
const fee = _fee !== undefined ? _fee : ZERO;
|
||||
if (!fee.eq(ZERO)) {
|
||||
await stakingApiWrapper.stakingContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||
poolOperator,
|
||||
poolOperator.getOwner(),
|
||||
takerAddress,
|
||||
fee,
|
||||
{ from: exchangeAddress, value: fee },
|
||||
@ -196,12 +202,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
(staker joins this epoch but is active next epoch)`, async () => {
|
||||
// delegate
|
||||
const amount = toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(amount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
amount,
|
||||
);
|
||||
await stakers[0].stakeWithPoolAsync(poolId, amount);
|
||||
// finalize
|
||||
const reward = toBaseUnitAmount(10);
|
||||
await payProtocolFeeAndFinalize(reward);
|
||||
@ -213,12 +214,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
it('Should give pool reward to delegator', async () => {
|
||||
// delegate
|
||||
const amount = toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(amount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
amount,
|
||||
);
|
||||
await stakers[0].stakeWithPoolAsync(poolId, amount);
|
||||
// skip epoch, so staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// finalize
|
||||
@ -232,22 +228,12 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
});
|
||||
});
|
||||
it('Should split pool reward between delegators', async () => {
|
||||
// first staker delegates
|
||||
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
||||
const totalStakeAmount = toBaseUnitAmount(10);
|
||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmounts[0],
|
||||
);
|
||||
// first staker delegates
|
||||
await stakers[0].stakeWithPoolAsync(poolId, stakeAmounts[0]);
|
||||
// second staker delegates
|
||||
await stakers[1].stakeAsync(stakeAmounts[1]);
|
||||
await stakers[1].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmounts[1],
|
||||
);
|
||||
await stakers[1].stakeWithPoolAsync(poolId, stakeAmounts[1]);
|
||||
// skip epoch, so staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// finalize
|
||||
@ -299,24 +285,14 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
});
|
||||
});
|
||||
it('Should give pool reward to delegators only for the epoch during which they delegated', async () => {
|
||||
// first staker delegates (epoch 0)
|
||||
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
||||
const totalStakeAmount = toBaseUnitAmount(10);
|
||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmounts[0],
|
||||
);
|
||||
// first staker delegates (epoch 0)
|
||||
await stakers[0].stakeWithPoolAsync(poolId, stakeAmounts[0]);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// second staker delegates (epoch 1)
|
||||
await stakers[1].stakeAsync(stakeAmounts[1]);
|
||||
await stakers[1].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmounts[1],
|
||||
);
|
||||
await stakers[1].stakeWithPoolAsync(poolId, stakeAmounts[1]);
|
||||
// only the first staker will get this reward
|
||||
const rewardForOnlyFirstDelegator = toBaseUnitAmount(10);
|
||||
await payProtocolFeeAndFinalize(rewardForOnlyFirstDelegator);
|
||||
@ -349,24 +325,14 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
return v.toNumber();
|
||||
});
|
||||
const totalSharedRewards = new BigNumber(totalSharedRewardsAsNumber);
|
||||
// first staker delegates (epoch 0)
|
||||
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
||||
const totalStakeAmount = toBaseUnitAmount(10);
|
||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmounts[0],
|
||||
);
|
||||
// first staker delegates (epoch 0)
|
||||
await stakers[0].stakeWithPoolAsync(poolId, stakeAmounts[0]);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// second staker delegates (epoch 1)
|
||||
await stakers[1].stakeAsync(stakeAmounts[1]);
|
||||
await stakers[1].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmounts[1],
|
||||
);
|
||||
await stakers[1].stakeWithPoolAsync(poolId, stakeAmounts[1]);
|
||||
// only the first staker will get this reward
|
||||
await payProtocolFeeAndFinalize(rewardForOnlyFirstDelegator);
|
||||
// earn a bunch of rewards
|
||||
@ -386,14 +352,9 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
});
|
||||
});
|
||||
it('Should send existing rewards from reward vault to eth vault correctly when undelegating stake', async () => {
|
||||
// first staker delegates (epoch 0)
|
||||
const stakeAmount = toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(stakeAmount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmount,
|
||||
);
|
||||
// first staker delegates (epoch 0)
|
||||
await stakers[0].stakeWithPoolAsync(poolId, stakeAmount);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// earn reward
|
||||
@ -412,26 +373,16 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
});
|
||||
});
|
||||
it('Should send existing rewards from reward vault to eth vault correctly when delegating more stake', async () => {
|
||||
// first staker delegates (epoch 0)
|
||||
const stakeAmount = toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(stakeAmount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmount,
|
||||
);
|
||||
// first staker delegates (epoch 0)
|
||||
await stakers[0].stakeWithPoolAsync(poolId, stakeAmount);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// earn reward
|
||||
const reward = toBaseUnitAmount(10);
|
||||
await payProtocolFeeAndFinalize(reward);
|
||||
// add more stake
|
||||
await stakers[0].stakeAsync(stakeAmount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmount,
|
||||
);
|
||||
await stakers[0].stakeWithPoolAsync(poolId, stakeAmount);
|
||||
// sanity check final balances
|
||||
await validateEndBalances({
|
||||
stakerRewardVaultBalance_1: ZERO,
|
||||
@ -453,23 +404,13 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
return v.toNumber();
|
||||
}),
|
||||
);
|
||||
// first staker delegates (epoch 0)
|
||||
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmounts[0],
|
||||
);
|
||||
// first staker delegates (epoch 0)
|
||||
await stakers[0].stakeWithPoolAsync(poolId, stakeAmounts[0]);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// second staker delegates (epoch 1)
|
||||
await stakers[0].stakeAsync(stakeAmounts[1]);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmounts[1],
|
||||
);
|
||||
await stakers[1].stakeWithPoolAsync(poolId, stakeAmounts[1]);
|
||||
// only the first staker will get this reward
|
||||
await payProtocolFeeAndFinalize(rewardBeforeAddingMoreStake);
|
||||
// earn a bunch of rewards
|
||||
@ -488,12 +429,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
const rewardForDelegator = toBaseUnitAmount(10);
|
||||
const rewardNotForDelegator = toBaseUnitAmount(7);
|
||||
const stakeAmount = toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(stakeAmount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmount,
|
||||
);
|
||||
await stakers[0].stakeWithPoolAsync(poolId, stakeAmount);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// earn reward
|
||||
@ -534,12 +470,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
}),
|
||||
);
|
||||
const stakeAmount = toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(stakeAmount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmount,
|
||||
);
|
||||
await stakers[0].stakeWithPoolAsync(poolId, stakeAmount);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// earn reward
|
||||
@ -566,12 +497,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
const rewardsForDelegator = [toBaseUnitAmount(10), toBaseUnitAmount(15)];
|
||||
const rewardNotForDelegator = toBaseUnitAmount(7);
|
||||
const stakeAmount = toBaseUnitAmount(4);
|
||||
await stakers[0].stakeAsync(stakeAmount);
|
||||
await stakers[0].moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmount,
|
||||
);
|
||||
await stakers[0].stakeWithPoolAsync(poolId, stakeAmount);
|
||||
// skip epoch, so first staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// earn reward
|
||||
@ -668,12 +594,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
const staker = stakers[0];
|
||||
const stakeAmount = toBaseUnitAmount(5);
|
||||
// stake and delegate
|
||||
await staker.stakeAsync(stakeAmount);
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmount,
|
||||
);
|
||||
await stakers[0].stakeWithPoolAsync(poolId, stakeAmount);
|
||||
// skip epoch, so staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
// undelegate some stake
|
||||
@ -703,12 +624,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
// stake and delegate both
|
||||
const stakersAndStake = _.zip(stakers.slice(0, 2), stakeAmounts) as Array<[StakerActor, BigNumber]>;
|
||||
for (const [staker, stakeAmount] of stakersAndStake) {
|
||||
await staker.stakeAsync(stakeAmount);
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmount,
|
||||
);
|
||||
await staker.stakeWithPoolAsync(poolId, stakeAmount);
|
||||
}
|
||||
// skip epoch, so staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
@ -739,12 +655,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
// stake and delegate both
|
||||
const stakersAndStake = _.zip(stakers.slice(0, 2), stakeAmounts) as Array<[StakerActor, BigNumber]>;
|
||||
for (const [staker, stakeAmount] of stakersAndStake) {
|
||||
await staker.stakeAsync(stakeAmount);
|
||||
await staker.moveStakeAsync(
|
||||
new StakeInfo(StakeStatus.Active),
|
||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
||||
stakeAmount,
|
||||
);
|
||||
await staker.stakeWithPoolAsync(poolId, stakeAmount);
|
||||
}
|
||||
// skip epoch, so staker can start earning rewards
|
||||
await payProtocolFeeAndFinalize();
|
||||
|
@ -7,7 +7,7 @@ blockchainTests('delegator rewards', env => {
|
||||
|
||||
before(async () => {
|
||||
testContract = await TestDelegatorRewardsContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestLibFixedMath,
|
||||
artifacts.TestDelegatorRewards,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
55
contracts/staking/test/unit_tests/finalizer_test.ts
Normal file
55
contracts/staking/test/unit_tests/finalizer_test.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { blockchainTests, expect, filterLogsToArguments, Numberish } from '@0x/contracts-test-utils';
|
||||
|
||||
import {
|
||||
artifacts,
|
||||
IStakingEventsEpochEndedEventArgs,
|
||||
IStakingEventsEpochFinalizedEventArgs,
|
||||
IStakingEventsEvents,
|
||||
TestFinalizerContract,
|
||||
} from '../../src';
|
||||
|
||||
blockchainTests.resets.only('finalization tests', env => {
|
||||
let testContract: TestFinalizerContract;
|
||||
const INITIAL_EPOCH = 0;
|
||||
|
||||
before(async () => {
|
||||
testContract = await TestFinalizerContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestFinalizer,
|
||||
env.provider,
|
||||
env.txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
});
|
||||
|
||||
describe('endEpoch()', () => {
|
||||
it('emits an `EpochEnded` event', async () => {
|
||||
const receipt = await testContract.endEpoch.awaitTransactionSuccessAsync();
|
||||
const [epochEndedEvent] = filterLogsToArguments<IStakingEventsEpochEndedEventArgs>(
|
||||
receipt.logs,
|
||||
IStakingEventsEvents.EpochEnded,
|
||||
);
|
||||
expect(epochEndedEvent.epoch).to.bignumber.eq(INITIAL_EPOCH);
|
||||
expect(epochEndedEvent.numActivePools).to.bignumber.eq(0);
|
||||
expect(epochEndedEvent.rewardsAvailable).to.bignumber.eq(0);
|
||||
expect(epochEndedEvent.totalFeesCollected).to.bignumber.eq(0);
|
||||
expect(epochEndedEvent.totalWeightedStake).to.bignumber.eq(0);
|
||||
});
|
||||
|
||||
it('advances the epoch', async () => {
|
||||
await testContract.endEpoch.awaitTransactionSuccessAsync();
|
||||
const currentEpoch = await testContract.getCurrentEpoch.callAsync();
|
||||
expect(currentEpoch).to.be.bignumber.eq(INITIAL_EPOCH + 1);
|
||||
});
|
||||
|
||||
it('immediately finalizes if there are no active pools', async () => {
|
||||
const receipt = await testContract.endEpoch.awaitTransactionSuccessAsync();
|
||||
const [epochFinalizedEvent] = filterLogsToArguments<IStakingEventsEpochFinalizedEventArgs>(
|
||||
receipt.logs,
|
||||
IStakingEventsEvents.EpochFinalized,
|
||||
);
|
||||
expect(epochFinalizedEvent.epoch).to.bignumber.eq(INITIAL_EPOCH);
|
||||
expect(epochFinalizedEvent.rewardsPaid).to.bignumber.eq(0);
|
||||
expect(epochFinalizedEvent.rewardsRemaining).to.bignumber.eq(0);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user