Merge pull request #2431 from 0xProject/feature/fuzz/revert-assertions
`@0x/contracts-integrations`: Negative assertions for fuzzing
This commit is contained in:
		@@ -9,6 +9,10 @@
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Fuzz tests for `matchOrders` and `matchOrdersWithMaximalFill`.",
 | 
			
		||||
                "pr": 2437
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "note": "Add various negative assertions for fuzz tests",
 | 
			
		||||
                "pr": 2431
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,11 @@ import { filterLogsToArguments, web3Wrapper } from '@0x/contracts-test-utils';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import { BlockParamLiteral, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
 | 
			
		||||
 | 
			
		||||
import { validEndEpochAssertion } from '../assertions/endEpoch';
 | 
			
		||||
import {
 | 
			
		||||
    endEpochTooEarlyAssertion,
 | 
			
		||||
    endEpochUnfinalizedPoolsAssertion,
 | 
			
		||||
    validEndEpochAssertion,
 | 
			
		||||
} from '../assertions/endEpoch';
 | 
			
		||||
import { validFinalizePoolAssertion } from '../assertions/finalizePool';
 | 
			
		||||
import { AssertionResult } from '../assertions/function_assertion';
 | 
			
		||||
import { Pseudorandom } from '../utils/pseudorandom';
 | 
			
		||||
@@ -43,6 +47,7 @@ export function KeeperMixin<TBase extends Constructor>(Base: TBase): TBase & Con
 | 
			
		||||
                ...this.actor.simulationActions,
 | 
			
		||||
                validFinalizePool: this._validFinalizePool(),
 | 
			
		||||
                validEndEpoch: this._validEndEpoch(),
 | 
			
		||||
                invalidEndEpoch: this._invalidEndEpoch(),
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -118,6 +123,27 @@ export function KeeperMixin<TBase extends Constructor>(Base: TBase): TBase & Con
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async *_invalidEndEpoch(): AsyncIterableIterator<AssertionResult | void> {
 | 
			
		||||
            const { stakingWrapper } = this.actor.deployment.staking;
 | 
			
		||||
            while (true) {
 | 
			
		||||
                const { simulationEnvironment } = this.actor;
 | 
			
		||||
                const aggregatedStats = AggregatedStats.fromArray(
 | 
			
		||||
                    await stakingWrapper
 | 
			
		||||
                        .aggregatedStatsByEpoch(simulationEnvironment!.currentEpoch.minus(1))
 | 
			
		||||
                        .callAsync(),
 | 
			
		||||
                );
 | 
			
		||||
                const assertion = aggregatedStats.numPoolsToFinalize.isGreaterThan(0)
 | 
			
		||||
                    ? endEpochUnfinalizedPoolsAssertion(
 | 
			
		||||
                          this.actor.deployment,
 | 
			
		||||
                          simulationEnvironment!,
 | 
			
		||||
                          aggregatedStats.numPoolsToFinalize,
 | 
			
		||||
                      )
 | 
			
		||||
                    : endEpochTooEarlyAssertion(this.actor.deployment);
 | 
			
		||||
 | 
			
		||||
                yield assertion.executeAsync([], { from: this.actor.address });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async _fastForwardToNextEpochAsync(): Promise<void> {
 | 
			
		||||
            const { stakingWrapper } = this.actor.deployment.staking;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,14 @@
 | 
			
		||||
import { constants, StakingPoolById } from '@0x/contracts-staking';
 | 
			
		||||
import { constants as stakingConstants, StakingPoolById } from '@0x/contracts-staking';
 | 
			
		||||
import { constants } from '@0x/contracts-test-utils';
 | 
			
		||||
import '@azure/core-asynciterator-polyfill';
 | 
			
		||||
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { validCreateStakingPoolAssertion } from '../assertions/createStakingPool';
 | 
			
		||||
import { validDecreaseStakingPoolOperatorShareAssertion } from '../assertions/decreaseStakingPoolOperatorShare';
 | 
			
		||||
import { invalidCreateStakingPoolAssertion, validCreateStakingPoolAssertion } from '../assertions/createStakingPool';
 | 
			
		||||
import {
 | 
			
		||||
    invalidDecreaseStakingPoolOperatorShareAssertion,
 | 
			
		||||
    validDecreaseStakingPoolOperatorShareAssertion,
 | 
			
		||||
} from '../assertions/decreaseStakingPoolOperatorShare';
 | 
			
		||||
import { AssertionResult } from '../assertions/function_assertion';
 | 
			
		||||
import { Distributions, Pseudorandom } from '../utils/pseudorandom';
 | 
			
		||||
 | 
			
		||||
@@ -41,7 +45,9 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
 | 
			
		||||
            this.actor.simulationActions = {
 | 
			
		||||
                ...this.actor.simulationActions,
 | 
			
		||||
                validCreateStakingPool: this._validCreateStakingPool(),
 | 
			
		||||
                invalidCreateStakingPool: this._invalidCreateStakingPool(),
 | 
			
		||||
                validDecreaseStakingPoolOperatorShare: this._validDecreaseStakingPoolOperatorShare(),
 | 
			
		||||
                invalidDecreaseStakingPoolOperatorShare: this._invalidDecreaseStakingPoolOperatorShare(),
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -85,8 +91,20 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
 | 
			
		||||
            while (true) {
 | 
			
		||||
                const operatorShare = Pseudorandom.integer(
 | 
			
		||||
                    0,
 | 
			
		||||
                    constants.PPM,
 | 
			
		||||
                    Distributions.Kumaraswamy(0.2, 0.2),
 | 
			
		||||
                    stakingConstants.PPM,
 | 
			
		||||
                    Distributions.Kumaraswamy(),
 | 
			
		||||
                ).toNumber();
 | 
			
		||||
                yield assertion.executeAsync([operatorShare, false], { from: this.actor.address });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async *_invalidCreateStakingPool(): AsyncIterableIterator<AssertionResult> {
 | 
			
		||||
            const assertion = invalidCreateStakingPoolAssertion(this.actor.deployment);
 | 
			
		||||
            while (true) {
 | 
			
		||||
                const operatorShare = Pseudorandom.integer(
 | 
			
		||||
                    (stakingConstants.PPM as number) + 1,
 | 
			
		||||
                    constants.MAX_UINT32,
 | 
			
		||||
                    Distributions.Kumaraswamy(),
 | 
			
		||||
                ).toNumber();
 | 
			
		||||
                yield assertion.executeAsync([operatorShare, false], { from: this.actor.address });
 | 
			
		||||
            }
 | 
			
		||||
@@ -103,7 +121,25 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
 | 
			
		||||
                    const operatorShare = Pseudorandom.integer(
 | 
			
		||||
                        0,
 | 
			
		||||
                        stakingPools[poolId].operatorShare,
 | 
			
		||||
                        Distributions.Kumaraswamy(0.2, 0.2),
 | 
			
		||||
                        Distributions.Kumaraswamy(),
 | 
			
		||||
                    ).toNumber();
 | 
			
		||||
                    yield assertion.executeAsync([poolId, operatorShare], { from: this.actor.address });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async *_invalidDecreaseStakingPoolOperatorShare(): AsyncIterableIterator<AssertionResult | void> {
 | 
			
		||||
            const { stakingPools } = this.actor.simulationEnvironment!;
 | 
			
		||||
            const assertion = invalidDecreaseStakingPoolOperatorShareAssertion(this.actor.deployment);
 | 
			
		||||
            while (true) {
 | 
			
		||||
                const poolId = Pseudorandom.sample(this._getOperatorPoolIds(stakingPools));
 | 
			
		||||
                if (poolId === undefined) {
 | 
			
		||||
                    yield undefined;
 | 
			
		||||
                } else {
 | 
			
		||||
                    const operatorShare = Pseudorandom.integer(
 | 
			
		||||
                        (stakingPools[poolId].operatorShare as number) + 1,
 | 
			
		||||
                        constants.MAX_UINT32,
 | 
			
		||||
                        Distributions.Kumaraswamy(),
 | 
			
		||||
                    ).toNumber();
 | 
			
		||||
                    yield assertion.executeAsync([poolId, operatorShare], { from: this.actor.address });
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,18 @@
 | 
			
		||||
import { OwnerStakeByStatus, StakeInfo, StakeStatus, StoredBalance } from '@0x/contracts-staking';
 | 
			
		||||
import { constants } from '@0x/contracts-test-utils';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import '@azure/core-asynciterator-polyfill';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { AssertionResult } from '../assertions/function_assertion';
 | 
			
		||||
import { validMoveStakeAssertion } from '../assertions/moveStake';
 | 
			
		||||
import { assetProxyTransferFailedAssertion } from '../assertions/generic_assertions';
 | 
			
		||||
import { moveStakeNonexistentPoolAssertion, validMoveStakeAssertion } from '../assertions/moveStake';
 | 
			
		||||
import { validStakeAssertion } from '../assertions/stake';
 | 
			
		||||
import { validUnstakeAssertion } from '../assertions/unstake';
 | 
			
		||||
import { validWithdrawDelegatorRewardsAssertion } from '../assertions/withdrawDelegatorRewards';
 | 
			
		||||
import { invalidUnstakeAssertion, validUnstakeAssertion } from '../assertions/unstake';
 | 
			
		||||
import {
 | 
			
		||||
    invalidWithdrawDelegatorRewardsAssertion,
 | 
			
		||||
    validWithdrawDelegatorRewardsAssertion,
 | 
			
		||||
} from '../assertions/withdrawDelegatorRewards';
 | 
			
		||||
import { Pseudorandom } from '../utils/pseudorandom';
 | 
			
		||||
 | 
			
		||||
import { Actor, Constructor } from './base';
 | 
			
		||||
@@ -45,9 +50,13 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
 | 
			
		||||
            this.actor.simulationActions = {
 | 
			
		||||
                ...this.actor.simulationActions,
 | 
			
		||||
                validStake: this._validStake(),
 | 
			
		||||
                invalidStake: this._invalidStake(),
 | 
			
		||||
                validUnstake: this._validUnstake(),
 | 
			
		||||
                invalidUnstake: this._invalidUnstake(),
 | 
			
		||||
                validMoveStake: this._validMoveStake(),
 | 
			
		||||
                moveStakeNonexistentPool: this._moveStakeNonexistentPool(),
 | 
			
		||||
                validWithdrawDelegatorRewards: this._validWithdrawDelegatorRewards(),
 | 
			
		||||
                invalidWithdrawDelegatorRewards: this._invalidWithdrawDelegatorRewards(),
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -84,6 +93,19 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async *_invalidStake(): AsyncIterableIterator<AssertionResult> {
 | 
			
		||||
            const { zrx } = this.actor.deployment.tokens;
 | 
			
		||||
            const { deployment, balanceStore } = this.actor.simulationEnvironment!;
 | 
			
		||||
            const assertion = assetProxyTransferFailedAssertion(deployment.staking.stakingWrapper, 'stake');
 | 
			
		||||
 | 
			
		||||
            while (true) {
 | 
			
		||||
                await balanceStore.updateErc20BalancesAsync();
 | 
			
		||||
                const zrxBalance = balanceStore.balances.erc20[this.actor.address][zrx.address];
 | 
			
		||||
                const amount = Pseudorandom.integer(zrxBalance.plus(1), constants.MAX_UINT256);
 | 
			
		||||
                yield assertion.executeAsync([amount], { from: this.actor.address });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async *_validUnstake(): AsyncIterableIterator<AssertionResult> {
 | 
			
		||||
            const { stakingWrapper } = this.actor.deployment.staking;
 | 
			
		||||
            const { deployment, balanceStore } = this.actor.simulationEnvironment!;
 | 
			
		||||
@@ -103,51 +125,108 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async *_validMoveStake(): AsyncIterableIterator<AssertionResult> {
 | 
			
		||||
            const { deployment, stakingPools } = this.actor.simulationEnvironment!;
 | 
			
		||||
            const assertion = validMoveStakeAssertion(deployment, this.actor.simulationEnvironment!, this.stake);
 | 
			
		||||
        private async *_invalidUnstake(): AsyncIterableIterator<AssertionResult> {
 | 
			
		||||
            const { stakingWrapper } = this.actor.deployment.staking;
 | 
			
		||||
            const { deployment, balanceStore } = this.actor.simulationEnvironment!;
 | 
			
		||||
 | 
			
		||||
            while (true) {
 | 
			
		||||
                const { currentEpoch } = this.actor.simulationEnvironment!;
 | 
			
		||||
                // Pick a random pool that this staker has delegated to (undefined if no such pools exist)
 | 
			
		||||
                const fromPoolId = Pseudorandom.sample(
 | 
			
		||||
                    Object.keys(_.omit(this.stake[StakeStatus.Delegated], ['total'])),
 | 
			
		||||
                await balanceStore.updateErc20BalancesAsync();
 | 
			
		||||
                const undelegatedStake = await stakingWrapper
 | 
			
		||||
                    .getOwnerStakeByStatus(this.actor.address, StakeStatus.Undelegated)
 | 
			
		||||
                    .callAsync();
 | 
			
		||||
                const withdrawableStake = BigNumber.min(
 | 
			
		||||
                    undelegatedStake.currentEpochBalance,
 | 
			
		||||
                    undelegatedStake.nextEpochBalance,
 | 
			
		||||
                );
 | 
			
		||||
                // The `from` status must be Undelegated if the staker isn't delegated to any pools
 | 
			
		||||
                // at the moment, or if the chosen pool is unfinalized
 | 
			
		||||
                const fromStatus =
 | 
			
		||||
                    fromPoolId === undefined || stakingPools[fromPoolId].lastFinalized.isLessThan(currentEpoch.minus(1))
 | 
			
		||||
                        ? StakeStatus.Undelegated
 | 
			
		||||
                        : (Pseudorandom.sample(
 | 
			
		||||
                              [StakeStatus.Undelegated, StakeStatus.Delegated],
 | 
			
		||||
                              [0.2, 0.8], // 20% chance of `Undelegated`, 80% chance of `Delegated`
 | 
			
		||||
                          ) as StakeStatus);
 | 
			
		||||
                const from = new StakeInfo(fromStatus, fromPoolId);
 | 
			
		||||
                const assertion = invalidUnstakeAssertion(deployment, withdrawableStake);
 | 
			
		||||
                const amount = Pseudorandom.integer(withdrawableStake.plus(1), constants.MAX_UINT256);
 | 
			
		||||
                yield assertion.executeAsync([amount], { from: this.actor.address });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
                // Pick a random pool to move the stake to
 | 
			
		||||
                const toPoolId = Pseudorandom.sample(Object.keys(stakingPools));
 | 
			
		||||
                // The `from` status must be Undelegated if no pools exist in the simulation yet,
 | 
			
		||||
                // or if the chosen pool is unfinalized
 | 
			
		||||
                const toStatus =
 | 
			
		||||
                    toPoolId === undefined || stakingPools[toPoolId].lastFinalized.isLessThan(currentEpoch.minus(1))
 | 
			
		||||
                        ? StakeStatus.Undelegated
 | 
			
		||||
                        : (Pseudorandom.sample(
 | 
			
		||||
                              [StakeStatus.Undelegated, StakeStatus.Delegated],
 | 
			
		||||
                              [0.2, 0.8], // 20% chance of `Undelegated`, 80% chance of `Delegated`
 | 
			
		||||
                          ) as StakeStatus);
 | 
			
		||||
                const to = new StakeInfo(toStatus, toPoolId);
 | 
			
		||||
        private _validMoveParams(): [StakeInfo, StakeInfo, BigNumber] {
 | 
			
		||||
            const { stakingPools, currentEpoch } = this.actor.simulationEnvironment!;
 | 
			
		||||
            // Pick a random pool that this staker has delegated to (undefined if no such pools exist)
 | 
			
		||||
            const fromPoolId = Pseudorandom.sample(Object.keys(_.omit(this.stake[StakeStatus.Delegated], ['total'])));
 | 
			
		||||
            // The `from` status must be Undelegated if the staker isn't delegated to any pools
 | 
			
		||||
            // at the moment, or if the chosen pool is unfinalized
 | 
			
		||||
            const fromStatus =
 | 
			
		||||
                fromPoolId === undefined || stakingPools[fromPoolId].lastFinalized.isLessThan(currentEpoch.minus(1))
 | 
			
		||||
                    ? StakeStatus.Undelegated
 | 
			
		||||
                    : (Pseudorandom.sample(
 | 
			
		||||
                          [StakeStatus.Undelegated, StakeStatus.Delegated],
 | 
			
		||||
                          [0.2, 0.8], // 20% chance of `Undelegated`, 80% chance of `Delegated`
 | 
			
		||||
                      ) as StakeStatus);
 | 
			
		||||
            const from = new StakeInfo(fromStatus, fromPoolId);
 | 
			
		||||
 | 
			
		||||
                // The next epoch balance of the `from` stake is the amount that can be moved
 | 
			
		||||
                const moveableStake =
 | 
			
		||||
                    from.status === StakeStatus.Undelegated
 | 
			
		||||
                        ? this.stake[StakeStatus.Undelegated].nextEpochBalance
 | 
			
		||||
                        : this.stake[StakeStatus.Delegated][from.poolId].nextEpochBalance;
 | 
			
		||||
                const amount = Pseudorandom.integer(0, moveableStake);
 | 
			
		||||
            // Pick a random pool to move the stake to
 | 
			
		||||
            const toPoolId = Pseudorandom.sample(Object.keys(stakingPools));
 | 
			
		||||
            // The `from` status must be Undelegated if no pools exist in the simulation yet,
 | 
			
		||||
            // or if the chosen pool is unfinalized
 | 
			
		||||
            const toStatus =
 | 
			
		||||
                toPoolId === undefined || stakingPools[toPoolId].lastFinalized.isLessThan(currentEpoch.minus(1))
 | 
			
		||||
                    ? StakeStatus.Undelegated
 | 
			
		||||
                    : (Pseudorandom.sample(
 | 
			
		||||
                          [StakeStatus.Undelegated, StakeStatus.Delegated],
 | 
			
		||||
                          [0.2, 0.8], // 20% chance of `Undelegated`, 80% chance of `Delegated`
 | 
			
		||||
                      ) as StakeStatus);
 | 
			
		||||
            const to = new StakeInfo(toStatus, toPoolId);
 | 
			
		||||
 | 
			
		||||
            // The next epoch balance of the `from` stake is the amount that can be moved
 | 
			
		||||
            const moveableStake =
 | 
			
		||||
                from.status === StakeStatus.Undelegated
 | 
			
		||||
                    ? this.stake[StakeStatus.Undelegated].nextEpochBalance
 | 
			
		||||
                    : this.stake[StakeStatus.Delegated][from.poolId].nextEpochBalance;
 | 
			
		||||
            const amount = Pseudorandom.integer(0, moveableStake);
 | 
			
		||||
 | 
			
		||||
            return [from, to, amount];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async *_validMoveStake(): AsyncIterableIterator<AssertionResult> {
 | 
			
		||||
            const assertion = validMoveStakeAssertion(
 | 
			
		||||
                this.actor.deployment,
 | 
			
		||||
                this.actor.simulationEnvironment!,
 | 
			
		||||
                this.stake,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            while (true) {
 | 
			
		||||
                const [from, to, amount] = this._validMoveParams();
 | 
			
		||||
                yield assertion.executeAsync([from, to, amount], { from: this.actor.address });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async *_moveStakeNonexistentPool(): AsyncIterableIterator<AssertionResult> {
 | 
			
		||||
            while (true) {
 | 
			
		||||
                const [from, to, amount] = this._validMoveParams();
 | 
			
		||||
 | 
			
		||||
                // If there is 0 moveable stake for the sampled `to` pool, we need to mutate the
 | 
			
		||||
                // `from` info, otherwise `moveStake` will just noop
 | 
			
		||||
                if (amount.isZero()) {
 | 
			
		||||
                    from.poolId = Pseudorandom.hex();
 | 
			
		||||
                    // Status must be delegated and amount must be nonzero to trigger _assertStakingPoolExists
 | 
			
		||||
                    from.status = StakeStatus.Delegated;
 | 
			
		||||
                    const randomAmount = Pseudorandom.integer(1, constants.MAX_UINT256);
 | 
			
		||||
                    const assertion = moveStakeNonexistentPoolAssertion(this.actor.deployment, from.poolId);
 | 
			
		||||
                    yield assertion.executeAsync([from, to, randomAmount], { from: this.actor.address });
 | 
			
		||||
                } else {
 | 
			
		||||
                    // One or both of the `from` and `to` poolId are invalid
 | 
			
		||||
                    const infoToMutate = Pseudorandom.sample([[from], [to], [from, to]]);
 | 
			
		||||
                    let nonExistentPoolId;
 | 
			
		||||
                    for (const info of infoToMutate!) {
 | 
			
		||||
                        info.poolId = Pseudorandom.hex();
 | 
			
		||||
                        nonExistentPoolId = nonExistentPoolId || info.poolId;
 | 
			
		||||
                        // Status must be delegated and amount must be nonzero to trigger _assertStakingPoolExists
 | 
			
		||||
                        info.status = StakeStatus.Delegated;
 | 
			
		||||
                    }
 | 
			
		||||
                    const assertion = moveStakeNonexistentPoolAssertion(
 | 
			
		||||
                        this.actor.deployment,
 | 
			
		||||
                        nonExistentPoolId as string,
 | 
			
		||||
                    );
 | 
			
		||||
                    yield assertion.executeAsync([from, to, amount], { from: this.actor.address });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async *_validWithdrawDelegatorRewards(): AsyncIterableIterator<AssertionResult | void> {
 | 
			
		||||
            const { stakingPools } = this.actor.simulationEnvironment!;
 | 
			
		||||
            const assertion = validWithdrawDelegatorRewardsAssertion(
 | 
			
		||||
@@ -169,6 +248,26 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async *_invalidWithdrawDelegatorRewards(): AsyncIterableIterator<AssertionResult | void> {
 | 
			
		||||
            const { stakingPools } = this.actor.simulationEnvironment!;
 | 
			
		||||
            const assertion = invalidWithdrawDelegatorRewardsAssertion(
 | 
			
		||||
                this.actor.deployment,
 | 
			
		||||
                this.actor.simulationEnvironment!,
 | 
			
		||||
            );
 | 
			
		||||
            while (true) {
 | 
			
		||||
                const prevEpoch = this.actor.simulationEnvironment!.currentEpoch.minus(1);
 | 
			
		||||
                // Pick an unfinalized pool
 | 
			
		||||
                const poolId = Pseudorandom.sample(
 | 
			
		||||
                    Object.keys(stakingPools).filter(id => stakingPools[id].lastFinalized.isLessThan(prevEpoch)),
 | 
			
		||||
                );
 | 
			
		||||
                if (poolId === undefined) {
 | 
			
		||||
                    yield;
 | 
			
		||||
                } else {
 | 
			
		||||
                    yield assertion.executeAsync([poolId], { from: this.actor.address });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import { StoredBalance } from '@0x/contracts-staking';
 | 
			
		||||
import { StakingRevertErrors, StoredBalance } from '@0x/contracts-staking';
 | 
			
		||||
import { expect } from '@0x/contracts-test-utils';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import { BigNumber, hexUtils } from '@0x/utils';
 | 
			
		||||
import { TxData } from 'ethereum-types';
 | 
			
		||||
 | 
			
		||||
import { DeploymentManager } from '../deployment_manager';
 | 
			
		||||
@@ -22,14 +22,11 @@ export function validCreateStakingPoolAssertion(
 | 
			
		||||
    const { stakingWrapper } = deployment.staking;
 | 
			
		||||
 | 
			
		||||
    return new FunctionAssertion<[number, boolean], string, string>(stakingWrapper, 'createStakingPool', {
 | 
			
		||||
        // Returns the expected ID of th created pool
 | 
			
		||||
        // Returns the expected ID of the 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)
 | 
			
		||||
                .padStart(64, '0')}`;
 | 
			
		||||
            return hexUtils.leftPad(new BigNumber(lastPoolId).plus(1));
 | 
			
		||||
        },
 | 
			
		||||
        after: async (
 | 
			
		||||
            expectedPoolId: string,
 | 
			
		||||
@@ -57,4 +54,38 @@ export function validCreateStakingPoolAssertion(
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a FunctionAssertion for `createStakingPool` which assumes an invalid operator share (i.e.
 | 
			
		||||
 * greater than 1,000,000) is provided. The FunctionAssertion checks that the transaction reverts
 | 
			
		||||
 * with the expected OperatorShareError.
 | 
			
		||||
 */
 | 
			
		||||
export function invalidCreateStakingPoolAssertion(
 | 
			
		||||
    deployment: DeploymentManager,
 | 
			
		||||
): FunctionAssertion<[number, boolean], string, string> {
 | 
			
		||||
    const { stakingWrapper } = deployment.staking;
 | 
			
		||||
 | 
			
		||||
    return new FunctionAssertion<[number, boolean], string, string>(stakingWrapper, 'createStakingPool', {
 | 
			
		||||
        // Returns the poolId we are expecting to revert with
 | 
			
		||||
        before: async () => {
 | 
			
		||||
            const lastPoolId = await stakingWrapper.lastPoolId().callAsync();
 | 
			
		||||
            // Effectively the last poolId + 1, but as a bytestring
 | 
			
		||||
            return hexUtils.leftPad(new BigNumber(lastPoolId).plus(1));
 | 
			
		||||
        },
 | 
			
		||||
        after: async (expectedPoolId: string, result: FunctionResult, args: [number, boolean]) => {
 | 
			
		||||
            // Ensure that the tx reverted.
 | 
			
		||||
            expect(result.success).to.be.false();
 | 
			
		||||
 | 
			
		||||
            // Check revert error
 | 
			
		||||
            const [operatorShare] = args;
 | 
			
		||||
            expect(result.data).to.equal(
 | 
			
		||||
                new StakingRevertErrors.OperatorShareError(
 | 
			
		||||
                    StakingRevertErrors.OperatorShareErrorCodes.OperatorShareTooLarge,
 | 
			
		||||
                    expectedPoolId,
 | 
			
		||||
                    operatorShare,
 | 
			
		||||
                ),
 | 
			
		||||
            );
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
/* tslint:enable:no-non-null-assertion*/
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { StakingPoolById } from '@0x/contracts-staking';
 | 
			
		||||
import { constants, StakingPoolById, StakingRevertErrors } from '@0x/contracts-staking';
 | 
			
		||||
import { expect } from '@0x/contracts-test-utils';
 | 
			
		||||
import { TxData } from 'ethereum-types';
 | 
			
		||||
 | 
			
		||||
@@ -32,3 +32,37 @@ export function validDecreaseStakingPoolOperatorShareAssertion(
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a FunctionAssertion for `decreaseStakingPoolOperatorShare` which assumes the given
 | 
			
		||||
 * operator share is larger than the current operator share for the pool. The FunctionAssertion
 | 
			
		||||
 * checks that the transaction reverts with the correct error in this scenario.
 | 
			
		||||
 */
 | 
			
		||||
export function invalidDecreaseStakingPoolOperatorShareAssertion(
 | 
			
		||||
    deployment: DeploymentManager,
 | 
			
		||||
): FunctionAssertion<[string, number], void, void> {
 | 
			
		||||
    const { stakingWrapper } = deployment.staking;
 | 
			
		||||
 | 
			
		||||
    return new FunctionAssertion<[string, number], void, void>(stakingWrapper, 'decreaseStakingPoolOperatorShare', {
 | 
			
		||||
        after: async (_beforeInfo: void, result: FunctionResult, args: [string, number], _txData: Partial<TxData>) => {
 | 
			
		||||
            // Ensure that the tx reverted.
 | 
			
		||||
            expect(result.success).to.be.false();
 | 
			
		||||
 | 
			
		||||
            // Check revert error
 | 
			
		||||
            const [poolId, operatorShare] = args;
 | 
			
		||||
            const expectedError =
 | 
			
		||||
                operatorShare > constants.PPM
 | 
			
		||||
                    ? new StakingRevertErrors.OperatorShareError(
 | 
			
		||||
                          StakingRevertErrors.OperatorShareErrorCodes.OperatorShareTooLarge,
 | 
			
		||||
                          poolId,
 | 
			
		||||
                          operatorShare,
 | 
			
		||||
                      )
 | 
			
		||||
                    : new StakingRevertErrors.OperatorShareError(
 | 
			
		||||
                          StakingRevertErrors.OperatorShareErrorCodes.CanOnlyDecreaseOperatorShare,
 | 
			
		||||
                          poolId,
 | 
			
		||||
                          operatorShare,
 | 
			
		||||
                      );
 | 
			
		||||
            expect(result.data).to.equal(expectedError);
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import {
 | 
			
		||||
    StakingEpochEndedEventArgs,
 | 
			
		||||
    StakingEpochFinalizedEventArgs,
 | 
			
		||||
    StakingEvents,
 | 
			
		||||
    StakingRevertErrors,
 | 
			
		||||
} from '@0x/contracts-staking';
 | 
			
		||||
import { constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
@@ -117,3 +118,49 @@ export function validEndEpochAssertion(
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a FunctionAssertion for `endEpoch` which assumes it has been called while the previous
 | 
			
		||||
 * epoch hasn't been fully finalized. Checks that the transaction reverts with PreviousEpochNotFinalizedError.
 | 
			
		||||
 */
 | 
			
		||||
export function endEpochUnfinalizedPoolsAssertion(
 | 
			
		||||
    deployment: DeploymentManager,
 | 
			
		||||
    simulationEnvironment: SimulationEnvironment,
 | 
			
		||||
    numPoolsToFinalizeFromPrevEpoch: BigNumber,
 | 
			
		||||
): FunctionAssertion<[], void, void> {
 | 
			
		||||
    return new FunctionAssertion(deployment.staking.stakingWrapper, 'endEpoch', {
 | 
			
		||||
        after: async (_beforeInfo: void, result: FunctionResult) => {
 | 
			
		||||
            // Ensure that the tx reverted.
 | 
			
		||||
            expect(result.success).to.be.false();
 | 
			
		||||
 | 
			
		||||
            // Check revert error
 | 
			
		||||
            expect(result.data).to.equal(
 | 
			
		||||
                new StakingRevertErrors.PreviousEpochNotFinalizedError(
 | 
			
		||||
                    simulationEnvironment.currentEpoch.minus(1),
 | 
			
		||||
                    numPoolsToFinalizeFromPrevEpoch,
 | 
			
		||||
                ),
 | 
			
		||||
            );
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a FunctionAssertion for `endEpoch` which assumes it has been called before the full epoch
 | 
			
		||||
 * duration has elapsed. Checks that the transaction reverts with BlockTimestampTooLowError.
 | 
			
		||||
 */
 | 
			
		||||
export function endEpochTooEarlyAssertion(deployment: DeploymentManager): FunctionAssertion<[], void, void> {
 | 
			
		||||
    const { stakingWrapper } = deployment.staking;
 | 
			
		||||
    return new FunctionAssertion(stakingWrapper, 'endEpoch', {
 | 
			
		||||
        after: async (_beforeInfo: void, result: FunctionResult) => {
 | 
			
		||||
            // Ensure that the tx reverted.
 | 
			
		||||
            expect(result.success).to.be.false();
 | 
			
		||||
 | 
			
		||||
            // Check revert error
 | 
			
		||||
            const epochEndTime = await stakingWrapper.getCurrentEpochEarliestEndTimeInSeconds().callAsync();
 | 
			
		||||
            const lastBlockTime = await deployment.web3Wrapper.getBlockTimestampAsync('latest');
 | 
			
		||||
            expect(result.data).to.equal(
 | 
			
		||||
                new StakingRevertErrors.BlockTimestampTooLowError(epochEndTime, lastBlockTime),
 | 
			
		||||
            );
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -166,20 +166,24 @@ export function validFinalizePoolAssertion(
 | 
			
		||||
            // Check that pool rewards have increased.
 | 
			
		||||
            const poolRewards = await stakingWrapper.rewardsByPoolId(poolId).callAsync();
 | 
			
		||||
            expect(poolRewards).to.bignumber.equal(beforeInfo.poolRewards.plus(membersReward));
 | 
			
		||||
            // Check that cumulative rewards have increased.
 | 
			
		||||
            const [
 | 
			
		||||
                mostRecentCumulativeRewards,
 | 
			
		||||
                cumulativeRewardsLastStored,
 | 
			
		||||
            ] = await stakingWrapper.getMostRecentCumulativeReward(poolId).callAsync();
 | 
			
		||||
            expect(cumulativeRewardsLastStored).to.bignumber.equal(currentEpoch);
 | 
			
		||||
            let [numerator, denominator] = ReferenceFunctions.LibFractions.add(
 | 
			
		||||
                beforeInfo.mostRecentCumulativeRewards.numerator,
 | 
			
		||||
                beforeInfo.mostRecentCumulativeRewards.denominator,
 | 
			
		||||
                membersReward,
 | 
			
		||||
                beforeInfo.poolStats.membersStake,
 | 
			
		||||
            );
 | 
			
		||||
            [numerator, denominator] = ReferenceFunctions.LibFractions.normalize(numerator, denominator);
 | 
			
		||||
            expect(mostRecentCumulativeRewards).to.deep.equal({ numerator, denominator });
 | 
			
		||||
 | 
			
		||||
            if (membersReward.isGreaterThan(0)) {
 | 
			
		||||
                // Check that cumulative rewards have increased.
 | 
			
		||||
                const [
 | 
			
		||||
                    mostRecentCumulativeRewards,
 | 
			
		||||
                    cumulativeRewardsLastStored,
 | 
			
		||||
                ] = await stakingWrapper.getMostRecentCumulativeReward(poolId).callAsync();
 | 
			
		||||
                expect(cumulativeRewardsLastStored).to.bignumber.equal(currentEpoch);
 | 
			
		||||
 | 
			
		||||
                let [numerator, denominator] = ReferenceFunctions.LibFractions.add(
 | 
			
		||||
                    beforeInfo.mostRecentCumulativeRewards.numerator,
 | 
			
		||||
                    beforeInfo.mostRecentCumulativeRewards.denominator,
 | 
			
		||||
                    membersReward,
 | 
			
		||||
                    beforeInfo.poolStats.membersStake,
 | 
			
		||||
                );
 | 
			
		||||
                [numerator, denominator] = ReferenceFunctions.LibFractions.normalize(numerator, denominator);
 | 
			
		||||
                expect(mostRecentCumulativeRewards).to.deep.equal({ numerator, denominator });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Check that aggregated stats have been updated
 | 
			
		||||
            const aggregatedStats = AggregatedStats.fromArray(
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,24 @@
 | 
			
		||||
import { BaseContract } from '@0x/base-contract';
 | 
			
		||||
import { expect } from '@0x/contracts-test-utils';
 | 
			
		||||
import { RevertReason } from '@0x/types';
 | 
			
		||||
import { StringRevertError } from '@0x/utils';
 | 
			
		||||
 | 
			
		||||
import { FunctionAssertion, FunctionResult } from './function_assertion';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a generic FunctionAssertion for the given contract function, asserting that the
 | 
			
		||||
 * function call reverts in an asset proxy contract with TRANSFER_FAILED.
 | 
			
		||||
 */
 | 
			
		||||
export function assetProxyTransferFailedAssertion<TArgs extends any[]>(
 | 
			
		||||
    contract: BaseContract,
 | 
			
		||||
    functionName: string,
 | 
			
		||||
): FunctionAssertion<TArgs, void, void> {
 | 
			
		||||
    return new FunctionAssertion(contract, functionName, {
 | 
			
		||||
        after: async (_beforeInfo: void, result: FunctionResult) => {
 | 
			
		||||
            // Ensure that the tx reverted.
 | 
			
		||||
            expect(result.success).to.be.false();
 | 
			
		||||
            // Check revert error
 | 
			
		||||
            expect(result.data).to.equal(new StringRevertError(RevertReason.TransferFailed));
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
@@ -5,6 +5,7 @@ import {
 | 
			
		||||
    OwnerStakeByStatus,
 | 
			
		||||
    StakeInfo,
 | 
			
		||||
    StakeStatus,
 | 
			
		||||
    StakingRevertErrors,
 | 
			
		||||
    StoredBalance,
 | 
			
		||||
} from '@0x/contracts-staking';
 | 
			
		||||
import { expect } from '@0x/contracts-test-utils';
 | 
			
		||||
@@ -196,4 +197,27 @@ export function validMoveStakeAssertion(
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a FunctionAssertion for `moveStake` which asserts that the transaction reverts with a
 | 
			
		||||
 * PoolExistenceError.
 | 
			
		||||
 */
 | 
			
		||||
export function moveStakeNonexistentPoolAssertion(
 | 
			
		||||
    deployment: DeploymentManager,
 | 
			
		||||
    nonExistentPoolId: string,
 | 
			
		||||
): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void> {
 | 
			
		||||
    return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void>(
 | 
			
		||||
        deployment.staking.stakingWrapper,
 | 
			
		||||
        'moveStake',
 | 
			
		||||
        {
 | 
			
		||||
            after: async (_beforeInfo: void, result: FunctionResult) => {
 | 
			
		||||
                // Ensure that the tx reverted.
 | 
			
		||||
                expect(result.success).to.be.false();
 | 
			
		||||
 | 
			
		||||
                // Check revert error
 | 
			
		||||
                expect(result.data).to.equal(new StakingRevertErrors.PoolExistenceError(nonExistentPoolId, false));
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
/* tslint:enable:no-unnecessary-type-assertion */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,9 @@
 | 
			
		||||
import { decreaseCurrentAndNextBalance, OwnerStakeByStatus, StakeStatus } from '@0x/contracts-staking';
 | 
			
		||||
import {
 | 
			
		||||
    decreaseCurrentAndNextBalance,
 | 
			
		||||
    OwnerStakeByStatus,
 | 
			
		||||
    StakeStatus,
 | 
			
		||||
    StakingRevertErrors,
 | 
			
		||||
} from '@0x/contracts-staking';
 | 
			
		||||
import { expect } from '@0x/contracts-test-utils';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import { TxData } from 'ethereum-types';
 | 
			
		||||
@@ -81,4 +86,24 @@ export function validUnstakeAssertion(
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a FunctionAssertion for `unstake` which assumes that the input exceeds the amount that
 | 
			
		||||
 * can be unstaked. Checks that the call reverts with an InsufficientBalanceError. Note that we
 | 
			
		||||
 * close over `withdrawableStake` to avoid duplicating work done in the assertion generator.
 | 
			
		||||
 */
 | 
			
		||||
export function invalidUnstakeAssertion(
 | 
			
		||||
    deployment: DeploymentManager,
 | 
			
		||||
    withdrawableStake: BigNumber,
 | 
			
		||||
): FunctionAssertion<[BigNumber], void, void> {
 | 
			
		||||
    return new FunctionAssertion<[BigNumber], void, void>(deployment.staking.stakingWrapper, 'unstake', {
 | 
			
		||||
        after: async (_beforeInfo: void, result: FunctionResult, args: [BigNumber]) => {
 | 
			
		||||
            // Ensure that the tx reverted.
 | 
			
		||||
            expect(result.success).to.be.false();
 | 
			
		||||
 | 
			
		||||
            const [amount] = args;
 | 
			
		||||
            expect(result.data).to.equal(new StakingRevertErrors.InsufficientBalanceError(amount, withdrawableStake));
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
/* tslint:enable:no-unnecessary-type-assertion */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { WETH9Events, WETH9TransferEventArgs } from '@0x/contracts-erc20';
 | 
			
		||||
import { loadCurrentBalance, StoredBalance } from '@0x/contracts-staking';
 | 
			
		||||
import { loadCurrentBalance, StakingRevertErrors, StoredBalance } from '@0x/contracts-staking';
 | 
			
		||||
import { expect, filterLogsToArguments } from '@0x/contracts-test-utils';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import { TxData } from 'ethereum-types';
 | 
			
		||||
@@ -73,4 +73,25 @@ export function validWithdrawDelegatorRewardsAssertion(
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a FunctionAssertion for `withdrawDelegatorRewards` which assumes the given pool hasn't
 | 
			
		||||
 * been finalized for the previous epoch. It checks that the call reverts with a PoolNotFinalizedError.
 | 
			
		||||
 */
 | 
			
		||||
export function invalidWithdrawDelegatorRewardsAssertion(
 | 
			
		||||
    deployment: DeploymentManager,
 | 
			
		||||
    simulationEnvironment: SimulationEnvironment,
 | 
			
		||||
): FunctionAssertion<[string], void, void> {
 | 
			
		||||
    return new FunctionAssertion(deployment.staking.stakingWrapper, 'withdrawDelegatorRewards', {
 | 
			
		||||
        after: async (_beforeInfo: void, result: FunctionResult, args: [string]) => {
 | 
			
		||||
            // Ensure that the tx reverted.
 | 
			
		||||
            expect(result.success).to.be.false();
 | 
			
		||||
 | 
			
		||||
            // Check revert error
 | 
			
		||||
            const [poolId] = args;
 | 
			
		||||
            const { currentEpoch } = simulationEnvironment;
 | 
			
		||||
            expect(result.data).to.equal(new StakingRevertErrors.PoolNotFinalizedError(poolId, currentEpoch.minus(1)));
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
/* tslint:enable:no-unnecessary-type-assertion */
 | 
			
		||||
 
 | 
			
		||||
@@ -16,10 +16,14 @@ export class PoolManagementSimulation extends Simulation {
 | 
			
		||||
        const operators = filterActorsByRole(actors, PoolOperator);
 | 
			
		||||
 | 
			
		||||
        const [actions, weights] = _.unzip([
 | 
			
		||||
            // 40% chance of executing validCreateStakingPool assertion for a random operator
 | 
			
		||||
            ...operators.map(operator => [operator.simulationActions.validCreateStakingPool, 0.4]),
 | 
			
		||||
            // 60% chance of executing validDecreaseStakingPoolOperatorShare for a random operator
 | 
			
		||||
            ...operators.map(operator => [operator.simulationActions.validDecreaseStakingPoolOperatorShare, 0.6]),
 | 
			
		||||
            // 38% chance of executing validCreateStakingPool assertion for a random operator
 | 
			
		||||
            ...operators.map(operator => [operator.simulationActions.validCreateStakingPool, 0.38]),
 | 
			
		||||
            // 2% chance of executing invalidCreateStakingPool assertion for a random operator
 | 
			
		||||
            ...operators.map(operator => [operator.simulationActions.invalidCreateStakingPool, 0.02]),
 | 
			
		||||
            // 58% chance of executing validDecreaseStakingPoolOperatorShare for a random operator
 | 
			
		||||
            ...operators.map(operator => [operator.simulationActions.validDecreaseStakingPoolOperatorShare, 0.58]),
 | 
			
		||||
            // 2% chance of executing invalidDecreaseStakingPoolOperatorShare for a random operator
 | 
			
		||||
            ...operators.map(operator => [operator.simulationActions.invalidDecreaseStakingPoolOperatorShare, 0.02]),
 | 
			
		||||
        ]) as [Array<AsyncIterableIterator<AssertionResult | void>>, number[]];
 | 
			
		||||
        while (true) {
 | 
			
		||||
            const action = Pseudorandom.sample(actions, weights);
 | 
			
		||||
 
 | 
			
		||||
@@ -22,12 +22,18 @@ export class StakeManagementSimulation extends Simulation {
 | 
			
		||||
        const poolManagement = new PoolManagementSimulation(this.environment);
 | 
			
		||||
 | 
			
		||||
        const [actions, weights] = _.unzip([
 | 
			
		||||
            // 30% chance of executing validStake for a random staker
 | 
			
		||||
            ...stakers.map(staker => [staker.simulationActions.validStake, 0.3 / stakers.length]),
 | 
			
		||||
            // 20% chance of executing validUnstake for a random staker
 | 
			
		||||
            ...stakers.map(staker => [staker.simulationActions.validUnstake, 0.2 / stakers.length]),
 | 
			
		||||
            // 30% chance of executing validMoveStake for a random staker
 | 
			
		||||
            ...stakers.map(staker => [staker.simulationActions.validMoveStake, 0.3 / stakers.length]),
 | 
			
		||||
            // 28% chance of executing validStake for a random staker
 | 
			
		||||
            ...stakers.map(staker => [staker.simulationActions.validStake, 0.28 / stakers.length]),
 | 
			
		||||
            // 2% chance of executing invalidUnstake for a random staker
 | 
			
		||||
            ...stakers.map(staker => [staker.simulationActions.invalidStake, 0.02 / stakers.length]),
 | 
			
		||||
            // 28% chance of executing validUnstake for a random staker
 | 
			
		||||
            ...stakers.map(staker => [staker.simulationActions.validStake, 0.28 / stakers.length]),
 | 
			
		||||
            // 2% chance of executing invalidUnstake for a random staker
 | 
			
		||||
            ...stakers.map(staker => [staker.simulationActions.validUnstake, 0.02 / stakers.length]),
 | 
			
		||||
            // 28% chance of executing validMoveStake for a random staker
 | 
			
		||||
            ...stakers.map(staker => [staker.simulationActions.validMoveStake, 0.28 / stakers.length]),
 | 
			
		||||
            // 2% chance of executing moveStakeNonexistentPool for a random staker
 | 
			
		||||
            ...stakers.map(staker => [staker.simulationActions.moveStakeNonexistentPool, 0.02 / stakers.length]),
 | 
			
		||||
            // 20% chance of executing an assertion generated from the pool management simulation
 | 
			
		||||
            [poolManagement.generator, 0.2],
 | 
			
		||||
        ]) as [Array<AsyncIterableIterator<AssertionResult | void>>, number[]];
 | 
			
		||||
 
 | 
			
		||||
@@ -38,8 +38,10 @@ export class StakingRewardsSimulation extends Simulation {
 | 
			
		||||
            ...stakers.map(staker => [staker.simulationActions.validWithdrawDelegatorRewards, 0.1 / stakers.length]),
 | 
			
		||||
            // 10% chance of executing validFinalizePool for a random keeper
 | 
			
		||||
            ...keepers.map(keeper => [keeper.simulationActions.validFinalizePool, 0.1 / keepers.length]),
 | 
			
		||||
            // 10% chance of executing validEndEpoch for a random keeper
 | 
			
		||||
            ...keepers.map(keeper => [keeper.simulationActions.validEndEpoch, 0.1 / keepers.length]),
 | 
			
		||||
            // 7% chance of executing validEndEpoch for a random keeper
 | 
			
		||||
            ...keepers.map(keeper => [keeper.simulationActions.validEndEpoch, 0.07 / keepers.length]),
 | 
			
		||||
            // 3% chance of executing invalidEndEpoch for a random keeper
 | 
			
		||||
            ...keepers.map(keeper => [keeper.simulationActions.invalidEndEpoch, 0.03 / keepers.length]),
 | 
			
		||||
            // 50% chance of executing an assertion generated from the pool membership simulation
 | 
			
		||||
            [poolMembership.generator, 0.5],
 | 
			
		||||
            // 20% chance of executing an assertion generated from the stake management simulation
 | 
			
		||||
 
 | 
			
		||||
@@ -60,6 +60,7 @@ export const constants = {
 | 
			
		||||
    NULL_BYTES32: '0x0000000000000000000000000000000000000000000000000000000000000000',
 | 
			
		||||
    UNLIMITED_ALLOWANCE_IN_BASE_UNITS: MAX_UINT256,
 | 
			
		||||
    MAX_UINT256,
 | 
			
		||||
    MAX_UINT32: new BigNumber(2).pow(32).minus(1),
 | 
			
		||||
    TESTRPC_PRIVATE_KEYS: _.map(TESTRPC_PRIVATE_KEYS_STRINGS, privateKeyString => ethUtil.toBuffer(privateKeyString)),
 | 
			
		||||
    INITIAL_ERC20_BALANCE: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18),
 | 
			
		||||
    INITIAL_ERC20_ALLOWANCE: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user