@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 solidity ^0.5.9;
|
||||||
pragma experimental ABIEncoderV2;
|
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/LibRichErrors.sol";
|
||||||
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
|
||||||
import "../libs/LibStakingRichErrors.sol";
|
import "../libs/LibStakingRichErrors.sol";
|
||||||
@ -71,7 +72,8 @@ contract MixinExchangeFees is
|
|||||||
{
|
{
|
||||||
_assertValidProtocolFee(protocolFeePaid);
|
_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) {
|
if (msg.value == 0) {
|
||||||
wethAssetProxy.transferFrom(
|
wethAssetProxy.transferFrom(
|
||||||
WETH_ASSET_DATA,
|
WETH_ASSET_DATA,
|
||||||
@ -89,7 +91,8 @@ contract MixinExchangeFees is
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 poolStake = getTotalStakeDelegatedToPool(poolId).currentEpochBalance;
|
uint256 poolStake =
|
||||||
|
getTotalStakeDelegatedToPool(poolId).currentEpochBalance;
|
||||||
// Ignore pools with dust stake.
|
// Ignore pools with dust stake.
|
||||||
if (poolStake < minimumPoolStake) {
|
if (poolStake < minimumPoolStake) {
|
||||||
return;
|
return;
|
||||||
@ -103,35 +106,24 @@ contract MixinExchangeFees is
|
|||||||
|
|
||||||
// If the pool was previously inactive in this epoch, initialize it.
|
// If the pool was previously inactive in this epoch, initialize it.
|
||||||
if (pool.feesCollected == 0) {
|
if (pool.feesCollected == 0) {
|
||||||
// Compute weighted stake.
|
// Compute member and total weighted stake.
|
||||||
uint256 operatorStake = getStakeDelegatedToPoolByOwner(
|
(pool.membersStake, pool.weightedStake) =
|
||||||
rewardVault.operatorOf(poolId),
|
_computeMembersAndWeightedStake(poolId, poolStake);
|
||||||
poolId
|
|
||||||
).currentEpochBalance;
|
|
||||||
pool.weightedStake = operatorStake.safeAdd(
|
|
||||||
poolStake
|
|
||||||
.safeSub(operatorStake)
|
|
||||||
.safeMul(rewardDelegatedStakeWeight)
|
|
||||||
.safeDiv(PPM_DENOMINATOR)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Compute delegated (non-operator) stake.
|
|
||||||
pool.delegatedStake = poolStake.safeSub(operatorStake);
|
|
||||||
|
|
||||||
// Increase the total weighted stake.
|
// Increase the total weighted stake.
|
||||||
totalWeightedStakeThisEpoch = totalWeightedStakeThisEpoch.safeAdd(
|
totalWeightedStakeThisEpoch =
|
||||||
pool.weightedStake
|
totalWeightedStakeThisEpoch.safeAdd(pool.weightedStake);
|
||||||
);
|
|
||||||
|
|
||||||
// Increase the numberof active pools.
|
// Increase the numberof active pools.
|
||||||
numActivePoolsThisEpoch += 1;
|
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);
|
emit StakingPoolActivated(currentEpoch, poolId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Credit the fees to the pool.
|
// Credit the fees to the pool.
|
||||||
pool.feesCollected = protocolFeePaid;
|
pool.feesCollected = pool.feesCollected.safeAdd(protocolFeePaid);
|
||||||
|
|
||||||
// Increase the total fees collected this epoch.
|
// Increase the total fees collected this epoch.
|
||||||
totalFeesCollectedThisEpoch = totalFeesCollectedThisEpoch.safeAdd(
|
totalFeesCollectedThisEpoch = totalFeesCollectedThisEpoch.safeAdd(
|
||||||
@ -142,7 +134,8 @@ contract MixinExchangeFees is
|
|||||||
activePoolsThisEpoch[poolId] = pool;
|
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.
|
/// @return _totalFeesCollectedThisEpoch Total fees collected this epoch.
|
||||||
function getTotalProtocolFeesThisEpoch()
|
function getTotalProtocolFeesThisEpoch()
|
||||||
external
|
external
|
||||||
@ -152,9 +145,21 @@ contract MixinExchangeFees is
|
|||||||
_totalFeesCollectedThisEpoch = totalFeesCollectedThisEpoch;
|
_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.
|
/// @dev Returns the amount of fees attributed to the input pool this epoch.
|
||||||
/// @param poolId Pool Id to query.
|
/// @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)
|
function getProtocolFeesThisEpochByPool(bytes32 poolId)
|
||||||
external
|
external
|
||||||
view
|
view
|
||||||
@ -168,20 +173,55 @@ contract MixinExchangeFees is
|
|||||||
feesCollected = pool.feesCollected;
|
feesCollected = pool.feesCollected;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Checks that the protocol fee passed into `payProtocolFee()` is valid.
|
/// @dev Computes the members and weighted stake for a pool at the current
|
||||||
/// @param protocolFeePaid The `protocolFeePaid` parameter to `payProtocolFee.`
|
/// 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)
|
function _assertValidProtocolFee(uint256 protocolFeePaid)
|
||||||
private
|
private
|
||||||
view
|
view
|
||||||
{
|
{
|
||||||
if (protocolFeePaid == 0 || (msg.value != protocolFeePaid && msg.value != 0)) {
|
if (protocolFeePaid == 0 ||
|
||||||
LibRichErrors.rrevert(LibStakingRichErrors.InvalidProtocolFeePaymentError(
|
(msg.value != protocolFeePaid && msg.value != 0)) {
|
||||||
|
LibRichErrors.rrevert(
|
||||||
|
LibStakingRichErrors.InvalidProtocolFeePaymentError(
|
||||||
protocolFeePaid == 0 ?
|
protocolFeePaid == 0 ?
|
||||||
LibStakingRichErrors.ProtocolFeePaymentErrorCodes.ZeroProtocolFeePaid :
|
LibStakingRichErrors
|
||||||
LibStakingRichErrors.ProtocolFeePaymentErrorCodes.MismatchedFeeAndPayment,
|
.ProtocolFeePaymentErrorCodes
|
||||||
|
.ZeroProtocolFeePaid :
|
||||||
|
LibStakingRichErrors
|
||||||
|
.ProtocolFeePaymentErrorCodes
|
||||||
|
.MismatchedFeeAndPayment,
|
||||||
protocolFeePaid,
|
protocolFeePaid,
|
||||||
msg.value
|
msg.value
|
||||||
));
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,11 @@ interface IStructs {
|
|||||||
/// (see MixinExchangeFees).
|
/// (see MixinExchangeFees).
|
||||||
/// @param feesCollected Fees collected in ETH by this pool.
|
/// @param feesCollected Fees collected in ETH by this pool.
|
||||||
/// @param weightedStake Amount of weighted stake in the 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 {
|
struct ActivePool {
|
||||||
uint256 feesCollected;
|
uint256 feesCollected;
|
||||||
uint256 weightedStake;
|
uint256 weightedStake;
|
||||||
uint256 delegatedStake;
|
uint256 membersStake;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Rewards credited to a pool during finalization.
|
/// @dev Rewards credited to a pool during finalization.
|
||||||
|
@ -113,6 +113,17 @@ contract MixinStakingPool is
|
|||||||
return poolById[poolId];
|
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.
|
/// @dev Computes the unique id that comes after the input pool id.
|
||||||
/// @param poolId Unique id of pool.
|
/// @param poolId Unique id of pool.
|
||||||
/// @return Next pool id after input pool.
|
/// @return Next pool id after input pool.
|
||||||
|
@ -93,7 +93,7 @@ contract MixinFinalizer is
|
|||||||
// Emit an event.
|
// Emit an event.
|
||||||
emit EpochEnded(
|
emit EpochEnded(
|
||||||
closingEpoch,
|
closingEpoch,
|
||||||
numActivePoolsThisEpoch,
|
unfinalizedPoolsRemaining,
|
||||||
unfinalizedRewardsAvailable,
|
unfinalizedRewardsAvailable,
|
||||||
unfinalizedTotalFeesCollected,
|
unfinalizedTotalFeesCollected,
|
||||||
unfinalizedTotalWeightedStake
|
unfinalizedTotalWeightedStake
|
||||||
@ -179,7 +179,9 @@ contract MixinFinalizer is
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Deposit all the rewards at once into the RewardVault.
|
// Deposit all the rewards at once into the RewardVault.
|
||||||
|
if (rewardsPaid != 0) {
|
||||||
_depositIntoStakingPoolRewardVault(rewardsPaid);
|
_depositIntoStakingPoolRewardVault(rewardsPaid);
|
||||||
|
}
|
||||||
|
|
||||||
// Update finalization states.
|
// Update finalization states.
|
||||||
totalRewardsPaidLastEpoch =
|
totalRewardsPaidLastEpoch =
|
||||||
@ -310,6 +312,11 @@ contract MixinFinalizer is
|
|||||||
|
|
||||||
IStructs.ActivePool memory pool =
|
IStructs.ActivePool memory pool =
|
||||||
_getActivePoolFromEpoch(epoch - 1, poolId);
|
_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.
|
// Use the cobb-douglas function to compute the total reward.
|
||||||
uint256 totalReward = LibCobbDouglas._cobbDouglas(
|
uint256 totalReward = LibCobbDouglas._cobbDouglas(
|
||||||
@ -323,16 +330,13 @@ contract MixinFinalizer is
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Split the reward between the operator and delegators.
|
// Split the reward between the operator and delegators.
|
||||||
if (pool.delegatedStake == 0) {
|
if (pool.membersStake == 0) {
|
||||||
rewards.operatorReward = totalReward;
|
rewards.operatorReward = totalReward;
|
||||||
} else {
|
} else {
|
||||||
(rewards.operatorReward, rewards.membersReward) =
|
(rewards.operatorReward, rewards.membersReward) =
|
||||||
rewardVault.splitAmountBetweenOperatorAndMembers(
|
_splitAmountBetweenOperatorAndMembers(poolId, totalReward);
|
||||||
poolId,
|
|
||||||
totalReward
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
rewards.membersStake = pool.delegatedStake;
|
rewards.membersStake = pool.membersStake;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Converts the entire WETH balance of the contract into ETH.
|
/// @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
|
/// @dev Computes the reward owed to a pool during finalization and
|
||||||
/// credits it to that pool for the CURRENT epoch.
|
/// credits it to that pool for the CURRENT epoch.
|
||||||
/// @param poolId The pool's ID.
|
/// @param poolId The pool's ID.
|
||||||
@ -356,6 +402,12 @@ contract MixinFinalizer is
|
|||||||
private
|
private
|
||||||
returns (IStructs.PoolRewards memory rewards)
|
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.
|
// Use the cobb-douglas function to compute the total reward.
|
||||||
uint256 totalReward = LibCobbDouglas._cobbDouglas(
|
uint256 totalReward = LibCobbDouglas._cobbDouglas(
|
||||||
unfinalizedRewardsAvailable,
|
unfinalizedRewardsAvailable,
|
||||||
@ -369,20 +421,20 @@ contract MixinFinalizer is
|
|||||||
|
|
||||||
// Credit the pool the reward in the RewardVault.
|
// Credit the pool the reward in the RewardVault.
|
||||||
(rewards.operatorReward, rewards.membersReward) =
|
(rewards.operatorReward, rewards.membersReward) =
|
||||||
rewardVault.recordDepositFor(
|
_recordDepositInRewardVaultFor(
|
||||||
poolId,
|
poolId,
|
||||||
totalReward,
|
totalReward,
|
||||||
// If no delegated stake, all rewards go to the operator.
|
// 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.
|
// Sync delegator rewards.
|
||||||
if (rewards.membersReward != 0) {
|
if (rewards.membersReward != 0) {
|
||||||
_recordRewardForDelegators(
|
_recordRewardForDelegators(
|
||||||
poolId,
|
poolId,
|
||||||
rewards.membersReward,
|
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;
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetProxy.sol";
|
import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetProxy.sol";
|
||||||
|
import "../src/interfaces/IStructs.sol";
|
||||||
import "../src/Staking.sol";
|
import "../src/Staking.sol";
|
||||||
|
|
||||||
|
|
||||||
@ -27,7 +28,8 @@ contract TestProtocolFees is
|
|||||||
Staking
|
Staking
|
||||||
{
|
{
|
||||||
struct TestPool {
|
struct TestPool {
|
||||||
uint256 stake;
|
uint256 operatorStake;
|
||||||
|
uint256 membersStake;
|
||||||
mapping(address => bool) isMaker;
|
mapping(address => bool) isMaker;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,13 +60,15 @@ contract TestProtocolFees is
|
|||||||
/// @dev Create a test pool.
|
/// @dev Create a test pool.
|
||||||
function createTestPool(
|
function createTestPool(
|
||||||
bytes32 poolId,
|
bytes32 poolId,
|
||||||
uint256 stake,
|
uint256 operatorStake,
|
||||||
|
uint256 membersStake,
|
||||||
address[] memory makerAddresses
|
address[] memory makerAddresses
|
||||||
)
|
)
|
||||||
public
|
public
|
||||||
{
|
{
|
||||||
TestPool storage pool = _testPools[poolId];
|
TestPool storage pool = _testPools[poolId];
|
||||||
pool.stake = stake;
|
pool.operatorStake = operatorStake;
|
||||||
|
pool.membersStake = membersStake;
|
||||||
for (uint256 i = 0; i < makerAddresses.length; ++i) {
|
for (uint256 i = 0; i < makerAddresses.length; ++i) {
|
||||||
pool.isMaker[makerAddresses[i]] = true;
|
pool.isMaker[makerAddresses[i]] = true;
|
||||||
_makersToTestPoolIds[makerAddresses[i]] = poolId;
|
_makersToTestPoolIds[makerAddresses[i]] = poolId;
|
||||||
@ -86,10 +90,34 @@ contract TestProtocolFees is
|
|||||||
view
|
view
|
||||||
returns (IStructs.StakeBalance memory balance)
|
returns (IStructs.StakeBalance memory balance)
|
||||||
{
|
{
|
||||||
uint256 stake = _testPools[poolId].stake;
|
TestPool memory pool = _testPools[poolId];
|
||||||
|
uint256 stake = pool.operatorStake + pool.membersStake;
|
||||||
return IStructs.StakeBalance({
|
return IStructs.StakeBalance({
|
||||||
currentEpochBalance: stake,
|
currentEpochBalance: stake,
|
||||||
nextEpochBalance: 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);
|
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> {
|
public async goToNextEpochAsync(): Promise<void> {
|
||||||
// cache balances
|
// cache balances
|
||||||
const initZrxBalanceOfVault = await this._stakingApiWrapper.utils.getZrxTokenBalanceOfZrxVaultAsync();
|
const initZrxBalanceOfVault = await this._stakingApiWrapper.utils.getZrxTokenBalanceOfZrxVaultAsync();
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
expect,
|
expect,
|
||||||
filterLogsToArguments,
|
filterLogsToArguments,
|
||||||
hexRandom,
|
hexRandom,
|
||||||
|
Numberish,
|
||||||
randomAddress,
|
randomAddress,
|
||||||
} from '@0x/contracts-test-utils';
|
} from '@0x/contracts-test-utils';
|
||||||
import { StakingRevertErrors } from '@0x/order-utils';
|
import { StakingRevertErrors } from '@0x/order-utils';
|
||||||
@ -54,9 +55,26 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
wethAssetData = await testContract.getWethAssetData.callAsync();
|
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();
|
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;
|
return poolId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +172,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
it('should not transfer WETH if value is sent', async () => {
|
it('should not transfer WETH if value is sent', async () => {
|
||||||
await createTestPoolAsync(minimumStake, []);
|
await createTestPoolAsync({ operatorStake: minimumStake });
|
||||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
payerAddress,
|
payerAddress,
|
||||||
@ -164,8 +182,8 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
assertNoWETHTransferLogs(receipt.logs);
|
assertNoWETHTransferLogs(receipt.logs);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update `protocolFeesThisEpochByPool` if the maker is in a pool', async () => {
|
it('should credit pool 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(
|
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
payerAddress,
|
payerAddress,
|
||||||
@ -177,8 +195,8 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
expect(poolFees).to.bignumber.eq(DEFAULT_PROTOCOL_FEE_PAID);
|
expect(poolFees).to.bignumber.eq(DEFAULT_PROTOCOL_FEE_PAID);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not update `protocolFeesThisEpochByPool` if maker is not in a pool', async () => {
|
it('should not credit the pool 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(
|
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
payerAddress,
|
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 () => {
|
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 payAsync = async () => {
|
||||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
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 () => {
|
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(
|
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
payerAddress,
|
payerAddress,
|
||||||
@ -236,7 +254,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should update `protocolFeesThisEpochByPool` if the maker is in a pool', async () => {
|
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(
|
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
payerAddress,
|
payerAddress,
|
||||||
@ -249,7 +267,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not update `protocolFeesThisEpochByPool` if maker is not in a pool', async () => {
|
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(
|
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
payerAddress,
|
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 () => {
|
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 payAsync = async () => {
|
||||||
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
const receipt = await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
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 () => {
|
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) => {
|
const payAsync = async (inWETH: boolean) => {
|
||||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
@ -303,7 +321,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
describe('Multiple makers', () => {
|
describe('Multiple makers', () => {
|
||||||
it('fees paid to different makers in the same pool go to that pool', async () => {
|
it('fees paid to different makers in the same pool go to that pool', async () => {
|
||||||
const otherMakerAddress = randomAddress();
|
const otherMakerAddress = randomAddress();
|
||||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress, otherMakerAddress]);
|
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress, otherMakerAddress] });
|
||||||
const payAsync = async (_makerAddress: string) => {
|
const payAsync = async (_makerAddress: string) => {
|
||||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
_makerAddress,
|
_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 () => {
|
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 [fee, otherFee] = _.times(2, () => getRandomPortion(DEFAULT_PROTOCOL_FEE_PAID));
|
||||||
const otherMakerAddress = randomAddress();
|
const otherMakerAddress = randomAddress();
|
||||||
const poolId = await createTestPoolAsync(minimumStake, [makerAddress]);
|
const poolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [makerAddress] });
|
||||||
const otherPoolId = await createTestPoolAsync(minimumStake, [otherMakerAddress]);
|
const otherPoolId = await createTestPoolAsync({ operatorStake: minimumStake, makers: [otherMakerAddress]});
|
||||||
const payAsync = async (_poolId: string, _makerAddress: string, _fee: BigNumber) => {
|
const payAsync = async (_poolId: string, _makerAddress: string, _fee: BigNumber) => {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
@ -346,7 +364,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
|
|
||||||
describe('Dust stake', () => {
|
describe('Dust stake', () => {
|
||||||
it('credits pools with stake > minimum', async () => {
|
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(
|
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
constants.NULL_ADDRESS,
|
constants.NULL_ADDRESS,
|
||||||
@ -358,7 +376,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('credits pools with stake == minimum', async () => {
|
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(
|
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
constants.NULL_ADDRESS,
|
constants.NULL_ADDRESS,
|
||||||
@ -370,7 +388,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not credit pools with stake < minimum', async () => {
|
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(
|
await testContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
makerAddress,
|
makerAddress,
|
||||||
constants.NULL_ADDRESS,
|
constants.NULL_ADDRESS,
|
||||||
|
@ -6,6 +6,7 @@ import * as _ from 'lodash';
|
|||||||
import { artifacts } from '../src';
|
import { artifacts } from '../src';
|
||||||
|
|
||||||
import { FinalizerActor } from './actors/finalizer_actor';
|
import { FinalizerActor } from './actors/finalizer_actor';
|
||||||
|
import { PoolOperatorActor } from './actors/pool_operator_actor';
|
||||||
import { StakerActor } from './actors/staker_actor';
|
import { StakerActor } from './actors/staker_actor';
|
||||||
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
|
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
|
||||||
import { toBaseUnitAmount } from './utils/number_utils';
|
import { toBaseUnitAmount } from './utils/number_utils';
|
||||||
@ -26,8 +27,9 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
let erc20Wrapper: ERC20Wrapper;
|
let erc20Wrapper: ERC20Wrapper;
|
||||||
// test parameters
|
// test parameters
|
||||||
let stakers: StakerActor[];
|
let stakers: StakerActor[];
|
||||||
|
let poolOperatorStaker: StakerActor;
|
||||||
let poolId: string;
|
let poolId: string;
|
||||||
let poolOperator: string;
|
let poolOperator: PoolOperatorActor;
|
||||||
let finalizer: FinalizerActor;
|
let finalizer: FinalizerActor;
|
||||||
// tests
|
// tests
|
||||||
before(async () => {
|
before(async () => {
|
||||||
@ -43,7 +45,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
stakingApiWrapper = await deployAndConfigureContractsAsync(env, owner, erc20Wrapper, artifacts.TestStaking);
|
stakingApiWrapper = await deployAndConfigureContractsAsync(env, owner, erc20Wrapper, artifacts.TestStaking);
|
||||||
// set up staking parameters
|
// set up staking parameters
|
||||||
await stakingApiWrapper.utils.setParamsAsync({
|
await stakingApiWrapper.utils.setParamsAsync({
|
||||||
minimumPoolStake: new BigNumber(0),
|
minimumPoolStake: new BigNumber(1),
|
||||||
cobbDouglasAlphaNumerator: new BigNumber(1),
|
cobbDouglasAlphaNumerator: new BigNumber(1),
|
||||||
cobbDouglasAlphaDenominator: new BigNumber(6),
|
cobbDouglasAlphaDenominator: new BigNumber(6),
|
||||||
rewardVaultAddress: stakingApiWrapper.rewardVaultContract.address,
|
rewardVaultAddress: stakingApiWrapper.rewardVaultContract.address,
|
||||||
@ -51,22 +53,26 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
zrxVaultAddress: stakingApiWrapper.zrxVaultContract.address,
|
zrxVaultAddress: stakingApiWrapper.zrxVaultContract.address,
|
||||||
});
|
});
|
||||||
// setup stakers
|
// 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
|
// setup pools
|
||||||
poolOperator = actors[2];
|
poolOperator = new PoolOperatorActor(actors[2], stakingApiWrapper);
|
||||||
poolId = await stakingApiWrapper.utils.createStakingPoolAsync(poolOperator, 0, true); // add operator as maker
|
// 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
|
// set exchange address
|
||||||
await stakingApiWrapper.stakingContract.addExchangeAddress.awaitTransactionSuccessAsync(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.getOwner();
|
||||||
operatorByPoolId[poolId] = poolOperator;
|
|
||||||
// associate actors with pools for tracking in Finalizer
|
// associate actors with pools for tracking in Finalizer
|
||||||
const membersByPoolId: MembersByPoolId = {};
|
const membersByPoolId: MembersByPoolId = {};
|
||||||
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], stakingApiWrapper, [poolId], operatorByPoolId, membersByPoolId);
|
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', () => {
|
describe('Reward Simulation', () => {
|
||||||
interface EndBalances {
|
interface EndBalances {
|
||||||
@ -154,7 +160,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
const fee = _fee !== undefined ? _fee : ZERO;
|
const fee = _fee !== undefined ? _fee : ZERO;
|
||||||
if (!fee.eq(ZERO)) {
|
if (!fee.eq(ZERO)) {
|
||||||
await stakingApiWrapper.stakingContract.payProtocolFee.awaitTransactionSuccessAsync(
|
await stakingApiWrapper.stakingContract.payProtocolFee.awaitTransactionSuccessAsync(
|
||||||
poolOperator,
|
poolOperator.getOwner(),
|
||||||
takerAddress,
|
takerAddress,
|
||||||
fee,
|
fee,
|
||||||
{ from: exchangeAddress, value: fee },
|
{ from: exchangeAddress, value: fee },
|
||||||
@ -196,12 +202,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
(staker joins this epoch but is active next epoch)`, async () => {
|
(staker joins this epoch but is active next epoch)`, async () => {
|
||||||
// delegate
|
// delegate
|
||||||
const amount = toBaseUnitAmount(4);
|
const amount = toBaseUnitAmount(4);
|
||||||
await stakers[0].stakeAsync(amount);
|
await stakers[0].stakeWithPoolAsync(poolId, amount);
|
||||||
await stakers[0].moveStakeAsync(
|
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
amount,
|
|
||||||
);
|
|
||||||
// finalize
|
// finalize
|
||||||
const reward = toBaseUnitAmount(10);
|
const reward = toBaseUnitAmount(10);
|
||||||
await payProtocolFeeAndFinalize(reward);
|
await payProtocolFeeAndFinalize(reward);
|
||||||
@ -213,12 +214,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
it('Should give pool reward to delegator', async () => {
|
it('Should give pool reward to delegator', async () => {
|
||||||
// delegate
|
// delegate
|
||||||
const amount = toBaseUnitAmount(4);
|
const amount = toBaseUnitAmount(4);
|
||||||
await stakers[0].stakeAsync(amount);
|
await stakers[0].stakeWithPoolAsync(poolId, amount);
|
||||||
await stakers[0].moveStakeAsync(
|
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
amount,
|
|
||||||
);
|
|
||||||
// skip epoch, so staker can start earning rewards
|
// skip epoch, so staker can start earning rewards
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
// finalize
|
// finalize
|
||||||
@ -232,22 +228,12 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('Should split pool reward between delegators', async () => {
|
it('Should split pool reward between delegators', async () => {
|
||||||
// first staker delegates
|
|
||||||
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
||||||
const totalStakeAmount = toBaseUnitAmount(10);
|
const totalStakeAmount = toBaseUnitAmount(10);
|
||||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
// first staker delegates
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].stakeWithPoolAsync(poolId, stakeAmounts[0]);
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
stakeAmounts[0],
|
|
||||||
);
|
|
||||||
// second staker delegates
|
// second staker delegates
|
||||||
await stakers[1].stakeAsync(stakeAmounts[1]);
|
await stakers[1].stakeWithPoolAsync(poolId, stakeAmounts[1]);
|
||||||
await stakers[1].moveStakeAsync(
|
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
stakeAmounts[1],
|
|
||||||
);
|
|
||||||
// skip epoch, so staker can start earning rewards
|
// skip epoch, so staker can start earning rewards
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
// finalize
|
// 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 () => {
|
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 stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
||||||
const totalStakeAmount = toBaseUnitAmount(10);
|
const totalStakeAmount = toBaseUnitAmount(10);
|
||||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
// first staker delegates (epoch 0)
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].stakeWithPoolAsync(poolId, stakeAmounts[0]);
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
stakeAmounts[0],
|
|
||||||
);
|
|
||||||
// skip epoch, so first staker can start earning rewards
|
// skip epoch, so first staker can start earning rewards
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
// second staker delegates (epoch 1)
|
// second staker delegates (epoch 1)
|
||||||
await stakers[1].stakeAsync(stakeAmounts[1]);
|
await stakers[1].stakeWithPoolAsync(poolId, stakeAmounts[1]);
|
||||||
await stakers[1].moveStakeAsync(
|
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
stakeAmounts[1],
|
|
||||||
);
|
|
||||||
// only the first staker will get this reward
|
// only the first staker will get this reward
|
||||||
const rewardForOnlyFirstDelegator = toBaseUnitAmount(10);
|
const rewardForOnlyFirstDelegator = toBaseUnitAmount(10);
|
||||||
await payProtocolFeeAndFinalize(rewardForOnlyFirstDelegator);
|
await payProtocolFeeAndFinalize(rewardForOnlyFirstDelegator);
|
||||||
@ -349,24 +325,14 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
return v.toNumber();
|
return v.toNumber();
|
||||||
});
|
});
|
||||||
const totalSharedRewards = new BigNumber(totalSharedRewardsAsNumber);
|
const totalSharedRewards = new BigNumber(totalSharedRewardsAsNumber);
|
||||||
// first staker delegates (epoch 0)
|
|
||||||
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
||||||
const totalStakeAmount = toBaseUnitAmount(10);
|
const totalStakeAmount = toBaseUnitAmount(10);
|
||||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
// first staker delegates (epoch 0)
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].stakeWithPoolAsync(poolId, stakeAmounts[0]);
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
stakeAmounts[0],
|
|
||||||
);
|
|
||||||
// skip epoch, so first staker can start earning rewards
|
// skip epoch, so first staker can start earning rewards
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
// second staker delegates (epoch 1)
|
// second staker delegates (epoch 1)
|
||||||
await stakers[1].stakeAsync(stakeAmounts[1]);
|
await stakers[1].stakeWithPoolAsync(poolId, stakeAmounts[1]);
|
||||||
await stakers[1].moveStakeAsync(
|
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
stakeAmounts[1],
|
|
||||||
);
|
|
||||||
// only the first staker will get this reward
|
// only the first staker will get this reward
|
||||||
await payProtocolFeeAndFinalize(rewardForOnlyFirstDelegator);
|
await payProtocolFeeAndFinalize(rewardForOnlyFirstDelegator);
|
||||||
// earn a bunch of rewards
|
// 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 () => {
|
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);
|
const stakeAmount = toBaseUnitAmount(4);
|
||||||
await stakers[0].stakeAsync(stakeAmount);
|
// first staker delegates (epoch 0)
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].stakeWithPoolAsync(poolId, stakeAmount);
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
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
|
||||||
@ -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 () => {
|
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);
|
const stakeAmount = toBaseUnitAmount(4);
|
||||||
await stakers[0].stakeAsync(stakeAmount);
|
// first staker delegates (epoch 0)
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].stakeWithPoolAsync(poolId, stakeAmount);
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
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 = 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].stakeWithPoolAsync(poolId, stakeAmount);
|
||||||
await stakers[0].moveStakeAsync(
|
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
stakeAmount,
|
|
||||||
);
|
|
||||||
// sanity check final balances
|
// sanity check final balances
|
||||||
await validateEndBalances({
|
await validateEndBalances({
|
||||||
stakerRewardVaultBalance_1: ZERO,
|
stakerRewardVaultBalance_1: ZERO,
|
||||||
@ -453,23 +404,13 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
return v.toNumber();
|
return v.toNumber();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
// first staker delegates (epoch 0)
|
|
||||||
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
const stakeAmounts = [toBaseUnitAmount(4), toBaseUnitAmount(6)];
|
||||||
await stakers[0].stakeAsync(stakeAmounts[0]);
|
// first staker delegates (epoch 0)
|
||||||
await stakers[0].moveStakeAsync(
|
await stakers[0].stakeWithPoolAsync(poolId, stakeAmounts[0]);
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
stakeAmounts[0],
|
|
||||||
);
|
|
||||||
// skip epoch, so first staker can start earning rewards
|
// skip epoch, so first staker can start earning rewards
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
// second staker delegates (epoch 1)
|
// second staker delegates (epoch 1)
|
||||||
await stakers[0].stakeAsync(stakeAmounts[1]);
|
await stakers[1].stakeWithPoolAsync(poolId, stakeAmounts[1]);
|
||||||
await stakers[0].moveStakeAsync(
|
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
stakeAmounts[1],
|
|
||||||
);
|
|
||||||
// only the first staker will get this reward
|
// only the first staker will get this reward
|
||||||
await payProtocolFeeAndFinalize(rewardBeforeAddingMoreStake);
|
await payProtocolFeeAndFinalize(rewardBeforeAddingMoreStake);
|
||||||
// earn a bunch of rewards
|
// earn a bunch of rewards
|
||||||
@ -488,12 +429,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
const rewardForDelegator = toBaseUnitAmount(10);
|
const rewardForDelegator = toBaseUnitAmount(10);
|
||||||
const rewardNotForDelegator = toBaseUnitAmount(7);
|
const rewardNotForDelegator = toBaseUnitAmount(7);
|
||||||
const stakeAmount = toBaseUnitAmount(4);
|
const stakeAmount = toBaseUnitAmount(4);
|
||||||
await stakers[0].stakeAsync(stakeAmount);
|
await stakers[0].stakeWithPoolAsync(poolId, stakeAmount);
|
||||||
await stakers[0].moveStakeAsync(
|
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
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
|
||||||
@ -534,12 +470,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const stakeAmount = toBaseUnitAmount(4);
|
const stakeAmount = toBaseUnitAmount(4);
|
||||||
await stakers[0].stakeAsync(stakeAmount);
|
await stakers[0].stakeWithPoolAsync(poolId, stakeAmount);
|
||||||
await stakers[0].moveStakeAsync(
|
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
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
|
||||||
@ -566,12 +497,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
const rewardsForDelegator = [toBaseUnitAmount(10), toBaseUnitAmount(15)];
|
const rewardsForDelegator = [toBaseUnitAmount(10), toBaseUnitAmount(15)];
|
||||||
const rewardNotForDelegator = toBaseUnitAmount(7);
|
const rewardNotForDelegator = toBaseUnitAmount(7);
|
||||||
const stakeAmount = toBaseUnitAmount(4);
|
const stakeAmount = toBaseUnitAmount(4);
|
||||||
await stakers[0].stakeAsync(stakeAmount);
|
await stakers[0].stakeWithPoolAsync(poolId, stakeAmount);
|
||||||
await stakers[0].moveStakeAsync(
|
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
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
|
||||||
@ -668,12 +594,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
const staker = stakers[0];
|
const staker = stakers[0];
|
||||||
const stakeAmount = toBaseUnitAmount(5);
|
const stakeAmount = toBaseUnitAmount(5);
|
||||||
// stake and delegate
|
// stake and delegate
|
||||||
await staker.stakeAsync(stakeAmount);
|
await stakers[0].stakeWithPoolAsync(poolId, stakeAmount);
|
||||||
await staker.moveStakeAsync(
|
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
stakeAmount,
|
|
||||||
);
|
|
||||||
// skip epoch, so staker can start earning rewards
|
// skip epoch, so staker can start earning rewards
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
// undelegate some stake
|
// undelegate some stake
|
||||||
@ -703,12 +624,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
// stake and delegate both
|
// stake and delegate both
|
||||||
const stakersAndStake = _.zip(stakers.slice(0, 2), stakeAmounts) as Array<[StakerActor, BigNumber]>;
|
const stakersAndStake = _.zip(stakers.slice(0, 2), stakeAmounts) as Array<[StakerActor, BigNumber]>;
|
||||||
for (const [staker, stakeAmount] of stakersAndStake) {
|
for (const [staker, stakeAmount] of stakersAndStake) {
|
||||||
await staker.stakeAsync(stakeAmount);
|
await staker.stakeWithPoolAsync(poolId, stakeAmount);
|
||||||
await staker.moveStakeAsync(
|
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
stakeAmount,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// skip epoch, so staker can start earning rewards
|
// skip epoch, so staker can start earning rewards
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
@ -739,12 +655,7 @@ blockchainTests.resets('Testing Rewards', env => {
|
|||||||
// stake and delegate both
|
// stake and delegate both
|
||||||
const stakersAndStake = _.zip(stakers.slice(0, 2), stakeAmounts) as Array<[StakerActor, BigNumber]>;
|
const stakersAndStake = _.zip(stakers.slice(0, 2), stakeAmounts) as Array<[StakerActor, BigNumber]>;
|
||||||
for (const [staker, stakeAmount] of stakersAndStake) {
|
for (const [staker, stakeAmount] of stakersAndStake) {
|
||||||
await staker.stakeAsync(stakeAmount);
|
await staker.stakeWithPoolAsync(poolId, stakeAmount);
|
||||||
await staker.moveStakeAsync(
|
|
||||||
new StakeInfo(StakeStatus.Active),
|
|
||||||
new StakeInfo(StakeStatus.Delegated, poolId),
|
|
||||||
stakeAmount,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// skip epoch, so staker can start earning rewards
|
// skip epoch, so staker can start earning rewards
|
||||||
await payProtocolFeeAndFinalize();
|
await payProtocolFeeAndFinalize();
|
||||||
|
@ -7,7 +7,7 @@ blockchainTests('delegator rewards', env => {
|
|||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
testContract = await TestDelegatorRewardsContract.deployFrom0xArtifactAsync(
|
testContract = await TestDelegatorRewardsContract.deployFrom0xArtifactAsync(
|
||||||
artifacts.TestLibFixedMath,
|
artifacts.TestDelegatorRewards,
|
||||||
env.provider,
|
env.provider,
|
||||||
env.txDefaults,
|
env.txDefaults,
|
||||||
artifacts,
|
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