@0x/contracts-staking
: Gag! All tests passing?
This commit is contained in:
committed by
Lawrence Forman
parent
d5bbbe802b
commit
86106713dd
@@ -161,25 +161,20 @@ contract MixinExchangeFees is
|
||||
view
|
||||
returns (uint256 totalBalance)
|
||||
{
|
||||
totalBalance = address(this).balance +
|
||||
IEtherToken(WETH_ADDRESS).balanceOf(address(this));
|
||||
totalBalance = address(this).balance.safeAdd(
|
||||
IEtherToken(WETH_ADDRESS).balanceOf(address(this))
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Returns the amount of fees attributed to the input pool this epoch.
|
||||
/// @dev Get information on an active staking pool in this epoch.
|
||||
/// @param poolId Pool Id to query.
|
||||
/// @return feesCollectedByPool Amount of fees collected by the pool this
|
||||
/// epoch.
|
||||
function getProtocolFeesThisEpochByPool(bytes32 poolId)
|
||||
/// @return pool ActivePool struct.
|
||||
function getActiveStakingPoolThisEpoch(bytes32 poolId)
|
||||
external
|
||||
view
|
||||
returns (uint256 feesCollected)
|
||||
returns (IStructs.ActivePool memory pool)
|
||||
{
|
||||
// Look up the pool for this epoch. The epoch index is `currentEpoch % 2`
|
||||
// because we only need to remember state in the current epoch and the
|
||||
// epoch prior.
|
||||
IStructs.ActivePool memory pool =
|
||||
_getActivePoolFromEpoch(currentEpoch, poolId);
|
||||
feesCollected = pool.feesCollected;
|
||||
pool = _getActivePoolFromEpoch(currentEpoch, poolId);
|
||||
}
|
||||
|
||||
/// @dev Computes the members and weighted stake for a pool at the current
|
||||
|
@@ -25,6 +25,15 @@ import "../src/Staking.sol";
|
||||
contract TestStaking is
|
||||
Staking
|
||||
{
|
||||
/// @dev Overridden to avoid hard-coded WETH.
|
||||
function getTotalBalance()
|
||||
external
|
||||
view
|
||||
returns (uint256 totalBalance)
|
||||
{
|
||||
totalBalance = address(this).balance;
|
||||
}
|
||||
|
||||
// Stub out `_unwrapWETH` to prevent the calls to `finalizeFees` from failing in tests
|
||||
// that do not relate to protocol fee payments in WETH.
|
||||
function _unwrapWETH()
|
||||
|
@@ -4,6 +4,7 @@ import * as _ from 'lodash';
|
||||
|
||||
import { StakingApiWrapper } from '../utils/api_wrapper';
|
||||
import {
|
||||
BalanceByOwner,
|
||||
DelegatorBalancesByPoolId,
|
||||
DelegatorsByPoolId,
|
||||
OperatorBalanceByPoolId,
|
||||
@@ -15,11 +16,6 @@ import {
|
||||
|
||||
import { BaseActor } from './base_actor';
|
||||
|
||||
interface Reward {
|
||||
reward: BigNumber;
|
||||
poolId: string;
|
||||
}
|
||||
|
||||
const { PPM_100_PERCENT } = constants;
|
||||
|
||||
// tslint:disable: prefer-conditional-expression
|
||||
@@ -41,30 +37,31 @@ export class FinalizerActor extends BaseActor {
|
||||
this._delegatorsByPoolId = _.cloneDeep(delegatorsByPoolId);
|
||||
}
|
||||
|
||||
public async finalizeAsync(rewards: Reward[] = []): Promise<void> {
|
||||
public async finalizeAsync(): Promise<void> {
|
||||
// cache initial info and balances
|
||||
const operatorShareByPoolId = await this._getOperatorShareByPoolIdAsync(this._poolIds);
|
||||
const rewardVaultBalanceByPoolId = await this._getRewardVaultBalanceByPoolIdAsync(this._poolIds);
|
||||
const delegatorBalancesByPoolId = await this._getDelegatorBalancesByPoolIdAsync(this._delegatorsByPoolId);
|
||||
const delegatorStakesByPoolId = await this._getDelegatorStakesByPoolIdAsync(this._delegatorsByPoolId);
|
||||
const operatorBalanceByPoolId = await this._getOperatorBalanceByPoolIdAsync(this._operatorByPoolId);
|
||||
const rewardByPoolId = await this._getRewardByPoolIdAsync(this._poolIds);
|
||||
// compute expected changes
|
||||
const [
|
||||
expectedOperatorBalanceByPoolId,
|
||||
expectedRewardVaultBalanceByPoolId,
|
||||
] = await this._computeExpectedRewardVaultBalanceAsyncByPoolIdAsync(
|
||||
rewards,
|
||||
rewardByPoolId,
|
||||
operatorBalanceByPoolId,
|
||||
rewardVaultBalanceByPoolId,
|
||||
delegatorStakesByPoolId,
|
||||
operatorShareByPoolId,
|
||||
);
|
||||
const totalRewardsByPoolId = _.zipObject(_.map(rewards, 'poolId'), _.map(rewards, 'reward'));
|
||||
const expectedDelegatorBalancesByPoolId = await this._computeExpectedDelegatorBalancesByPoolIdAsync(
|
||||
this._delegatorsByPoolId,
|
||||
delegatorBalancesByPoolId,
|
||||
delegatorStakesByPoolId,
|
||||
operatorShareByPoolId,
|
||||
totalRewardsByPoolId,
|
||||
rewardByPoolId,
|
||||
);
|
||||
// finalize
|
||||
await this._stakingApiWrapper.utils.skipToNextEpochAndFinalizeAsync();
|
||||
@@ -90,33 +87,26 @@ export class FinalizerActor extends BaseActor {
|
||||
delegatorBalancesByPoolId: DelegatorBalancesByPoolId,
|
||||
delegatorStakesByPoolId: DelegatorBalancesByPoolId,
|
||||
operatorShareByPoolId: OperatorShareByPoolId,
|
||||
totalRewardByPoolId: RewardByPoolId,
|
||||
rewardByPoolId: RewardByPoolId,
|
||||
): Promise<DelegatorBalancesByPoolId> {
|
||||
const expectedDelegatorBalancesByPoolId = _.cloneDeep(delegatorBalancesByPoolId);
|
||||
for (const poolId of Object.keys(delegatorsByPoolId)) {
|
||||
if (totalRewardByPoolId[poolId] === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const operator = this._operatorByPoolId[poolId];
|
||||
const [, membersStakeInPool] = await this._getOperatorAndDelegatorsStakeInPoolAsync(poolId);
|
||||
const operatorShare = operatorShareByPoolId[poolId].dividedBy(PPM_100_PERCENT);
|
||||
const totalReward = totalRewardByPoolId[poolId];
|
||||
const totalStakeInPool = BigNumber.sum(...Object.values(delegatorStakesByPoolId[poolId]));
|
||||
const operatorStakeInPool = delegatorStakesByPoolId[poolId][operator];
|
||||
const membersStakeInPool = totalStakeInPool.minus(operatorStakeInPool);
|
||||
const operatorShare = operatorShareByPoolId[poolId];
|
||||
const totalReward = rewardByPoolId[poolId];
|
||||
const operatorReward = membersStakeInPool.eq(0)
|
||||
? totalReward
|
||||
: totalReward.times(operatorShare).integerValue(BigNumber.ROUND_DOWN);
|
||||
: totalReward.times(operatorShare).dividedToIntegerBy(PPM_100_PERCENT);
|
||||
const membersTotalReward = totalReward.minus(operatorReward);
|
||||
|
||||
for (const delegator of delegatorsByPoolId[poolId]) {
|
||||
let delegatorReward = new BigNumber(0);
|
||||
if (delegator === operator) {
|
||||
delegatorReward = operatorReward;
|
||||
} else if (membersStakeInPool.gt(0)) {
|
||||
if (delegator !== operator && membersStakeInPool.gt(0)) {
|
||||
const delegatorStake = delegatorStakesByPoolId[poolId][delegator];
|
||||
delegatorReward = delegatorStake
|
||||
.times(membersTotalReward)
|
||||
.dividedBy(membersStakeInPool)
|
||||
.integerValue(BigNumber.ROUND_DOWN);
|
||||
delegatorReward = delegatorStake.times(membersTotalReward).dividedToIntegerBy(membersStakeInPool);
|
||||
}
|
||||
const currentBalance = expectedDelegatorBalancesByPoolId[poolId][delegator] || 0;
|
||||
expectedDelegatorBalancesByPoolId[poolId][delegator] = delegatorReward.plus(currentBalance);
|
||||
@@ -168,23 +158,25 @@ export class FinalizerActor extends BaseActor {
|
||||
}
|
||||
|
||||
private async _computeExpectedRewardVaultBalanceAsyncByPoolIdAsync(
|
||||
rewards: Reward[],
|
||||
rewardByPoolId: RewardByPoolId,
|
||||
operatorBalanceByPoolId: OperatorBalanceByPoolId,
|
||||
rewardVaultBalanceByPoolId: RewardVaultBalanceByPoolId,
|
||||
delegatorStakesByPoolId: DelegatorBalancesByPoolId,
|
||||
operatorShareByPoolId: OperatorShareByPoolId,
|
||||
): Promise<[RewardVaultBalanceByPoolId, OperatorBalanceByPoolId]> {
|
||||
const expectedOperatorBalanceByPoolId = _.cloneDeep(operatorBalanceByPoolId);
|
||||
const expectedRewardVaultBalanceByPoolId = _.cloneDeep(rewardVaultBalanceByPoolId);
|
||||
for (const reward of rewards) {
|
||||
const operatorShare = operatorShareByPoolId[reward.poolId];
|
||||
for (const poolId of Object.keys(rewardByPoolId)) {
|
||||
const operatorShare = operatorShareByPoolId[poolId];
|
||||
[
|
||||
expectedOperatorBalanceByPoolId[reward.poolId],
|
||||
expectedRewardVaultBalanceByPoolId[reward.poolId],
|
||||
expectedOperatorBalanceByPoolId[poolId],
|
||||
expectedRewardVaultBalanceByPoolId[poolId],
|
||||
] = await this._computeExpectedRewardVaultBalanceAsync(
|
||||
reward.poolId,
|
||||
reward.reward,
|
||||
expectedOperatorBalanceByPoolId[reward.poolId],
|
||||
expectedRewardVaultBalanceByPoolId[reward.poolId],
|
||||
poolId,
|
||||
rewardByPoolId[poolId],
|
||||
expectedOperatorBalanceByPoolId[poolId],
|
||||
expectedRewardVaultBalanceByPoolId[poolId],
|
||||
delegatorStakesByPoolId[poolId],
|
||||
operatorShare,
|
||||
);
|
||||
}
|
||||
@@ -196,12 +188,13 @@ export class FinalizerActor extends BaseActor {
|
||||
reward: BigNumber,
|
||||
operatorBalance: BigNumber,
|
||||
rewardVaultBalance: BigNumber,
|
||||
stakeBalances: BalanceByOwner,
|
||||
operatorShare: BigNumber,
|
||||
): Promise<[BigNumber, BigNumber]> {
|
||||
const totalStakeDelegatedToPool = (await this._stakingApiWrapper.stakingContract.getTotalStakeDelegatedToPool.callAsync(
|
||||
poolId,
|
||||
)).currentEpochBalance;
|
||||
const operatorPortion = totalStakeDelegatedToPool.eq(0)
|
||||
const totalStakeDelegatedToPool = BigNumber.sum(...Object.values(stakeBalances));
|
||||
const stakeDelegatedToPoolByOperator = stakeBalances[this._operatorByPoolId[poolId]];
|
||||
const membersStakeDelegatedToPool = totalStakeDelegatedToPool.minus(stakeDelegatedToPoolByOperator);
|
||||
const operatorPortion = membersStakeDelegatedToPool.eq(0)
|
||||
? reward
|
||||
: reward.times(operatorShare).dividedToIntegerBy(PPM_100_PERCENT);
|
||||
const membersPortion = reward.minus(operatorPortion);
|
||||
@@ -220,17 +213,6 @@ export class FinalizerActor extends BaseActor {
|
||||
return operatorBalanceByPoolId;
|
||||
}
|
||||
|
||||
private async _getOperatorAndDelegatorsStakeInPoolAsync(poolId: string): Promise<[BigNumber, BigNumber]> {
|
||||
const stakingContract = this._stakingApiWrapper.stakingContract;
|
||||
const operator = (await stakingContract.getStakingPool.callAsync(poolId)).operator;
|
||||
const totalStakeInPool = (await stakingContract.getTotalStakeDelegatedToPool.callAsync(poolId))
|
||||
.currentEpochBalance;
|
||||
const operatorStakeInPool = (await stakingContract.getStakeDelegatedToPoolByOwner.callAsync(operator, poolId))
|
||||
.currentEpochBalance;
|
||||
const membersStakeInPool = totalStakeInPool.minus(operatorStakeInPool);
|
||||
return [operatorStakeInPool, membersStakeInPool];
|
||||
}
|
||||
|
||||
private async _getOperatorShareByPoolIdAsync(poolIds: string[]): Promise<OperatorShareByPoolId> {
|
||||
const operatorShareByPoolId: OperatorShareByPoolId = {};
|
||||
for (const poolId of poolIds) {
|
||||
@@ -250,4 +232,30 @@ export class FinalizerActor extends BaseActor {
|
||||
}
|
||||
return rewardVaultBalanceByPoolId;
|
||||
}
|
||||
|
||||
private async _getRewardByPoolIdAsync(poolIds: string[]): Promise<RewardByPoolId> {
|
||||
const activePools = await Promise.all(
|
||||
poolIds.map(async poolId =>
|
||||
this._stakingApiWrapper.stakingContract.getActiveStakingPoolThisEpoch.callAsync(poolId),
|
||||
),
|
||||
);
|
||||
const totalRewards = await this._stakingApiWrapper.stakingContract.getTotalBalance.callAsync();
|
||||
const totalFeesCollected = BigNumber.sum(...activePools.map(p => p.feesCollected));
|
||||
const totalWeightedStake = BigNumber.sum(...activePools.map(p => p.weightedStake));
|
||||
if (totalRewards.eq(0) || totalFeesCollected.eq(0) || totalWeightedStake.eq(0)) {
|
||||
return _.zipObject(poolIds, _.times(poolIds.length, () => new BigNumber(0)));
|
||||
}
|
||||
const rewards = await Promise.all(
|
||||
activePools.map(async pool =>
|
||||
this._stakingApiWrapper.utils.cobbDouglas(
|
||||
totalRewards,
|
||||
pool.feesCollected,
|
||||
totalFeesCollected,
|
||||
pool.weightedStake,
|
||||
totalWeightedStake,
|
||||
),
|
||||
),
|
||||
);
|
||||
return _.zipObject(poolIds, rewards);
|
||||
}
|
||||
}
|
||||
|
@@ -163,7 +163,7 @@ blockchainTests('Protocol Fee Unit Tests', env => {
|
||||
});
|
||||
|
||||
async function getProtocolFeesAsync(poolId: string): Promise<BigNumber> {
|
||||
return testContract.getProtocolFeesThisEpochByPool.callAsync(poolId);
|
||||
return (await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId)).feesCollected;
|
||||
}
|
||||
|
||||
describe('ETH fees', () => {
|
||||
|
@@ -14,7 +14,7 @@ import { DelegatorsByPoolId, OperatorByPoolId, StakeInfo, StakeStatus } from './
|
||||
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
// tslint:disable:max-file-line-count
|
||||
blockchainTests.resets.skip('Testing Rewards', env => {
|
||||
blockchainTests.resets('Testing Rewards', env => {
|
||||
// tokens & addresses
|
||||
let accounts: string[];
|
||||
let owner: string;
|
||||
@@ -166,7 +166,7 @@ blockchainTests.resets.skip('Testing Rewards', env => {
|
||||
{ from: exchangeAddress, value: fee },
|
||||
);
|
||||
}
|
||||
await finalizer.finalizeAsync([{ reward: fee, poolId }]);
|
||||
await finalizer.finalizeAsync();
|
||||
};
|
||||
const ZERO = new BigNumber(0);
|
||||
it('Reward balance should be zero if not delegated', async () => {
|
||||
|
@@ -16,6 +16,7 @@ import {
|
||||
StakingEvents,
|
||||
StakingPoolRewardVaultContract,
|
||||
StakingProxyContract,
|
||||
TestCobbDouglasContract,
|
||||
ZrxVaultContract,
|
||||
} from '../../src';
|
||||
|
||||
@@ -33,6 +34,7 @@ export class StakingApiWrapper {
|
||||
public ethVaultContract: EthVaultContract;
|
||||
public rewardVaultContract: StakingPoolRewardVaultContract;
|
||||
public zrxTokenContract: DummyERC20TokenContract;
|
||||
public cobbDouglasContract: TestCobbDouglasContract;
|
||||
public utils = {
|
||||
// Epoch Utils
|
||||
fastForwardToNextEpochAsync: async (): Promise<void> => {
|
||||
@@ -141,6 +143,25 @@ export class StakingApiWrapper {
|
||||
await this.stakingContract.getParams.callAsync(),
|
||||
) as any) as StakingParams;
|
||||
},
|
||||
|
||||
cobbDouglas: async (
|
||||
totalRewards: BigNumber,
|
||||
ownerFees: BigNumber,
|
||||
totalFees: BigNumber,
|
||||
ownerStake: BigNumber,
|
||||
totalStake: BigNumber,
|
||||
): Promise<BigNumber> => {
|
||||
const { cobbDouglasAlphaNumerator, cobbDouglasAlphaDenominator } = await this.utils.getParamsAsync();
|
||||
return this.cobbDouglasContract.cobbDouglas.callAsync(
|
||||
totalRewards,
|
||||
ownerFees,
|
||||
totalFees,
|
||||
ownerStake,
|
||||
totalStake,
|
||||
new BigNumber(cobbDouglasAlphaNumerator),
|
||||
new BigNumber(cobbDouglasAlphaDenominator),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
private readonly _web3Wrapper: Web3Wrapper;
|
||||
@@ -154,12 +175,14 @@ export class StakingApiWrapper {
|
||||
ethVaultContract: EthVaultContract,
|
||||
rewardVaultContract: StakingPoolRewardVaultContract,
|
||||
zrxTokenContract: DummyERC20TokenContract,
|
||||
cobbDouglasContract: TestCobbDouglasContract,
|
||||
) {
|
||||
this._web3Wrapper = env.web3Wrapper;
|
||||
this.zrxVaultContract = zrxVaultContract;
|
||||
this.ethVaultContract = ethVaultContract;
|
||||
this.rewardVaultContract = rewardVaultContract;
|
||||
this.zrxTokenContract = zrxTokenContract;
|
||||
this.cobbDouglasContract = cobbDouglasContract;
|
||||
|
||||
this.stakingContractAddress = stakingContract.address;
|
||||
this.stakingProxyContract = stakingProxyContract;
|
||||
@@ -246,6 +269,13 @@ export async function deployAndConfigureContractsAsync(
|
||||
rewardVaultContract.address,
|
||||
zrxVaultContract.address,
|
||||
);
|
||||
// deploy cobb douglas contract
|
||||
const cobbDouglasContract = await TestCobbDouglasContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestCobbDouglas,
|
||||
env.provider,
|
||||
txDefaults,
|
||||
artifacts,
|
||||
);
|
||||
|
||||
// configure erc20 proxy to accept calls from zrx vault
|
||||
await erc20ProxyContract.addAuthorizedAddress.awaitTransactionSuccessAsync(zrxVaultContract.address);
|
||||
@@ -264,5 +294,6 @@ export async function deployAndConfigureContractsAsync(
|
||||
ethVaultContract,
|
||||
rewardVaultContract,
|
||||
zrxTokenContract,
|
||||
cobbDouglasContract,
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user