import { ERC20Wrapper } from '@0x/contracts-asset-proxy';
import { blockchainTests, constants, expect } from '@0x/contracts-test-utils';
import { StakingRevertErrors } from '@0x/order-utils';
import * as _ from 'lodash';

import { MakerActor } from './actors/maker_actor';
import { PoolOperatorActor } from './actors/pool_operator_actor';
import { deployAndConfigureContractsAsync, StakingApiWrapper } from './utils/api_wrapper';
import { constants as stakingConstants } from './utils/constants';

// tslint:disable:no-unnecessary-type-assertion
// tslint:disable:max-file-line-count
blockchainTests('Staking Pool Management', env => {
    // constants
    const { PPM_100_PERCENT, PPM_DENOMINATOR } = constants;
    // tokens & addresses
    let accounts: string[];
    let owner: string;
    let users: string[];
    // wrappers
    let stakingApiWrapper: StakingApiWrapper;
    let erc20Wrapper: ERC20Wrapper;
    // tests
    before(async () => {
        // create accounts
        accounts = await env.getAccountAddressesAsync();
        [owner, ...users] = accounts;
        // set up ERC20Wrapper
        erc20Wrapper = new ERC20Wrapper(env.provider, accounts, owner);
        // deploy staking contracts
        stakingApiWrapper = await deployAndConfigureContractsAsync(env, owner, erc20Wrapper);
    });
    blockchainTests.resets('Staking Pool Management', () => {
        it('Should successfully create a pool', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            // check that the next pool id was incremented
            const nextPoolId = await stakingApiWrapper.stakingContract.nextPoolId.callAsync();
            expect(nextPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
        });
        it('Should successfully create several staking pools, as long as the operator is only a maker in one', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            // create pool
            const poolId1 = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId1).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            const poolId2 = await poolOperator.createStakingPoolAsync(operatorShare, false);
            expect(poolId2).to.be.equal(stakingConstants.SECOND_POOL_ID);
        });
        it('Should fail to create several staking pools with the operator as a maker in each', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            // create pool
            const poolId1 = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId1).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            await poolOperator.createStakingPoolAsync(
                operatorShare,
                true,
                new StakingRevertErrors.MakerPoolAssignmentError(
                    StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressAlreadyRegistered,
                    poolOperator.getOwner(),
                    poolId1,
                ),
            );
        });
        it('Should fail to create a pool with operator share > 100', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (101 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);

            const revertError = new StakingRevertErrors.OperatorShareError(
                StakingRevertErrors.OperatorShareErrorCodes.OperatorShareTooLarge,
                stakingConstants.INITIAL_POOL_ID,
                operatorShare,
            );
            // create pool
            await poolOperator.createStakingPoolAsync(operatorShare, false, revertError);
        });
        it('should fail to add a maker to a pool if maker has not called `joinStakingPoolAsMaker` yet', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            const makerAddress = users[1];
            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            await poolOperator.addMakerToStakingPoolAsync(
                poolId,
                makerAddress,
                new StakingRevertErrors.MakerPoolAssignmentError(
                    StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotPendingAdd,
                    makerAddress,
                    constants.NULL_BYTES32,
                ),
            );
        });
        it('should fail to add a maker to a pool if maker has called `joinStakingPoolAsMaker` on a different pool', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            const otherPoolOperator = new PoolOperatorActor(users[1], stakingApiWrapper);
            const makerAddress = users[2];
            const maker = new MakerActor(makerAddress, stakingApiWrapper);
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            const otherPoolId = await otherPoolOperator.createStakingPoolAsync(operatorShare, true);
            // create pool
            await maker.joinStakingPoolAsMakerAsync(poolId);
            await otherPoolOperator.addMakerToStakingPoolAsync(
                otherPoolId,
                makerAddress,
                new StakingRevertErrors.MakerPoolAssignmentError(
                    StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotPendingAdd,
                    makerAddress,
                    poolId,
                ),
            );
        });
        it('Should successfully create a pool and add owner as a maker', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            // check that the next pool id was incremented
            const nextPoolId = await stakingApiWrapper.stakingContract.nextPoolId.callAsync();
            expect(nextPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);
        });
        it('Should throw if operatorShare is > PPM_DENOMINATOR', async () => {
            // test parameters
            const operatorAddress = users[0];
            // tslint:disable-next-line
            const operatorShare = PPM_100_PERCENT + 1;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            // create pool
            const tx = poolOperator.createStakingPoolAsync(operatorShare, true);
            const expectedPoolId = stakingConstants.INITIAL_POOL_ID;
            const expectedError = new StakingRevertErrors.OperatorShareError(
                StakingRevertErrors.OperatorShareErrorCodes.OperatorShareTooLarge,
                expectedPoolId,
                operatorShare,
            );
            return expect(tx).to.revertWith(expectedError);
        });
        it('Should successfully add/remove a maker to a pool', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            const makerAddress = users[1];
            const maker = new MakerActor(makerAddress, stakingApiWrapper);
            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            // maker joins pool
            await maker.joinStakingPoolAsMakerAsync(poolId);
            // operator adds maker to pool
            await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
            // operator removes maker from pool
            await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress);
        });
        it('Should successfully add/remove a maker to a pool if approved by maker', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            const maker1Address = users[1];
            const maker1 = new MakerActor(maker1Address, stakingApiWrapper);
            const maker2Address = users[2];
            const maker2 = new MakerActor(maker2Address, stakingApiWrapper);
            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            // maker joins pool
            await maker1.joinStakingPoolAsMakerAsync(poolId);
            // operator adds maker to pool
            await poolOperator.addMakerToStakingPoolAsync(poolId, maker1Address);
            // maker joins pool
            await maker2.joinStakingPoolAsMakerAsync(poolId);
            // approved maker adds new maker to pool
            await maker1.addMakerToStakingPoolAsync(poolId, maker2Address);
        });
        it('should fail to add a maker to a pool if called by pending maker', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            const makerAddress = users[1];
            const maker = new MakerActor(makerAddress, stakingApiWrapper);
            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            // maker joins pool
            await maker.joinStakingPoolAsMakerAsync(poolId);
            await maker.addMakerToStakingPoolAsync(
                poolId,
                makerAddress,
                new StakingRevertErrors.OnlyCallableByPoolOperatorOrMakerError(makerAddress, poolId),
            );
        });
        it('should fail to add a maker to a pool if not called by operator/registered maker', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            const maker1Address = users[1];
            const maker1 = new MakerActor(maker1Address, stakingApiWrapper);
            const maker2Address = users[2];
            const maker2 = new MakerActor(maker2Address, stakingApiWrapper);
            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            // maker joins pool
            await maker1.joinStakingPoolAsMakerAsync(poolId);
            await maker2.addMakerToStakingPoolAsync(
                poolId,
                maker1Address,
                new StakingRevertErrors.OnlyCallableByPoolOperatorOrMakerError(maker2Address, poolId),
            );
        });
        it('Maker should successfully remove themselves from a pool', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            const makerAddress = users[1];
            const maker = new MakerActor(makerAddress, stakingApiWrapper);
            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            // maker joins pool
            await maker.joinStakingPoolAsMakerAsync(poolId);
            // operator adds maker to pool
            await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
            // maker removes themselves from pool
            await maker.removeMakerFromStakingPoolAsync(poolId, makerAddress);
        });
        it('operator can remove a maker from their pool', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            const makerAddress = users[1];
            const maker = new MakerActor(makerAddress, stakingApiWrapper);
            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            // maker joins pool
            await maker.joinStakingPoolAsMakerAsync(poolId);
            // operator adds maker to pool
            await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
            // operator removes maker from pool
            await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress);
        });
        it('Should successfully add/remove multiple makers to the same pool', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            const makerAddresses = users.slice(1, 4);
            const makers = makerAddresses.map(makerAddress => new MakerActor(makerAddress, stakingApiWrapper));

            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);

            // add makers to pool
            await Promise.all(makers.map(async maker => maker.joinStakingPoolAsMakerAsync(poolId)));
            await Promise.all(
                makerAddresses.map(async makerAddress => poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress)),
            );

            // check the number of makers in the pool
            let pool = await stakingApiWrapper.stakingContract.getStakingPool.callAsync(poolId);
            expect(pool.numberOfMakers, 'number of makers in pool after adding').to.be.bignumber.equal(3);

            // remove maker from pool
            await Promise.all(
                makerAddresses.map(async makerAddress =>
                    poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress),
                ),
            );

            // check the number of makers in the pool
            pool = await stakingApiWrapper.stakingContract.getStakingPool.callAsync(poolId);
            expect(pool.numberOfMakers, 'number of makers in pool after removing').to.be.bignumber.equal(0);
        });
        it('should fail to remove a maker from a pool if not called by operator/registered maker', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            const maker1Address = users[1];
            const maker1 = new MakerActor(maker1Address, stakingApiWrapper);
            const maker2Address = users[2];
            const maker2 = new MakerActor(maker2Address, stakingApiWrapper);
            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            // maker joins pool
            await maker1.joinStakingPoolAsMakerAsync(poolId);
            // operator adds maker to pool
            await poolOperator.addMakerToStakingPoolAsync(poolId, maker1Address);
            await maker2.removeMakerFromStakingPoolAsync(
                poolId,
                maker1Address,
                new StakingRevertErrors.OnlyCallableByPoolOperatorOrMakerError(maker2Address, poolId),
            );
        });
        it('Should fail if maker already assigned to another pool tries to join', async () => {
            // test parameters
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const assignedPoolOperator = new PoolOperatorActor(users[0], stakingApiWrapper);
            const otherPoolOperator = new PoolOperatorActor(users[1], stakingApiWrapper);

            const makerAddress = users[2];
            const maker = new MakerActor(makerAddress, stakingApiWrapper);

            // create pools
            const assignedPoolId = await assignedPoolOperator.createStakingPoolAsync(operatorShare, true);
            const otherPoolId = await otherPoolOperator.createStakingPoolAsync(operatorShare, true);
            expect(assignedPoolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            expect(otherPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);

            // maker joins first pool
            await maker.joinStakingPoolAsMakerAsync(assignedPoolId);
            // first pool operator adds maker
            await assignedPoolOperator.addMakerToStakingPoolAsync(assignedPoolId, makerAddress);

            const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
                StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressAlreadyRegistered,
                makerAddress,
                assignedPoolId,
            );
            // second pool operator now tries to add maker
            await otherPoolOperator.addMakerToStakingPoolAsync(otherPoolId, makerAddress, revertError);
        });
        it('Should fail to add maker to pool if the maker has not joined any pools', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);

            const makerAddress = users[1];

            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);

            const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
                StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotPendingAdd,
                makerAddress,
                stakingConstants.NIL_POOL_ID,
            );
            // operator adds maker to pool
            await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, revertError);
        });
        it('Should fail to add maker to pool if the maker joined a different pool', async () => {
            // test parameters
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const assignedPoolOperator = new PoolOperatorActor(users[0], stakingApiWrapper);
            const otherPoolOperator = new PoolOperatorActor(users[1], stakingApiWrapper);

            const makerAddress = users[2];
            const maker = new MakerActor(makerAddress, stakingApiWrapper);

            // create pools
            const joinedPoolId = await assignedPoolOperator.createStakingPoolAsync(operatorShare, true);
            const otherPoolId = await otherPoolOperator.createStakingPoolAsync(operatorShare, true);
            expect(joinedPoolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            expect(otherPoolId).to.be.equal(stakingConstants.SECOND_POOL_ID);

            // maker joins first pool
            await maker.joinStakingPoolAsMakerAsync(joinedPoolId);

            const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
                StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotPendingAdd,
                makerAddress,
                joinedPoolId,
            );
            // second pool operator now tries to add maker
            await otherPoolOperator.addMakerToStakingPoolAsync(otherPoolId, makerAddress, revertError);
        });
        it('Should fail to add the same maker twice', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            const makerAddress = users[1];
            const maker = new MakerActor(makerAddress, stakingApiWrapper);
            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            // add maker to pool
            await maker.joinStakingPoolAsMakerAsync(poolId);
            await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
            const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
                StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressAlreadyRegistered,
                makerAddress,
                poolId,
            );
            // add same maker to pool again
            await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress, revertError);
        });
        it('Should fail to remove a maker that does not exist', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            const makerAddress = users[1];
            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
                StakingRevertErrors.MakerPoolAssignmentErrorCodes.MakerAddressNotRegistered,
                makerAddress,
                stakingConstants.NIL_POOL_ID,
            );
            // remove non-existent maker from pool
            await poolOperator.removeMakerFromStakingPoolAsync(poolId, makerAddress, revertError);
        });
        it('Should fail to remove a maker when called by someone other than the pool operator or maker', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            const makerAddress = users[1];
            const maker = new MakerActor(makerAddress, stakingApiWrapper);
            const neitherOperatorNorMakerAddress = users[2];
            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            // add maker to pool
            await maker.joinStakingPoolAsMakerAsync(poolId);
            await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);
            // try to remove the maker address from an address other than the operator
            const revertError = new StakingRevertErrors.OnlyCallableByPoolOperatorOrMakerError(
                neitherOperatorNorMakerAddress,
                poolId,
            );
            const tx = stakingApiWrapper.stakingContract.removeMakerFromStakingPool.awaitTransactionSuccessAsync(
                poolId,
                makerAddress,
                { from: neitherOperatorNorMakerAddress },
            );
            await expect(tx).to.revertWith(revertError);
        });
        it('Should fail to add a maker if the pool is full', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);

            const makerAddresses = users.slice(1, stakingConstants.DEFAULT_PARAMS.maximumMakersInPool.toNumber() + 2);
            const makers = makerAddresses.map(makerAddress => new MakerActor(makerAddress, stakingApiWrapper));

            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);

            // add makers to pool
            await Promise.all(makers.map(async maker => maker.joinStakingPoolAsMakerAsync(poolId)));
            await Promise.all(
                _.initial(makerAddresses).map(async makerAddress =>
                    poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress),
                ),
            );

            // check the number of makers in the pool
            const pool = await stakingApiWrapper.stakingContract.getStakingPool.callAsync(poolId);
            expect(pool.numberOfMakers, 'number of makers in pool').to.be.bignumber.equal(
                stakingConstants.DEFAULT_PARAMS.maximumMakersInPool,
            );

            const lastMakerAddress = _.last(makerAddresses) as string;
            // Try to add last maker to the pool
            const revertError = new StakingRevertErrors.MakerPoolAssignmentError(
                StakingRevertErrors.MakerPoolAssignmentErrorCodes.PoolIsFull,
                lastMakerAddress,
                poolId,
            );
            await poolOperator.addMakerToStakingPoolAsync(poolId, lastMakerAddress, revertError);
        });
        it('Operator should successfully decrease their share of rewards', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);

            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);

            // decrease operator share
            await poolOperator.decreaseStakingPoolOperatorShareAsync(poolId, operatorShare - 1);
        });
        it('Maker should successfuly decrease their share of rewards', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            const makerAddress = users[1];
            const maker = new MakerActor(makerAddress, stakingApiWrapper);
            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            // maker joins pool
            await maker.joinStakingPoolAsMakerAsync(poolId);
            // operator adds maker to pool
            await poolOperator.addMakerToStakingPoolAsync(poolId, makerAddress);

            // decrease operator share
            await maker.decreaseStakingPoolOperatorShareAsync(poolId, operatorShare - 1);
        });
        it('Should fail if operator tries to increase their share of rewards', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);

            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);

            const increasedOperatorShare = operatorShare + 1;
            const revertError = new StakingRevertErrors.OperatorShareError(
                StakingRevertErrors.OperatorShareErrorCodes.CanOnlyDecreaseOperatorShare,
                poolId,
                increasedOperatorShare,
            );
            // decrease operator share
            await poolOperator.decreaseStakingPoolOperatorShareAsync(poolId, increasedOperatorShare, revertError);
        });
        it('Should fail if operator calls decreaseStakingPoolOperatorShare but newOperatorShare == oldOperatorShare', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);

            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, false);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);

            const revertError = new StakingRevertErrors.OperatorShareError(
                StakingRevertErrors.OperatorShareErrorCodes.CanOnlyDecreaseOperatorShare,
                poolId,
                operatorShare,
            );
            // decrease operator share
            await poolOperator.decreaseStakingPoolOperatorShareAsync(poolId, operatorShare, revertError);
        });
        it('should fail to decrease operator share if not called by operator/registered maker', async () => {
            // test parameters
            const operatorAddress = users[0];
            const operatorShare = (39 / 100) * PPM_DENOMINATOR;
            const poolOperator = new PoolOperatorActor(operatorAddress, stakingApiWrapper);
            const makerAddress = users[1];
            const maker = new MakerActor(makerAddress, stakingApiWrapper);
            // create pool
            const poolId = await poolOperator.createStakingPoolAsync(operatorShare, true);
            expect(poolId).to.be.equal(stakingConstants.INITIAL_POOL_ID);
            await maker.decreaseStakingPoolOperatorShareAsync(
                poolId,
                operatorShare - 1,
                new StakingRevertErrors.OnlyCallableByPoolOperatorOrMakerError(makerAddress, poolId),
            );
        });
    });
});
// tslint:enable:no-unnecessary-type-assertion
