import { ExchangeContract } from '@0x/contracts-exchange';
import {
    blockchainTests,
    constants,
    expect,
    orderHashUtils,
    signingUtils,
    transactionHashUtils,
} from '@0x/contracts-test-utils';
import { Order, SignatureType, ZeroExTransaction } from '@0x/types';
import { hexUtils, logUtils } from '@0x/utils';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';

import { artifacts } from '../artifacts';
import { AssertionResult } from '../framework/assertions/function_assertion';
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
import { DeploymentManager } from '../framework/deployment_manager';
import { Simulation, SimulationEnvironment } from '../framework/simulation';
import { Pseudorandom } from '../framework/utils/pseudorandom';
import { TestSignatureValidationWalletContract } from '../wrappers';

// tslint:disable: max-classes-per-file no-non-null-assertion no-unnecessary-type-assertion
const tests = process.env.FUZZ_TEST === 'exchange/signature_validation' ? blockchainTests : blockchainTests.skip;

tests('Exchange signature validation fuzz tests', env => {
    const ALL_SIGNATURE_TYPES = [
        SignatureType.Illegal,
        SignatureType.Invalid,
        SignatureType.EthSign,
        SignatureType.EIP712,
        SignatureType.Wallet,
        SignatureType.Validator,
        SignatureType.PreSigned,
        SignatureType.EIP1271Wallet,
    ];
    const ALL_WORKING_SIGNATURE_TYPES = [
        SignatureType.EthSign,
        SignatureType.EIP712,
        SignatureType.Wallet,
        SignatureType.Validator,
        SignatureType.PreSigned,
        SignatureType.EIP1271Wallet,
    ];
    const HASH_COMPATIBLE_SIGNATURE_TYPES = [
        SignatureType.EthSign,
        SignatureType.EIP712,
        SignatureType.Wallet,
        SignatureType.PreSigned,
    ];
    const STATIC_SIGNATURE_TYPES = [SignatureType.EthSign, SignatureType.EIP712, SignatureType.PreSigned];
    const ALWAYS_FAILING_SIGNATURE_TYPES = [SignatureType.Illegal, SignatureType.Invalid];
    const WALLET_SIGNATURE_TYPES = [SignatureType.Wallet, SignatureType.EIP1271Wallet];
    const STRICT_LENGTH_SIGNATURE_TYPES = [SignatureType.EthSign, SignatureType.EIP712];
    const CALLBACK_SIGNATURE_TYPES = [SignatureType.Wallet, SignatureType.EIP1271Wallet, SignatureType.Validator];

    let walletContractAddress: string;
    let notWalletContractAddress: string;
    let deployment: DeploymentManager;
    let exchange: ExchangeContract;
    let accounts: string[];
    let privateKeys: { [address: string]: Buffer };
    let chainId: number;

    interface SignatureTestParams {
        signatureType: SignatureType;
        signer: string;
        signature: string;
        hash: string;
        signerKey?: Buffer;
        validator?: string;
        payload?: string;
        order?: Order;
        transaction?: ZeroExTransaction;
    }

    before(async () => {
        chainId = await env.web3Wrapper.getChainIdAsync();
        accounts = await env.getAccountAddressesAsync();
        privateKeys = _.zipObject(
            accounts,
            accounts.map((a, i) => constants.TESTRPC_PRIVATE_KEYS[i]),
        );
        deployment = await DeploymentManager.deployAsync(env, {
            numErc20TokensToDeploy: 0,
            numErc721TokensToDeploy: 0,
            numErc1155TokensToDeploy: 0,
        });
        exchange = deployment.exchange;
        walletContractAddress = (
            await TestSignatureValidationWalletContract.deployFrom0xArtifactAsync(
                artifacts.TestSignatureValidationWallet,
                env.provider,
                env.txDefaults,
                {},
            )
        ).address;
        // This just has to be a contract address that doesn't implement the
        // wallet spec.
        notWalletContractAddress = exchange.address;
    });

    function randomPayload(): string {
        return Pseudorandom.hex(Pseudorandom.integer(0, 66).toNumber());
    }

    async function presignHashAsync(signer: string, hash: string): Promise<void> {
        await exchange.preSign(hash).awaitTransactionSuccessAsync({ from: signer });
    }

    async function approveValidatorAsync(signer: string, validator: string, approved: boolean = true): Promise<void> {
        await exchange
            .setSignatureValidatorApproval(validator, approved)
            .awaitTransactionSuccessAsync({ from: signer });
    }

    function createSignature(params: {
        signatureType: SignatureType;
        hash?: string;
        signerKey?: Buffer;
        validator?: string;
        payload?: string;
    }): string {
        const payload = params.payload || constants.NULL_BYTES;
        const signatureByte = hexUtils.leftPad(params.signatureType, 1);
        switch (params.signatureType) {
            default:
            case SignatureType.Illegal:
            case SignatureType.Invalid:
            case SignatureType.PreSigned:
                return hexUtils.concat(payload, signatureByte);
            case SignatureType.EIP712:
            case SignatureType.EthSign:
                return hexUtils.concat(
                    payload,
                    ethUtil.bufferToHex(
                        signingUtils.signMessage(
                            ethUtil.toBuffer(params.hash),
                            params.signerKey!,
                            params.signatureType,
                        ),
                    ),
                );
            case SignatureType.Wallet:
            case SignatureType.EIP1271Wallet:
                return hexUtils.concat(payload, params.signatureType);
            case SignatureType.Validator:
                return hexUtils.concat(payload, params.validator!, params.signatureType);
        }
    }

    async function mangleSignatureParamsAsync(params: SignatureTestParams): Promise<SignatureTestParams> {
        const mangled = { ...params };
        const MANGLE_MODES = [
            'TRUNCATE_SIGNATURE',
            'RETYPE_SIGNATURE',
            'RANDOM_HASH',
            'RANDOM_ORDER',
            'RANDOM_TRANSACTION',
            'RANDOM_SIGNER',
        ];
        const invalidModes = [];
        if (!STRICT_LENGTH_SIGNATURE_TYPES.includes(mangled.signatureType)) {
            invalidModes.push('TRUNCATE_SIGNATURE');
        }
        if (CALLBACK_SIGNATURE_TYPES.includes(mangled.signatureType)) {
            invalidModes.push('RANDOM_HASH');
        }
        if (params.transaction === undefined) {
            invalidModes.push('RANDOM_TRANSACTION');
        }
        if (params.order === undefined) {
            invalidModes.push('RANDOM_ORDER');
        }
        if (params.order !== undefined || params.hash !== undefined) {
            invalidModes.push('RANDOM_HASH');
        }
        const mode = Pseudorandom.sample(_.without(MANGLE_MODES, ...invalidModes))!;
        switch (mode) {
            case 'TRUNCATE_SIGNATURE':
                while (hexUtils.slice(mangled.signature, -1) === hexUtils.leftPad(mangled.signatureType, 1)) {
                    mangled.signature = hexUtils.slice(mangled.signature, 0, -1);
                }
                break;
            case 'RETYPE_SIGNATURE':
                mangled.signatureType = WALLET_SIGNATURE_TYPES.includes(mangled.signatureType)
                    ? Pseudorandom.sample(_.without(ALL_SIGNATURE_TYPES, ...WALLET_SIGNATURE_TYPES))!
                    : Pseudorandom.sample(_.without(ALL_SIGNATURE_TYPES, mangled.signatureType))!;
                mangled.signature = hexUtils.concat(hexUtils.slice(mangled.signature, 0, -1), mangled.signatureType);
                break;
            case 'RANDOM_SIGNER':
                mangled.signer = Pseudorandom.hex(constants.ADDRESS_LENGTH);
                if (mangled.order) {
                    mangled.order.makerAddress = mangled.signer;
                }
                if (mangled.transaction) {
                    mangled.transaction.signerAddress = mangled.signer;
                }
                break;
            case 'RANDOM_HASH':
                mangled.hash = Pseudorandom.hex();
                break;
            case 'RANDOM_ORDER':
                mangled.order = randomOrder({
                    exchangeAddress: mangled.order!.exchangeAddress,
                    chainId: mangled.order!.chainId,
                });
                mangled.hash = orderHashUtils.getOrderHashHex(mangled.order);
                break;
            case 'RANDOM_TRANSACTION':
                mangled.transaction = randomTransaction({
                    domain: mangled.transaction!.domain,
                });
                mangled.hash = await transactionHashUtils.getTransactionHashHex(mangled.transaction);
                break;
            default:
                throw new Error(`Unhandled mangle mode: ${mode}`);
        }
        return mangled;
    }

    function createHashTestParams(fields: Partial<SignatureTestParams> = {}): SignatureTestParams {
        const signatureType =
            fields.signatureType === undefined
                ? Pseudorandom.sample(HASH_COMPATIBLE_SIGNATURE_TYPES)!
                : fields.signatureType;
        const signer =
            fields.signer ||
            (WALLET_SIGNATURE_TYPES.includes(signatureType) ? walletContractAddress : Pseudorandom.sample(accounts)!);
        const validator =
            fields.validator || (signatureType === SignatureType.Validator ? walletContractAddress : undefined);
        const signerKey = fields.signerKey || privateKeys[signer];
        const hash = fields.hash || Pseudorandom.hex();
        const payload =
            fields.payload ||
            (STRICT_LENGTH_SIGNATURE_TYPES.includes(signatureType) ? constants.NULL_BYTES : randomPayload());
        const signature = createSignature({ signatureType, hash, signerKey, payload, validator });
        return {
            hash,
            payload,
            signature,
            signatureType,
            signer,
            signerKey,
            validator,
        };
    }

    async function assertValidHashSignatureAsync(params: {
        hash: string;
        signer: string;
        signature: string;
        isValid: boolean;
    }): Promise<void> {
        try {
            let result;
            try {
                result = await exchange.isValidHashSignature(params.hash, params.signer, params.signature).callAsync();
            } catch (err) {
                if (params.isValid) {
                    throw err;
                }
                return;
            }
            expect(result).to.eq(!!params.isValid);
        } catch (err) {
            logUtils.warn(params);
            throw err;
        }
    }

    async function* validTestHashSignature(): AsyncIterableIterator<void> {
        while (true) {
            const { hash, signature, signatureType, signer } = createHashTestParams();
            yield (async () => {
                if (signatureType === SignatureType.PreSigned) {
                    await presignHashAsync(signer, hash);
                }
                await assertValidHashSignatureAsync({
                    hash,
                    signer,
                    signature,
                    isValid: true,
                });
            })();
        }
    }

    async function* invalidTestHashStaticSignature(): AsyncIterableIterator<void> {
        while (true) {
            const randomSignerKey = ethUtil.toBuffer(Pseudorandom.hex());
            const signer = Pseudorandom.sample([notWalletContractAddress, walletContractAddress, ...accounts])!;
            const { hash, signature } = createHashTestParams({
                signatureType: Pseudorandom.sample([...STATIC_SIGNATURE_TYPES, ...ALWAYS_FAILING_SIGNATURE_TYPES])!,
                signer,
                // Always sign with a random key.
                signerKey: randomSignerKey,
            });
            yield assertValidHashSignatureAsync({
                hash,
                signer,
                signature,
                isValid: false,
            });
        }
    }

    async function* invalidTestHashWalletSignature(): AsyncIterableIterator<void> {
        while (true) {
            const signer = Pseudorandom.sample([notWalletContractAddress, ...accounts])!;
            const { hash, signature } = createHashTestParams({
                signatureType: SignatureType.Wallet,
                signer,
            });
            yield assertValidHashSignatureAsync({
                hash,
                signer,
                signature,
                isValid: false,
            });
        }
    }

    async function* invalidTestHashValidatorSignature(): AsyncIterableIterator<void> {
        while (true) {
            const isNotApproved = Pseudorandom.sample([true, false])!;
            const signer = Pseudorandom.sample([...accounts])!;
            const validator = isNotApproved
                ? walletContractAddress
                : Pseudorandom.sample([
                      // All validator signatures are invalid for the hash test, so passing a valid
                      // wallet contract should still fail.
                      walletContractAddress,
                      notWalletContractAddress,
                      ...accounts,
                  ])!;
            const { hash, signature } = createHashTestParams({
                signatureType: SignatureType.Validator,
                validator,
            });
            yield (async () => {
                if (!isNotApproved) {
                    await approveValidatorAsync(signer, validator);
                }
                await assertValidHashSignatureAsync({
                    hash,
                    signer,
                    signature,
                    isValid: false,
                });
            })();
        }
    }

    async function* invalidTestHashMangledSignature(): AsyncIterableIterator<void> {
        while (true) {
            const params = createHashTestParams({ signatureType: Pseudorandom.sample(ALL_SIGNATURE_TYPES)! });
            const mangled = await mangleSignatureParamsAsync(params);
            yield (async () => {
                await assertValidHashSignatureAsync({
                    hash: mangled.hash,
                    signer: mangled.signer,
                    signature: mangled.signature,
                    isValid: false,
                });
            })();
        }
    }

    function randomOrder(fields: Partial<Order> = {}): Order {
        return {
            chainId,
            exchangeAddress: exchange.address,
            expirationTimeSeconds: Pseudorandom.integer(1, 2 ** 32),
            salt: Pseudorandom.integer(0, constants.MAX_UINT256),
            makerAssetData: Pseudorandom.hex(36),
            takerAssetData: Pseudorandom.hex(36),
            makerFeeAssetData: Pseudorandom.hex(36),
            takerFeeAssetData: Pseudorandom.hex(36),
            makerAssetAmount: Pseudorandom.integer(0, 100e18),
            takerAssetAmount: Pseudorandom.integer(0, 100e18),
            makerFee: Pseudorandom.integer(0, 100e18),
            takerFee: Pseudorandom.integer(0, 100e18),
            feeRecipientAddress: Pseudorandom.hex(constants.ADDRESS_LENGTH),
            makerAddress: Pseudorandom.hex(constants.ADDRESS_LENGTH),
            takerAddress: Pseudorandom.hex(constants.ADDRESS_LENGTH),
            senderAddress: Pseudorandom.hex(constants.ADDRESS_LENGTH),
            ...fields,
        };
    }

    async function createOrderTestParamsAsync(
        fields: Partial<SignatureTestParams> = {},
    ): Promise<SignatureTestParams & { order: Order }> {
        const signatureType =
            fields.signatureType === undefined
                ? Pseudorandom.sample(ALL_WORKING_SIGNATURE_TYPES)!
                : fields.signatureType;
        const signer =
            fields.signer ||
            (WALLET_SIGNATURE_TYPES.includes(signatureType) ? walletContractAddress : Pseudorandom.sample(accounts)!);
        const validator =
            fields.validator || (signatureType === SignatureType.Validator ? walletContractAddress : undefined);
        const signerKey = fields.signerKey || privateKeys[signer];
        const order = fields.order || randomOrder({ makerAddress: signer });
        const hash = fields.hash || orderHashUtils.getOrderHashHex(order);
        const payload =
            fields.payload ||
            (STRICT_LENGTH_SIGNATURE_TYPES.includes(signatureType) ? constants.NULL_BYTES : randomPayload());
        const signature = createSignature({ signatureType, hash, signerKey, payload, validator });
        return {
            hash,
            order,
            payload,
            signature,
            signatureType,
            signer,
            signerKey,
            validator,
        };
    }

    async function assertValidOrderSignatureAsync(params: {
        order: Order;
        signature: string;
        isValid: boolean;
    }): Promise<void> {
        try {
            let result;
            try {
                result = await exchange.isValidOrderSignature(params.order, params.signature).callAsync();
            } catch (err) {
                if (params.isValid) {
                    throw err;
                }
                return;
            }
            expect(result).to.eq(!!params.isValid);
        } catch (err) {
            logUtils.warn(params);
            throw err;
        }
    }

    async function* validTestOrderSignature(): AsyncIterableIterator<void> {
        while (true) {
            const { hash, order, signature, signatureType, signer, validator } = await createOrderTestParamsAsync();
            yield (async () => {
                if (signatureType === SignatureType.PreSigned) {
                    await presignHashAsync(signer, hash);
                } else if (signatureType === SignatureType.Validator) {
                    await approveValidatorAsync(signer, validator!);
                }
                await assertValidOrderSignatureAsync({
                    order,
                    signature,
                    isValid: true,
                });
            })();
        }
    }

    async function* invalidTestOrderStaticSignature(): AsyncIterableIterator<void> {
        while (true) {
            const randomSignerKey = ethUtil.toBuffer(Pseudorandom.hex());
            const signer = Pseudorandom.sample([notWalletContractAddress, walletContractAddress, ...accounts])!;
            const { order, signature } = await createOrderTestParamsAsync({
                signatureType: Pseudorandom.sample([...STATIC_SIGNATURE_TYPES, ...ALWAYS_FAILING_SIGNATURE_TYPES])!,
                signer,
                // Always sign with a random key.
                signerKey: randomSignerKey,
            });
            yield assertValidOrderSignatureAsync({
                order,
                signature,
                isValid: false,
            });
        }
    }

    async function* invalidTestOrderWalletSignature(): AsyncIterableIterator<void> {
        while (true) {
            const signer = Pseudorandom.sample([notWalletContractAddress, ...accounts])!;
            const { order, signature } = await createOrderTestParamsAsync({
                signatureType: Pseudorandom.sample(WALLET_SIGNATURE_TYPES)!,
                signer,
            });
            yield assertValidOrderSignatureAsync({
                order,
                signature,
                isValid: false,
            });
        }
    }

    async function* invalidTestOrderValidatorSignature(): AsyncIterableIterator<void> {
        while (true) {
            const isNotApproved = Pseudorandom.sample([true, false])!;
            const signer = Pseudorandom.sample([...accounts])!;
            const validator = isNotApproved
                ? walletContractAddress
                : Pseudorandom.sample([notWalletContractAddress, ...accounts])!;
            const { order, signature } = await createOrderTestParamsAsync({
                signatureType: SignatureType.Validator,
                validator,
            });
            yield (async () => {
                if (!isNotApproved) {
                    await approveValidatorAsync(signer, validator);
                }
                await assertValidOrderSignatureAsync({
                    order,
                    signature,
                    isValid: false,
                });
            })();
        }
    }

    async function* invalidTestOrderMangledSignature(): AsyncIterableIterator<void> {
        while (true) {
            const params = await createOrderTestParamsAsync({
                signatureType: Pseudorandom.sample(ALL_SIGNATURE_TYPES)!,
            });
            const mangled = await mangleSignatureParamsAsync(params);
            yield (async () => {
                await assertValidOrderSignatureAsync({
                    order: mangled.order!,
                    signature: mangled.signature,
                    isValid: false,
                });
            })();
        }
    }

    function randomTransaction(fields: Partial<ZeroExTransaction> = {}): ZeroExTransaction {
        return {
            domain: {
                chainId,
                verifyingContract: exchange.address,
                name: '0x Protocol',
                version: '3.0.0',
            },
            gasPrice: Pseudorandom.integer(1e9, 100e9),
            expirationTimeSeconds: Pseudorandom.integer(1, 2 ** 32),
            salt: Pseudorandom.integer(0, constants.MAX_UINT256),
            signerAddress: Pseudorandom.hex(constants.ADDRESS_LENGTH),
            data: Pseudorandom.hex(Pseudorandom.integer(4, 128).toNumber()),
            ...fields,
        };
    }

    async function createTransactionTestParamsAsync(
        fields: Partial<SignatureTestParams> = {},
    ): Promise<SignatureTestParams & { transaction: ZeroExTransaction }> {
        const signatureType =
            fields.signatureType === undefined
                ? Pseudorandom.sample(ALL_WORKING_SIGNATURE_TYPES)!
                : fields.signatureType;
        const signer =
            fields.signer ||
            (WALLET_SIGNATURE_TYPES.includes(signatureType) ? walletContractAddress : Pseudorandom.sample(accounts)!);
        const validator =
            fields.validator || (signatureType === SignatureType.Validator ? walletContractAddress : undefined);
        const signerKey = fields.signerKey || privateKeys[signer];
        const transaction = fields.transaction || randomTransaction({ signerAddress: signer });
        const hash = fields.hash || transactionHashUtils.getTransactionHashHex(transaction);
        const payload =
            fields.payload ||
            (STRICT_LENGTH_SIGNATURE_TYPES.includes(signatureType) ? constants.NULL_BYTES : randomPayload());
        const signature = createSignature({ signatureType, hash, signerKey, payload, validator });
        return {
            hash,
            transaction,
            payload,
            signature,
            signatureType,
            signer,
            signerKey,
            validator,
        };
    }

    async function assertValidTransactionSignatureAsync(params: {
        transaction: ZeroExTransaction;
        signature: string;
        isValid: boolean;
    }): Promise<void> {
        try {
            let result;
            try {
                result = await exchange.isValidTransactionSignature(params.transaction, params.signature).callAsync();
            } catch (err) {
                if (params.isValid) {
                    throw err;
                }
                return;
            }
            expect(result).to.eq(!!params.isValid);
        } catch (err) {
            logUtils.warn(params);
            throw err;
        }
    }

    async function* validTestTransactionSignature(): AsyncIterableIterator<void> {
        while (true) {
            const {
                hash,
                transaction,
                signature,
                signatureType,
                signer,
                validator,
            } = await createTransactionTestParamsAsync();
            yield (async () => {
                if (signatureType === SignatureType.PreSigned) {
                    await presignHashAsync(signer, hash);
                } else if (signatureType === SignatureType.Validator) {
                    await approveValidatorAsync(signer, validator!);
                }
                await assertValidTransactionSignatureAsync({
                    transaction,
                    signature,
                    isValid: true,
                });
            })();
        }
    }

    async function* invalidTestTransactionStaticSignature(): AsyncIterableIterator<void> {
        while (true) {
            const randomSignerKey = ethUtil.toBuffer(Pseudorandom.hex());
            const signer = Pseudorandom.sample([notWalletContractAddress, walletContractAddress, ...accounts])!;
            const { transaction, signature } = await createTransactionTestParamsAsync({
                signatureType: Pseudorandom.sample([...STATIC_SIGNATURE_TYPES, ...ALWAYS_FAILING_SIGNATURE_TYPES])!,
                signer,
                // Always sign with a random key.
                signerKey: randomSignerKey,
            });
            yield assertValidTransactionSignatureAsync({
                transaction,
                signature,
                isValid: false,
            });
        }
    }

    async function* invalidTestTransactionWalletSignature(): AsyncIterableIterator<void> {
        while (true) {
            const signer = Pseudorandom.sample([notWalletContractAddress, ...accounts])!;
            const { transaction, signature } = await createTransactionTestParamsAsync({
                signatureType: Pseudorandom.sample(WALLET_SIGNATURE_TYPES)!,
                signer,
            });
            yield assertValidTransactionSignatureAsync({
                transaction,
                signature,
                isValid: false,
            });
        }
    }

    async function* invalidTestTransactionValidatorSignature(): AsyncIterableIterator<void> {
        while (true) {
            const isNotApproved = Pseudorandom.sample([true, false])!;
            const signer = Pseudorandom.sample([...accounts])!;
            const validator = isNotApproved
                ? walletContractAddress
                : Pseudorandom.sample([notWalletContractAddress, ...accounts])!;
            const { transaction, signature } = await createTransactionTestParamsAsync({
                signatureType: SignatureType.Validator,
                validator,
            });
            yield (async () => {
                if (!isNotApproved) {
                    await approveValidatorAsync(signer, validator);
                }
                await assertValidTransactionSignatureAsync({
                    transaction,
                    signature,
                    isValid: false,
                });
            })();
        }
    }

    async function* invalidTestTransactionMangledSignature(): AsyncIterableIterator<void> {
        while (true) {
            const params = await createTransactionTestParamsAsync({
                signatureType: Pseudorandom.sample(ALL_SIGNATURE_TYPES)!,
            });
            const mangled = await mangleSignatureParamsAsync(params);
            yield (async () => {
                await assertValidTransactionSignatureAsync({
                    transaction: mangled.transaction!,
                    signature: mangled.signature,
                    isValid: false,
                });
            })();
        }
    }

    it('fuzz', async () => {
        const FUZZ_ACTIONS = [
            validTestHashSignature(),
            invalidTestHashStaticSignature(),
            invalidTestHashWalletSignature(),
            invalidTestHashValidatorSignature(),
            invalidTestHashMangledSignature(),
            validTestOrderSignature(),
            invalidTestOrderStaticSignature(),
            invalidTestOrderWalletSignature(),
            invalidTestOrderValidatorSignature(),
            invalidTestOrderMangledSignature(),
            validTestTransactionSignature(),
            invalidTestTransactionStaticSignature(),
            invalidTestTransactionWalletSignature(),
            invalidTestTransactionValidatorSignature(),
            invalidTestTransactionMangledSignature(),
        ];
        const simulationEnvironment = new SimulationEnvironment(deployment, new BlockchainBalanceStore({}, {}), []);
        const simulation = new (class extends Simulation {
            // tslint:disable-next-line: prefer-function-over-method
            protected async *_assertionGenerator(): AsyncIterableIterator<AssertionResult | void> {
                while (true) {
                    const action = Pseudorandom.sample(FUZZ_ACTIONS)!;
                    yield (await action!.next()).value;
                }
            }
        })(simulationEnvironment);
        simulation.resets = true;
        return simulation.fuzzAsync();
    });
});
// tslint:disable-next-line max-file-line-count
