598 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			598 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
 | 
						|
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
 | 
						|
import { blockchainTests, describe, expect, provider, web3Wrapper } from '@0x/contracts-test-utils';
 | 
						|
import { BigNumber } from '@0x/utils';
 | 
						|
import * as _ from 'lodash';
 | 
						|
 | 
						|
import { FinalizerActor } from './actors/finalizer_actor';
 | 
						|
import { StakerActor } from './actors/staker_actor';
 | 
						|
import { StakingWrapper } from './utils/staking_wrapper';
 | 
						|
import { MembersByPoolId, OperatorByPoolId, StakeStatus } from './utils/types';
 | 
						|
 | 
						|
// tslint:disable:no-unnecessary-type-assertion
 | 
						|
// tslint:disable:max-file-line-count
 | 
						|
blockchainTests.resets('Testing Rewards', () => {
 | 
						|
    // constants
 | 
						|
    const ZRX_TOKEN_DECIMALS = new BigNumber(18);
 | 
						|
    // tokens & addresses
 | 
						|
    let accounts: string[];
 | 
						|
    let owner: string;
 | 
						|
    let actors: string[];
 | 
						|
    let exchangeAddress: string;
 | 
						|
    let takerAddress: string;
 | 
						|
    let zrxTokenContract: DummyERC20TokenContract;
 | 
						|
    let erc20ProxyContract: ERC20ProxyContract;
 | 
						|
    // wrappers
 | 
						|
    let stakingWrapper: StakingWrapper;
 | 
						|
    // let testWrapper: TestRewardBalancesContract;
 | 
						|
    let erc20Wrapper: ERC20Wrapper;
 | 
						|
    // test parameters
 | 
						|
    let stakers: StakerActor[];
 | 
						|
    let poolId: string;
 | 
						|
    let poolOperator: string;
 | 
						|
    let finalizer: FinalizerActor;
 | 
						|
    // tests
 | 
						|
    before(async () => {
 | 
						|
        // create accounts
 | 
						|
        accounts = await web3Wrapper.getAvailableAddressesAsync();
 | 
						|
        owner = accounts[0];
 | 
						|
        exchangeAddress = accounts[1];
 | 
						|
        takerAddress = accounts[2];
 | 
						|
        actors = accounts.slice(3);
 | 
						|
        // deploy erƒsc20 proxy
 | 
						|
        erc20Wrapper = new ERC20Wrapper(provider, accounts, owner);
 | 
						|
        erc20ProxyContract = await erc20Wrapper.deployProxyAsync();
 | 
						|
        // deploy zrx token
 | 
						|
        [zrxTokenContract] = await erc20Wrapper.deployDummyTokensAsync(1, ZRX_TOKEN_DECIMALS);
 | 
						|
        await erc20Wrapper.setBalancesAndAllowancesAsync();
 | 
						|
        // deploy staking contracts
 | 
						|
        stakingWrapper = new StakingWrapper(provider, owner, erc20ProxyContract, zrxTokenContract);
 | 
						|
        await stakingWrapper.deployAndConfigureContractsAsync();
 | 
						|
        // setup stakers
 | 
						|
        stakers = [new StakerActor(actors[0], stakingWrapper), new StakerActor(actors[1], stakingWrapper)];
 | 
						|
        // setup pools
 | 
						|
        poolOperator = actors[2];
 | 
						|
        poolId = await stakingWrapper.createStakingPoolAsync(poolOperator, 0, true);
 | 
						|
        // set exchange address
 | 
						|
        await stakingWrapper.addExchangeAddressAsync(exchangeAddress);
 | 
						|
        // associate operators for tracking in Finalizer
 | 
						|
        const operatorByPoolId: OperatorByPoolId = {};
 | 
						|
        operatorByPoolId[poolId] = poolOperator;
 | 
						|
        operatorByPoolId[poolId] = poolOperator;
 | 
						|
        // associate actors with pools for tracking in Finalizer
 | 
						|
        const membersByPoolId: MembersByPoolId = {};
 | 
						|
        membersByPoolId[poolId] = [actors[0], actors[1]];
 | 
						|
        membersByPoolId[poolId] = [actors[0], actors[1]];
 | 
						|
        // create Finalizer actor
 | 
						|
        finalizer = new FinalizerActor(actors[3], stakingWrapper, [poolId], operatorByPoolId, membersByPoolId);
 | 
						|
    });
 | 
						|
    describe('Reward Simulation', () => {
 | 
						|
        interface EndBalances {
 | 
						|
            // staker 1
 | 
						|
            stakerRewardVaultBalance_1?: BigNumber;
 | 
						|
            stakerEthVaultBalance_1?: BigNumber;
 | 
						|
            // staker 2
 | 
						|
            stakerRewardVaultBalance_2?: BigNumber;
 | 
						|
            stakerEthVaultBalance_2?: BigNumber;
 | 
						|
            // operator
 | 
						|
            operatorRewardVaultBalance?: BigNumber;
 | 
						|
            operatorEthVaultBalance?: BigNumber;
 | 
						|
            // undivided balance in reward pool
 | 
						|
            poolRewardVaultBalance?: BigNumber;
 | 
						|
            membersRewardVaultBalance?: BigNumber;
 | 
						|
        }
 | 
						|
        const validateEndBalances = async (_expectedEndBalances: EndBalances): Promise<void> => {
 | 
						|
            const expectedEndBalances = {
 | 
						|
                // staker 1
 | 
						|
                stakerRewardVaultBalance_1:
 | 
						|
                    _expectedEndBalances.stakerRewardVaultBalance_1 !== undefined
 | 
						|
                        ? _expectedEndBalances.stakerRewardVaultBalance_1
 | 
						|
                        : ZERO,
 | 
						|
                stakerEthVaultBalance_1:
 | 
						|
                    _expectedEndBalances.stakerEthVaultBalance_1 !== undefined
 | 
						|
                        ? _expectedEndBalances.stakerEthVaultBalance_1
 | 
						|
                        : ZERO,
 | 
						|
                // staker 2
 | 
						|
                stakerRewardVaultBalance_2:
 | 
						|
                    _expectedEndBalances.stakerRewardVaultBalance_2 !== undefined
 | 
						|
                        ? _expectedEndBalances.stakerRewardVaultBalance_2
 | 
						|
                        : ZERO,
 | 
						|
                stakerEthVaultBalance_2:
 | 
						|
                    _expectedEndBalances.stakerEthVaultBalance_2 !== undefined
 | 
						|
                        ? _expectedEndBalances.stakerEthVaultBalance_2
 | 
						|
                        : ZERO,
 | 
						|
                // operator
 | 
						|
                operatorRewardVaultBalance:
 | 
						|
                    _expectedEndBalances.operatorRewardVaultBalance !== undefined
 | 
						|
                        ? _expectedEndBalances.operatorRewardVaultBalance
 | 
						|
                        : ZERO,
 | 
						|
                operatorEthVaultBalance:
 | 
						|
                    _expectedEndBalances.operatorEthVaultBalance !== undefined
 | 
						|
                        ? _expectedEndBalances.operatorEthVaultBalance
 | 
						|
                        : ZERO,
 | 
						|
                // undivided balance in reward pool
 | 
						|
                poolRewardVaultBalance:
 | 
						|
                    _expectedEndBalances.poolRewardVaultBalance !== undefined
 | 
						|
                        ? _expectedEndBalances.poolRewardVaultBalance
 | 
						|
                        : ZERO,
 | 
						|
                membersRewardVaultBalance:
 | 
						|
                    _expectedEndBalances.membersRewardVaultBalance !== undefined
 | 
						|
                        ? _expectedEndBalances.membersRewardVaultBalance
 | 
						|
                        : ZERO,
 | 
						|
            };
 | 
						|
            const finalEndBalancesAsArray = await Promise.all([
 | 
						|
                // staker 1
 | 
						|
                stakingWrapper.computeRewardBalanceOfStakingPoolMemberAsync(poolId, stakers[0].getOwner()),
 | 
						|
                stakingWrapper.getEthVaultContract().balanceOf.callAsync(stakers[0].getOwner()),
 | 
						|
                // staker 2
 | 
						|
                stakingWrapper.computeRewardBalanceOfStakingPoolMemberAsync(poolId, stakers[1].getOwner()),
 | 
						|
                stakingWrapper.getEthVaultContract().balanceOf.callAsync(stakers[1].getOwner()),
 | 
						|
                // operator
 | 
						|
                stakingWrapper.rewardVaultBalanceOfOperatorAsync(poolId),
 | 
						|
                stakingWrapper.getEthVaultContract().balanceOf.callAsync(poolOperator),
 | 
						|
                // undivided balance in reward pool
 | 
						|
                stakingWrapper.rewardVaultBalanceOfAsync(poolId),
 | 
						|
                stakingWrapper.rewardVaultBalanceOfMembersAsync(poolId),
 | 
						|
            ]);
 | 
						|
            expect(finalEndBalancesAsArray[0], 'stakerRewardVaultBalance_1').to.be.bignumber.equal(
 | 
						|
                expectedEndBalances.stakerRewardVaultBalance_1,
 | 
						|
            );
 | 
						|
            expect(finalEndBalancesAsArray[1], 'stakerEthVaultBalance_1').to.be.bignumber.equal(
 | 
						|
                expectedEndBalances.stakerEthVaultBalance_1,
 | 
						|
            );
 | 
						|
            expect(finalEndBalancesAsArray[2], 'stakerRewardVaultBalance_2').to.be.bignumber.equal(
 | 
						|
                expectedEndBalances.stakerRewardVaultBalance_2,
 | 
						|
            );
 | 
						|
            expect(finalEndBalancesAsArray[3], 'stakerEthVaultBalance_2').to.be.bignumber.equal(
 | 
						|
                expectedEndBalances.stakerEthVaultBalance_2,
 | 
						|
            );
 | 
						|
            expect(finalEndBalancesAsArray[4], 'operatorRewardVaultBalance').to.be.bignumber.equal(
 | 
						|
                expectedEndBalances.operatorRewardVaultBalance,
 | 
						|
            );
 | 
						|
            expect(finalEndBalancesAsArray[5], 'operatorEthVaultBalance').to.be.bignumber.equal(
 | 
						|
                expectedEndBalances.operatorEthVaultBalance,
 | 
						|
            );
 | 
						|
            expect(finalEndBalancesAsArray[6], 'poolRewardVaultBalance').to.be.bignumber.equal(
 | 
						|
                expectedEndBalances.poolRewardVaultBalance,
 | 
						|
            );
 | 
						|
            expect(finalEndBalancesAsArray[7], 'membersRewardVaultBalance').to.be.bignumber.equal(
 | 
						|
                expectedEndBalances.membersRewardVaultBalance,
 | 
						|
            );
 | 
						|
        };
 | 
						|
        const payProtocolFeeAndFinalize = async (_fee?: BigNumber) => {
 | 
						|
            const fee = _fee !== undefined ? _fee : ZERO;
 | 
						|
            if (!fee.eq(ZERO)) {
 | 
						|
                await stakingWrapper.payProtocolFeeAsync(poolOperator, takerAddress, fee, fee, exchangeAddress);
 | 
						|
            }
 | 
						|
            await finalizer.finalizeAsync([{ reward: fee, poolId }]);
 | 
						|
        };
 | 
						|
        const ZERO = new BigNumber(0);
 | 
						|
        it('Reward balance should be zero in same epoch as delegation', async () => {
 | 
						|
            const amount = StakingWrapper.toBaseUnitAmount(4);
 | 
						|
            await stakers[0].stakeAsync(amount);
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                amount,
 | 
						|
            );
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // sanit check final balances - all zero
 | 
						|
            await validateEndBalances({});
 | 
						|
        });
 | 
						|
        it('Operator should receive entire reward if no delegators in their pool', async () => {
 | 
						|
            const reward = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            await payProtocolFeeAndFinalize(reward);
 | 
						|
            // sanity check final balances - all zero
 | 
						|
            await validateEndBalances({
 | 
						|
                operatorRewardVaultBalance: reward,
 | 
						|
                poolRewardVaultBalance: reward,
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('Operator should receive entire reward if no delegators in their pool (staker joins this epoch but is active next epoch)', async () => {
 | 
						|
            // delegate
 | 
						|
            const amount = StakingWrapper.toBaseUnitAmount(4);
 | 
						|
            await stakers[0].stakeAsync(amount);
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                amount,
 | 
						|
            );
 | 
						|
            // finalize
 | 
						|
            const reward = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            await payProtocolFeeAndFinalize(reward);
 | 
						|
            // sanity check final balances
 | 
						|
            await validateEndBalances({
 | 
						|
                operatorRewardVaultBalance: reward,
 | 
						|
                poolRewardVaultBalance: reward,
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('Should give pool reward to delegator', async () => {
 | 
						|
            // delegate
 | 
						|
            const amount = StakingWrapper.toBaseUnitAmount(4);
 | 
						|
            await stakers[0].stakeAsync(amount);
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                amount,
 | 
						|
            );
 | 
						|
            // skip epoch, so staker can start earning rewards
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // finalize
 | 
						|
            const reward = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            await payProtocolFeeAndFinalize(reward);
 | 
						|
            // sanity check final balances
 | 
						|
            await validateEndBalances({
 | 
						|
                stakerRewardVaultBalance_1: reward,
 | 
						|
                poolRewardVaultBalance: reward,
 | 
						|
                membersRewardVaultBalance: reward,
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('Should split pool reward between delegators', async () => {
 | 
						|
            // first staker delegates
 | 
						|
            const stakeAmounts = [StakingWrapper.toBaseUnitAmount(4), StakingWrapper.toBaseUnitAmount(6)];
 | 
						|
            const totalStakeAmount = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            await stakers[0].stakeAsync(stakeAmounts[0]);
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmounts[0],
 | 
						|
            );
 | 
						|
            // second staker delegates
 | 
						|
            await stakers[1].stakeAsync(stakeAmounts[1]);
 | 
						|
            await stakers[1].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmounts[1],
 | 
						|
            );
 | 
						|
            // skip epoch, so staker can start earning rewards
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // finalize
 | 
						|
            const reward = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            await payProtocolFeeAndFinalize(reward);
 | 
						|
            // sanity check final balances
 | 
						|
            await validateEndBalances({
 | 
						|
                stakerRewardVaultBalance_1: reward.times(stakeAmounts[0]).dividedToIntegerBy(totalStakeAmount),
 | 
						|
                stakerRewardVaultBalance_2: reward.times(stakeAmounts[1]).dividedToIntegerBy(totalStakeAmount),
 | 
						|
                poolRewardVaultBalance: reward,
 | 
						|
                membersRewardVaultBalance: reward,
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('Should split pool reward between delegators, when they join in different epochs', async () => {
 | 
						|
            // first staker delegates (epoch 0)
 | 
						|
            const stakeAmounts = [StakingWrapper.toBaseUnitAmount(4), StakingWrapper.toBaseUnitAmount(6)];
 | 
						|
            const totalStakeAmount = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            await stakers[0].stakeAsync(stakeAmounts[0]);
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmounts[0],
 | 
						|
            );
 | 
						|
            // skip epoch, so staker can start earning rewards
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // second staker delegates (epoch 1)
 | 
						|
            await stakers[1].stakeAsync(stakeAmounts[1]);
 | 
						|
            await stakers[1].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmounts[1],
 | 
						|
            );
 | 
						|
            // skip epoch, so staker can start earning rewards
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // finalize
 | 
						|
            const reward = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            await payProtocolFeeAndFinalize(reward);
 | 
						|
            // sanity check final balances
 | 
						|
            await validateEndBalances({
 | 
						|
                stakerRewardVaultBalance_1: reward.times(stakeAmounts[0]).dividedToIntegerBy(totalStakeAmount),
 | 
						|
                stakerRewardVaultBalance_2: reward.times(stakeAmounts[1]).dividedToIntegerBy(totalStakeAmount),
 | 
						|
                poolRewardVaultBalance: reward,
 | 
						|
                membersRewardVaultBalance: reward,
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('Should give pool reward to delegators only for the epoch during which they delegated', async () => {
 | 
						|
            // first staker delegates (epoch 0)
 | 
						|
            const stakeAmounts = [StakingWrapper.toBaseUnitAmount(4), StakingWrapper.toBaseUnitAmount(6)];
 | 
						|
            const totalStakeAmount = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            await stakers[0].stakeAsync(stakeAmounts[0]);
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmounts[0],
 | 
						|
            );
 | 
						|
            // skip epoch, so first staker can start earning rewards
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // second staker delegates (epoch 1)
 | 
						|
            await stakers[1].stakeAsync(stakeAmounts[1]);
 | 
						|
            await stakers[1].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmounts[1],
 | 
						|
            );
 | 
						|
            // only the first staker will get this reward
 | 
						|
            const rewardForOnlyFirstDelegator = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            await payProtocolFeeAndFinalize(rewardForOnlyFirstDelegator);
 | 
						|
            // finalize
 | 
						|
            const rewardForBothDelegators = StakingWrapper.toBaseUnitAmount(20);
 | 
						|
            await payProtocolFeeAndFinalize(rewardForBothDelegators);
 | 
						|
            // sanity check final balances
 | 
						|
            await validateEndBalances({
 | 
						|
                stakerRewardVaultBalance_1: rewardForOnlyFirstDelegator.plus(
 | 
						|
                    rewardForBothDelegators.times(stakeAmounts[0]).dividedToIntegerBy(totalStakeAmount),
 | 
						|
                ),
 | 
						|
                stakerRewardVaultBalance_2: rewardForBothDelegators
 | 
						|
                    .times(stakeAmounts[1])
 | 
						|
                    .dividedToIntegerBy(totalStakeAmount),
 | 
						|
                poolRewardVaultBalance: rewardForOnlyFirstDelegator.plus(rewardForBothDelegators),
 | 
						|
                membersRewardVaultBalance: rewardForOnlyFirstDelegator.plus(rewardForBothDelegators),
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('Should split pool reward between delegators, over several consecutive epochs', async () => {
 | 
						|
            const rewardForOnlyFirstDelegator = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            const sharedRewards = [
 | 
						|
                StakingWrapper.toBaseUnitAmount(20),
 | 
						|
                StakingWrapper.toBaseUnitAmount(16),
 | 
						|
                StakingWrapper.toBaseUnitAmount(24),
 | 
						|
                StakingWrapper.toBaseUnitAmount(5),
 | 
						|
                StakingWrapper.toBaseUnitAmount(0),
 | 
						|
                StakingWrapper.toBaseUnitAmount(17),
 | 
						|
            ];
 | 
						|
            const totalSharedRewardsAsNumber = _.sumBy(sharedRewards, v => {
 | 
						|
                return v.toNumber();
 | 
						|
            });
 | 
						|
            const totalSharedRewards = new BigNumber(totalSharedRewardsAsNumber);
 | 
						|
            // first staker delegates (epoch 0)
 | 
						|
            const stakeAmounts = [StakingWrapper.toBaseUnitAmount(4), StakingWrapper.toBaseUnitAmount(6)];
 | 
						|
            const totalStakeAmount = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            await stakers[0].stakeAsync(stakeAmounts[0]);
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmounts[0],
 | 
						|
            );
 | 
						|
            // skip epoch, so first staker can start earning rewards
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // second staker delegates (epoch 1)
 | 
						|
            await stakers[1].stakeAsync(stakeAmounts[1]);
 | 
						|
            await stakers[1].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmounts[1],
 | 
						|
            );
 | 
						|
            // only the first staker will get this reward
 | 
						|
            await payProtocolFeeAndFinalize(rewardForOnlyFirstDelegator);
 | 
						|
            // earn a bunch of rewards
 | 
						|
            for (const reward of sharedRewards) {
 | 
						|
                await payProtocolFeeAndFinalize(reward);
 | 
						|
            }
 | 
						|
            // sanity check final balances
 | 
						|
            await validateEndBalances({
 | 
						|
                stakerRewardVaultBalance_1: rewardForOnlyFirstDelegator.plus(
 | 
						|
                    totalSharedRewards.times(stakeAmounts[0]).dividedToIntegerBy(totalStakeAmount),
 | 
						|
                ),
 | 
						|
                stakerRewardVaultBalance_2: totalSharedRewards
 | 
						|
                    .times(stakeAmounts[1])
 | 
						|
                    .dividedToIntegerBy(totalStakeAmount),
 | 
						|
                poolRewardVaultBalance: rewardForOnlyFirstDelegator.plus(totalSharedRewards),
 | 
						|
                membersRewardVaultBalance: rewardForOnlyFirstDelegator.plus(totalSharedRewards),
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('Should send existing rewards from reward vault to eth vault correctly when undelegating stake', async () => {
 | 
						|
            // first staker delegates (epoch 0)
 | 
						|
            const stakeAmount = StakingWrapper.toBaseUnitAmount(4);
 | 
						|
            await stakers[0].stakeAsync(stakeAmount);
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmount,
 | 
						|
            );
 | 
						|
            // skip epoch, so first staker can start earning rewards
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // earn reward
 | 
						|
            const reward = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            await payProtocolFeeAndFinalize(reward);
 | 
						|
            // undelegate (moves delegator's from the transient reward vault into the eth vault)
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                stakeAmount,
 | 
						|
            );
 | 
						|
            // sanity check final balances
 | 
						|
            await validateEndBalances({
 | 
						|
                stakerRewardVaultBalance_1: ZERO,
 | 
						|
                stakerEthVaultBalance_1: reward,
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('Should send existing rewards from reward vault to eth vault correctly when delegating more stake', async () => {
 | 
						|
            // first staker delegates (epoch 0)
 | 
						|
            const stakeAmount = StakingWrapper.toBaseUnitAmount(4);
 | 
						|
            await stakers[0].stakeAsync(stakeAmount);
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmount,
 | 
						|
            );
 | 
						|
            // skip epoch, so first staker can start earning rewards
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // earn reward
 | 
						|
            const reward = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            await payProtocolFeeAndFinalize(reward);
 | 
						|
            // add more stake
 | 
						|
            await stakers[0].stakeAsync(stakeAmount);
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmount,
 | 
						|
            );
 | 
						|
            // sanity check final balances
 | 
						|
            await validateEndBalances({
 | 
						|
                stakerRewardVaultBalance_1: ZERO,
 | 
						|
                stakerEthVaultBalance_1: reward,
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('Should continue earning rewards after adding more stake and progressing several epochs', async () => {
 | 
						|
            const rewardBeforeAddingMoreStake = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            const rewardsAfterAddingMoreStake = [
 | 
						|
                StakingWrapper.toBaseUnitAmount(20),
 | 
						|
                StakingWrapper.toBaseUnitAmount(16),
 | 
						|
                StakingWrapper.toBaseUnitAmount(24),
 | 
						|
                StakingWrapper.toBaseUnitAmount(5),
 | 
						|
                StakingWrapper.toBaseUnitAmount(0),
 | 
						|
                StakingWrapper.toBaseUnitAmount(17),
 | 
						|
            ];
 | 
						|
            const totalRewardsAfterAddingMoreStake = new BigNumber(
 | 
						|
                _.sumBy(rewardsAfterAddingMoreStake, v => {
 | 
						|
                    return v.toNumber();
 | 
						|
                }),
 | 
						|
            );
 | 
						|
            // first staker delegates (epoch 0)
 | 
						|
            const stakeAmounts = [StakingWrapper.toBaseUnitAmount(4), StakingWrapper.toBaseUnitAmount(6)];
 | 
						|
            await stakers[0].stakeAsync(stakeAmounts[0]);
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmounts[0],
 | 
						|
            );
 | 
						|
            // skip epoch, so first staker can start earning rewards
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // second staker delegates (epoch 1)
 | 
						|
            await stakers[0].stakeAsync(stakeAmounts[1]);
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmounts[1],
 | 
						|
            );
 | 
						|
            // only the first staker will get this reward
 | 
						|
            await payProtocolFeeAndFinalize(rewardBeforeAddingMoreStake);
 | 
						|
            // earn a bunch of rewards
 | 
						|
            for (const reward of rewardsAfterAddingMoreStake) {
 | 
						|
                await payProtocolFeeAndFinalize(reward);
 | 
						|
            }
 | 
						|
            // sanity check final balances
 | 
						|
            await validateEndBalances({
 | 
						|
                stakerRewardVaultBalance_1: rewardBeforeAddingMoreStake.plus(totalRewardsAfterAddingMoreStake),
 | 
						|
                poolRewardVaultBalance: rewardBeforeAddingMoreStake.plus(totalRewardsAfterAddingMoreStake),
 | 
						|
                membersRewardVaultBalance: rewardBeforeAddingMoreStake.plus(totalRewardsAfterAddingMoreStake),
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('Should stop collecting rewards after undelegating', async () => {
 | 
						|
            // first staker delegates (epoch 0)
 | 
						|
            const rewardForDelegator = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            const rewardNotForDelegator = StakingWrapper.toBaseUnitAmount(7);
 | 
						|
            const stakeAmount = StakingWrapper.toBaseUnitAmount(4);
 | 
						|
            await stakers[0].stakeAsync(stakeAmount);
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmount,
 | 
						|
            );
 | 
						|
            // skip epoch, so first staker can start earning rewards
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // earn reward
 | 
						|
            await payProtocolFeeAndFinalize(rewardForDelegator);
 | 
						|
            // undelegate stake and finalize epoch
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                stakeAmount,
 | 
						|
            );
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // this should not go do the delegator
 | 
						|
            await payProtocolFeeAndFinalize(rewardNotForDelegator);
 | 
						|
            // sanity check final balances
 | 
						|
            await validateEndBalances({
 | 
						|
                stakerEthVaultBalance_1: rewardForDelegator,
 | 
						|
                poolRewardVaultBalance: rewardNotForDelegator,
 | 
						|
                operatorRewardVaultBalance: rewardNotForDelegator,
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('Should stop collecting rewards after undelegating, after several epochs', async () => {
 | 
						|
            // first staker delegates (epoch 0)
 | 
						|
            const rewardForDelegator = StakingWrapper.toBaseUnitAmount(10);
 | 
						|
            const rewardsNotForDelegator = [
 | 
						|
                StakingWrapper.toBaseUnitAmount(20),
 | 
						|
                StakingWrapper.toBaseUnitAmount(16),
 | 
						|
                StakingWrapper.toBaseUnitAmount(24),
 | 
						|
                StakingWrapper.toBaseUnitAmount(5),
 | 
						|
                StakingWrapper.toBaseUnitAmount(0),
 | 
						|
                StakingWrapper.toBaseUnitAmount(17),
 | 
						|
            ];
 | 
						|
            const totalRewardsNotForDelegator = new BigNumber(
 | 
						|
                _.sumBy(rewardsNotForDelegator, v => {
 | 
						|
                    return v.toNumber();
 | 
						|
                }),
 | 
						|
            );
 | 
						|
            const stakeAmount = StakingWrapper.toBaseUnitAmount(4);
 | 
						|
            await stakers[0].stakeAsync(stakeAmount);
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmount,
 | 
						|
            );
 | 
						|
            // skip epoch, so first staker can start earning rewards
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // earn reward
 | 
						|
            await payProtocolFeeAndFinalize(rewardForDelegator);
 | 
						|
            // undelegate stake and finalize epoch
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                stakeAmount,
 | 
						|
            );
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // this should not go do the delegator
 | 
						|
            for (const reward of rewardsNotForDelegator) {
 | 
						|
                await payProtocolFeeAndFinalize(reward);
 | 
						|
            }
 | 
						|
            // sanity check final balances
 | 
						|
            await validateEndBalances({
 | 
						|
                stakerEthVaultBalance_1: rewardForDelegator,
 | 
						|
                poolRewardVaultBalance: totalRewardsNotForDelegator,
 | 
						|
                operatorRewardVaultBalance: totalRewardsNotForDelegator,
 | 
						|
            });
 | 
						|
        });
 | 
						|
        it('Should collect fees correctly when leaving and returning to a pool', async () => {
 | 
						|
            // first staker delegates (epoch 0)
 | 
						|
            const rewardsForDelegator = [StakingWrapper.toBaseUnitAmount(10), StakingWrapper.toBaseUnitAmount(15)];
 | 
						|
            const rewardNotForDelegator = StakingWrapper.toBaseUnitAmount(7);
 | 
						|
            const stakeAmount = StakingWrapper.toBaseUnitAmount(4);
 | 
						|
            await stakers[0].stakeAsync(stakeAmount);
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmount,
 | 
						|
            );
 | 
						|
            // skip epoch, so first staker can start earning rewards
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // earn reward
 | 
						|
            await payProtocolFeeAndFinalize(rewardsForDelegator[0]);
 | 
						|
            // undelegate stake and finalize epoch
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                stakeAmount,
 | 
						|
            );
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // this should not go do the delegator
 | 
						|
            await payProtocolFeeAndFinalize(rewardNotForDelegator);
 | 
						|
            // delegate stake and go to next epoch
 | 
						|
            await stakers[0].moveStakeAsync(
 | 
						|
                { status: StakeStatus.Active },
 | 
						|
                { status: StakeStatus.Delegated, poolId },
 | 
						|
                stakeAmount,
 | 
						|
            );
 | 
						|
            await payProtocolFeeAndFinalize();
 | 
						|
            // this reward should go to delegator
 | 
						|
            await payProtocolFeeAndFinalize(rewardsForDelegator[1]);
 | 
						|
            // sanity check final balances
 | 
						|
            await validateEndBalances({
 | 
						|
                stakerRewardVaultBalance_1: rewardsForDelegator[1],
 | 
						|
                stakerEthVaultBalance_1: rewardsForDelegator[0],
 | 
						|
                operatorRewardVaultBalance: rewardNotForDelegator,
 | 
						|
                poolRewardVaultBalance: rewardNotForDelegator.plus(rewardsForDelegator[1]),
 | 
						|
                membersRewardVaultBalance: rewardsForDelegator[1],
 | 
						|
            });
 | 
						|
        });
 | 
						|
    });
 | 
						|
});
 | 
						|
// tslint:enable:no-unnecessary-type-assertion
 |