Merge pull request #2312 from 0xProject/feat/contracts/staking/MixinStakingPoolRewards-unit-tests
MixinStakingPoolRewards unit tests
This commit is contained in:
commit
47e050cbaf
281
contracts/staking/contracts/test/TestMixinStakingPoolRewards.sol
Normal file
281
contracts/staking/contracts/test/TestMixinStakingPoolRewards.sol
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
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 "./TestStakingNoWETH.sol";
|
||||||
|
|
||||||
|
|
||||||
|
contract TestMixinStakingPoolRewards is
|
||||||
|
TestStakingNoWETH
|
||||||
|
{
|
||||||
|
// solhint-disable no-simple-event-func-name
|
||||||
|
event UpdateCumulativeReward(
|
||||||
|
bytes32 poolId
|
||||||
|
);
|
||||||
|
|
||||||
|
event WithdrawAndSyncDelegatorRewards(
|
||||||
|
bytes32 poolId,
|
||||||
|
address delegator
|
||||||
|
);
|
||||||
|
|
||||||
|
struct UnfinalizedPoolReward {
|
||||||
|
uint256 reward;
|
||||||
|
uint256 membersStake;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() public {
|
||||||
|
_addAuthorizedAddress(msg.sender);
|
||||||
|
init();
|
||||||
|
_removeAuthorizedAddressAtIndex(msg.sender, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewards returned by `_computeMemberRewardOverInterval()`, indexed
|
||||||
|
// by `_getMemberRewardOverIntervalHash()`.
|
||||||
|
mapping (bytes32 => uint256) private _memberRewardsOverInterval;
|
||||||
|
// Rewards returned by `_getUnfinalizedPoolRewards()`, indexed by pool ID.
|
||||||
|
mapping (bytes32 => UnfinalizedPoolReward) private _unfinalizedPoolRewards;
|
||||||
|
|
||||||
|
// Set pool `rewardsByPoolId`.
|
||||||
|
function setPoolRewards(
|
||||||
|
bytes32 poolId,
|
||||||
|
uint256 _rewardsByPoolId
|
||||||
|
)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
rewardsByPoolId[poolId] = _rewardsByPoolId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set `wethReservedForPoolRewards`.
|
||||||
|
function setWethReservedForPoolRewards(
|
||||||
|
uint256 _wethReservedForPoolRewards
|
||||||
|
)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
wethReservedForPoolRewards = _wethReservedForPoolRewards;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the rewards returned by a call to `_computeMemberRewardOverInterval()`.
|
||||||
|
function setMemberRewardsOverInterval(
|
||||||
|
bytes32 poolId,
|
||||||
|
uint256 memberStakeOverInterval,
|
||||||
|
uint256 beginEpoch,
|
||||||
|
uint256 endEpoch,
|
||||||
|
uint256 reward
|
||||||
|
)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
bytes32 rewardHash = _getMemberRewardOverIntervalHash(
|
||||||
|
poolId,
|
||||||
|
memberStakeOverInterval,
|
||||||
|
beginEpoch,
|
||||||
|
endEpoch
|
||||||
|
);
|
||||||
|
_memberRewardsOverInterval[rewardHash] = reward;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the rewards returned by `_getUnfinalizedPoolRewards()`.
|
||||||
|
function setUnfinalizedPoolRewards(
|
||||||
|
bytes32 poolId,
|
||||||
|
uint256 reward,
|
||||||
|
uint256 membersStake
|
||||||
|
)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
_unfinalizedPoolRewards[poolId] = UnfinalizedPoolReward(
|
||||||
|
reward,
|
||||||
|
membersStake
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set `currentEpoch`.
|
||||||
|
function setCurrentEpoch(uint256 epoch) external {
|
||||||
|
currentEpoch = epoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose `_syncPoolRewards()` for testing.
|
||||||
|
function syncPoolRewards(
|
||||||
|
bytes32 poolId,
|
||||||
|
uint256 reward,
|
||||||
|
uint256 membersStake
|
||||||
|
)
|
||||||
|
external
|
||||||
|
returns (uint256 operatorReward, uint256 membersReward)
|
||||||
|
{
|
||||||
|
return _syncPoolRewards(poolId, reward, membersStake);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose `_withdrawAndSyncDelegatorRewards()` for testing.
|
||||||
|
function withdrawAndSyncDelegatorRewards(
|
||||||
|
bytes32 poolId,
|
||||||
|
address member
|
||||||
|
)
|
||||||
|
external
|
||||||
|
{
|
||||||
|
return _withdrawAndSyncDelegatorRewards(
|
||||||
|
poolId,
|
||||||
|
member
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose `_computePoolRewardsSplit()` for testing.
|
||||||
|
function computePoolRewardsSplit(
|
||||||
|
uint32 operatorShare,
|
||||||
|
uint256 totalReward,
|
||||||
|
uint256 membersStake
|
||||||
|
)
|
||||||
|
external
|
||||||
|
pure
|
||||||
|
returns (uint256 operatorReward, uint256 membersReward)
|
||||||
|
{
|
||||||
|
return _computePoolRewardsSplit(
|
||||||
|
operatorShare,
|
||||||
|
totalReward,
|
||||||
|
membersStake
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access `_delegatedStakeToPoolByOwner`
|
||||||
|
function delegatedStakeToPoolByOwner(address member, bytes32 poolId)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (IStructs.StoredBalance memory balance)
|
||||||
|
{
|
||||||
|
return _delegatedStakeToPoolByOwner[member][poolId];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set `_delegatedStakeToPoolByOwner`
|
||||||
|
function setDelegatedStakeToPoolByOwner(
|
||||||
|
address member,
|
||||||
|
bytes32 poolId,
|
||||||
|
IStructs.StoredBalance memory balance
|
||||||
|
)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
_delegatedStakeToPoolByOwner[member][poolId] = balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set `_poolById`.
|
||||||
|
function setPool(
|
||||||
|
bytes32 poolId,
|
||||||
|
IStructs.Pool memory pool
|
||||||
|
)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
_poolById[poolId] = pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overridden to emit an event.
|
||||||
|
function _withdrawAndSyncDelegatorRewards(
|
||||||
|
bytes32 poolId,
|
||||||
|
address member
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
emit WithdrawAndSyncDelegatorRewards(poolId, member);
|
||||||
|
return MixinStakingPoolRewards._withdrawAndSyncDelegatorRewards(
|
||||||
|
poolId,
|
||||||
|
member
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overridden to use `_memberRewardsOverInterval`
|
||||||
|
function _computeMemberRewardOverInterval(
|
||||||
|
bytes32 poolId,
|
||||||
|
uint256 memberStakeOverInterval,
|
||||||
|
uint256 beginEpoch,
|
||||||
|
uint256 endEpoch
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
view
|
||||||
|
returns (uint256 reward)
|
||||||
|
{
|
||||||
|
bytes32 rewardHash = _getMemberRewardOverIntervalHash(
|
||||||
|
poolId,
|
||||||
|
memberStakeOverInterval,
|
||||||
|
beginEpoch,
|
||||||
|
endEpoch
|
||||||
|
);
|
||||||
|
return _memberRewardsOverInterval[rewardHash];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overridden to use `_unfinalizedPoolRewards`
|
||||||
|
function _getUnfinalizedPoolRewards(
|
||||||
|
bytes32 poolId
|
||||||
|
)
|
||||||
|
internal
|
||||||
|
view
|
||||||
|
returns (uint256 reward, uint256 membersStake)
|
||||||
|
{
|
||||||
|
(reward, membersStake) = (
|
||||||
|
_unfinalizedPoolRewards[poolId].reward,
|
||||||
|
_unfinalizedPoolRewards[poolId].membersStake
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overridden to just increase `currentEpoch`.
|
||||||
|
function _loadCurrentBalance(IStructs.StoredBalance storage balancePtr)
|
||||||
|
internal
|
||||||
|
view
|
||||||
|
returns (IStructs.StoredBalance memory balance)
|
||||||
|
{
|
||||||
|
balance = balancePtr;
|
||||||
|
balance.currentEpoch += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overridden to revert if a pool has unfinalized rewards.
|
||||||
|
function _assertPoolFinalizedLastEpoch(bytes32 poolId)
|
||||||
|
internal
|
||||||
|
view
|
||||||
|
{
|
||||||
|
require(
|
||||||
|
_unfinalizedPoolRewards[poolId].membersStake == 0,
|
||||||
|
"POOL_NOT_FINALIZED"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overridden to just emit an event.
|
||||||
|
function _updateCumulativeReward(bytes32 poolId)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
emit UpdateCumulativeReward(poolId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute a hash to index `_memberRewardsOverInterval`
|
||||||
|
function _getMemberRewardOverIntervalHash(
|
||||||
|
bytes32 poolId,
|
||||||
|
uint256 memberStakeOverInterval,
|
||||||
|
uint256 beginEpoch,
|
||||||
|
uint256 endEpoch
|
||||||
|
)
|
||||||
|
private
|
||||||
|
pure
|
||||||
|
returns (bytes32 rewardHash)
|
||||||
|
{
|
||||||
|
return keccak256(
|
||||||
|
abi.encode(
|
||||||
|
poolId,
|
||||||
|
memberStakeOverInterval,
|
||||||
|
beginEpoch,
|
||||||
|
endEpoch
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -37,7 +37,7 @@
|
|||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
|
||||||
"abis": "./generated-artifacts/@(IStaking|IStakingEvents|IStakingProxy|IStorage|IStorageInit|IStructs|IZrxVault|LibCobbDouglas|LibFixedMath|LibFixedMathRichErrors|LibSafeDowncast|LibStakingRichErrors|MixinAbstract|MixinConstants|MixinCumulativeRewards|MixinDeploymentConstants|MixinExchangeFees|MixinExchangeManager|MixinFinalizer|MixinParams|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewards|MixinStorage|Staking|StakingProxy|TestAssertStorageParams|TestCobbDouglas|TestCumulativeRewardTracking|TestDelegatorRewards|TestExchangeManager|TestFinalizer|TestInitTarget|TestLibFixedMath|TestLibSafeDowncast|TestMixinParams|TestMixinStake|TestMixinStakeBalances|TestMixinStakeStorage|TestMixinStakingPool|TestProtocolFees|TestProxyDestination|TestStaking|TestStakingNoWETH|TestStakingProxy|TestStakingProxyUnit|TestStorageLayoutAndConstants|ZrxVault).json"
|
"abis": "./generated-artifacts/@(IStaking|IStakingEvents|IStakingProxy|IStorage|IStorageInit|IStructs|IZrxVault|LibCobbDouglas|LibFixedMath|LibFixedMathRichErrors|LibSafeDowncast|LibStakingRichErrors|MixinAbstract|MixinConstants|MixinCumulativeRewards|MixinDeploymentConstants|MixinExchangeFees|MixinExchangeManager|MixinFinalizer|MixinParams|MixinScheduler|MixinStake|MixinStakeBalances|MixinStakeStorage|MixinStakingPool|MixinStakingPoolRewards|MixinStorage|Staking|StakingProxy|TestAssertStorageParams|TestCobbDouglas|TestCumulativeRewardTracking|TestDelegatorRewards|TestExchangeManager|TestFinalizer|TestInitTarget|TestLibFixedMath|TestLibSafeDowncast|TestMixinParams|TestMixinStake|TestMixinStakeBalances|TestMixinStakeStorage|TestMixinStakingPool|TestMixinStakingPoolRewards|TestProtocolFees|TestProxyDestination|TestStaking|TestStakingNoWETH|TestStakingProxy|TestStakingProxyUnit|TestStorageLayoutAndConstants|ZrxVault).json"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -50,6 +50,7 @@
|
|||||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md",
|
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/tokens/README.md",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@0x/abi-gen": "^4.3.0-beta.0",
|
"@0x/abi-gen": "^4.3.0-beta.0",
|
||||||
|
"@0x/contracts-exchange-libs": "^3.1.0-beta.0",
|
||||||
"@0x/contracts-gen": "^1.1.0-beta.0",
|
"@0x/contracts-gen": "^1.1.0-beta.0",
|
||||||
"@0x/contracts-test-utils": "^3.2.0-beta.0",
|
"@0x/contracts-test-utils": "^3.2.0-beta.0",
|
||||||
"@0x/dev-utils": "^2.4.0-beta.0",
|
"@0x/dev-utils": "^2.4.0-beta.0",
|
||||||
|
@ -48,6 +48,7 @@ import * as TestMixinStake from '../generated-artifacts/TestMixinStake.json';
|
|||||||
import * as TestMixinStakeBalances from '../generated-artifacts/TestMixinStakeBalances.json';
|
import * as TestMixinStakeBalances from '../generated-artifacts/TestMixinStakeBalances.json';
|
||||||
import * as TestMixinStakeStorage from '../generated-artifacts/TestMixinStakeStorage.json';
|
import * as TestMixinStakeStorage from '../generated-artifacts/TestMixinStakeStorage.json';
|
||||||
import * as TestMixinStakingPool from '../generated-artifacts/TestMixinStakingPool.json';
|
import * as TestMixinStakingPool from '../generated-artifacts/TestMixinStakingPool.json';
|
||||||
|
import * as TestMixinStakingPoolRewards from '../generated-artifacts/TestMixinStakingPoolRewards.json';
|
||||||
import * as TestProtocolFees from '../generated-artifacts/TestProtocolFees.json';
|
import * as TestProtocolFees from '../generated-artifacts/TestProtocolFees.json';
|
||||||
import * as TestProxyDestination from '../generated-artifacts/TestProxyDestination.json';
|
import * as TestProxyDestination from '../generated-artifacts/TestProxyDestination.json';
|
||||||
import * as TestStaking from '../generated-artifacts/TestStaking.json';
|
import * as TestStaking from '../generated-artifacts/TestStaking.json';
|
||||||
@ -101,6 +102,7 @@ export const artifacts = {
|
|||||||
TestMixinStakeBalances: TestMixinStakeBalances as ContractArtifact,
|
TestMixinStakeBalances: TestMixinStakeBalances as ContractArtifact,
|
||||||
TestMixinStakeStorage: TestMixinStakeStorage as ContractArtifact,
|
TestMixinStakeStorage: TestMixinStakeStorage as ContractArtifact,
|
||||||
TestMixinStakingPool: TestMixinStakingPool as ContractArtifact,
|
TestMixinStakingPool: TestMixinStakingPool as ContractArtifact,
|
||||||
|
TestMixinStakingPoolRewards: TestMixinStakingPoolRewards as ContractArtifact,
|
||||||
TestProtocolFees: TestProtocolFees as ContractArtifact,
|
TestProtocolFees: TestProtocolFees as ContractArtifact,
|
||||||
TestProxyDestination: TestProxyDestination as ContractArtifact,
|
TestProxyDestination: TestProxyDestination as ContractArtifact,
|
||||||
TestStaking: TestStaking as ContractArtifact,
|
TestStaking: TestStaking as ContractArtifact,
|
||||||
|
@ -46,6 +46,7 @@ export * from '../generated-wrappers/test_mixin_stake';
|
|||||||
export * from '../generated-wrappers/test_mixin_stake_balances';
|
export * from '../generated-wrappers/test_mixin_stake_balances';
|
||||||
export * from '../generated-wrappers/test_mixin_stake_storage';
|
export * from '../generated-wrappers/test_mixin_stake_storage';
|
||||||
export * from '../generated-wrappers/test_mixin_staking_pool';
|
export * from '../generated-wrappers/test_mixin_staking_pool';
|
||||||
|
export * from '../generated-wrappers/test_mixin_staking_pool_rewards';
|
||||||
export * from '../generated-wrappers/test_protocol_fees';
|
export * from '../generated-wrappers/test_protocol_fees';
|
||||||
export * from '../generated-wrappers/test_proxy_destination';
|
export * from '../generated-wrappers/test_proxy_destination';
|
||||||
export * from '../generated-wrappers/test_staking';
|
export * from '../generated-wrappers/test_staking';
|
||||||
|
496
contracts/staking/test/unit_tests/mixin_staking_pool_rewards.ts
Normal file
496
contracts/staking/test/unit_tests/mixin_staking_pool_rewards.ts
Normal file
@ -0,0 +1,496 @@
|
|||||||
|
import { ReferenceFunctions } from '@0x/contracts-exchange-libs';
|
||||||
|
import {
|
||||||
|
blockchainTests,
|
||||||
|
constants,
|
||||||
|
expect,
|
||||||
|
getRandomInteger,
|
||||||
|
getRandomPortion,
|
||||||
|
hexRandom,
|
||||||
|
Numberish,
|
||||||
|
randomAddress,
|
||||||
|
TransactionHelper,
|
||||||
|
verifyEventsFromLogs,
|
||||||
|
} from '@0x/contracts-test-utils';
|
||||||
|
import { BigNumber } from '@0x/utils';
|
||||||
|
import { LogEntry, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||||
|
|
||||||
|
import { StoredBalance } from '../utils/types';
|
||||||
|
|
||||||
|
import { artifacts, TestMixinStakingPoolRewardsContract, TestMixinStakingPoolRewardsEvents as Events } from '../../src';
|
||||||
|
|
||||||
|
blockchainTests.resets('MixinStakingPoolRewards unit tests', env => {
|
||||||
|
let testContract: TestMixinStakingPoolRewardsContract;
|
||||||
|
let txHelper: TransactionHelper;
|
||||||
|
|
||||||
|
const POOL_ID = hexRandom();
|
||||||
|
const OPERATOR = randomAddress();
|
||||||
|
const OPERATOR_SHARE = getRandomInteger(1, constants.PPM_100_PERCENT);
|
||||||
|
let caller: string;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
testContract = await TestMixinStakingPoolRewardsContract.deployFrom0xArtifactAsync(
|
||||||
|
artifacts.TestMixinStakingPoolRewards,
|
||||||
|
env.provider,
|
||||||
|
env.txDefaults,
|
||||||
|
artifacts,
|
||||||
|
);
|
||||||
|
await testContract.setPool.awaitTransactionSuccessAsync(POOL_ID, {
|
||||||
|
operator: OPERATOR,
|
||||||
|
operatorShare: OPERATOR_SHARE,
|
||||||
|
});
|
||||||
|
[caller] = await env.getAccountAddressesAsync();
|
||||||
|
txHelper = new TransactionHelper(env.web3Wrapper, artifacts);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function setUnfinalizedPoolRewardsAsync(
|
||||||
|
poolId: string,
|
||||||
|
reward: Numberish,
|
||||||
|
membersStake: Numberish,
|
||||||
|
): Promise<void> {
|
||||||
|
await testContract.setUnfinalizedPoolRewards.awaitTransactionSuccessAsync(
|
||||||
|
poolId,
|
||||||
|
new BigNumber(reward),
|
||||||
|
new BigNumber(membersStake),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the delegated stake of a delegator in a pool.
|
||||||
|
// Omitted fields will be randomly generated.
|
||||||
|
async function setStakeAsync(
|
||||||
|
poolId: string,
|
||||||
|
delegator: string,
|
||||||
|
stake?: Partial<StoredBalance>,
|
||||||
|
): Promise<StoredBalance> {
|
||||||
|
const _stake = {
|
||||||
|
currentEpoch: getRandomInteger(1, 4e9),
|
||||||
|
currentEpochBalance: getRandomInteger(1, 1e18),
|
||||||
|
nextEpochBalance: getRandomInteger(1, 1e18),
|
||||||
|
...stake,
|
||||||
|
};
|
||||||
|
await testContract.setDelegatedStakeToPoolByOwner.awaitTransactionSuccessAsync(delegator, poolId, {
|
||||||
|
currentEpoch: _stake.currentEpoch,
|
||||||
|
currentEpochBalance: _stake.currentEpochBalance,
|
||||||
|
nextEpochBalance: _stake.nextEpochBalance,
|
||||||
|
});
|
||||||
|
return _stake;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets up state for a call to `_computeDelegatorReward()` and return the
|
||||||
|
// finalized rewards it will compute.
|
||||||
|
async function setComputeDelegatorRewardStateAsync(
|
||||||
|
poolId: string,
|
||||||
|
delegator: string,
|
||||||
|
finalizedReward?: Numberish,
|
||||||
|
): Promise<BigNumber> {
|
||||||
|
const stake = await testContract.delegatedStakeToPoolByOwner.callAsync(delegator, poolId);
|
||||||
|
// Split the rewards up across the two calls to `_computeMemberRewardOverInterval()`
|
||||||
|
const reward = finalizedReward === undefined ? getRandomInteger(1, 1e18) : new BigNumber(finalizedReward);
|
||||||
|
const oldRewards = getRandomPortion(reward);
|
||||||
|
await testContract.setMemberRewardsOverInterval.awaitTransactionSuccessAsync(
|
||||||
|
poolId,
|
||||||
|
stake.currentEpochBalance,
|
||||||
|
stake.currentEpoch,
|
||||||
|
stake.currentEpoch.plus(1),
|
||||||
|
oldRewards,
|
||||||
|
);
|
||||||
|
const newRewards = reward.minus(oldRewards);
|
||||||
|
await testContract.setMemberRewardsOverInterval.awaitTransactionSuccessAsync(
|
||||||
|
poolId,
|
||||||
|
stake.nextEpochBalance,
|
||||||
|
stake.currentEpoch.plus(1),
|
||||||
|
await testContract.currentEpoch.callAsync(),
|
||||||
|
newRewards,
|
||||||
|
);
|
||||||
|
return reward;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toOperatorPortion(operatorShare: Numberish, reward: Numberish): BigNumber {
|
||||||
|
return ReferenceFunctions.getPartialAmountCeil(
|
||||||
|
new BigNumber(operatorShare),
|
||||||
|
new BigNumber(constants.PPM_DENOMINATOR),
|
||||||
|
new BigNumber(reward),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toMembersPortion(operatorShare: Numberish, reward: Numberish): BigNumber {
|
||||||
|
return new BigNumber(reward).minus(toOperatorPortion(operatorShare, reward));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('withdrawDelegatorRewards()', () => {
|
||||||
|
it('calls `_withdrawAndSyncDelegatorRewards()` with the sender as the member', async () => {
|
||||||
|
const { logs } = await testContract.withdrawDelegatorRewards.awaitTransactionSuccessAsync(POOL_ID);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
logs,
|
||||||
|
[{ poolId: POOL_ID, delegator: caller }],
|
||||||
|
Events.WithdrawAndSyncDelegatorRewards,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('_withdrawAndSyncDelegatorRewards()', () => {
|
||||||
|
const POOL_REWARD = getRandomInteger(1, 100e18);
|
||||||
|
const WETH_RESERVED_FOR_POOL_REWARDS = POOL_REWARD.plus(getRandomInteger(1, 100e18));
|
||||||
|
const DELEGATOR = randomAddress();
|
||||||
|
let stake: StoredBalance;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
stake = await setStakeAsync(POOL_ID, DELEGATOR);
|
||||||
|
await testContract.setPoolRewards.awaitTransactionSuccessAsync(POOL_ID, POOL_REWARD);
|
||||||
|
await testContract.setWethReservedForPoolRewards.awaitTransactionSuccessAsync(
|
||||||
|
WETH_RESERVED_FOR_POOL_REWARDS,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function withdrawAndSyncDelegatorRewardsAsync(): Promise<TransactionReceiptWithDecodedLogs> {
|
||||||
|
return testContract.withdrawAndSyncDelegatorRewards.awaitTransactionSuccessAsync(POOL_ID, DELEGATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('reverts if the pool is not finalized', async () => {
|
||||||
|
await setUnfinalizedPoolRewardsAsync(POOL_ID, 0, 1);
|
||||||
|
const tx = withdrawAndSyncDelegatorRewardsAsync();
|
||||||
|
return expect(tx).to.revertWith('POOL_NOT_FINALIZED');
|
||||||
|
});
|
||||||
|
it('calls `_updateCumulativeReward()`', async () => {
|
||||||
|
const { logs } = await withdrawAndSyncDelegatorRewardsAsync();
|
||||||
|
verifyEventsFromLogs(logs, [{ poolId: POOL_ID }], Events.UpdateCumulativeReward);
|
||||||
|
});
|
||||||
|
it('transfers finalized rewards to the sender', async () => {
|
||||||
|
const finalizedReward = getRandomPortion(POOL_REWARD);
|
||||||
|
await setComputeDelegatorRewardStateAsync(POOL_ID, DELEGATOR, finalizedReward);
|
||||||
|
const { logs } = await withdrawAndSyncDelegatorRewardsAsync();
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
logs,
|
||||||
|
[{ _from: testContract.address, _to: DELEGATOR, _value: finalizedReward }],
|
||||||
|
Events.Transfer,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('reduces `rewardsByPoolId` for the pool', async () => {
|
||||||
|
const finalizedReward = getRandomPortion(POOL_REWARD);
|
||||||
|
await setComputeDelegatorRewardStateAsync(POOL_ID, DELEGATOR, finalizedReward);
|
||||||
|
await withdrawAndSyncDelegatorRewardsAsync();
|
||||||
|
const poolReward = await testContract.rewardsByPoolId.callAsync(POOL_ID);
|
||||||
|
expect(poolReward).to.bignumber.eq(POOL_REWARD.minus(finalizedReward));
|
||||||
|
});
|
||||||
|
it('reduces `wethReservedForPoolRewards` for the pool', async () => {
|
||||||
|
const finalizedReward = getRandomPortion(POOL_REWARD);
|
||||||
|
await setComputeDelegatorRewardStateAsync(POOL_ID, DELEGATOR, finalizedReward);
|
||||||
|
await withdrawAndSyncDelegatorRewardsAsync();
|
||||||
|
const wethReserved = await testContract.wethReservedForPoolRewards.callAsync();
|
||||||
|
expect(wethReserved).to.bignumber.eq(WETH_RESERVED_FOR_POOL_REWARDS.minus(finalizedReward));
|
||||||
|
});
|
||||||
|
it('syncs `_delegatedStakeToPoolByOwner`', async () => {
|
||||||
|
await setComputeDelegatorRewardStateAsync(POOL_ID, DELEGATOR, getRandomPortion(POOL_REWARD));
|
||||||
|
await withdrawAndSyncDelegatorRewardsAsync();
|
||||||
|
const stakeAfter = await testContract.delegatedStakeToPoolByOwner.callAsync(DELEGATOR, POOL_ID);
|
||||||
|
// `_loadCurrentBalance` is overridden to just increment `currentEpoch`.
|
||||||
|
expect(stakeAfter).to.deep.eq({
|
||||||
|
currentEpoch: stake.currentEpoch.plus(1),
|
||||||
|
currentEpochBalance: stake.currentEpochBalance,
|
||||||
|
nextEpochBalance: stake.nextEpochBalance,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('does not transfer zero rewards', async () => {
|
||||||
|
await setComputeDelegatorRewardStateAsync(POOL_ID, DELEGATOR, 0);
|
||||||
|
const { logs } = await withdrawAndSyncDelegatorRewardsAsync();
|
||||||
|
verifyEventsFromLogs(logs, [], Events.Transfer);
|
||||||
|
});
|
||||||
|
it('no rewards if the delegated stake epoch == current epoch', async () => {
|
||||||
|
// Set some finalized rewards that should be ignored.
|
||||||
|
await setComputeDelegatorRewardStateAsync(POOL_ID, DELEGATOR, getRandomInteger(1, POOL_REWARD));
|
||||||
|
await testContract.setCurrentEpoch.awaitTransactionSuccessAsync(stake.currentEpoch);
|
||||||
|
const { logs } = await withdrawAndSyncDelegatorRewardsAsync();
|
||||||
|
// There will be no Transfer events if computed rewards are zero.
|
||||||
|
verifyEventsFromLogs(logs, [], Events.Transfer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('computeRewardBalanceOfOperator()', () => {
|
||||||
|
async function computeRewardBalanceOfOperatorAsync(): Promise<BigNumber> {
|
||||||
|
return testContract.computeRewardBalanceOfOperator.callAsync(POOL_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('returns only unfinalized rewards', async () => {
|
||||||
|
const unfinalizedReward = getRandomInteger(1, 1e18);
|
||||||
|
await setUnfinalizedPoolRewardsAsync(POOL_ID, unfinalizedReward, getRandomInteger(1, 1e18));
|
||||||
|
// Set some unfinalized state for a call to `_computeDelegatorReward()`,
|
||||||
|
// which should not be called.
|
||||||
|
await setComputeDelegatorRewardStateAsync(POOL_ID, OPERATOR, getRandomInteger(1, 1e18));
|
||||||
|
const reward = await computeRewardBalanceOfOperatorAsync();
|
||||||
|
const expectedReward = toOperatorPortion(OPERATOR_SHARE, unfinalizedReward);
|
||||||
|
expect(reward).to.bignumber.eq(expectedReward);
|
||||||
|
});
|
||||||
|
it('returns operator portion of unfinalized rewards', async () => {
|
||||||
|
const unfinalizedReward = getRandomInteger(1, 1e18);
|
||||||
|
await setUnfinalizedPoolRewardsAsync(POOL_ID, unfinalizedReward, getRandomInteger(1, 1e18));
|
||||||
|
const reward = await computeRewardBalanceOfOperatorAsync();
|
||||||
|
const expectedReward = toOperatorPortion(OPERATOR_SHARE, unfinalizedReward);
|
||||||
|
expect(reward).to.bignumber.eq(expectedReward);
|
||||||
|
});
|
||||||
|
it('returns zero if no unfinalized rewards', async () => {
|
||||||
|
await setUnfinalizedPoolRewardsAsync(POOL_ID, 0, getRandomInteger(1, 1e18));
|
||||||
|
const reward = await computeRewardBalanceOfOperatorAsync();
|
||||||
|
expect(reward).to.bignumber.eq(0);
|
||||||
|
});
|
||||||
|
it('returns all unfinalized reward if member stake is zero', async () => {
|
||||||
|
const unfinalizedReward = getRandomInteger(1, 1e18);
|
||||||
|
await setUnfinalizedPoolRewardsAsync(POOL_ID, unfinalizedReward, 0);
|
||||||
|
const reward = await computeRewardBalanceOfOperatorAsync();
|
||||||
|
expect(reward).to.bignumber.eq(unfinalizedReward);
|
||||||
|
});
|
||||||
|
it('returns no reward if operator share is zero', async () => {
|
||||||
|
await testContract.setPool.awaitTransactionSuccessAsync(POOL_ID, {
|
||||||
|
operator: OPERATOR,
|
||||||
|
operatorShare: constants.ZERO_AMOUNT,
|
||||||
|
});
|
||||||
|
await setUnfinalizedPoolRewardsAsync(POOL_ID, getRandomInteger(1, 1e18), getRandomInteger(1, 1e18));
|
||||||
|
const reward = await computeRewardBalanceOfOperatorAsync();
|
||||||
|
expect(reward).to.bignumber.eq(0);
|
||||||
|
});
|
||||||
|
it('returns all unfinalized reward if operator share is 100%', async () => {
|
||||||
|
await testContract.setPool.awaitTransactionSuccessAsync(POOL_ID, {
|
||||||
|
operator: OPERATOR,
|
||||||
|
operatorShare: constants.PPM_100_PERCENT,
|
||||||
|
});
|
||||||
|
const unfinalizedReward = getRandomInteger(1, 1e18);
|
||||||
|
await setUnfinalizedPoolRewardsAsync(POOL_ID, unfinalizedReward, getRandomInteger(1, 1e18));
|
||||||
|
const reward = await computeRewardBalanceOfOperatorAsync();
|
||||||
|
expect(reward).to.bignumber.eq(unfinalizedReward);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('computeRewardBalanceOfDelegator()', () => {
|
||||||
|
const DELEGATOR = randomAddress();
|
||||||
|
let currentEpoch: BigNumber;
|
||||||
|
let stake: StoredBalance;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
currentEpoch = await testContract.currentEpoch.callAsync();
|
||||||
|
stake = await setStakeAsync(POOL_ID, DELEGATOR);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function computeRewardBalanceOfDelegatorAsync(): Promise<BigNumber> {
|
||||||
|
return testContract.computeRewardBalanceOfDelegator.callAsync(POOL_ID, DELEGATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDelegatorPortionOfUnfinalizedReward(
|
||||||
|
unfinalizedReward: Numberish,
|
||||||
|
unfinalizedMembersStake: Numberish,
|
||||||
|
): BigNumber {
|
||||||
|
const unfinalizedStakeBalance = stake.currentEpoch.gte(currentEpoch)
|
||||||
|
? stake.currentEpochBalance
|
||||||
|
: stake.nextEpochBalance;
|
||||||
|
return ReferenceFunctions.getPartialAmountFloor(
|
||||||
|
unfinalizedStakeBalance,
|
||||||
|
new BigNumber(unfinalizedMembersStake),
|
||||||
|
toMembersPortion(OPERATOR_SHARE, unfinalizedReward),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('returns zero when no finalized or unfinalized rewards', async () => {
|
||||||
|
const reward = await computeRewardBalanceOfDelegatorAsync();
|
||||||
|
expect(reward).to.bignumber.eq(0);
|
||||||
|
});
|
||||||
|
it('returns only unfinalized rewards when no finalized rewards', async () => {
|
||||||
|
const unfinalizedReward = getRandomInteger(1, 1e18);
|
||||||
|
const unfinalizedMembersStake = getRandomInteger(1, 1e18);
|
||||||
|
await setUnfinalizedPoolRewardsAsync(POOL_ID, unfinalizedReward, unfinalizedMembersStake);
|
||||||
|
const expectedReward = getDelegatorPortionOfUnfinalizedReward(unfinalizedReward, unfinalizedMembersStake);
|
||||||
|
const reward = await computeRewardBalanceOfDelegatorAsync();
|
||||||
|
expect(reward).to.bignumber.eq(expectedReward);
|
||||||
|
});
|
||||||
|
it("returns zero when delegator's synced stake was zero in the last epoch and no finalized rewards", async () => {
|
||||||
|
await setStakeAsync(POOL_ID, DELEGATOR, {
|
||||||
|
...stake,
|
||||||
|
currentEpoch: currentEpoch.minus(1),
|
||||||
|
currentEpochBalance: constants.ZERO_AMOUNT,
|
||||||
|
});
|
||||||
|
await setUnfinalizedPoolRewardsAsync(POOL_ID, getRandomInteger(1, 1e18), getRandomInteger(1, 1e18));
|
||||||
|
const reward = await computeRewardBalanceOfDelegatorAsync();
|
||||||
|
expect(reward).to.bignumber.eq(0);
|
||||||
|
});
|
||||||
|
it("returns zero when delegator's unsynced stake was zero in the last epoch and no finalized rewards", async () => {
|
||||||
|
const epoch = 2;
|
||||||
|
await setStakeAsync(POOL_ID, DELEGATOR, {
|
||||||
|
...stake,
|
||||||
|
currentEpoch: new BigNumber(epoch - 2),
|
||||||
|
nextEpochBalance: constants.ZERO_AMOUNT,
|
||||||
|
});
|
||||||
|
await testContract.setCurrentEpoch.awaitTransactionSuccessAsync(new BigNumber(epoch));
|
||||||
|
await setUnfinalizedPoolRewardsAsync(POOL_ID, getRandomInteger(1, 1e18), getRandomInteger(1, 1e18));
|
||||||
|
const reward = await computeRewardBalanceOfDelegatorAsync();
|
||||||
|
expect(reward).to.bignumber.eq(0);
|
||||||
|
});
|
||||||
|
it('returns only finalized rewards when no unfinalized rewards', async () => {
|
||||||
|
const finalizedReward = getRandomInteger(1, 1e18);
|
||||||
|
await setComputeDelegatorRewardStateAsync(POOL_ID, DELEGATOR, finalizedReward);
|
||||||
|
const reward = await computeRewardBalanceOfDelegatorAsync();
|
||||||
|
expect(reward).to.bignumber.eq(finalizedReward);
|
||||||
|
});
|
||||||
|
it('returns both unfinalized and finalized rewards', async () => {
|
||||||
|
const unfinalizedReward = getRandomInteger(1, 1e18);
|
||||||
|
const unfinalizedMembersStake = getRandomInteger(1, 1e18);
|
||||||
|
await setUnfinalizedPoolRewardsAsync(POOL_ID, unfinalizedReward, unfinalizedMembersStake);
|
||||||
|
const finalizedReward = getRandomInteger(1, 1e18);
|
||||||
|
await setComputeDelegatorRewardStateAsync(POOL_ID, DELEGATOR, finalizedReward);
|
||||||
|
const delegatorUnfinalizedReward = getDelegatorPortionOfUnfinalizedReward(
|
||||||
|
unfinalizedReward,
|
||||||
|
unfinalizedMembersStake,
|
||||||
|
);
|
||||||
|
const expectedReward = delegatorUnfinalizedReward.plus(finalizedReward);
|
||||||
|
const reward = await computeRewardBalanceOfDelegatorAsync();
|
||||||
|
expect(reward).to.bignumber.eq(expectedReward);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('_syncPoolRewards()', async () => {
|
||||||
|
const POOL_REWARD = getRandomInteger(1, 100e18);
|
||||||
|
const WETH_RESERVED_FOR_POOL_REWARDS = POOL_REWARD.plus(getRandomInteger(1, 100e18));
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
await testContract.setPoolRewards.awaitTransactionSuccessAsync(POOL_ID, POOL_REWARD);
|
||||||
|
await testContract.setWethReservedForPoolRewards.awaitTransactionSuccessAsync(
|
||||||
|
WETH_RESERVED_FOR_POOL_REWARDS,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function syncPoolRewardsAsync(
|
||||||
|
reward: Numberish,
|
||||||
|
membersStake: Numberish,
|
||||||
|
): Promise<[[BigNumber, BigNumber], LogEntry[]]> {
|
||||||
|
const [result, receipt] = await txHelper.getResultAndReceiptAsync(
|
||||||
|
testContract.syncPoolRewards,
|
||||||
|
POOL_ID,
|
||||||
|
new BigNumber(reward),
|
||||||
|
new BigNumber(membersStake),
|
||||||
|
);
|
||||||
|
return [result, receipt.logs];
|
||||||
|
}
|
||||||
|
|
||||||
|
it("transfers operator's portion of the reward to the operator", async () => {
|
||||||
|
const totalReward = getRandomInteger(1, 1e18);
|
||||||
|
const membersStake = getRandomInteger(1, 1e18);
|
||||||
|
const [, logs] = await syncPoolRewardsAsync(totalReward, membersStake);
|
||||||
|
const expectedOperatorReward = toOperatorPortion(OPERATOR_SHARE, totalReward);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
logs,
|
||||||
|
[{ _from: testContract.address, _to: OPERATOR, _value: expectedOperatorReward }],
|
||||||
|
Events.Transfer,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("increases `rewardsByPoolId` with members' portion of rewards", async () => {
|
||||||
|
const totalReward = getRandomInteger(1, 1e18);
|
||||||
|
const membersStake = getRandomInteger(1, 1e18);
|
||||||
|
await syncPoolRewardsAsync(totalReward, membersStake);
|
||||||
|
const expectedMembersReward = toMembersPortion(OPERATOR_SHARE, totalReward);
|
||||||
|
const poolReward = await testContract.rewardsByPoolId.callAsync(POOL_ID);
|
||||||
|
expect(poolReward).to.bignumber.eq(POOL_REWARD.plus(expectedMembersReward));
|
||||||
|
});
|
||||||
|
it("increases `wethReservedForPoolRewards` with members' portion of rewards", async () => {
|
||||||
|
const totalReward = getRandomInteger(1, 1e18);
|
||||||
|
const membersStake = getRandomInteger(1, 1e18);
|
||||||
|
await syncPoolRewardsAsync(totalReward, membersStake);
|
||||||
|
const expectedMembersReward = toMembersPortion(OPERATOR_SHARE, totalReward);
|
||||||
|
const wethReserved = await testContract.wethReservedForPoolRewards.callAsync();
|
||||||
|
expect(wethReserved).to.bignumber.eq(WETH_RESERVED_FOR_POOL_REWARDS.plus(expectedMembersReward));
|
||||||
|
});
|
||||||
|
it("returns operator and members' portion of the reward", async () => {
|
||||||
|
const totalReward = getRandomInteger(1, 1e18);
|
||||||
|
const membersStake = getRandomInteger(1, 1e18);
|
||||||
|
const [[operatorReward, membersReward]] = await syncPoolRewardsAsync(totalReward, membersStake);
|
||||||
|
const expectedOperatorReward = toOperatorPortion(OPERATOR_SHARE, totalReward);
|
||||||
|
const expectedMembersReward = toMembersPortion(OPERATOR_SHARE, totalReward);
|
||||||
|
expect(operatorReward).to.bignumber.eq(expectedOperatorReward);
|
||||||
|
expect(membersReward).to.bignumber.eq(expectedMembersReward);
|
||||||
|
});
|
||||||
|
it("gives all rewards to operator if members' stake is zero", async () => {
|
||||||
|
const totalReward = getRandomInteger(1, 1e18);
|
||||||
|
const [[operatorReward, membersReward], logs] = await syncPoolRewardsAsync(totalReward, 0);
|
||||||
|
expect(operatorReward).to.bignumber.eq(totalReward);
|
||||||
|
expect(membersReward).to.bignumber.eq(0);
|
||||||
|
verifyEventsFromLogs(
|
||||||
|
logs,
|
||||||
|
[{ _from: testContract.address, _to: OPERATOR, _value: totalReward }],
|
||||||
|
Events.Transfer,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("gives all rewards to members if operator's share is zero", async () => {
|
||||||
|
const totalReward = getRandomInteger(1, 1e18);
|
||||||
|
await testContract.setPool.awaitTransactionSuccessAsync(POOL_ID, {
|
||||||
|
operator: OPERATOR,
|
||||||
|
operatorShare: constants.ZERO_AMOUNT,
|
||||||
|
});
|
||||||
|
const [[operatorReward, membersReward], logs] = await syncPoolRewardsAsync(
|
||||||
|
totalReward,
|
||||||
|
getRandomInteger(1, 1e18),
|
||||||
|
);
|
||||||
|
expect(operatorReward).to.bignumber.eq(0);
|
||||||
|
expect(membersReward).to.bignumber.eq(totalReward);
|
||||||
|
// Should be no transfer to the operator.
|
||||||
|
verifyEventsFromLogs(logs, [], Events.Transfer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('_computePoolRewardsSplit', () => {
|
||||||
|
it("gives all rewards to operator if members' stake is zero", async () => {
|
||||||
|
const operatorShare = getRandomPortion(constants.PPM_100_PERCENT);
|
||||||
|
const totalReward = getRandomInteger(1, 1e18);
|
||||||
|
const membersStake = constants.ZERO_AMOUNT;
|
||||||
|
const [operatorReward, membersReward] = await testContract.computePoolRewardsSplit.callAsync(
|
||||||
|
operatorShare,
|
||||||
|
totalReward,
|
||||||
|
membersStake,
|
||||||
|
);
|
||||||
|
expect(operatorReward).to.bignumber.eq(totalReward);
|
||||||
|
expect(membersReward).to.bignumber.eq(0);
|
||||||
|
});
|
||||||
|
it("gives all rewards to operator if members' stake is zero and operator share is zero", async () => {
|
||||||
|
const operatorShare = constants.ZERO_AMOUNT;
|
||||||
|
const totalReward = getRandomInteger(1, 1e18);
|
||||||
|
const membersStake = constants.ZERO_AMOUNT;
|
||||||
|
const [operatorReward, membersReward] = await testContract.computePoolRewardsSplit.callAsync(
|
||||||
|
operatorShare,
|
||||||
|
totalReward,
|
||||||
|
membersStake,
|
||||||
|
);
|
||||||
|
expect(operatorReward).to.bignumber.eq(totalReward);
|
||||||
|
expect(membersReward).to.bignumber.eq(0);
|
||||||
|
});
|
||||||
|
it('gives all rewards to operator if operator share is 100%', async () => {
|
||||||
|
const operatorShare = constants.PPM_100_PERCENT;
|
||||||
|
const totalReward = getRandomInteger(1, 1e18);
|
||||||
|
const membersStake = getRandomInteger(1, 1e18);
|
||||||
|
const [operatorReward, membersReward] = await testContract.computePoolRewardsSplit.callAsync(
|
||||||
|
operatorShare,
|
||||||
|
totalReward,
|
||||||
|
membersStake,
|
||||||
|
);
|
||||||
|
expect(operatorReward).to.bignumber.eq(totalReward);
|
||||||
|
expect(membersReward).to.bignumber.eq(0);
|
||||||
|
});
|
||||||
|
it('gives all rewards to members if operator share is 0%', async () => {
|
||||||
|
const operatorShare = constants.ZERO_AMOUNT;
|
||||||
|
const totalReward = getRandomInteger(1, 1e18);
|
||||||
|
const membersStake = getRandomInteger(1, 1e18);
|
||||||
|
const [operatorReward, membersReward] = await testContract.computePoolRewardsSplit.callAsync(
|
||||||
|
operatorShare,
|
||||||
|
totalReward,
|
||||||
|
membersStake,
|
||||||
|
);
|
||||||
|
expect(operatorReward).to.bignumber.eq(0);
|
||||||
|
expect(membersReward).to.bignumber.eq(totalReward);
|
||||||
|
});
|
||||||
|
it('splits rewards between operator and members based on operator share', async () => {
|
||||||
|
const operatorShare = getRandomPortion(constants.PPM_100_PERCENT);
|
||||||
|
const totalReward = getRandomInteger(1, 1e18);
|
||||||
|
const membersStake = getRandomInteger(1, 1e18);
|
||||||
|
const [operatorReward, membersReward] = await testContract.computePoolRewardsSplit.callAsync(
|
||||||
|
operatorShare,
|
||||||
|
totalReward,
|
||||||
|
membersStake,
|
||||||
|
);
|
||||||
|
expect(operatorReward).to.bignumber.eq(toOperatorPortion(operatorShare, totalReward));
|
||||||
|
expect(membersReward).to.bignumber.eq(toMembersPortion(operatorShare, totalReward));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// tslint:disable: max-file-line-count
|
@ -46,6 +46,7 @@
|
|||||||
"generated-artifacts/TestMixinStakeBalances.json",
|
"generated-artifacts/TestMixinStakeBalances.json",
|
||||||
"generated-artifacts/TestMixinStakeStorage.json",
|
"generated-artifacts/TestMixinStakeStorage.json",
|
||||||
"generated-artifacts/TestMixinStakingPool.json",
|
"generated-artifacts/TestMixinStakingPool.json",
|
||||||
|
"generated-artifacts/TestMixinStakingPoolRewards.json",
|
||||||
"generated-artifacts/TestProtocolFees.json",
|
"generated-artifacts/TestProtocolFees.json",
|
||||||
"generated-artifacts/TestProxyDestination.json",
|
"generated-artifacts/TestProxyDestination.json",
|
||||||
"generated-artifacts/TestStaking.json",
|
"generated-artifacts/TestStaking.json",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user