Merge pull request #2366 from 0xProject/feature/fuzz/makers-and-takers
Pool Member Fuzz Tests
This commit is contained in:
@@ -3,9 +3,11 @@ import { KeeperMixin } from './keeper';
|
||||
import { MakerMixin } from './maker';
|
||||
import { PoolOperatorMixin } from './pool_operator';
|
||||
import { StakerMixin } from './staker';
|
||||
import { TakerMixin } from './taker';
|
||||
|
||||
export class OperatorMaker extends PoolOperatorMixin(MakerMixin(Actor)) {}
|
||||
export class StakerMaker extends StakerMixin(MakerMixin(Actor)) {}
|
||||
export class StakerOperator extends StakerMixin(PoolOperatorMixin(Actor)) {}
|
||||
export class OperatorStakerMaker extends PoolOperatorMixin(StakerMixin(MakerMixin(Actor))) {}
|
||||
export class StakerKeeper extends StakerMixin(KeeperMixin(Actor)) {}
|
||||
export class MakerTaker extends MakerMixin(TakerMixin(Actor)) {}
|
||||
|
@@ -1,6 +1,10 @@
|
||||
import { constants, OrderFactory } from '@0x/contracts-test-utils';
|
||||
import { Order, SignedOrder } from '@0x/types';
|
||||
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { AssertionResult } from '../assertions/function_assertion';
|
||||
import { validJoinStakingPoolAssertion } from '../assertions/joinStakingPool';
|
||||
|
||||
import { Actor, ActorConfig, Constructor } from './base';
|
||||
|
||||
@@ -45,6 +49,12 @@ export function MakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
|
||||
...orderConfig,
|
||||
};
|
||||
this.orderFactory = new OrderFactory(this.actor.privateKey, defaultOrderParams);
|
||||
|
||||
// Register this mixin's assertion generators
|
||||
this.actor.simulationActions = {
|
||||
...this.actor.simulationActions,
|
||||
validJoinStakingPool: this._validJoinStakingPool(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,6 +83,19 @@ export function MakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
|
||||
from: this.actor.address,
|
||||
});
|
||||
}
|
||||
|
||||
private async *_validJoinStakingPool(): AsyncIterableIterator<AssertionResult | void> {
|
||||
const { stakingPools } = this.actor.simulationEnvironment!;
|
||||
const assertion = validJoinStakingPoolAssertion(this.actor.deployment);
|
||||
while (true) {
|
||||
const poolId = _.sample(Object.keys(stakingPools));
|
||||
if (poolId === undefined) {
|
||||
yield undefined;
|
||||
} else {
|
||||
yield assertion.executeAsync([poolId], { from: this.actor.address });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -83,8 +83,8 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
|
||||
const { stakingPools } = this.actor.simulationEnvironment!;
|
||||
const assertion = validCreateStakingPoolAssertion(this.actor.deployment, stakingPools);
|
||||
while (true) {
|
||||
const operatorShare = getRandomInteger(0, constants.PPM);
|
||||
yield assertion.executeAsync(operatorShare, false, { from: this.actor.address });
|
||||
const operatorShare = getRandomInteger(0, constants.PPM).toNumber();
|
||||
yield assertion.executeAsync([operatorShare, false], { from: this.actor.address });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,8 +96,8 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
|
||||
if (poolId === undefined) {
|
||||
yield undefined;
|
||||
} else {
|
||||
const operatorShare = getRandomInteger(0, stakingPools[poolId].operatorShare);
|
||||
yield assertion.executeAsync(poolId, operatorShare, { from: this.actor.address });
|
||||
const operatorShare = getRandomInteger(0, stakingPools[poolId].operatorShare).toNumber();
|
||||
yield assertion.executeAsync([poolId, operatorShare], { from: this.actor.address });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -76,7 +76,7 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
|
||||
await balanceStore.updateErc20BalancesAsync();
|
||||
const zrxBalance = balanceStore.balances.erc20[this.actor.address][zrx.address];
|
||||
const amount = getRandomInteger(0, zrxBalance);
|
||||
yield assertion.executeAsync(amount, { from: this.actor.address });
|
||||
yield assertion.executeAsync([amount], { from: this.actor.address });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
|
||||
undelegatedStake.nextEpochBalance,
|
||||
);
|
||||
const amount = getRandomInteger(0, withdrawableStake);
|
||||
yield assertion.executeAsync(amount, { from: this.actor.address });
|
||||
yield assertion.executeAsync([amount], { from: this.actor.address });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
|
||||
: this.stake[StakeStatus.Delegated][from.poolId].nextEpochBalance;
|
||||
const amount = getRandomInteger(0, moveableStake);
|
||||
|
||||
yield assertion.executeAsync(from, to, amount, { from: this.actor.address });
|
||||
yield assertion.executeAsync([from, to, amount], { from: this.actor.address });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,7 +1,11 @@
|
||||
import { constants, getRandomInteger } from '@0x/contracts-test-utils';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { validFillOrderCompleteFillAssertion } from '../assertions/fillOrder';
|
||||
import { AssertionResult } from '../assertions/function_assertion';
|
||||
import { DeploymentManager } from '../deployment_manager';
|
||||
|
||||
import { Actor, Constructor } from './base';
|
||||
@@ -31,6 +35,12 @@ export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
|
||||
// tslint:disable-next-line:no-inferred-empty-object-type
|
||||
super(...args);
|
||||
this.actor = (this as any) as Actor;
|
||||
|
||||
// Register this mixin's assertion generators
|
||||
this.actor.simulationActions = {
|
||||
...this.actor.simulationActions,
|
||||
validFillOrderCompleteFill: this._validFillOrderCompleteFill(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,6 +60,37 @@ export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
|
||||
...txData,
|
||||
});
|
||||
}
|
||||
|
||||
private async *_validFillOrderCompleteFill(): AsyncIterableIterator<AssertionResult | void> {
|
||||
const { marketMakers } = this.actor.simulationEnvironment!;
|
||||
const assertion = validFillOrderCompleteFillAssertion(this.actor.deployment);
|
||||
while (true) {
|
||||
const maker = _.sample(marketMakers);
|
||||
if (maker === undefined) {
|
||||
yield undefined;
|
||||
} 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),
|
||||
),
|
||||
this.actor.configureERC20TokenAsync(
|
||||
this.actor.deployment.tokens.weth,
|
||||
this.actor.deployment.staking.stakingProxy.address,
|
||||
),
|
||||
]);
|
||||
|
||||
const order = await maker.signOrderAsync({
|
||||
makerAssetAmount: getRandomInteger(constants.ZERO_AMOUNT, constants.INITIAL_ERC20_BALANCE),
|
||||
takerAssetAmount: getRandomInteger(constants.ZERO_AMOUNT, constants.INITIAL_ERC20_BALANCE),
|
||||
});
|
||||
yield assertion.executeAsync([order, order.takerAssetAmount, order.signature], {
|
||||
from: this.actor.address,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -13,42 +13,48 @@ import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||
* Returns a FunctionAssertion for `createStakingPool` which assumes valid input is provided. The
|
||||
* FunctionAssertion checks that the new poolId is one more than the last poolId.
|
||||
*/
|
||||
/* tslint:disable:no-non-null-assertion */
|
||||
export function validCreateStakingPoolAssertion(
|
||||
deployment: DeploymentManager,
|
||||
pools: StakingPoolById,
|
||||
): FunctionAssertion<string, string> {
|
||||
): FunctionAssertion<[number, boolean], string, string> {
|
||||
const { stakingWrapper } = deployment.staking;
|
||||
|
||||
return new FunctionAssertion(stakingWrapper.createStakingPool, {
|
||||
// Returns the expected ID of th created pool
|
||||
before: async () => {
|
||||
const lastPoolId = await stakingWrapper.lastPoolId().callAsync();
|
||||
// Effectively the last poolId + 1, but as a bytestring
|
||||
return `0x${new BigNumber(lastPoolId)
|
||||
.plus(1)
|
||||
.toString(16)
|
||||
.padStart(64, '0')}`;
|
||||
},
|
||||
after: async (
|
||||
expectedPoolId: string,
|
||||
result: FunctionResult,
|
||||
operatorShare: number,
|
||||
addOperatorAsMaker: boolean,
|
||||
txData: Partial<TxData>,
|
||||
) => {
|
||||
logUtils.log(`createStakingPool(${operatorShare}, ${addOperatorAsMaker}) => ${expectedPoolId}`);
|
||||
return new FunctionAssertion<[number, boolean], string, string>(
|
||||
stakingWrapper.createStakingPool.bind(stakingWrapper),
|
||||
{
|
||||
// Returns the expected ID of th created pool
|
||||
before: async () => {
|
||||
const lastPoolId = await stakingWrapper.lastPoolId().callAsync();
|
||||
// Effectively the last poolId + 1, but as a bytestring
|
||||
return `0x${new BigNumber(lastPoolId)
|
||||
.plus(1)
|
||||
.toString(16)
|
||||
.padStart(64, '0')}`;
|
||||
},
|
||||
after: async (
|
||||
expectedPoolId: string,
|
||||
result: FunctionResult,
|
||||
args: [number, boolean],
|
||||
txData: Partial<TxData>,
|
||||
) => {
|
||||
const [operatorShare, shouldAddMakerAsOperator] = args;
|
||||
|
||||
// Checks the logs for the new poolId, verifies that it is as expected
|
||||
const log = result.receipt!.logs[0]; // tslint:disable-line:no-non-null-assertion
|
||||
const actualPoolId = (log as any).args.poolId;
|
||||
expect(actualPoolId).to.equal(expectedPoolId);
|
||||
logUtils.log(`createStakingPool(${operatorShare}, ${shouldAddMakerAsOperator}) => ${expectedPoolId}`);
|
||||
|
||||
// Adds the new pool to local state
|
||||
pools[actualPoolId] = {
|
||||
operator: txData.from as string,
|
||||
operatorShare,
|
||||
delegatedStake: new StoredBalance(),
|
||||
};
|
||||
// Checks the logs for the new poolId, verifies that it is as expected
|
||||
const log = result.receipt!.logs[0];
|
||||
const actualPoolId = (log as any).args.poolId;
|
||||
expect(actualPoolId).to.equal(expectedPoolId);
|
||||
|
||||
// Adds the new pool to local state
|
||||
pools[actualPoolId] = {
|
||||
operator: txData.from!,
|
||||
operatorShare,
|
||||
delegatedStake: new StoredBalance(),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
}
|
||||
/* tslint:enable:no-non-null-assertion*/
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { StakingPoolById } from '@0x/contracts-staking';
|
||||
import { expect } from '@0x/contracts-test-utils';
|
||||
import { logUtils } from '@0x/utils';
|
||||
import { TxData } from 'ethereum-types';
|
||||
|
||||
import { DeploymentManager } from '../deployment_manager';
|
||||
|
||||
@@ -13,18 +14,24 @@ import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||
export function validDecreaseStakingPoolOperatorShareAssertion(
|
||||
deployment: DeploymentManager,
|
||||
pools: StakingPoolById,
|
||||
): FunctionAssertion<{}, void> {
|
||||
): FunctionAssertion<[string, number], {}, void> {
|
||||
const { stakingWrapper } = deployment.staking;
|
||||
|
||||
return new FunctionAssertion<{}, void>(stakingWrapper.decreaseStakingPoolOperatorShare, {
|
||||
after: async (_beforeInfo, _result: FunctionResult, poolId: string, expectedOperatorShare: number) => {
|
||||
logUtils.log(`decreaseStakingPoolOperatorShare(${poolId}, ${expectedOperatorShare})`);
|
||||
return new FunctionAssertion<[string, number], {}, void>(
|
||||
stakingWrapper.decreaseStakingPoolOperatorShare.bind(stakingWrapper),
|
||||
{
|
||||
after: async (_beforeInfo, _result: FunctionResult, args: [string, number], txData: Partial<TxData>) => {
|
||||
const [poolId, expectedOperatorShare] = args;
|
||||
|
||||
// Checks that the on-chain pool's operator share has been updated.
|
||||
const { operatorShare } = await stakingWrapper.getStakingPool(poolId).callAsync();
|
||||
expect(operatorShare).to.bignumber.equal(expectedOperatorShare);
|
||||
// Updates the pool in local state.
|
||||
pools[poolId].operatorShare = operatorShare;
|
||||
logUtils.log(`decreaseStakingPoolOperatorShare(${poolId}, ${expectedOperatorShare})`);
|
||||
|
||||
// Checks that the on-chain pool's operator share has been updated.
|
||||
const { operatorShare } = await stakingWrapper.getStakingPool(poolId).callAsync();
|
||||
expect(operatorShare).to.bignumber.equal(expectedOperatorShare);
|
||||
|
||||
// Updates the pool in local state.
|
||||
pools[poolId].operatorShare = operatorShare;
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
}
|
||||
|
@@ -0,0 +1,99 @@
|
||||
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 { FillResults, Order } from '@0x/types';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DeploymentManager } from '../deployment_manager';
|
||||
|
||||
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||
|
||||
function verifyFillEvents(
|
||||
takerAddress: string,
|
||||
order: Order,
|
||||
receipt: TransactionReceiptWithDecodedLogs,
|
||||
deployment: DeploymentManager,
|
||||
): void {
|
||||
// Ensure that the fill event was correct.
|
||||
verifyEvents<ExchangeFillEventArgs>(
|
||||
receipt,
|
||||
[
|
||||
{
|
||||
makerAddress: order.makerAddress,
|
||||
feeRecipientAddress: order.feeRecipientAddress,
|
||||
makerAssetData: order.makerAssetData,
|
||||
takerAssetData: order.takerAssetData,
|
||||
makerFeeAssetData: order.makerFeeAssetData,
|
||||
takerFeeAssetData: order.takerFeeAssetData,
|
||||
orderHash: orderHashUtils.getOrderHashHex(order),
|
||||
takerAddress,
|
||||
senderAddress: takerAddress,
|
||||
makerAssetFilledAmount: order.makerAssetAmount,
|
||||
takerAssetFilledAmount: order.takerAssetAmount,
|
||||
makerFeePaid: constants.ZERO_AMOUNT,
|
||||
takerFeePaid: constants.ZERO_AMOUNT,
|
||||
protocolFeePaid: DeploymentManager.protocolFee,
|
||||
},
|
||||
],
|
||||
ExchangeEvents.Fill,
|
||||
);
|
||||
|
||||
// Ensure that the transfer events were correctly emitted.
|
||||
verifyEvents<ERC20TokenTransferEventArgs>(
|
||||
receipt,
|
||||
[
|
||||
{
|
||||
_from: takerAddress,
|
||||
_to: order.makerAddress,
|
||||
_value: order.takerAssetAmount,
|
||||
},
|
||||
{
|
||||
_from: order.makerAddress,
|
||||
_to: takerAddress,
|
||||
_value: order.makerAssetAmount,
|
||||
},
|
||||
{
|
||||
_from: takerAddress,
|
||||
_to: deployment.staking.stakingProxy.address,
|
||||
_value: DeploymentManager.protocolFee,
|
||||
},
|
||||
],
|
||||
ERC20TokenEvents.Transfer,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A function assertion that verifies that a complete and valid fill succeeded and emitted the correct logs.
|
||||
*/
|
||||
/* tslint:disable:no-unnecessary-type-assertion */
|
||||
/* tslint:disable:no-non-null-assertion */
|
||||
export function validFillOrderCompleteFillAssertion(
|
||||
deployment: DeploymentManager,
|
||||
): FunctionAssertion<[Order, BigNumber, string], {}, FillResults> {
|
||||
const exchange = deployment.exchange;
|
||||
|
||||
return new FunctionAssertion<[Order, BigNumber, string], {}, FillResults>(exchange.fillOrder.bind(exchange), {
|
||||
after: async (
|
||||
_beforeInfo,
|
||||
result: FunctionResult,
|
||||
args: [Order, BigNumber, string],
|
||||
txData: Partial<TxData>,
|
||||
) => {
|
||||
const [order] = args;
|
||||
|
||||
// Ensure that the tx succeeded.
|
||||
expect(result.success).to.be.true();
|
||||
|
||||
// Ensure that the correct events were emitted.
|
||||
verifyFillEvents(txData.from!, order, result.receipt!, deployment);
|
||||
|
||||
logUtils.log(`Order filled by ${txData.from}`);
|
||||
|
||||
// TODO: Add validation for on-chain state (like balances)
|
||||
},
|
||||
});
|
||||
}
|
||||
/* tslint:enable:no-non-null-assertion */
|
||||
/* tslint:enable:no-unnecessary-type-assertion */
|
@@ -1,9 +1,8 @@
|
||||
import { ContractFunctionObj, ContractTxFunctionObj } from '@0x/base-contract';
|
||||
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
// tslint:disable:max-classes-per-file
|
||||
|
||||
export type GenericContractFunction<T> = (...args: any[]) => ContractFunctionObj<T>;
|
||||
|
||||
export interface FunctionResult {
|
||||
@@ -22,9 +21,9 @@ export interface FunctionResult {
|
||||
* @param after A function that will be run after a call to the contract wrapper
|
||||
* function.
|
||||
*/
|
||||
export interface Condition<TBefore> {
|
||||
before: (...args: any[]) => Promise<TBefore>;
|
||||
after: (beforeInfo: TBefore, result: FunctionResult, ...args: any[]) => Promise<any>;
|
||||
export interface Condition<TArgs extends any[], TBefore> {
|
||||
before: (args: TArgs, txData: Partial<TxData>) => Promise<TBefore>;
|
||||
after: (beforeInfo: TBefore, result: FunctionResult, args: TArgs, txData: Partial<TxData>) => Promise<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,8 +33,8 @@ export interface Condition<TBefore> {
|
||||
* our `Assertion` implementations will do in practice).
|
||||
* @param runAsync The function to execute for the assertion.
|
||||
*/
|
||||
export interface Assertion {
|
||||
executeAsync: (...args: any[]) => Promise<any>;
|
||||
export interface Assertion<TArgs extends any[]> {
|
||||
executeAsync: (args: TArgs, txData: TxData) => Promise<any>;
|
||||
}
|
||||
|
||||
export interface AssertionResult<TBefore = unknown> {
|
||||
@@ -47,24 +46,28 @@ export interface AssertionResult<TBefore = unknown> {
|
||||
* This class implements `Assertion` and represents a "Hoare Triple" that can be
|
||||
* executed.
|
||||
*/
|
||||
export class FunctionAssertion<TBefore, ReturnDataType> implements Assertion {
|
||||
export class FunctionAssertion<TArgs extends any[], TBefore, ReturnDataType> implements Assertion<TArgs> {
|
||||
// A condition that will be applied to `wrapperFunction`.
|
||||
public condition: Condition<TBefore>;
|
||||
public condition: Condition<TArgs, TBefore>;
|
||||
|
||||
// The wrapper function that will be wrapped in assertions.
|
||||
public wrapperFunction: (
|
||||
...args: any[] // tslint:disable-line:trailing-comma
|
||||
...args: TArgs // tslint:disable-line:trailing-comma
|
||||
) => ContractTxFunctionObj<ReturnDataType> | ContractFunctionObj<ReturnDataType>;
|
||||
|
||||
constructor(
|
||||
wrapperFunction: (
|
||||
...args: any[] // tslint:disable-line:trailing-comma
|
||||
...args: TArgs // tslint:disable-line:trailing-comma
|
||||
) => ContractTxFunctionObj<ReturnDataType> | ContractFunctionObj<ReturnDataType>,
|
||||
condition: Partial<Condition<TBefore>> = {},
|
||||
condition: Partial<Condition<TArgs, TBefore>> = {},
|
||||
) {
|
||||
this.condition = {
|
||||
before: _.noop.bind(this),
|
||||
after: _.noop.bind(this),
|
||||
before: async (args: TArgs, txData: Partial<TxData>) => {
|
||||
return ({} as any) as TBefore;
|
||||
},
|
||||
after: async (beforeInfo: TBefore, result: FunctionResult, args: TArgs, txData: Partial<TxData>) => {
|
||||
return ({} as any) as TBefore;
|
||||
},
|
||||
...condition,
|
||||
};
|
||||
this.wrapperFunction = wrapperFunction;
|
||||
@@ -74,9 +77,9 @@ export class FunctionAssertion<TBefore, ReturnDataType> implements Assertion {
|
||||
* Runs the wrapped function and fails if the before or after assertions fail.
|
||||
* @param ...args The args to the contract wrapper function.
|
||||
*/
|
||||
public async executeAsync(...args: any[]): Promise<AssertionResult<TBefore>> {
|
||||
public async executeAsync(args: TArgs, txData: Partial<TxData>): Promise<AssertionResult<TBefore>> {
|
||||
// Call the before condition.
|
||||
const beforeInfo = await this.condition.before(...args);
|
||||
const beforeInfo = await this.condition.before(args, txData);
|
||||
|
||||
// Initialize the callResult so that the default success value is true.
|
||||
const callResult: FunctionResult = { success: true };
|
||||
@@ -85,10 +88,10 @@ export class FunctionAssertion<TBefore, ReturnDataType> implements Assertion {
|
||||
// result and receipt to the after condition.
|
||||
try {
|
||||
const functionWithArgs = this.wrapperFunction(...args) as ContractTxFunctionObj<ReturnDataType>;
|
||||
callResult.data = await functionWithArgs.callAsync();
|
||||
callResult.data = await functionWithArgs.callAsync(txData);
|
||||
callResult.receipt =
|
||||
functionWithArgs.awaitTransactionSuccessAsync !== undefined
|
||||
? await functionWithArgs.awaitTransactionSuccessAsync() // tslint:disable-line:await-promise
|
||||
? await functionWithArgs.awaitTransactionSuccessAsync(txData) // tslint:disable-line:await-promise
|
||||
: undefined;
|
||||
// tslint:enable:await-promise
|
||||
} catch (error) {
|
||||
@@ -98,7 +101,7 @@ export class FunctionAssertion<TBefore, ReturnDataType> implements Assertion {
|
||||
}
|
||||
|
||||
// Call the after condition.
|
||||
const afterInfo = await this.condition.after(beforeInfo, callResult, ...args);
|
||||
const afterInfo = await this.condition.after(beforeInfo, callResult, args, txData);
|
||||
|
||||
return {
|
||||
beforeInfo,
|
||||
|
@@ -0,0 +1,43 @@
|
||||
import { StakingEvents, StakingMakerStakingPoolSetEventArgs } from '@0x/contracts-staking';
|
||||
import { expect, filterLogsToArguments } from '@0x/contracts-test-utils';
|
||||
import { logUtils } from '@0x/utils';
|
||||
import { TxData } from 'ethereum-types';
|
||||
|
||||
import { DeploymentManager } from '../deployment_manager';
|
||||
|
||||
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||
|
||||
/**
|
||||
* Returns a function assertion that verifies valid pool joining.
|
||||
*/
|
||||
/* tslint:disable:no-unnecessary-type-assertion */
|
||||
/* tslint:disable:no-non-null-assertion */
|
||||
export function validJoinStakingPoolAssertion(deployment: DeploymentManager): FunctionAssertion<[string], {}, void> {
|
||||
const { stakingWrapper } = deployment.staking;
|
||||
|
||||
return new FunctionAssertion<[string], {}, void>(stakingWrapper.joinStakingPoolAsMaker.bind(stakingWrapper), {
|
||||
after: async (_beforeInfo, _result: FunctionResult, args: [string], txData: Partial<TxData>) => {
|
||||
const [poolId] = args;
|
||||
|
||||
expect(_result.success).to.be.true();
|
||||
|
||||
const logs = _result.receipt!.logs;
|
||||
const logArgs = filterLogsToArguments<StakingMakerStakingPoolSetEventArgs>(
|
||||
logs,
|
||||
StakingEvents.MakerStakingPoolSet,
|
||||
);
|
||||
expect(logArgs).to.be.deep.eq([
|
||||
{
|
||||
makerAddress: txData.from!,
|
||||
poolId,
|
||||
},
|
||||
]);
|
||||
const joinedPoolId = await deployment.staking.stakingWrapper.poolIdByMaker(txData.from!).callAsync();
|
||||
expect(joinedPoolId).to.be.eq(poolId);
|
||||
|
||||
logUtils.log(`Pool ${poolId} joined by ${txData.from}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
/* tslint:enable:no-non-null-assertion */
|
||||
/* tslint:enable:no-unnecessary-type-assertion */
|
@@ -13,7 +13,7 @@ import * as _ from 'lodash';
|
||||
|
||||
import { DeploymentManager } from '../deployment_manager';
|
||||
|
||||
import { FunctionAssertion } from './function_assertion';
|
||||
import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||
|
||||
function incrementNextEpochBalance(stakeBalance: StoredBalance, amount: BigNumber): void {
|
||||
_.update(stakeBalance, ['nextEpochBalance'], balance => (balance || constants.ZERO_AMOUNT).plus(amount));
|
||||
@@ -77,63 +77,70 @@ function updateNextEpochBalances(
|
||||
* Returns a FunctionAssertion for `moveStake` which assumes valid input is provided. The
|
||||
* FunctionAssertion checks that the staker's
|
||||
*/
|
||||
/* tslint:disable:no-unnecessary-type-assertion */
|
||||
export function validMoveStakeAssertion(
|
||||
deployment: DeploymentManager,
|
||||
globalStake: GlobalStakeByStatus,
|
||||
ownerStake: OwnerStakeByStatus,
|
||||
pools: StakingPoolById,
|
||||
): FunctionAssertion<{}, void> {
|
||||
): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void> {
|
||||
const { stakingWrapper } = deployment.staking;
|
||||
|
||||
return new FunctionAssertion<{}, void>(stakingWrapper.moveStake, {
|
||||
after: async (
|
||||
_beforeInfo,
|
||||
_result,
|
||||
from: StakeInfo,
|
||||
to: StakeInfo,
|
||||
amount: BigNumber,
|
||||
txData: Partial<TxData>,
|
||||
) => {
|
||||
logUtils.log(
|
||||
`moveStake({status: ${StakeStatus[from.status]}, poolId: ${from.poolId} }, { status: ${
|
||||
StakeStatus[to.status]
|
||||
}, poolId: ${to.poolId} }, ${amount})`,
|
||||
);
|
||||
return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void>(
|
||||
stakingWrapper.moveStake.bind(stakingWrapper),
|
||||
{
|
||||
after: async (
|
||||
_beforeInfo: {},
|
||||
_result: FunctionResult,
|
||||
args: [StakeInfo, StakeInfo, BigNumber],
|
||||
txData: Partial<TxData>,
|
||||
) => {
|
||||
const [from, to, amount] = args;
|
||||
|
||||
const owner = txData.from as string;
|
||||
logUtils.log(
|
||||
`moveStake({status: ${StakeStatus[from.status]}, poolId: ${from.poolId} }, { status: ${
|
||||
StakeStatus[to.status]
|
||||
}, poolId: ${to.poolId} }, ${amount})`,
|
||||
);
|
||||
|
||||
// Update local balances to match the expected result of this `moveStake` operation
|
||||
const updatedPools = updateNextEpochBalances(globalStake, ownerStake, pools, from, to, amount);
|
||||
const owner = txData.from!; // tslint:disable-line:no-non-null-assertion
|
||||
|
||||
// Fetches on-chain owner stake balances and checks against local balances
|
||||
const ownerUndelegatedStake = {
|
||||
...new StoredBalance(),
|
||||
...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Undelegated).callAsync()),
|
||||
};
|
||||
const ownerDelegatedStake = {
|
||||
...new StoredBalance(),
|
||||
...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Delegated).callAsync()),
|
||||
};
|
||||
expect(ownerUndelegatedStake).to.deep.equal(ownerStake[StakeStatus.Undelegated]);
|
||||
expect(ownerDelegatedStake).to.deep.equal(ownerStake[StakeStatus.Delegated].total);
|
||||
// Update local balances to match the expected result of this `moveStake` operation
|
||||
const updatedPools = updateNextEpochBalances(globalStake, ownerStake, pools, from, to, amount);
|
||||
|
||||
// Fetches on-chain global stake balances and checks against local balances
|
||||
const globalUndelegatedStake = await stakingWrapper
|
||||
.getGlobalStakeByStatus(StakeStatus.Undelegated)
|
||||
.callAsync();
|
||||
const globalDelegatedStake = await stakingWrapper.getGlobalStakeByStatus(StakeStatus.Delegated).callAsync();
|
||||
expect(globalUndelegatedStake).to.deep.equal(globalStake[StakeStatus.Undelegated]);
|
||||
expect(globalDelegatedStake).to.deep.equal(globalStake[StakeStatus.Delegated]);
|
||||
// Fetches on-chain owner stake balances and checks against local balances
|
||||
const ownerUndelegatedStake = {
|
||||
...new StoredBalance(),
|
||||
...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Undelegated).callAsync()),
|
||||
};
|
||||
const ownerDelegatedStake = {
|
||||
...new StoredBalance(),
|
||||
...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Delegated).callAsync()),
|
||||
};
|
||||
expect(ownerUndelegatedStake).to.deep.equal(ownerStake[StakeStatus.Undelegated]);
|
||||
expect(ownerDelegatedStake).to.deep.equal(ownerStake[StakeStatus.Delegated].total);
|
||||
|
||||
// Fetches on-chain pool stake balances and checks against local balances
|
||||
for (const poolId of updatedPools) {
|
||||
const stakeDelegatedByOwner = await stakingWrapper
|
||||
.getStakeDelegatedToPoolByOwner(owner, poolId)
|
||||
// Fetches on-chain global stake balances and checks against local balances
|
||||
const globalUndelegatedStake = await stakingWrapper
|
||||
.getGlobalStakeByStatus(StakeStatus.Undelegated)
|
||||
.callAsync();
|
||||
const totalStakeDelegated = await stakingWrapper.getTotalStakeDelegatedToPool(poolId).callAsync();
|
||||
expect(stakeDelegatedByOwner).to.deep.equal(ownerStake[StakeStatus.Delegated][poolId]);
|
||||
expect(totalStakeDelegated).to.deep.equal(pools[poolId].delegatedStake);
|
||||
}
|
||||
const globalDelegatedStake = await stakingWrapper
|
||||
.getGlobalStakeByStatus(StakeStatus.Delegated)
|
||||
.callAsync();
|
||||
expect(globalUndelegatedStake).to.deep.equal(globalStake[StakeStatus.Undelegated]);
|
||||
expect(globalDelegatedStake).to.deep.equal(globalStake[StakeStatus.Delegated]);
|
||||
|
||||
// Fetches on-chain pool stake balances and checks against local balances
|
||||
for (const poolId of updatedPools) {
|
||||
const stakeDelegatedByOwner = await stakingWrapper
|
||||
.getStakeDelegatedToPoolByOwner(owner, poolId)
|
||||
.callAsync();
|
||||
const totalStakeDelegated = await stakingWrapper.getTotalStakeDelegatedToPool(poolId).callAsync();
|
||||
expect(stakeDelegatedByOwner).to.deep.equal(ownerStake[StakeStatus.Delegated][poolId]);
|
||||
expect(totalStakeDelegated).to.deep.equal(pools[poolId].delegatedStake);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
}
|
||||
/* tslint:enable:no-unnecessary-type-assertion */
|
||||
|
@@ -25,20 +25,23 @@ function expectedUndelegatedStake(
|
||||
* FunctionAssertion checks that the staker and zrxVault's balances of ZRX decrease and increase,
|
||||
* respectively, by the input amount.
|
||||
*/
|
||||
/* tslint:disable:no-unnecessary-type-assertion */
|
||||
export function validStakeAssertion(
|
||||
deployment: DeploymentManager,
|
||||
balanceStore: BlockchainBalanceStore,
|
||||
globalStake: GlobalStakeByStatus,
|
||||
ownerStake: OwnerStakeByStatus,
|
||||
): FunctionAssertion<LocalBalanceStore, void> {
|
||||
): FunctionAssertion<[BigNumber], LocalBalanceStore, void> {
|
||||
const { stakingWrapper, zrxVault } = deployment.staking;
|
||||
|
||||
return new FunctionAssertion(stakingWrapper.stake, {
|
||||
before: async (amount: BigNumber, txData: Partial<TxData>) => {
|
||||
return new FunctionAssertion(stakingWrapper.stake.bind(stakingWrapper), {
|
||||
before: async (args: [BigNumber], txData: Partial<TxData>) => {
|
||||
const [amount] = args;
|
||||
|
||||
// Simulates the transfer of ZRX from staker to vault
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
expectedBalances.transferAsset(
|
||||
txData.from as string,
|
||||
txData.from!, // tslint:disable-line:no-non-null-assertion
|
||||
zrxVault.address,
|
||||
amount,
|
||||
deployment.assetDataEncoder.ERC20Token(deployment.tokens.zrx.address).getABIEncodedTransactionData(),
|
||||
@@ -48,9 +51,11 @@ export function validStakeAssertion(
|
||||
after: async (
|
||||
expectedBalances: LocalBalanceStore,
|
||||
_result: FunctionResult,
|
||||
amount: BigNumber,
|
||||
args: [BigNumber],
|
||||
txData: Partial<TxData>,
|
||||
) => {
|
||||
const [amount] = args;
|
||||
|
||||
logUtils.log(`stake(${amount})`);
|
||||
|
||||
// Checks that the ZRX transfer updated balances as expected.
|
||||
@@ -59,7 +64,7 @@ export function validStakeAssertion(
|
||||
|
||||
// Checks that the owner's undelegated stake has increased by the stake amount
|
||||
const ownerUndelegatedStake = await stakingWrapper
|
||||
.getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated)
|
||||
.getOwnerStakeByStatus(txData.from!, StakeStatus.Undelegated) // tslint:disable-line:no-non-null-assertion
|
||||
.callAsync();
|
||||
const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount);
|
||||
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake);
|
||||
@@ -77,3 +82,4 @@ export function validStakeAssertion(
|
||||
},
|
||||
});
|
||||
}
|
||||
/* tslint:enable:no-unnecessary-type-assertion */
|
||||
|
@@ -25,21 +25,25 @@ function expectedUndelegatedStake(
|
||||
* FunctionAssertion checks that the staker and zrxVault's balances of ZRX increase and decrease,
|
||||
* respectively, by the input amount.
|
||||
*/
|
||||
/* tslint:disable:no-unnecessary-type-assertion */
|
||||
/* tslint:disable:no-non-null-assertion */
|
||||
export function validUnstakeAssertion(
|
||||
deployment: DeploymentManager,
|
||||
balanceStore: BlockchainBalanceStore,
|
||||
globalStake: GlobalStakeByStatus,
|
||||
ownerStake: OwnerStakeByStatus,
|
||||
): FunctionAssertion<LocalBalanceStore, void> {
|
||||
): FunctionAssertion<[BigNumber], LocalBalanceStore, void> {
|
||||
const { stakingWrapper, zrxVault } = deployment.staking;
|
||||
|
||||
return new FunctionAssertion(stakingWrapper.unstake, {
|
||||
before: async (amount: BigNumber, txData: Partial<TxData>) => {
|
||||
return new FunctionAssertion(stakingWrapper.unstake.bind(stakingWrapper), {
|
||||
before: async (args: [BigNumber], txData: Partial<TxData>) => {
|
||||
const [amount] = args;
|
||||
|
||||
// Simulates the transfer of ZRX from vault to staker
|
||||
const expectedBalances = LocalBalanceStore.create(balanceStore);
|
||||
expectedBalances.transferAsset(
|
||||
zrxVault.address,
|
||||
txData.from as string,
|
||||
txData.from!,
|
||||
amount,
|
||||
deployment.assetDataEncoder.ERC20Token(deployment.tokens.zrx.address).getABIEncodedTransactionData(),
|
||||
);
|
||||
@@ -48,9 +52,11 @@ export function validUnstakeAssertion(
|
||||
after: async (
|
||||
expectedBalances: LocalBalanceStore,
|
||||
_result: FunctionResult,
|
||||
amount: BigNumber,
|
||||
args: [BigNumber],
|
||||
txData: Partial<TxData>,
|
||||
) => {
|
||||
const [amount] = args;
|
||||
|
||||
logUtils.log(`unstake(${amount})`);
|
||||
|
||||
// Checks that the ZRX transfer updated balances as expected.
|
||||
@@ -59,7 +65,7 @@ export function validUnstakeAssertion(
|
||||
|
||||
// Checks that the owner's undelegated stake has decreased by the stake amount
|
||||
const ownerUndelegatedStake = await stakingWrapper
|
||||
.getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated)
|
||||
.getOwnerStakeByStatus(txData.from!, StakeStatus.Undelegated)
|
||||
.callAsync();
|
||||
const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount);
|
||||
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake);
|
||||
@@ -77,3 +83,5 @@ export function validUnstakeAssertion(
|
||||
},
|
||||
});
|
||||
}
|
||||
/* tslint:enable:no-non-null-assertion */
|
||||
/* tslint:enable:no-unnecessary-type-assertion */
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { GlobalStakeByStatus, StakeStatus, StakingPoolById, StoredBalance } from '@0x/contracts-staking';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { Maker } from './actors/maker';
|
||||
import { AssertionResult } from './assertions/function_assertion';
|
||||
import { BlockchainBalanceStore } from './balances/blockchain_balance_store';
|
||||
import { DeploymentManager } from './deployment_manager';
|
||||
@@ -14,7 +15,11 @@ export class SimulationEnvironment {
|
||||
};
|
||||
public stakingPools: StakingPoolById = {};
|
||||
|
||||
public constructor(public readonly deployment: DeploymentManager, public balanceStore: BlockchainBalanceStore) {}
|
||||
public constructor(
|
||||
public readonly deployment: DeploymentManager,
|
||||
public balanceStore: BlockchainBalanceStore,
|
||||
public marketMakers: Maker[] = [],
|
||||
) {}
|
||||
}
|
||||
|
||||
export abstract class Simulation {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { blockchainTests, constants, expect, filterLogsToArguments, getRandomInteger } from '@0x/contracts-test-utils';
|
||||
import { BigNumber, StringRevertError } from '@0x/utils';
|
||||
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
|
||||
|
||||
import { artifacts } from '../../artifacts';
|
||||
import { TestFrameworkContract, TestFrameworkEventEventArgs, TestFrameworkEvents } from '../../wrappers';
|
||||
@@ -23,84 +23,107 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
||||
describe('executeAsync', () => {
|
||||
it('should call the before function with the provided arguments', async () => {
|
||||
let sideEffectTarget = ZERO_AMOUNT;
|
||||
const assertion = new FunctionAssertion<void, BigNumber>(
|
||||
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(
|
||||
exampleContract.returnInteger.bind(exampleContract),
|
||||
{
|
||||
before: async (_input: BigNumber) => {
|
||||
before: async (args: [BigNumber], txData: Partial<TxData>) => {
|
||||
sideEffectTarget = randomInput;
|
||||
},
|
||||
},
|
||||
);
|
||||
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
||||
await assertion.executeAsync(randomInput);
|
||||
await assertion.executeAsync([randomInput], {});
|
||||
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
||||
});
|
||||
|
||||
it('should call the after function with the provided arguments', async () => {
|
||||
let sideEffectTarget = ZERO_AMOUNT;
|
||||
const assertion = new FunctionAssertion<void, BigNumber>(
|
||||
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(
|
||||
exampleContract.returnInteger.bind(exampleContract),
|
||||
{
|
||||
after: async (_beforeInfo: any, _result: FunctionResult, input: BigNumber) => {
|
||||
sideEffectTarget = input;
|
||||
after: async (
|
||||
_beforeInfo: any,
|
||||
_result: FunctionResult,
|
||||
args: [BigNumber],
|
||||
txData: Partial<TxData>,
|
||||
) => {
|
||||
[sideEffectTarget] = args;
|
||||
},
|
||||
},
|
||||
);
|
||||
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
||||
await assertion.executeAsync(randomInput);
|
||||
await assertion.executeAsync([randomInput], {});
|
||||
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
||||
});
|
||||
|
||||
it('should not fail immediately if the wrapped function fails', async () => {
|
||||
const assertion = new FunctionAssertion<{}, void>(exampleContract.emptyRevert.bind(exampleContract));
|
||||
await assertion.executeAsync();
|
||||
const assertion = new FunctionAssertion<[], {}, void>(exampleContract.emptyRevert.bind(exampleContract));
|
||||
await assertion.executeAsync([], {});
|
||||
});
|
||||
|
||||
it('should pass the return value of "before" to "after"', async () => {
|
||||
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
||||
let sideEffectTarget = ZERO_AMOUNT;
|
||||
const assertion = new FunctionAssertion<BigNumber, BigNumber>(
|
||||
const assertion = new FunctionAssertion<[BigNumber], BigNumber, BigNumber>(
|
||||
exampleContract.returnInteger.bind(exampleContract),
|
||||
{
|
||||
before: async (_input: BigNumber) => {
|
||||
before: async (_args: [BigNumber], _txData: Partial<TxData>) => {
|
||||
return randomInput;
|
||||
},
|
||||
after: async (beforeInfo: any, _result: FunctionResult, _input: BigNumber) => {
|
||||
after: async (
|
||||
beforeInfo: any,
|
||||
_result: FunctionResult,
|
||||
_args: [BigNumber],
|
||||
_txData: Partial<TxData>,
|
||||
) => {
|
||||
sideEffectTarget = beforeInfo;
|
||||
},
|
||||
},
|
||||
);
|
||||
await assertion.executeAsync(randomInput);
|
||||
await assertion.executeAsync([randomInput], {});
|
||||
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
||||
});
|
||||
|
||||
it('should pass the result from the function call to "after"', async () => {
|
||||
let sideEffectTarget = ZERO_AMOUNT;
|
||||
const assertion = new FunctionAssertion<void, BigNumber>(
|
||||
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(
|
||||
exampleContract.returnInteger.bind(exampleContract),
|
||||
{
|
||||
after: async (_beforeInfo: any, result: FunctionResult, _input: BigNumber) => {
|
||||
after: async (
|
||||
_beforeInfo: any,
|
||||
result: FunctionResult,
|
||||
_args: [BigNumber],
|
||||
_txData: Partial<TxData>,
|
||||
) => {
|
||||
sideEffectTarget = result.data;
|
||||
},
|
||||
},
|
||||
);
|
||||
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
||||
await assertion.executeAsync(randomInput);
|
||||
await assertion.executeAsync([randomInput], {});
|
||||
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
||||
});
|
||||
|
||||
it('should pass the receipt from the function call to "after"', async () => {
|
||||
let sideEffectTarget: TransactionReceiptWithDecodedLogs;
|
||||
const assertion = new FunctionAssertion<void, void>(exampleContract.emitEvent.bind(exampleContract), {
|
||||
after: async (_beforeInfo: any, result: FunctionResult, _input: string) => {
|
||||
if (result.receipt) {
|
||||
sideEffectTarget = result.receipt;
|
||||
}
|
||||
const assertion = new FunctionAssertion<[string], void, void>(
|
||||
exampleContract.emitEvent.bind(exampleContract),
|
||||
{
|
||||
after: async (
|
||||
_beforeInfo: any,
|
||||
result: FunctionResult,
|
||||
_args: [string],
|
||||
_txData: Partial<TxData>,
|
||||
) => {
|
||||
if (result.receipt) {
|
||||
sideEffectTarget = result.receipt;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
const input = 'emitted data';
|
||||
await assertion.executeAsync(input);
|
||||
await assertion.executeAsync([input], {});
|
||||
|
||||
// Ensure that the correct events were emitted.
|
||||
const [event] = filterLogsToArguments<TestFrameworkEventEventArgs>(
|
||||
@@ -112,13 +135,21 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
||||
|
||||
it('should pass the error to "after" if the function call fails', async () => {
|
||||
let sideEffectTarget: Error;
|
||||
const assertion = new FunctionAssertion<void, void>(exampleContract.stringRevert.bind(exampleContract), {
|
||||
after: async (_beforeInfo: any, result: FunctionResult, _input: string) => {
|
||||
sideEffectTarget = result.data;
|
||||
const assertion = new FunctionAssertion<[string], void, void>(
|
||||
exampleContract.stringRevert.bind(exampleContract),
|
||||
{
|
||||
after: async (
|
||||
_beforeInfo: any,
|
||||
result: FunctionResult,
|
||||
_args: [string],
|
||||
_txData: Partial<TxData>,
|
||||
) => {
|
||||
sideEffectTarget = result.data;
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
const message = 'error message';
|
||||
await assertion.executeAsync(message);
|
||||
await assertion.executeAsync([message], {});
|
||||
|
||||
const expectedError = new StringRevertError(message);
|
||||
return expect(Promise.reject(sideEffectTarget!)).to.revertWith(expectedError); // tslint:disable-line
|
||||
|
@@ -0,0 +1,86 @@
|
||||
import { blockchainTests, constants } from '@0x/contracts-test-utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { MakerTaker } from '../framework/actors/hybrids';
|
||||
import { Maker } from '../framework/actors/maker';
|
||||
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 { PoolManagementSimulation } from './pool_management_test';
|
||||
|
||||
class PoolMembershipSimulation extends Simulation {
|
||||
protected async *_assertionGenerator(): AsyncIterableIterator<AssertionResult | void> {
|
||||
const { deployment } = this.environment;
|
||||
|
||||
const poolManagement = new PoolManagementSimulation(this.environment);
|
||||
|
||||
const member = new MakerTaker({
|
||||
name: 'member',
|
||||
deployment,
|
||||
simulationEnvironment: this.environment,
|
||||
});
|
||||
|
||||
const actions = [
|
||||
member.simulationActions.validJoinStakingPool,
|
||||
member.simulationActions.validFillOrderCompleteFill,
|
||||
poolManagement.generator,
|
||||
];
|
||||
|
||||
while (true) {
|
||||
const action = _.sample(actions);
|
||||
yield (await action!.next()).value; // tslint:disable-line:no-non-null-assertion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blockchainTests.skip('pool membership fuzz test', env => {
|
||||
let deployment: DeploymentManager;
|
||||
let maker: Maker;
|
||||
|
||||
before(async () => {
|
||||
deployment = await DeploymentManager.deployAsync(env, {
|
||||
numErc20TokensToDeploy: 2,
|
||||
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,
|
||||
ZRXVault: deployment.staking.zrxVault.address,
|
||||
},
|
||||
{ erc20: { ZRX: deployment.tokens.zrx } },
|
||||
);
|
||||
|
||||
const simulationEnv = new SimulationEnvironment(deployment, balanceStore, [maker]);
|
||||
const simulation = new PoolMembershipSimulation(simulationEnv);
|
||||
return simulation.fuzzAsync();
|
||||
});
|
||||
});
|
147
yarn.lock
147
yarn.lock
@@ -668,12 +668,52 @@
|
||||
lodash "^4.17.11"
|
||||
valid-url "^1.0.9"
|
||||
|
||||
"@0x/assert@^2.2.0-beta.2", "@0x/assert@^2.2.0-beta.3":
|
||||
version "2.2.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/@0x/assert/-/assert-2.2.0-beta.3.tgz#8fb95c265000532cd8dced44d44d29ca544b2bfc"
|
||||
integrity sha512-ShENc8QJU4ur/5TkRl3l7J3Yt7WaxHAbuoTRm/djcA0iwYyUVlP5yN9Ab9ua+VLizQQTNw1n+kF1mJWg5lQXuA==
|
||||
dependencies:
|
||||
"@0x/json-schemas" "^4.1.0-beta.3"
|
||||
"@0x/typescript-typings" "^4.4.0-beta.2"
|
||||
"@0x/utils" "^4.6.0-beta.3"
|
||||
lodash "^4.17.11"
|
||||
valid-url "^1.0.9"
|
||||
|
||||
"@0x/base-contract@^5.5.0-beta.2", "@0x/base-contract@^5.5.0-beta.3", "@0x/base-contract@^5.5.0-beta.4":
|
||||
version "5.5.0-beta.4"
|
||||
resolved "https://registry.yarnpkg.com/@0x/base-contract/-/base-contract-5.5.0-beta.4.tgz#eb26473e033e1a305a9fa87ab9e26325c9face59"
|
||||
integrity sha512-6OC3Rg2ESoi1k4wABLt67aMGxqdb6FraeEHdAGb07VuPElpyH9jT8dl2aJFAI8tVcnrUQTXeDYJjuK7rimNReg==
|
||||
dependencies:
|
||||
"@0x/assert" "^2.2.0-beta.3"
|
||||
"@0x/json-schemas" "^4.1.0-beta.3"
|
||||
"@0x/utils" "^4.6.0-beta.3"
|
||||
"@0x/web3-wrapper" "^6.1.0-beta.3"
|
||||
ethereumjs-account "^3.0.0"
|
||||
ethereumjs-blockstream "^7.0.0"
|
||||
ethereumjs-util "^5.1.1"
|
||||
ethereumjs-vm "^4.0.0"
|
||||
ethers "~4.0.4"
|
||||
js-sha3 "^0.7.0"
|
||||
uuid "^3.3.2"
|
||||
|
||||
"@0x/contract-addresses@3.3.0-beta.3":
|
||||
version "3.3.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/@0x/contract-addresses/-/contract-addresses-3.3.0-beta.3.tgz#0fa8ad47e22aecdb99f9a044ba3c705c4173b61e"
|
||||
dependencies:
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@0x/contract-addresses@^3.3.0-beta.3", "@0x/contract-addresses@^3.3.0-beta.4", "@0x/contract-addresses@^3.3.0-beta.5":
|
||||
version "3.3.0-beta.5"
|
||||
resolved "https://registry.yarnpkg.com/@0x/contract-addresses/-/contract-addresses-3.3.0-beta.5.tgz#9d5f80a258f1d103b127159c237f9bcdad182e80"
|
||||
integrity sha512-/8de6W1MnVc2zElnCGjK3zgWabBAJZck+zCmNRIMiGtPJIWEW+F3EkAFPfMX6bSZIyrUBlYJIr0xVIWLPU45Aw==
|
||||
dependencies:
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@0x/contract-artifacts@^2.3.0-beta.3":
|
||||
version "2.3.0-beta.4"
|
||||
resolved "https://registry.yarnpkg.com/@0x/contract-artifacts/-/contract-artifacts-2.3.0-beta.4.tgz#ca056885be387344aaccf5c69fe80aec248df37d"
|
||||
integrity sha512-NpZk3PVE9c2g5kolCcZej2i1l1XlvCFm9FXAny0tCz+/vNb3RhI0m6ecoiS7b1nFEFZ9q6jjsCCb5OEsxMudnw==
|
||||
|
||||
"@0x/contract-wrappers@12.2.0-beta.2":
|
||||
version "12.2.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/@0x/contract-wrappers/-/contract-wrappers-12.2.0-beta.2.tgz#62b3c13e35282df14734d1f6b1a617ed51901a27"
|
||||
@@ -686,6 +726,28 @@
|
||||
ethers "~4.0.4"
|
||||
http-status-codes "^1.3.2"
|
||||
|
||||
"@0x/contract-wrappers@^12.2.0-beta.4":
|
||||
version "12.2.0-beta.4"
|
||||
resolved "https://registry.yarnpkg.com/@0x/contract-wrappers/-/contract-wrappers-12.2.0-beta.4.tgz#7a7301dd50c28887879df4d385e80a49e040748d"
|
||||
integrity sha512-JVoYG3Rd430fZw9ogBSqeLOaJXkqp9N7g614X5/bzzuG/dSDdwXl48X02m66aGFWcNofx/iMsT4tpOZrJ2bDBg==
|
||||
dependencies:
|
||||
"@0x/assert" "^2.2.0-beta.3"
|
||||
"@0x/base-contract" "^5.5.0-beta.4"
|
||||
"@0x/contract-addresses" "^3.3.0-beta.5"
|
||||
"@0x/json-schemas" "^4.1.0-beta.3"
|
||||
"@0x/types" "^2.5.0-beta.3"
|
||||
"@0x/utils" "^4.6.0-beta.3"
|
||||
"@0x/web3-wrapper" "^6.1.0-beta.3"
|
||||
ethereum-types "^2.2.0-beta.2"
|
||||
ethers "~4.0.4"
|
||||
|
||||
"@0x/contracts-dev-utils@^0.1.0-beta.2":
|
||||
version "0.1.0-beta.4"
|
||||
resolved "https://registry.yarnpkg.com/@0x/contracts-dev-utils/-/contracts-dev-utils-0.1.0-beta.4.tgz#87f51a7ed778b619beb8b32bf08ea6b3e4495d4e"
|
||||
integrity sha512-6fQ13/L8aKKnO/vs2WIg0D7FXz3QpMKBHrTYdzvpwFrOcd6MPavQEj0KCQdT/4aplMvWnWDSVAOqn8i/lJJ8jQ==
|
||||
dependencies:
|
||||
"@0x/base-contract" "^5.5.0-beta.4"
|
||||
|
||||
"@0x/contracts-erc20@2.3.0-beta.2":
|
||||
version "2.3.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/@0x/contracts-erc20/-/contracts-erc20-2.3.0-beta.2.tgz#218239f5594fdbbf8c1ff757a6356ac6fb787421"
|
||||
@@ -734,6 +796,16 @@
|
||||
jsonschema "^1.2.0"
|
||||
lodash.values "^4.3.0"
|
||||
|
||||
"@0x/json-schemas@^4.1.0-beta.2", "@0x/json-schemas@^4.1.0-beta.3":
|
||||
version "4.1.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/@0x/json-schemas/-/json-schemas-4.1.0-beta.3.tgz#af70a35691108ea162140640bae93a7fc84ca6ee"
|
||||
integrity sha512-vcgzSeaOXiUQ4KjqdLTTBHbkWnp4IE7cXbUblRy8Y0XYPQsPywhs9mtjY4lBVNmm1DDpLhreo1mwrvPS3HW5YA==
|
||||
dependencies:
|
||||
"@0x/typescript-typings" "^4.4.0-beta.2"
|
||||
"@types/node" "*"
|
||||
jsonschema "^1.2.0"
|
||||
lodash.values "^4.3.0"
|
||||
|
||||
"@0x/mesh-rpc-client@^7.0.4-beta-0xv3":
|
||||
version "7.0.4-beta-0xv3"
|
||||
resolved "https://registry.yarnpkg.com/@0x/mesh-rpc-client/-/mesh-rpc-client-7.0.4-beta-0xv3.tgz#5e933a0b9cf20ca900f309fc4adee03b081eb335"
|
||||
@@ -769,6 +841,20 @@
|
||||
ethers "~4.0.4"
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@0x/order-utils@^8.5.0-beta.2":
|
||||
version "8.5.0-beta.4"
|
||||
resolved "https://registry.yarnpkg.com/@0x/order-utils/-/order-utils-8.5.0-beta.4.tgz#900387631008cc9dc9ece125d28450d2beaea462"
|
||||
integrity sha512-a3vFDAETaPVo/hN5iFr1gxgshfSzAO23NnaUSjeEhs4Ff8eKxuISc6UARFaNzdQeXygDWHMjwlmZ7iegBHe93g==
|
||||
dependencies:
|
||||
"@0x/assert" "^2.2.0-beta.3"
|
||||
"@0x/contract-wrappers" "^12.2.0-beta.4"
|
||||
"@0x/json-schemas" "^4.1.0-beta.3"
|
||||
"@0x/utils" "^4.6.0-beta.3"
|
||||
"@0x/web3-wrapper" "^6.1.0-beta.3"
|
||||
ethereumjs-util "^5.1.1"
|
||||
ethers "~4.0.4"
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@0x/subproviders@5.1.0-beta.2":
|
||||
version "5.1.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/@0x/subproviders/-/subproviders-5.1.0-beta.2.tgz#020369711330755448397b3b8cecf2868ae7c54c"
|
||||
@@ -827,6 +913,26 @@
|
||||
bignumber.js "~8.0.2"
|
||||
ethereum-types "^2.1.6"
|
||||
|
||||
"@0x/types@^2.5.0-beta.2", "@0x/types@^2.5.0-beta.3":
|
||||
version "2.5.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/@0x/types/-/types-2.5.0-beta.3.tgz#e010e9dbf62e37e59177c1d6df8d1acf3a9ea1b4"
|
||||
integrity sha512-5wJs4/EZGPcU6W5IZ87zuya9vQUPD4DchyP29bXyguGHg9dOxuUOF4WauJZExWlPCS7eivviiUHpZD9DZhni+w==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
bignumber.js "~9.0.0"
|
||||
ethereum-types "^2.2.0-beta.2"
|
||||
|
||||
"@0x/typescript-typings@4.4.0-beta.2", "@0x/typescript-typings@^4.4.0-beta.2":
|
||||
version "4.4.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/@0x/typescript-typings/-/typescript-typings-4.4.0-beta.2.tgz#67c621252f162914186b8f684ac5e306206c1cf2"
|
||||
integrity sha512-Fq2nOKvopdLMEjuPiKqomGog06bxAXGjqnodCwv9OKr11V5W1twFTUM3c1TENfHeGvcqf1aMl1hsH3fuVP61jg==
|
||||
dependencies:
|
||||
"@types/bn.js" "^4.11.0"
|
||||
"@types/react" "*"
|
||||
bignumber.js "~9.0.0"
|
||||
ethereum-types "^2.2.0-beta.2"
|
||||
popper.js "1.14.3"
|
||||
|
||||
"@0x/typescript-typings@^4.2.2", "@0x/typescript-typings@^4.3.0":
|
||||
version "4.3.0"
|
||||
resolved "https://registry.npmjs.org/@0x/typescript-typings/-/typescript-typings-4.3.0.tgz#4813a996ac5101841d1c22f4aa1738ab56168857"
|
||||
@@ -874,6 +980,25 @@
|
||||
js-sha3 "^0.7.0"
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@0x/utils@^4.6.0-beta.2", "@0x/utils@^4.6.0-beta.3":
|
||||
version "4.6.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/@0x/utils/-/utils-4.6.0-beta.3.tgz#d40278916d98c48ea05821ae4987c88f032c7bff"
|
||||
integrity sha512-aPIUgfhaDhwgddJAlIQJ2Ki87A60ovatBLCjareLUbsQSvFS5i3iujUBQHgFxZAv9tgl35fyg2ISEJ1YkQyubA==
|
||||
dependencies:
|
||||
"@0x/types" "^2.5.0-beta.3"
|
||||
"@0x/typescript-typings" "^4.4.0-beta.2"
|
||||
"@types/node" "*"
|
||||
abortcontroller-polyfill "^1.1.9"
|
||||
bignumber.js "~9.0.0"
|
||||
chalk "^2.3.0"
|
||||
detect-node "2.0.3"
|
||||
ethereum-types "^2.2.0-beta.2"
|
||||
ethereumjs-util "^5.1.1"
|
||||
ethers "~4.0.4"
|
||||
isomorphic-fetch "2.2.1"
|
||||
js-sha3 "^0.7.0"
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@0x/web3-providers-fork@0.0.7":
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@0x/web3-providers-fork/-/web3-providers-fork-0.0.7.tgz#9cf40ebb6a2aa230283c5accb195d92594bb0aa7"
|
||||
@@ -904,6 +1029,20 @@
|
||||
ethers "~4.0.4"
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@0x/web3-wrapper@^6.1.0-beta.2", "@0x/web3-wrapper@^6.1.0-beta.3":
|
||||
version "6.1.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/@0x/web3-wrapper/-/web3-wrapper-6.1.0-beta.3.tgz#82161147e9283391e0c7cd6027c971749c5a2f77"
|
||||
integrity sha512-mc8120n8w88gICbDm8pkmC83Ul3RgE4BGsjY5BRBFefmKbv/XLeBZiWdhsaWYmkk8v4f+ZxAQ+HHTBDsRH87Og==
|
||||
dependencies:
|
||||
"@0x/assert" "^2.2.0-beta.3"
|
||||
"@0x/json-schemas" "^4.1.0-beta.3"
|
||||
"@0x/typescript-typings" "^4.4.0-beta.2"
|
||||
"@0x/utils" "^4.6.0-beta.3"
|
||||
ethereum-types "^2.2.0-beta.2"
|
||||
ethereumjs-util "^5.1.1"
|
||||
ethers "~4.0.4"
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@0xproject/npm-cli-login@^0.0.11":
|
||||
version "0.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@0xproject/npm-cli-login/-/npm-cli-login-0.0.11.tgz#3f1ec06112ce62aad300ff0575358f68aeecde2e"
|
||||
@@ -6626,6 +6765,14 @@ ethereum-types@^2.1.6:
|
||||
"@types/node" "*"
|
||||
bignumber.js "~8.0.2"
|
||||
|
||||
ethereum-types@^2.2.0-beta.2:
|
||||
version "2.2.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/ethereum-types/-/ethereum-types-2.2.0-beta.2.tgz#0b446842474c2afacd351258ed4a2d0841f2608f"
|
||||
integrity sha512-5ANYHI/InHqf4Nt8oYrpvcph9/D6gi3sbM7Rlr8r0QjXb2mqocqEvOH460Zkf1robc7WDqurp9baeMy+um8kww==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
bignumber.js "~9.0.0"
|
||||
|
||||
ethereumjs-abi@0.6.5:
|
||||
version "0.6.5"
|
||||
resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz#5a637ef16ab43473fa72a29ad90871405b3f5241"
|
||||
|
Reference in New Issue
Block a user