diff --git a/contracts/exchange/src/balance_stores/balance_store.ts b/contracts/exchange/src/balance_stores/balance_store.ts index 38962461f7..95631fe32d 100644 --- a/contracts/exchange/src/balance_stores/balance_store.ts +++ b/contracts/exchange/src/balance_stores/balance_store.ts @@ -1,5 +1,5 @@ import { BaseContract } from '@0x/base-contract'; -import { expect, TokenBalances } from '@0x/contracts-test-utils'; +import { constants, expect, TokenBalances } from '@0x/contracts-test-utils'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; @@ -37,7 +37,7 @@ export class BalanceStore { } /** - * Registers the given token owner in this balance store. The token owner's balane will be + * Registers the given token owner in this balance store. The token owner's balance will be * tracked in subsequent operations. * @param address Address of the token owner * @param name Name of the token owner @@ -83,8 +83,8 @@ export class BalanceStore { */ private _assertEthBalancesEqual(rhs: BalanceStore): void { for (const ownerAddress of [...this._ownerAddresses, ...rhs._ownerAddresses]) { - const thisBalance = _.get(this.balances.eth, [ownerAddress], new BigNumber(0)); - const rhsBalance = _.get(rhs.balances.eth, [ownerAddress], new BigNumber(0)); + const thisBalance = _.get(this.balances.eth, [ownerAddress], constants.ZERO_AMOUNT); + const rhsBalance = _.get(rhs.balances.eth, [ownerAddress], constants.ZERO_AMOUNT); expect(thisBalance, `${this._readableAddressName(ownerAddress)} ETH balance`).to.bignumber.equal( rhsBalance, ); @@ -98,8 +98,8 @@ export class BalanceStore { private _assertErc20BalancesEqual(rhs: BalanceStore): void { for (const ownerAddress of [...this._ownerAddresses, ...rhs._ownerAddresses]) { for (const tokenAddress of [...this._tokenAddresses.erc20, ...rhs._tokenAddresses.erc20]) { - const thisBalance = _.get(this.balances.erc20, [ownerAddress, tokenAddress], new BigNumber(0)); - const rhsBalance = _.get(rhs.balances.erc20, [ownerAddress, tokenAddress], new BigNumber(0)); + const thisBalance = _.get(this.balances.erc20, [ownerAddress, tokenAddress], constants.ZERO_AMOUNT); + const rhsBalance = _.get(rhs.balances.erc20, [ownerAddress, tokenAddress], constants.ZERO_AMOUNT); expect( thisBalance, `${this._readableAddressName(ownerAddress)} ${this._readableAddressName(tokenAddress)} balance`, diff --git a/contracts/integrations/test/actors/pool_operator.ts b/contracts/integrations/test/actors/pool_operator.ts index e2e81410cf..cd0d08381a 100644 --- a/contracts/integrations/test/actors/pool_operator.ts +++ b/contracts/integrations/test/actors/pool_operator.ts @@ -1,4 +1,4 @@ -import { constants } from '@0x/contracts-staking'; +import { constants, StakingPoolById } from '@0x/contracts-staking'; import { getRandomInteger } from '@0x/contracts-test-utils'; import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import * as _ from 'lodash'; @@ -7,7 +7,6 @@ import { validCreateStakingPoolAssertion, validDecreaseStakingPoolOperatorShareAssertion, } from '../function-assertions'; -import { SimulationEnvironment } from '../simulation/simulation'; import { AssertionResult } from '../utils/function_assertions'; import { Actor, Constructor } from './base'; @@ -39,15 +38,11 @@ export function PoolOperatorMixin(Base: TBase): TBase this.actor = (this as any) as Actor; // Register this mixin's assertion generators - if (this.actor.simulationEnvironment !== undefined) { - this.actor.simulationActions = { - ...this.actor.simulationActions, - validCreateStakingPool: this._validCreateStakingPool(this.actor.simulationEnvironment), - validDecreaseStakingPoolOperatorShare: this._validDecreaseStakingPoolOperatorShare( - this.actor.simulationEnvironment, - ), - }; - } + this.actor.simulationActions = { + ...this.actor.simulationActions, + validCreateStakingPool: this._validCreateStakingPool(), + validDecreaseStakingPoolOperatorShare: this._validDecreaseStakingPoolOperatorShare(), + }; } /** @@ -84,40 +79,29 @@ export function PoolOperatorMixin(Base: TBase): TBase ); } - private _getOperatorPoolIds(simulationEnvironment: SimulationEnvironment): string[] { - const operatorPools = _.pickBy( - simulationEnvironment.stakingPools, - pool => pool.operator === this.actor.address, - ); + private _getOperatorPoolIds(stakingPools: StakingPoolById): string[] { + const operatorPools = _.pickBy(stakingPools, pool => pool.operator === this.actor.address); return Object.keys(operatorPools); } - private async *_validCreateStakingPool( - simulationEnvironment: SimulationEnvironment, - ): AsyncIterableIterator { - const assertion = validCreateStakingPoolAssertion( - this.actor.deployment, - simulationEnvironment.stakingPools, - ); + private async *_validCreateStakingPool(): AsyncIterableIterator { + const { stakingPools } = this.actor.simulationEnvironment!; + const assertion = validCreateStakingPoolAssertion(this.actor.deployment, stakingPools); while (true) { const operatorShare = getRandomInteger(0, constants.PPM); yield assertion.executeAsync(operatorShare, false, { from: this.actor.address }); } } - private async *_validDecreaseStakingPoolOperatorShare( - simulationEnvironment: SimulationEnvironment, - ): AsyncIterableIterator { - const assertion = validDecreaseStakingPoolOperatorShareAssertion( - this.actor.deployment, - simulationEnvironment.stakingPools, - ); + private async *_validDecreaseStakingPoolOperatorShare(): AsyncIterableIterator { + const { stakingPools } = this.actor.simulationEnvironment!; + const assertion = validDecreaseStakingPoolOperatorShareAssertion(this.actor.deployment, stakingPools); while (true) { - const poolId = _.sample(this._getOperatorPoolIds(simulationEnvironment)); + const poolId = _.sample(this._getOperatorPoolIds(stakingPools)); if (poolId === undefined) { yield undefined; } else { - const operatorShare = getRandomInteger(0, simulationEnvironment.stakingPools[poolId].operatorShare); + const operatorShare = getRandomInteger(0, stakingPools[poolId].operatorShare); yield assertion.executeAsync(poolId, operatorShare, { from: this.actor.address }); } } diff --git a/contracts/integrations/test/actors/staker.ts b/contracts/integrations/test/actors/staker.ts index 4437c6ecac..351270e80b 100644 --- a/contracts/integrations/test/actors/staker.ts +++ b/contracts/integrations/test/actors/staker.ts @@ -4,7 +4,6 @@ import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { validMoveStakeAssertion, validStakeAssertion, validUnstakeAssertion } from '../function-assertions'; -import { SimulationEnvironment } from '../simulation/simulation'; import { AssertionResult } from '../utils/function_assertions'; import { Actor, Constructor } from './base'; @@ -37,14 +36,12 @@ export function StakerMixin(Base: TBase): TBase & Con }; // Register this mixin's assertion generators - if (this.actor.simulationEnvironment !== undefined) { - this.actor.simulationActions = { - ...this.actor.simulationActions, - validStake: this._validStake(this.actor.simulationEnvironment), - validUnstake: this._validUnstake(this.actor.simulationEnvironment), - validMoveStake: this._validMoveStake(this.actor.simulationEnvironment), - }; - } + this.actor.simulationActions = { + ...this.actor.simulationActions, + validStake: this._validStake(), + validUnstake: this._validUnstake(), + validMoveStake: this._validMoveStake(), + }; } /** @@ -66,30 +63,26 @@ export function StakerMixin(Base: TBase): TBase & Con } } - private async *_validStake( - simulationEnvironment: SimulationEnvironment, - ): AsyncIterableIterator { + private async *_validStake(): AsyncIterableIterator { const { zrx } = this.actor.deployment.tokens; - const { deployment, balanceStore, globalStake } = simulationEnvironment; + const { deployment, balanceStore, globalStake } = this.actor.simulationEnvironment!; const assertion = validStakeAssertion(deployment, balanceStore, globalStake, this.stake); while (true) { - await simulationEnvironment.balanceStore.updateErc20BalancesAsync(); - const zrxBalance = simulationEnvironment.balanceStore.balances.erc20[this.actor.address][zrx.address]; + await balanceStore.updateErc20BalancesAsync(); + const zrxBalance = balanceStore.balances.erc20[this.actor.address][zrx.address]; const amount = getRandomInteger(0, zrxBalance); yield assertion.executeAsync(amount, { from: this.actor.address }); } } - private async *_validUnstake( - simulationEnvironment: SimulationEnvironment, - ): AsyncIterableIterator { + private async *_validUnstake(): AsyncIterableIterator { const { stakingWrapper } = this.actor.deployment.staking; - const { deployment, balanceStore, globalStake } = simulationEnvironment; + const { deployment, balanceStore, globalStake } = this.actor.simulationEnvironment!; const assertion = validUnstakeAssertion(deployment, balanceStore, globalStake, this.stake); while (true) { - await simulationEnvironment.balanceStore.updateErc20BalancesAsync(); + await balanceStore.updateErc20BalancesAsync(); const undelegatedStake = await stakingWrapper.getOwnerStakeByStatus.callAsync( this.actor.address, StakeStatus.Undelegated, @@ -103,16 +96,9 @@ export function StakerMixin(Base: TBase): TBase & Con } } - private async *_validMoveStake( - simulationEnvironment: SimulationEnvironment, - ): AsyncIterableIterator { - const { deployment, globalStake } = simulationEnvironment; - const assertion = validMoveStakeAssertion( - deployment, - globalStake, - this.stake, - simulationEnvironment.stakingPools, - ); + private async *_validMoveStake(): AsyncIterableIterator { + const { deployment, globalStake, stakingPools } = this.actor.simulationEnvironment!; + const assertion = validMoveStakeAssertion(deployment, globalStake, this.stake, stakingPools); while (true) { const fromPoolId = _.sample(Object.keys(_.omit(this.stake[StakeStatus.Delegated], ['total']))); @@ -122,7 +108,7 @@ export function StakerMixin(Base: TBase): TBase & Con : (_.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus); const from = new StakeInfo(fromStatus, fromPoolId); - const toPoolId = _.sample(Object.keys(simulationEnvironment.stakingPools)); + const toPoolId = _.sample(Object.keys(stakingPools)); const toStatus = toPoolId === undefined ? StakeStatus.Undelegated diff --git a/contracts/integrations/test/actors/tslint.json b/contracts/integrations/test/actors/tslint.json index afa3246052..db108598aa 100644 --- a/contracts/integrations/test/actors/tslint.json +++ b/contracts/integrations/test/actors/tslint.json @@ -1,6 +1,7 @@ { "extends": ["@0x/tslint-config"], "rules": { - "max-classes-per-file": false + "max-classes-per-file": false, + "no-non-null-assertion": false } } diff --git a/contracts/integrations/test/function-assertions/createStakingPool.ts b/contracts/integrations/test/function-assertions/createStakingPool.ts index 116f1e458a..d4ac8eb332 100644 --- a/contracts/integrations/test/function-assertions/createStakingPool.ts +++ b/contracts/integrations/test/function-assertions/createStakingPool.ts @@ -19,8 +19,10 @@ export function validCreateStakingPoolAssertion( const { stakingWrapper } = deployment.staking; return new FunctionAssertion(stakingWrapper.createStakingPool, { + // Returns the expected ID of th created pool before: async () => { const lastPoolId = await stakingWrapper.lastPoolId.callAsync(); + // Effectively the last poolId + 1, but as a bytestring return `0x${new BigNumber(lastPoolId) .plus(1) .toString(16) @@ -35,9 +37,12 @@ export function validCreateStakingPoolAssertion( ) => { logUtils.log(`createStakingPool(${operatorShare}, ${addOperatorAsMaker}) => ${expectedPoolId}`); + // Checks the logs for the new poolId, verifies that it is as expected const log = result.receipt!.logs[0]; // tslint:disable-line:no-non-null-assertion const actualPoolId = (log as any).args.poolId; expect(actualPoolId).to.equal(expectedPoolId); + + // Adds the new pool to local state pools[actualPoolId] = { operator: txData.from as string, operatorShare, diff --git a/contracts/integrations/test/function-assertions/decreaseStakingPoolOperatorShare.ts b/contracts/integrations/test/function-assertions/decreaseStakingPoolOperatorShare.ts index 8c193dc79c..25d4977f5a 100644 --- a/contracts/integrations/test/function-assertions/decreaseStakingPoolOperatorShare.ts +++ b/contracts/integrations/test/function-assertions/decreaseStakingPoolOperatorShare.ts @@ -19,8 +19,10 @@ export function validDecreaseStakingPoolOperatorShareAssertion( after: async (_beforeInfo, _result: FunctionResult, poolId: string, expectedOperatorShare: number) => { logUtils.log(`decreaseStakingPoolOperatorShare(${poolId}, ${expectedOperatorShare})`); + // Checks that the on-chain pool's operator share has been updated. const { operatorShare } = await stakingWrapper.getStakingPool.callAsync(poolId); expect(operatorShare).to.bignumber.equal(expectedOperatorShare); + // Updates the pool in local state. pools[poolId].operatorShare = operatorShare; }, }); diff --git a/contracts/integrations/test/function-assertions/moveStake.ts b/contracts/integrations/test/function-assertions/moveStake.ts index a42551aeea..b4d2de7991 100644 --- a/contracts/integrations/test/function-assertions/moveStake.ts +++ b/contracts/integrations/test/function-assertions/moveStake.ts @@ -22,6 +22,56 @@ function decrementNextEpochBalance(stakeBalance: StoredBalance, amount: BigNumbe _.update(stakeBalance, ['nextEpochBalance'], balance => (balance || constants.ZERO_AMOUNT).minus(amount)); } +function updateNextEpochBalances( + globalStake: GlobalStakeByStatus, + ownerStake: OwnerStakeByStatus, + pools: StakingPoolById, + from: StakeInfo, + to: StakeInfo, + amount: BigNumber, +): string[] { + // The on-chain state of these updated pools will be verified in the `after` of the assertion. + const updatedPools = []; + + // Decrement next epoch balances associated with the `from` stake + if (from.status === StakeStatus.Undelegated) { + // Decrement owner undelegated stake + decrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount); + // Decrement global undelegated stake + decrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount); + } else if (from.status === StakeStatus.Delegated) { + // Decrement owner's delegated stake to this pool + decrementNextEpochBalance(ownerStake[StakeStatus.Delegated][from.poolId], amount); + // Decrement owner's total delegated stake + decrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount); + // Decrement global delegated stake + decrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount); + // Decrement pool's delegated stake + decrementNextEpochBalance(pools[from.poolId].delegatedStake, amount); + updatedPools.push(from.poolId); + } + + // Increment next epoch balances associated with the `to` stake + if (to.status === StakeStatus.Undelegated) { + incrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount); + incrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount); + } else if (to.status === StakeStatus.Delegated) { + // Initializes the balance for this pool if the user has not previously delegated to it + _.defaults(ownerStake[StakeStatus.Delegated], { + [to.poolId]: new StoredBalance(), + }); + // Increment owner's delegated stake to this pool + incrementNextEpochBalance(ownerStake[StakeStatus.Delegated][to.poolId], amount); + // Increment owner's total delegated stake + incrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount); + // Increment global delegated stake + incrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount); + // Increment pool's delegated stake + incrementNextEpochBalance(pools[to.poolId].delegatedStake, amount); + updatedPools.push(to.poolId); + } + return updatedPools; +} /** * Returns a FunctionAssertion for `moveStake` which assumes valid input is provided. The * FunctionAssertion checks that the staker's @@ -50,35 +100,11 @@ export function validMoveStakeAssertion( ); const owner = txData.from as string; - const updatedPools = []; - if (from.status === StakeStatus.Undelegated) { - decrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount); - decrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount); - } else if (from.status === StakeStatus.Delegated) { - _.defaults(ownerStake[StakeStatus.Delegated], { - [from.poolId]: new StoredBalance(), - }); - decrementNextEpochBalance(ownerStake[StakeStatus.Delegated][from.poolId], amount); - decrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount); - decrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount); - decrementNextEpochBalance(pools[from.poolId].delegatedStake, amount); - updatedPools.push(from.poolId); - } - if (to.status === StakeStatus.Undelegated) { - incrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount); - incrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount); - } else if (to.status === StakeStatus.Delegated) { - _.defaults(ownerStake[StakeStatus.Delegated], { - [to.poolId]: new StoredBalance(), - }); - incrementNextEpochBalance(ownerStake[StakeStatus.Delegated][to.poolId], amount); - incrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount); - incrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount); - incrementNextEpochBalance(pools[to.poolId].delegatedStake, amount); - updatedPools.push(to.poolId); - } + // Update local balances to match the expected result of this `moveStake` operation + const updatedPools = updateNextEpochBalances(globalStake, ownerStake, pools, from, to, amount); + // Fetches on-chain owner stake balances and checks against local balances const ownerUndelegatedStake = { ...new StoredBalance(), ...(await stakingWrapper.getOwnerStakeByStatus.callAsync(owner, StakeStatus.Undelegated)), @@ -90,6 +116,7 @@ export function validMoveStakeAssertion( expect(ownerUndelegatedStake).to.deep.equal(ownerStake[StakeStatus.Undelegated]); expect(ownerDelegatedStake).to.deep.equal(ownerStake[StakeStatus.Delegated].total); + // Fetches on-chain global stake balances and checks against local balances const globalUndelegatedStake = await stakingWrapper.getGlobalStakeByStatus.callAsync( StakeStatus.Undelegated, ); @@ -97,6 +124,7 @@ export function validMoveStakeAssertion( expect(globalUndelegatedStake).to.deep.equal(globalStake[StakeStatus.Undelegated]); expect(globalDelegatedStake).to.deep.equal(globalStake[StakeStatus.Delegated]); + // Fetches on-chain pool stake balances and checks against local balances for (const poolId of updatedPools) { const stakeDelegatedByOwner = await stakingWrapper.getStakeDelegatedToPoolByOwner.callAsync( owner, diff --git a/contracts/integrations/test/function-assertions/stake.ts b/contracts/integrations/test/function-assertions/stake.ts index 51bb35fc1a..9319679550 100644 --- a/contracts/integrations/test/function-assertions/stake.ts +++ b/contracts/integrations/test/function-assertions/stake.ts @@ -35,6 +35,7 @@ export function validStakeAssertion( return new FunctionAssertion(stakingWrapper.stake, { before: async (amount: BigNumber, txData: Partial) => { + // Simulates the transfer of ZRX from staker to vault const expectedBalances = LocalBalanceStore.create(balanceStore); expectedBalances.transferAsset( txData.from as string, @@ -52,22 +53,27 @@ export function validStakeAssertion( ) => { logUtils.log(`stake(${amount})`); + // Checks that the ZRX transfer updated balances as expected. await balanceStore.updateErc20BalancesAsync(); balanceStore.assertEquals(expectedBalances); + // Checks that the owner's undelegated stake has increased by the stake amount const ownerUndelegatedStake = await stakingWrapper.getOwnerStakeByStatus.callAsync( txData.from as string, StakeStatus.Undelegated, ); const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount); expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake); + // Updates local state accordingly ownerStake[StakeStatus.Undelegated] = expectedOwnerUndelegatedStake; + // Checks that the global undelegated stake has also increased by the stake amount const globalUndelegatedStake = await stakingWrapper.getGlobalStakeByStatus.callAsync( StakeStatus.Undelegated, ); const expectedGlobalUndelegatedStake = expectedUndelegatedStake(globalStake, amount); expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(expectedGlobalUndelegatedStake); + // Updates local state accordingly globalStake[StakeStatus.Undelegated] = expectedGlobalUndelegatedStake; }, }); diff --git a/contracts/integrations/test/function-assertions/unstake.ts b/contracts/integrations/test/function-assertions/unstake.ts index 0979aaa23a..9c17a943fd 100644 --- a/contracts/integrations/test/function-assertions/unstake.ts +++ b/contracts/integrations/test/function-assertions/unstake.ts @@ -35,6 +35,7 @@ export function validUnstakeAssertion( return new FunctionAssertion(stakingWrapper.unstake, { before: async (amount: BigNumber, txData: Partial) => { + // Simulates the transfer of ZRX from vault to staker const expectedBalances = LocalBalanceStore.create(balanceStore); expectedBalances.transferAsset( zrxVault.address, @@ -52,22 +53,27 @@ export function validUnstakeAssertion( ) => { logUtils.log(`unstake(${amount})`); + // Checks that the ZRX transfer updated balances as expected. await balanceStore.updateErc20BalancesAsync(); balanceStore.assertEquals(expectedBalances); + // Checks that the owner's undelegated stake has decreased by the stake amount const ownerUndelegatedStake = await stakingWrapper.getOwnerStakeByStatus.callAsync( txData.from as string, StakeStatus.Undelegated, ); const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount); expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake); + // Updates local state accordingly ownerStake[StakeStatus.Undelegated] = expectedOwnerUndelegatedStake; + // Checks that the global undelegated stake has also decreased by the stake amount const globalUndelegatedStake = await stakingWrapper.getGlobalStakeByStatus.callAsync( StakeStatus.Undelegated, ); const expectedGlobalUndelegatedStake = expectedUndelegatedStake(globalStake, amount); expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(expectedGlobalUndelegatedStake); + // Updates local state accordingly globalStake[StakeStatus.Undelegated] = expectedGlobalUndelegatedStake; }, }); diff --git a/contracts/integrations/test/internal-integration-tests/fillorder_test.ts b/contracts/integrations/test/internal-integration-tests/fillorder_test.ts index 048d03b5c3..d1abf141ea 100644 --- a/contracts/integrations/test/internal-integration-tests/fillorder_test.ts +++ b/contracts/integrations/test/internal-integration-tests/fillorder_test.ts @@ -6,6 +6,7 @@ import { ExchangeFillEventArgs, LocalBalanceStore, } from '@0x/contracts-exchange'; +import { ReferenceFunctions } from '@0x/contracts-exchange-libs'; import { constants as stakingConstants, IStakingEventsEpochEndedEventArgs, @@ -284,7 +285,11 @@ blockchainTests.resets('fillOrder integration tests', env => { ); // The rewards are split between the operator and delegator based on the pool's operatorShare - const operatorReward = rewardsAvailable.times(operatorShare).dividedToIntegerBy(constants.PPM_DENOMINATOR); + const operatorReward = ReferenceFunctions.getPartialAmountFloor( + new BigNumber(operatorShare), + new BigNumber(constants.PPM_DENOMINATOR), + rewardsAvailable, + ); const delegatorReward = rewardsAvailable.minus(operatorReward); // Finalize the pool. This should automatically pay the operator in WETH. @@ -363,7 +368,11 @@ blockchainTests.resets('fillOrder integration tests', env => { balanceStore.assertEquals(expectedBalances); // The rewards are split between the operator and delegator based on the pool's operatorShare - const operatorReward = rewardsAvailable.times(operatorShare).dividedToIntegerBy(constants.PPM_DENOMINATOR); + const operatorReward = ReferenceFunctions.getPartialAmountFloor( + new BigNumber(operatorShare), + new BigNumber(constants.PPM_DENOMINATOR), + rewardsAvailable, + ); // Finalize the pool. This should automatically pay the operator in WETH. const [finalizePoolReceipt] = await delegator.finalizePoolsAsync([poolId]);