update pool membership simulation to use multiple makers and takers, partial fills
This commit is contained in:
		@@ -103,7 +103,7 @@ export function calculateFillResults(
 | 
			
		||||
        order.takerAssetAmount,
 | 
			
		||||
        order.makerAssetAmount,
 | 
			
		||||
    );
 | 
			
		||||
    const makerFeePaid = safeGetPartialAmountFloor(makerAssetFilledAmount, order.makerAssetAmount, order.makerFee);
 | 
			
		||||
    const makerFeePaid = safeGetPartialAmountFloor(takerAssetFilledAmount, order.takerAssetAmount, order.makerFee);
 | 
			
		||||
    const takerFeePaid = safeGetPartialAmountFloor(takerAssetFilledAmount, order.takerAssetAmount, order.takerFee);
 | 
			
		||||
    return {
 | 
			
		||||
        makerAssetFilledAmount,
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,10 @@ export class Actor {
 | 
			
		||||
        this.name = config.name || this.address;
 | 
			
		||||
        this.deployment = config.deployment;
 | 
			
		||||
        this.privateKey = constants.TESTRPC_PRIVATE_KEYS[config.deployment.accounts.indexOf(this.address)];
 | 
			
		||||
        if (config.simulationEnvironment !== undefined) {
 | 
			
		||||
            this.simulationEnvironment = config.simulationEnvironment;
 | 
			
		||||
            this.simulationEnvironment.actors.push(this);
 | 
			
		||||
        }
 | 
			
		||||
        this._transactionFactory = new TransactionFactory(
 | 
			
		||||
            this.privateKey,
 | 
			
		||||
            config.deployment.exchange.address,
 | 
			
		||||
@@ -123,7 +126,6 @@ export class Actor {
 | 
			
		||||
        if (logs.length !== 1) {
 | 
			
		||||
            throw new Error('Invalid number of `TransferSingle` logs');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const { id } = logs[0];
 | 
			
		||||
 | 
			
		||||
        // Mint the token
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ export interface FeeRecipientInterface {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This mixin encapsulates functionaltiy associated with fee recipients within the 0x ecosystem.
 | 
			
		||||
 * This mixin encapsulates functionality associated with fee recipients within the 0x ecosystem.
 | 
			
		||||
 * As of writing, the only extra functionality provided is signing Coordinator approvals.
 | 
			
		||||
 */
 | 
			
		||||
export function FeeRecipientMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<FeeRecipientInterface> {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,8 @@
 | 
			
		||||
import { IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs, TestStakingEvents } from '@0x/contracts-staking';
 | 
			
		||||
import {
 | 
			
		||||
    IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs,
 | 
			
		||||
    TestStakingContract,
 | 
			
		||||
    TestStakingEvents,
 | 
			
		||||
} from '@0x/contracts-staking';
 | 
			
		||||
import { filterLogsToArguments, web3Wrapper } from '@0x/contracts-test-utils';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import { BlockParamLiteral, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
 | 
			
		||||
@@ -10,8 +14,19 @@ export interface KeeperInterface {
 | 
			
		||||
    finalizePoolsAsync: (poolIds?: string[]) => Promise<TransactionReceiptWithDecodedLogs[]>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function fastForwardToNextEpochAsync(stakingContract: TestStakingContract): Promise<void> {
 | 
			
		||||
    // increase timestamp of next block by how many seconds we need to
 | 
			
		||||
    // get to the next epoch.
 | 
			
		||||
    const epochEndTime = await stakingContract.getCurrentEpochEarliestEndTimeInSeconds().callAsync();
 | 
			
		||||
    const lastBlockTime = await web3Wrapper.getBlockTimestampAsync('latest');
 | 
			
		||||
    const dt = Math.max(0, epochEndTime.minus(lastBlockTime).toNumber());
 | 
			
		||||
    await web3Wrapper.increaseTimeAsync(dt);
 | 
			
		||||
    // mine next block
 | 
			
		||||
    await web3Wrapper.mineBlockAsync();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This mixin encapsulates functionaltiy associated with keepers within the 0x ecosystem.
 | 
			
		||||
 * This mixin encapsulates functionality associated with keepers within the 0x ecosystem.
 | 
			
		||||
 * This includes ending epochs sand finalizing pools in the staking system.
 | 
			
		||||
 */
 | 
			
		||||
export function KeeperMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<KeeperInterface> {
 | 
			
		||||
@@ -35,14 +50,7 @@ export function KeeperMixin<TBase extends Constructor>(Base: TBase): TBase & Con
 | 
			
		||||
        public async endEpochAsync(shouldFastForward: boolean = true): Promise<TransactionReceiptWithDecodedLogs> {
 | 
			
		||||
            const { stakingWrapper } = this.actor.deployment.staking;
 | 
			
		||||
            if (shouldFastForward) {
 | 
			
		||||
                // increase timestamp of next block by how many seconds we need to
 | 
			
		||||
                // get to the next epoch.
 | 
			
		||||
                const epochEndTime = await stakingWrapper.getCurrentEpochEarliestEndTimeInSeconds().callAsync();
 | 
			
		||||
                const lastBlockTime = await web3Wrapper.getBlockTimestampAsync('latest');
 | 
			
		||||
                const dt = Math.max(0, epochEndTime.minus(lastBlockTime).toNumber());
 | 
			
		||||
                await web3Wrapper.increaseTimeAsync(dt);
 | 
			
		||||
                // mine next block
 | 
			
		||||
                await web3Wrapper.mineBlockAsync();
 | 
			
		||||
                await fastForwardToNextEpochAsync(stakingWrapper);
 | 
			
		||||
            }
 | 
			
		||||
            return stakingWrapper.endEpoch().awaitTransactionSuccessAsync({ from: this.actor.address });
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ export interface MakerInterface {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This mixin encapsulates functionaltiy associated with makers within the 0x ecosystem.
 | 
			
		||||
 * This mixin encapsulates functionality associated with makers within the 0x ecosystem.
 | 
			
		||||
 * This includes signing and canceling orders, as well as joining a staking pool as a maker.
 | 
			
		||||
 */
 | 
			
		||||
export function MakerMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<MakerInterface> {
 | 
			
		||||
@@ -90,7 +90,7 @@ export function MakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
 | 
			
		||||
            while (true) {
 | 
			
		||||
                const poolId = Pseudorandom.sample(Object.keys(stakingPools));
 | 
			
		||||
                if (poolId === undefined) {
 | 
			
		||||
                    yield undefined;
 | 
			
		||||
                    yield;
 | 
			
		||||
                } else {
 | 
			
		||||
                    yield assertion.executeAsync([poolId], { from: this.actor.address });
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ export interface PoolOperatorInterface {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This mixin encapsulates functionaltiy associated with pool operators within the 0x ecosystem.
 | 
			
		||||
 * This mixin encapsulates functionality associated with pool operators within the 0x ecosystem.
 | 
			
		||||
 * This includes creating staking pools and decreasing the operator share of a pool.
 | 
			
		||||
 */
 | 
			
		||||
export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<PoolOperatorInterface> {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ export interface StakerInterface {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This mixin encapsulates functionaltiy associated with stakers within the 0x ecosystem.
 | 
			
		||||
 * This mixin encapsulates functionality associated with stakers within the 0x ecosystem.
 | 
			
		||||
 * This includes staking ZRX (and optionally delegating it to a specific pool).
 | 
			
		||||
 */
 | 
			
		||||
export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<StakerInterface> {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,17 @@
 | 
			
		||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
 | 
			
		||||
import { constants } from '@0x/contracts-test-utils';
 | 
			
		||||
import { SignedOrder } from '@0x/types';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
 | 
			
		||||
 | 
			
		||||
import { validFillOrderCompleteFillAssertion } from '../assertions/fillOrder';
 | 
			
		||||
import { validFillOrderAssertion } from '../assertions/fillOrder';
 | 
			
		||||
import { AssertionResult } from '../assertions/function_assertion';
 | 
			
		||||
import { DeploymentManager } from '../deployment_manager';
 | 
			
		||||
import { Pseudorandom } from '../utils/pseudorandom';
 | 
			
		||||
 | 
			
		||||
import { Actor, Constructor } from './base';
 | 
			
		||||
import { Maker } from './maker';
 | 
			
		||||
import { filterActorsByRole } from './utils';
 | 
			
		||||
 | 
			
		||||
export interface TakerInterface {
 | 
			
		||||
    fillOrderAsync: (
 | 
			
		||||
@@ -19,7 +22,7 @@ export interface TakerInterface {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This mixin encapsulates functionaltiy associated with takers within the 0x ecosystem.
 | 
			
		||||
 * This mixin encapsulates functionality associated with takers within the 0x ecosystem.
 | 
			
		||||
 * As of writing, the only extra functionality provided is a utility wrapper around `fillOrder`,
 | 
			
		||||
 */
 | 
			
		||||
export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Constructor<TakerInterface> {
 | 
			
		||||
@@ -39,7 +42,7 @@ export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
 | 
			
		||||
            // Register this mixin's assertion generators
 | 
			
		||||
            this.actor.simulationActions = {
 | 
			
		||||
                ...this.actor.simulationActions,
 | 
			
		||||
                validFillOrderCompleteFill: this._validFillOrderCompleteFill(),
 | 
			
		||||
                validFillOrder: this._validFillOrder(),
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -61,31 +64,66 @@ export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async *_validFillOrderCompleteFill(): AsyncIterableIterator<AssertionResult | void> {
 | 
			
		||||
            const { marketMakers } = this.actor.simulationEnvironment!;
 | 
			
		||||
            const assertion = validFillOrderCompleteFillAssertion(this.actor.deployment);
 | 
			
		||||
        private async *_validFillOrder(): AsyncIterableIterator<AssertionResult | void> {
 | 
			
		||||
            const { actors, balanceStore } = this.actor.simulationEnvironment!;
 | 
			
		||||
            const assertion = validFillOrderAssertion(this.actor.deployment);
 | 
			
		||||
            while (true) {
 | 
			
		||||
                const maker = Pseudorandom.sample(marketMakers);
 | 
			
		||||
                const maker = Pseudorandom.sample(filterActorsByRole(actors, Maker));
 | 
			
		||||
                if (maker === undefined) {
 | 
			
		||||
                    yield undefined;
 | 
			
		||||
                    yield;
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Configure the maker's token balances so that the order will definitely be fillable.
 | 
			
		||||
                    await Promise.all([
 | 
			
		||||
                        ...this.actor.deployment.tokens.erc20.map(async token => maker.configureERC20TokenAsync(token)),
 | 
			
		||||
                        ...this.actor.deployment.tokens.erc20.map(async token =>
 | 
			
		||||
                            this.actor.configureERC20TokenAsync(token),
 | 
			
		||||
                    await balanceStore.updateErc20BalancesAsync();
 | 
			
		||||
                    const [makerToken, makerFeeToken, takerToken, takerFeeToken] = Pseudorandom.sampleSize(
 | 
			
		||||
                        this.actor.deployment.tokens.erc20,
 | 
			
		||||
                        4, // tslint:disable-line:custom-no-magic-numbers
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    const configureOrderAssetAsync = async (
 | 
			
		||||
                        owner: Actor,
 | 
			
		||||
                        token: DummyERC20TokenContract,
 | 
			
		||||
                    ): Promise<BigNumber> => {
 | 
			
		||||
                        let balance = balanceStore.balances.erc20[owner.address][token.address];
 | 
			
		||||
                        if (balance === undefined || balance.isZero()) {
 | 
			
		||||
                            await owner.configureERC20TokenAsync(token);
 | 
			
		||||
                            balance = balanceStore.balances.erc20[owner.address][token.address] =
 | 
			
		||||
                                constants.INITIAL_ERC20_BALANCE;
 | 
			
		||||
                        }
 | 
			
		||||
                        return Pseudorandom.integer(balance.dividedToIntegerBy(2));
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    const [makerAssetAmount, makerFee, takerAssetAmount, takerFee] = await Promise.all(
 | 
			
		||||
                        [
 | 
			
		||||
                            [maker, makerToken],
 | 
			
		||||
                            [maker, makerFeeToken],
 | 
			
		||||
                            [this.actor, takerToken],
 | 
			
		||||
                            [this.actor, takerFeeToken],
 | 
			
		||||
                        ].map(async ([owner, token]) =>
 | 
			
		||||
                            configureOrderAssetAsync(owner as Actor, token as DummyERC20TokenContract),
 | 
			
		||||
                        ),
 | 
			
		||||
                        this.actor.configureERC20TokenAsync(
 | 
			
		||||
                            this.actor.deployment.tokens.weth,
 | 
			
		||||
                            this.actor.deployment.staking.stakingProxy.address,
 | 
			
		||||
                        ),
 | 
			
		||||
                    ]);
 | 
			
		||||
                    );
 | 
			
		||||
                    const [makerAssetData, makerFeeAssetData, takerAssetData, takerFeeAssetData] = [
 | 
			
		||||
                        makerToken,
 | 
			
		||||
                        makerFeeToken,
 | 
			
		||||
                        takerToken,
 | 
			
		||||
                        takerFeeToken,
 | 
			
		||||
                    ].map(token =>
 | 
			
		||||
                        this.actor.deployment.assetDataEncoder.ERC20Token(token.address).getABIEncodedTransactionData(),
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    const order = await maker.signOrderAsync({
 | 
			
		||||
                        makerAssetAmount: Pseudorandom.integer(constants.INITIAL_ERC20_BALANCE),
 | 
			
		||||
                        takerAssetAmount: Pseudorandom.integer(constants.INITIAL_ERC20_BALANCE),
 | 
			
		||||
                        makerAssetData,
 | 
			
		||||
                        takerAssetData,
 | 
			
		||||
                        makerFeeAssetData,
 | 
			
		||||
                        takerFeeAssetData,
 | 
			
		||||
                        makerAssetAmount,
 | 
			
		||||
                        takerAssetAmount,
 | 
			
		||||
                        makerFee,
 | 
			
		||||
                        takerFee,
 | 
			
		||||
                        feeRecipientAddress: Pseudorandom.sample(actors)!.address,
 | 
			
		||||
                    });
 | 
			
		||||
                    yield assertion.executeAsync([order, order.takerAssetAmount, order.signature], {
 | 
			
		||||
 | 
			
		||||
                    const fillAmount = Pseudorandom.integer(order.takerAssetAmount);
 | 
			
		||||
                    yield assertion.executeAsync([order, fillAmount, order.signature], {
 | 
			
		||||
                        from: this.actor.address,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import { ObjectMap } from '@0x/types';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
import { Actor } from './base';
 | 
			
		||||
import { Actor, Constructor } from './base';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Utility function to convert Actors into an object mapping readable names to addresses.
 | 
			
		||||
@@ -10,3 +10,13 @@ import { Actor } from './base';
 | 
			
		||||
export function actorAddressesByName(actors: Actor[]): ObjectMap<string> {
 | 
			
		||||
    return _.zipObject(actors.map(actor => actor.name), actors.map(actor => actor.address));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Filters the given actors by class.
 | 
			
		||||
 */
 | 
			
		||||
export function filterActorsByRole<TClass extends Constructor>(
 | 
			
		||||
    actors: Actor[],
 | 
			
		||||
    role: TClass,
 | 
			
		||||
): Array<InstanceType<typeof role>> {
 | 
			
		||||
    return actors.filter(actor => actor instanceof role) as InstanceType<typeof role>;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { StakingPoolById, StoredBalance } from '@0x/contracts-staking';
 | 
			
		||||
import { StakingPool, StakingPoolById } from '@0x/contracts-staking';
 | 
			
		||||
import { expect } from '@0x/contracts-test-utils';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import { TxData } from 'ethereum-types';
 | 
			
		||||
@@ -44,11 +44,7 @@ export function validCreateStakingPoolAssertion(
 | 
			
		||||
            expect(actualPoolId).to.equal(expectedPoolId);
 | 
			
		||||
 | 
			
		||||
            // Adds the new pool to local state
 | 
			
		||||
            pools[actualPoolId] = {
 | 
			
		||||
                operator: txData.from!,
 | 
			
		||||
                operatorShare,
 | 
			
		||||
                delegatedStake: new StoredBalance(),
 | 
			
		||||
            };
 | 
			
		||||
            pools[actualPoolId] = new StakingPool(txData.from!, operatorShare);
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import { ERC20TokenEvents, ERC20TokenTransferEventArgs } from '@0x/contracts-erc20';
 | 
			
		||||
import { ExchangeEvents, ExchangeFillEventArgs } from '@0x/contracts-exchange';
 | 
			
		||||
import { constants, expect, orderHashUtils, verifyEvents } from '@0x/contracts-test-utils';
 | 
			
		||||
import { ReferenceFunctions } from '@0x/contracts-exchange-libs';
 | 
			
		||||
import { expect, orderHashUtils, verifyEvents } from '@0x/contracts-test-utils';
 | 
			
		||||
import { FillResults, Order } from '@0x/types';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
 | 
			
		||||
@@ -15,7 +16,14 @@ function verifyFillEvents(
 | 
			
		||||
    order: Order,
 | 
			
		||||
    receipt: TransactionReceiptWithDecodedLogs,
 | 
			
		||||
    deployment: DeploymentManager,
 | 
			
		||||
    takerAssetFillAmount: BigNumber,
 | 
			
		||||
): void {
 | 
			
		||||
    const fillResults = ReferenceFunctions.calculateFillResults(
 | 
			
		||||
        order,
 | 
			
		||||
        takerAssetFillAmount,
 | 
			
		||||
        DeploymentManager.protocolFeeMultiplier,
 | 
			
		||||
        DeploymentManager.gasPrice,
 | 
			
		||||
    );
 | 
			
		||||
    // Ensure that the fill event was correct.
 | 
			
		||||
    verifyEvents<ExchangeFillEventArgs>(
 | 
			
		||||
        receipt,
 | 
			
		||||
@@ -30,11 +38,7 @@ function verifyFillEvents(
 | 
			
		||||
                orderHash: orderHashUtils.getOrderHashHex(order),
 | 
			
		||||
                takerAddress,
 | 
			
		||||
                senderAddress: takerAddress,
 | 
			
		||||
                makerAssetFilledAmount: order.makerAssetAmount,
 | 
			
		||||
                takerAssetFilledAmount: order.takerAssetAmount,
 | 
			
		||||
                makerFeePaid: constants.ZERO_AMOUNT,
 | 
			
		||||
                takerFeePaid: constants.ZERO_AMOUNT,
 | 
			
		||||
                protocolFeePaid: DeploymentManager.protocolFee,
 | 
			
		||||
                ...fillResults,
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
        ExchangeEvents.Fill,
 | 
			
		||||
@@ -47,12 +51,22 @@ function verifyFillEvents(
 | 
			
		||||
            {
 | 
			
		||||
                _from: takerAddress,
 | 
			
		||||
                _to: order.makerAddress,
 | 
			
		||||
                _value: order.takerAssetAmount,
 | 
			
		||||
                _value: fillResults.takerAssetFilledAmount,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                _from: order.makerAddress,
 | 
			
		||||
                _to: takerAddress,
 | 
			
		||||
                _value: order.makerAssetAmount,
 | 
			
		||||
                _value: fillResults.makerAssetFilledAmount,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                _from: takerAddress,
 | 
			
		||||
                _to: order.feeRecipientAddress,
 | 
			
		||||
                _value: fillResults.takerFeePaid,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                _from: order.makerAddress,
 | 
			
		||||
                _to: order.feeRecipientAddress,
 | 
			
		||||
                _value: fillResults.makerFeePaid,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                _from: takerAddress,
 | 
			
		||||
@@ -69,7 +83,7 @@ function verifyFillEvents(
 | 
			
		||||
 */
 | 
			
		||||
/* tslint:disable:no-unnecessary-type-assertion */
 | 
			
		||||
/* tslint:disable:no-non-null-assertion */
 | 
			
		||||
export function validFillOrderCompleteFillAssertion(
 | 
			
		||||
export function validFillOrderAssertion(
 | 
			
		||||
    deployment: DeploymentManager,
 | 
			
		||||
): FunctionAssertion<[Order, BigNumber, string], {}, FillResults> {
 | 
			
		||||
    const exchange = deployment.exchange;
 | 
			
		||||
@@ -81,13 +95,13 @@ export function validFillOrderCompleteFillAssertion(
 | 
			
		||||
            args: [Order, BigNumber, string],
 | 
			
		||||
            txData: Partial<TxData>,
 | 
			
		||||
        ) => {
 | 
			
		||||
            const [order] = args;
 | 
			
		||||
            const [order, fillAmount] = args;
 | 
			
		||||
 | 
			
		||||
            // Ensure that the tx succeeded.
 | 
			
		||||
            expect(result.success).to.be.true();
 | 
			
		||||
            expect(result.success, `Error: ${result.data}`).to.be.true();
 | 
			
		||||
 | 
			
		||||
            // Ensure that the correct events were emitted.
 | 
			
		||||
            verifyFillEvents(txData.from!, order, result.receipt!, deployment);
 | 
			
		||||
            verifyFillEvents(txData.from!, order, result.receipt!, deployment, fillAmount);
 | 
			
		||||
 | 
			
		||||
            // TODO: Add validation for on-chain state (like balances)
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,13 @@
 | 
			
		||||
import { GlobalStakeByStatus, StakeStatus, StakingPoolById, StoredBalance } from '@0x/contracts-staking';
 | 
			
		||||
import {
 | 
			
		||||
    constants as stakingConstants,
 | 
			
		||||
    GlobalStakeByStatus,
 | 
			
		||||
    StakeStatus,
 | 
			
		||||
    StakingPoolById,
 | 
			
		||||
    StoredBalance,
 | 
			
		||||
} from '@0x/contracts-staking';
 | 
			
		||||
import { BigNumber } from '@0x/utils';
 | 
			
		||||
 | 
			
		||||
import { Maker } from './actors/maker';
 | 
			
		||||
import { Actor } from './actors/base';
 | 
			
		||||
import { AssertionResult } from './assertions/function_assertion';
 | 
			
		||||
import { BlockchainBalanceStore } from './balances/blockchain_balance_store';
 | 
			
		||||
import { DeploymentManager } from './deployment_manager';
 | 
			
		||||
@@ -14,11 +21,12 @@ export class SimulationEnvironment {
 | 
			
		||||
        [StakeStatus.Delegated]: new StoredBalance(),
 | 
			
		||||
    };
 | 
			
		||||
    public stakingPools: StakingPoolById = {};
 | 
			
		||||
    public currentEpoch: BigNumber = stakingConstants.INITIAL_EPOCH;
 | 
			
		||||
 | 
			
		||||
    public constructor(
 | 
			
		||||
        public readonly deployment: DeploymentManager,
 | 
			
		||||
        public balanceStore: BlockchainBalanceStore,
 | 
			
		||||
        public marketMakers: Maker[] = [],
 | 
			
		||||
        public actors: Actor[] = [],
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public state(): any {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,21 @@ class PRNGWrapper {
 | 
			
		||||
        return arr[index];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Pseudorandom version of _.sampleSize. Returns an array of `n` samples from the given array
 | 
			
		||||
     * (with replacement), chosen with uniform probability. Return undefined if the array is empty.
 | 
			
		||||
     */
 | 
			
		||||
    public sampleSize<T>(arr: T[], n: number): T[] | undefined {
 | 
			
		||||
        if (arr.length === 0) {
 | 
			
		||||
            return undefined;
 | 
			
		||||
        }
 | 
			
		||||
        const samples = [];
 | 
			
		||||
        for (let i = 0; i < n; i++) {
 | 
			
		||||
            samples.push(this.sample(arr) as T);
 | 
			
		||||
        }
 | 
			
		||||
        return samples;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // tslint:disable:unified-signatures
 | 
			
		||||
    /*
 | 
			
		||||
     * Pseudorandom version of getRandomPortion/getRandomInteger. If two arguments are provided,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,10 @@
 | 
			
		||||
import { blockchainTests, constants } from '@0x/contracts-test-utils';
 | 
			
		||||
import { blockchainTests } from '@0x/contracts-test-utils';
 | 
			
		||||
 | 
			
		||||
import { Actor } from '../framework/actors/base';
 | 
			
		||||
import { MakerTaker } from '../framework/actors/hybrids';
 | 
			
		||||
import { Maker } from '../framework/actors/maker';
 | 
			
		||||
import { Taker } from '../framework/actors/taker';
 | 
			
		||||
import { filterActorsByRole } from '../framework/actors/utils';
 | 
			
		||||
import { AssertionResult } from '../framework/assertions/function_assertion';
 | 
			
		||||
import { BlockchainBalanceStore } from '../framework/balances/blockchain_balance_store';
 | 
			
		||||
import { DeploymentManager } from '../framework/deployment_manager';
 | 
			
		||||
@@ -12,19 +15,15 @@ import { PoolManagementSimulation } from './pool_management_test';
 | 
			
		||||
 | 
			
		||||
class PoolMembershipSimulation extends Simulation {
 | 
			
		||||
    protected async *_assertionGenerator(): AsyncIterableIterator<AssertionResult | void> {
 | 
			
		||||
        const { deployment } = this.environment;
 | 
			
		||||
        const { actors } = this.environment;
 | 
			
		||||
        const makers = filterActorsByRole(actors, Maker);
 | 
			
		||||
        const takers = filterActorsByRole(actors, Taker);
 | 
			
		||||
 | 
			
		||||
        const poolManagement = new PoolManagementSimulation(this.environment);
 | 
			
		||||
 | 
			
		||||
        const member = new MakerTaker({
 | 
			
		||||
            name: 'member',
 | 
			
		||||
            deployment,
 | 
			
		||||
            simulationEnvironment: this.environment,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const actions = [
 | 
			
		||||
            member.simulationActions.validJoinStakingPool,
 | 
			
		||||
            member.simulationActions.validFillOrderCompleteFill,
 | 
			
		||||
            ...makers.map(maker => maker.simulationActions.validJoinStakingPool),
 | 
			
		||||
            ...takers.map(taker => taker.simulationActions.validFillOrder),
 | 
			
		||||
            poolManagement.generator,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
@@ -36,45 +35,23 @@ class PoolMembershipSimulation extends Simulation {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
blockchainTests('pool membership fuzz test', env => {
 | 
			
		||||
    let deployment: DeploymentManager;
 | 
			
		||||
    let maker: Maker;
 | 
			
		||||
 | 
			
		||||
    before(async function(): Promise<void> {
 | 
			
		||||
        if (process.env.FUZZ_TEST !== 'pool_membership') {
 | 
			
		||||
            this.skip();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
        deployment = await DeploymentManager.deployAsync(env, {
 | 
			
		||||
            numErc20TokensToDeploy: 2,
 | 
			
		||||
    after(async () => {
 | 
			
		||||
        Actor.count = 0;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('fuzz', async () => {
 | 
			
		||||
        const deployment = await DeploymentManager.deployAsync(env, {
 | 
			
		||||
            numErc20TokensToDeploy: 4,
 | 
			
		||||
            numErc721TokensToDeploy: 0,
 | 
			
		||||
            numErc1155TokensToDeploy: 0,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const makerToken = deployment.tokens.erc20[0];
 | 
			
		||||
        const takerToken = deployment.tokens.erc20[1];
 | 
			
		||||
 | 
			
		||||
        const orderConfig = {
 | 
			
		||||
            feeRecipientAddress: constants.NULL_ADDRESS,
 | 
			
		||||
            makerAssetData: deployment.assetDataEncoder.ERC20Token(makerToken.address).getABIEncodedTransactionData(),
 | 
			
		||||
            takerAssetData: deployment.assetDataEncoder.ERC20Token(takerToken.address).getABIEncodedTransactionData(),
 | 
			
		||||
            makerFeeAssetData: deployment.assetDataEncoder
 | 
			
		||||
                .ERC20Token(makerToken.address)
 | 
			
		||||
                .getABIEncodedTransactionData(),
 | 
			
		||||
            takerFeeAssetData: deployment.assetDataEncoder
 | 
			
		||||
                .ERC20Token(takerToken.address)
 | 
			
		||||
                .getABIEncodedTransactionData(),
 | 
			
		||||
            makerFee: constants.ZERO_AMOUNT,
 | 
			
		||||
            takerFee: constants.ZERO_AMOUNT,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        maker = new Maker({
 | 
			
		||||
            name: 'maker',
 | 
			
		||||
            deployment,
 | 
			
		||||
            orderConfig,
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('fuzz', async () => {
 | 
			
		||||
        const balanceStore = new BlockchainBalanceStore(
 | 
			
		||||
            {
 | 
			
		||||
                StakingProxy: deployment.staking.stakingProxy.address,
 | 
			
		||||
@@ -82,9 +59,25 @@ blockchainTests('pool membership fuzz test', env => {
 | 
			
		||||
            },
 | 
			
		||||
            { erc20: { ZRX: deployment.tokens.zrx } },
 | 
			
		||||
        );
 | 
			
		||||
        const simulationEnvironment = new SimulationEnvironment(deployment, balanceStore);
 | 
			
		||||
 | 
			
		||||
        const simulationEnv = new SimulationEnvironment(deployment, balanceStore, [maker]);
 | 
			
		||||
        const simulation = new PoolMembershipSimulation(simulationEnv);
 | 
			
		||||
        const actors = [
 | 
			
		||||
            new Maker({ deployment, simulationEnvironment, name: 'Maker 1' }),
 | 
			
		||||
            new Maker({ deployment, simulationEnvironment, name: 'Maker 2' }),
 | 
			
		||||
            new Taker({ deployment, simulationEnvironment, name: 'Taker 1' }),
 | 
			
		||||
            new Taker({ deployment, simulationEnvironment, name: 'Taker 2' }),
 | 
			
		||||
            new MakerTaker({ deployment, simulationEnvironment, name: 'Maker/Taker' }),
 | 
			
		||||
        ];
 | 
			
		||||
        const takers = filterActorsByRole(actors, Taker);
 | 
			
		||||
        for (const taker of takers) {
 | 
			
		||||
            await taker.configureERC20TokenAsync(deployment.tokens.weth, deployment.staking.stakingProxy.address);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const actor of actors) {
 | 
			
		||||
            balanceStore.registerTokenOwner(actor.address, actor.name);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const simulation = new PoolMembershipSimulation(simulationEnvironment);
 | 
			
		||||
        return simulation.fuzzAsync();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,7 @@ export { artifacts } from './artifacts';
 | 
			
		||||
export { StakingRevertErrors, FixedMathRevertErrors } from '@0x/utils';
 | 
			
		||||
export { constants } from './constants';
 | 
			
		||||
export {
 | 
			
		||||
    AggregatedStats,
 | 
			
		||||
    StakeInfo,
 | 
			
		||||
    StakeStatus,
 | 
			
		||||
    StoredBalance,
 | 
			
		||||
 
 | 
			
		||||
@@ -147,12 +147,45 @@ export interface OwnerStakeByStatus {
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface StakingPool {
 | 
			
		||||
    operator: string;
 | 
			
		||||
    operatorShare: number;
 | 
			
		||||
    delegatedStake: StoredBalance;
 | 
			
		||||
interface Fraction {
 | 
			
		||||
    numerator: BigNumber;
 | 
			
		||||
    denominator: BigNumber;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class StakingPool {
 | 
			
		||||
    public delegatedStake: StoredBalance = new StoredBalance();
 | 
			
		||||
    public rewards: BigNumber = constants.ZERO_AMOUNT;
 | 
			
		||||
    public cumulativeRewards: {
 | 
			
		||||
        [epoch: string]: Fraction;
 | 
			
		||||
    } = {};
 | 
			
		||||
    public cumulativeRewardsLastStored: string = stakingConstants.INITIAL_EPOCH.toString();
 | 
			
		||||
    public stats: {
 | 
			
		||||
        [epoch: string]: PoolStats;
 | 
			
		||||
    } = {};
 | 
			
		||||
 | 
			
		||||
    constructor(public readonly operator: string, public operatorShare: number) {}
 | 
			
		||||
 | 
			
		||||
    public finalize(): void {}
 | 
			
		||||
    public creditProtocolFee(): void {}
 | 
			
		||||
    public withdrawDelegatorRewards(delegator: string): void {}
 | 
			
		||||
    public delegateStake(delegator: string, amount: BigNumber): void {}
 | 
			
		||||
    public undelegateStake(delegator: string, amount: BigNumber): void {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface StakingPoolById {
 | 
			
		||||
    [poolId: string]: StakingPool;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface PoolStats {
 | 
			
		||||
    feesCollected: BigNumber;
 | 
			
		||||
    weightedStake: BigNumber;
 | 
			
		||||
    membersStake: BigNumber;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface AggregatedStats {
 | 
			
		||||
    rewardsAvailable: BigNumber;
 | 
			
		||||
    numPoolsToFinalize: BigNumber;
 | 
			
		||||
    totalFeesCollected: BigNumber;
 | 
			
		||||
    totalWeightedStake: BigNumber;
 | 
			
		||||
    totalRewardsFinalized: BigNumber;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user