Simulation logging, hopefully address function assertion lifetime issue
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { StakingPoolById, StoredBalance } from '@0x/contracts-staking';
|
||||
import { expect } from '@0x/contracts-test-utils';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { TxData } from 'ethereum-types';
|
||||
|
||||
import { DeploymentManager } from '../deployment_manager';
|
||||
@@ -20,41 +20,36 @@ export function validCreateStakingPoolAssertion(
|
||||
): FunctionAssertion<[number, boolean], string, string> {
|
||||
const { stakingWrapper } = deployment.staking;
|
||||
|
||||
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;
|
||||
|
||||
logUtils.log(`createStakingPool(${operatorShare}, ${shouldAddMakerAsOperator}) => ${expectedPoolId}`);
|
||||
|
||||
// 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(),
|
||||
};
|
||||
},
|
||||
return new FunctionAssertion<[number, boolean], string, string>(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,
|
||||
args: [number, boolean],
|
||||
txData: Partial<TxData>,
|
||||
) => {
|
||||
const [operatorShare] = args;
|
||||
|
||||
// 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,5 @@
|
||||
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';
|
||||
@@ -17,21 +16,16 @@ export function validDecreaseStakingPoolOperatorShareAssertion(
|
||||
): FunctionAssertion<[string, number], {}, void> {
|
||||
const { stakingWrapper } = deployment.staking;
|
||||
|
||||
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;
|
||||
return new FunctionAssertion<[string, number], {}, void>(stakingWrapper, 'decreaseStakingPoolOperatorShare', {
|
||||
after: async (_beforeInfo, _result: FunctionResult, args: [string, number], _txData: Partial<TxData>) => {
|
||||
const [poolId, expectedOperatorShare] = args;
|
||||
|
||||
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);
|
||||
|
||||
// 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;
|
||||
},
|
||||
// Updates the pool in local state.
|
||||
pools[poolId].operatorShare = operatorShare;
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ERC20TokenEvents, ERC20TokenTransferEventArgs } from '@0x/contracts-erc
|
||||
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 { BigNumber } from '@0x/utils';
|
||||
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
@@ -74,7 +74,7 @@ export function validFillOrderCompleteFillAssertion(
|
||||
): FunctionAssertion<[Order, BigNumber, string], {}, FillResults> {
|
||||
const exchange = deployment.exchange;
|
||||
|
||||
return new FunctionAssertion<[Order, BigNumber, string], {}, FillResults>(exchange.fillOrder.bind(exchange), {
|
||||
return new FunctionAssertion<[Order, BigNumber, string], {}, FillResults>(exchange, 'fillOrder', {
|
||||
after: async (
|
||||
_beforeInfo,
|
||||
result: FunctionResult,
|
||||
@@ -89,8 +89,6 @@ export function validFillOrderCompleteFillAssertion(
|
||||
// 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)
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ContractFunctionObj, ContractTxFunctionObj } from '@0x/base-contract';
|
||||
import { BaseContract, ContractFunctionObj, ContractTxFunctionObj } from '@0x/base-contract';
|
||||
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { logger } from '../logger';
|
||||
|
||||
// tslint:disable:max-classes-per-file
|
||||
export type GenericContractFunction<T> = (...args: any[]) => ContractFunctionObj<T>;
|
||||
|
||||
@@ -48,29 +50,22 @@ export interface AssertionResult<TBefore = unknown> {
|
||||
*/
|
||||
export class FunctionAssertion<TArgs extends any[], TBefore, ReturnDataType> implements Assertion<TArgs> {
|
||||
// A condition that will be applied to `wrapperFunction`.
|
||||
public condition: Condition<TArgs, TBefore>;
|
||||
|
||||
// The wrapper function that will be wrapped in assertions.
|
||||
public wrapperFunction: (
|
||||
...args: TArgs // tslint:disable-line:trailing-comma
|
||||
) => ContractTxFunctionObj<ReturnDataType> | ContractFunctionObj<ReturnDataType>;
|
||||
public readonly condition: Condition<TArgs, TBefore>;
|
||||
|
||||
constructor(
|
||||
wrapperFunction: (
|
||||
...args: TArgs // tslint:disable-line:trailing-comma
|
||||
) => ContractTxFunctionObj<ReturnDataType> | ContractFunctionObj<ReturnDataType>,
|
||||
private readonly _contractWrapper: BaseContract,
|
||||
private readonly _functionName: string,
|
||||
condition: Partial<Condition<TArgs, TBefore>> = {},
|
||||
) {
|
||||
this.condition = {
|
||||
before: async (args: TArgs, txData: Partial<TxData>) => {
|
||||
before: async (_args: TArgs, _txData: Partial<TxData>) => {
|
||||
return ({} as any) as TBefore;
|
||||
},
|
||||
after: async (beforeInfo: TBefore, result: FunctionResult, args: TArgs, txData: Partial<TxData>) => {
|
||||
after: async (_beforeInfo: TBefore, _result: FunctionResult, _args: TArgs, _txData: Partial<TxData>) => {
|
||||
return ({} as any) as TBefore;
|
||||
},
|
||||
...condition,
|
||||
};
|
||||
this.wrapperFunction = wrapperFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +82,10 @@ export class FunctionAssertion<TArgs extends any[], TBefore, ReturnDataType> imp
|
||||
// Try to make the call to the function. If it is successful, pass the
|
||||
// result and receipt to the after condition.
|
||||
try {
|
||||
const functionWithArgs = this.wrapperFunction(...args) as ContractTxFunctionObj<ReturnDataType>;
|
||||
const functionWithArgs = (this._contractWrapper as any)[this._functionName](
|
||||
...args,
|
||||
) as ContractTxFunctionObj<ReturnDataType>;
|
||||
logger.logFunctionAssertion(this._functionName, args, txData);
|
||||
callResult.data = await functionWithArgs.callAsync(txData);
|
||||
callResult.receipt =
|
||||
functionWithArgs.awaitTransactionSuccessAsync !== undefined
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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';
|
||||
@@ -15,7 +14,7 @@ import { FunctionAssertion, FunctionResult } from './function_assertion';
|
||||
export function validJoinStakingPoolAssertion(deployment: DeploymentManager): FunctionAssertion<[string], {}, void> {
|
||||
const { stakingWrapper } = deployment.staking;
|
||||
|
||||
return new FunctionAssertion<[string], {}, void>(stakingWrapper.joinStakingPoolAsMaker.bind(stakingWrapper), {
|
||||
return new FunctionAssertion<[string], {}, void>(stakingWrapper, 'joinStakingPoolAsMaker', {
|
||||
after: async (_beforeInfo, _result: FunctionResult, args: [string], txData: Partial<TxData>) => {
|
||||
const [poolId] = args;
|
||||
|
||||
@@ -34,8 +33,6 @@ export function validJoinStakingPoolAssertion(deployment: DeploymentManager): Fu
|
||||
]);
|
||||
const joinedPoolId = await deployment.staking.stakingWrapper.poolIdByMaker(txData.from!).callAsync();
|
||||
expect(joinedPoolId).to.be.eq(poolId);
|
||||
|
||||
logUtils.log(`Pool ${poolId} joined by ${txData.from}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
StoredBalance,
|
||||
} from '@0x/contracts-staking';
|
||||
import { constants, expect } from '@0x/contracts-test-utils';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { TxData } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
@@ -86,61 +86,50 @@ export function validMoveStakeAssertion(
|
||||
): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void> {
|
||||
const { stakingWrapper } = deployment.staking;
|
||||
|
||||
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;
|
||||
return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void>(stakingWrapper, 'moveStake', {
|
||||
after: async (
|
||||
_beforeInfo: {},
|
||||
_result: FunctionResult,
|
||||
args: [StakeInfo, StakeInfo, BigNumber],
|
||||
txData: Partial<TxData>,
|
||||
) => {
|
||||
const [from, to, amount] = args;
|
||||
|
||||
logUtils.log(
|
||||
`moveStake({status: ${StakeStatus[from.status]}, poolId: ${from.poolId} }, { status: ${
|
||||
StakeStatus[to.status]
|
||||
}, poolId: ${to.poolId} }, ${amount})`,
|
||||
);
|
||||
const owner = txData.from!; // tslint:disable-line:no-non-null-assertion
|
||||
|
||||
const owner = txData.from!; // tslint:disable-line:no-non-null-assertion
|
||||
// Update local balances to match the expected result of this `moveStake` operation
|
||||
const updatedPools = updateNextEpochBalances(globalStake, ownerStake, pools, from, to, amount);
|
||||
|
||||
// Update local balances to match the expected result of this `moveStake` operation
|
||||
const updatedPools = updateNextEpochBalances(globalStake, ownerStake, pools, from, to, amount);
|
||||
// Fetches on-chain owner stake balances and checks against local balances
|
||||
const ownerUndelegatedStake = {
|
||||
...new StoredBalance(),
|
||||
...(await stakingWrapper.getOwnerStakeByStatus(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 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 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 global stake balances and checks against local balances
|
||||
const globalUndelegatedStake = await stakingWrapper
|
||||
.getGlobalStakeByStatus(StakeStatus.Undelegated)
|
||||
// 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 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);
|
||||
}
|
||||
},
|
||||
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 */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { GlobalStakeByStatus, OwnerStakeByStatus, StakeStatus, StoredBalance } from '@0x/contracts-staking';
|
||||
import { expect } from '@0x/contracts-test-utils';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { TxData } from 'ethereum-types';
|
||||
|
||||
import { BlockchainBalanceStore } from '../balances/blockchain_balance_store';
|
||||
@@ -34,7 +34,7 @@ export function validStakeAssertion(
|
||||
): FunctionAssertion<[BigNumber], LocalBalanceStore, void> {
|
||||
const { stakingWrapper, zrxVault } = deployment.staking;
|
||||
|
||||
return new FunctionAssertion(stakingWrapper.stake.bind(stakingWrapper), {
|
||||
return new FunctionAssertion(stakingWrapper, 'stake', {
|
||||
before: async (args: [BigNumber], txData: Partial<TxData>) => {
|
||||
const [amount] = args;
|
||||
|
||||
@@ -56,8 +56,6 @@ export function validStakeAssertion(
|
||||
) => {
|
||||
const [amount] = args;
|
||||
|
||||
logUtils.log(`stake(${amount})`);
|
||||
|
||||
// Checks that the ZRX transfer updated balances as expected.
|
||||
await balanceStore.updateErc20BalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { GlobalStakeByStatus, OwnerStakeByStatus, StakeStatus, StoredBalance } from '@0x/contracts-staking';
|
||||
import { expect } from '@0x/contracts-test-utils';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { TxData } from 'ethereum-types';
|
||||
|
||||
import { BlockchainBalanceStore } from '../balances/blockchain_balance_store';
|
||||
@@ -35,7 +35,7 @@ export function validUnstakeAssertion(
|
||||
): FunctionAssertion<[BigNumber], LocalBalanceStore, void> {
|
||||
const { stakingWrapper, zrxVault } = deployment.staking;
|
||||
|
||||
return new FunctionAssertion(stakingWrapper.unstake.bind(stakingWrapper), {
|
||||
return new FunctionAssertion(stakingWrapper, 'unstake', {
|
||||
before: async (args: [BigNumber], txData: Partial<TxData>) => {
|
||||
const [amount] = args;
|
||||
|
||||
@@ -57,8 +57,6 @@ export function validUnstakeAssertion(
|
||||
) => {
|
||||
const [amount] = args;
|
||||
|
||||
logUtils.log(`unstake(${amount})`);
|
||||
|
||||
// Checks that the ZRX transfer updated balances as expected.
|
||||
await balanceStore.updateErc20BalancesAsync();
|
||||
balanceStore.assertEquals(expectedBalances);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseContract } from '@0x/base-contract';
|
||||
import { constants, expect, TokenBalances } from '@0x/contracts-test-utils';
|
||||
import { constants, expect, replaceKeysDeep, TokenBalances } from '@0x/contracts-test-utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { TokenAddresses, TokenContractsByName, TokenOwnersByName } from './types';
|
||||
@@ -68,6 +68,14 @@ export class BalanceStore {
|
||||
this._addressNames = _.cloneDeep(balanceStore._addressNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a version of balances where keys are replaced with their readable counterparts, if
|
||||
* they exist.
|
||||
*/
|
||||
public toReadable(): _.Dictionary<{}> {
|
||||
return replaceKeysDeep(this.balances, this._readableAddressName.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the human-readable name for the given address, if it exists.
|
||||
* @param address The address to get the name for.
|
||||
|
||||
55
contracts/integrations/test/framework/logger.ts
Normal file
55
contracts/integrations/test/framework/logger.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { TxData } from 'ethereum-types';
|
||||
|
||||
import { Pseudorandom} from './pseudorandom';
|
||||
|
||||
// tslint:disable:no-console
|
||||
|
||||
class Logger {
|
||||
private _step = 0;
|
||||
|
||||
constructor() {
|
||||
console.warn(
|
||||
JSON.stringify({
|
||||
level: 'info',
|
||||
time: new Date(),
|
||||
msg: `Pseudorandom seed: ${Pseudorandom.seed}`,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Logs the name of the function executed, the arguments and transaction data it was
|
||||
* called with, and the current step of the simulation.
|
||||
*/
|
||||
public logFunctionAssertion(functionName: string, functionArgs: any[], txData: Partial<TxData>): void {
|
||||
console.warn(
|
||||
JSON.stringify({
|
||||
level: 'info',
|
||||
time: new Date(),
|
||||
msg: `Function called: ${functionName}(${functionArgs
|
||||
.map(arg => JSON.stringify(arg).replace(/"/g, "'"))
|
||||
.join(', ')})`,
|
||||
step: this._step++,
|
||||
txData,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Logs information about a assertion failure. Dumps the error thrown and arbitrary data from
|
||||
* the calling context.
|
||||
*/
|
||||
public logFailure(error: Error, data: string): void {
|
||||
console.warn(
|
||||
JSON.stringify({
|
||||
level: 'error',
|
||||
time: new Date(),
|
||||
step: this._step,
|
||||
error,
|
||||
data,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = new Logger();
|
||||
@@ -1,10 +1,10 @@
|
||||
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';
|
||||
import { logger } from './logger';
|
||||
|
||||
// tslint:disable:max-classes-per-file
|
||||
|
||||
@@ -20,6 +20,14 @@ export class SimulationEnvironment {
|
||||
public balanceStore: BlockchainBalanceStore,
|
||||
public marketMakers: Maker[] = [],
|
||||
) {}
|
||||
|
||||
public state(): any {
|
||||
return {
|
||||
globalStake: this.globalStake,
|
||||
stakingPools: this.stakingPools,
|
||||
balanceStore: this.balanceStore.toReadable(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class Simulation {
|
||||
@@ -30,14 +38,23 @@ export abstract class Simulation {
|
||||
public async fuzzAsync(steps?: number): Promise<void> {
|
||||
if (steps !== undefined) {
|
||||
for (let i = 0; i < steps; i++) {
|
||||
await this.generator.next();
|
||||
await this._stepAsync();
|
||||
}
|
||||
} else {
|
||||
while (true) {
|
||||
await this.generator.next();
|
||||
await this._stepAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract _assertionGenerator(): AsyncIterableIterator<AssertionResult | void>;
|
||||
|
||||
private async _stepAsync(): Promise<void> {
|
||||
try {
|
||||
await this.generator.next();
|
||||
} catch (error) {
|
||||
logger.logFailure(error, this.environment.state());
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,14 +23,11 @@ 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<[BigNumber], void, BigNumber>(
|
||||
exampleContract.returnInteger.bind(exampleContract),
|
||||
{
|
||||
before: async (args: [BigNumber], txData: Partial<TxData>) => {
|
||||
sideEffectTarget = randomInput;
|
||||
},
|
||||
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(exampleContract, 'returnInteger', {
|
||||
before: async (args: [BigNumber], txData: Partial<TxData>) => {
|
||||
sideEffectTarget = randomInput;
|
||||
},
|
||||
);
|
||||
});
|
||||
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
||||
await assertion.executeAsync([randomInput], {});
|
||||
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
||||
@@ -38,26 +35,23 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
||||
|
||||
it('should call the after function with the provided arguments', async () => {
|
||||
let sideEffectTarget = ZERO_AMOUNT;
|
||||
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(
|
||||
exampleContract.returnInteger.bind(exampleContract),
|
||||
{
|
||||
after: async (
|
||||
_beforeInfo: any,
|
||||
_result: FunctionResult,
|
||||
args: [BigNumber],
|
||||
txData: Partial<TxData>,
|
||||
) => {
|
||||
[sideEffectTarget] = args;
|
||||
},
|
||||
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(exampleContract, 'returnInteger', {
|
||||
after: async (
|
||||
_beforeInfo: any,
|
||||
_result: FunctionResult,
|
||||
args: [BigNumber],
|
||||
txData: Partial<TxData>,
|
||||
) => {
|
||||
[sideEffectTarget] = args;
|
||||
},
|
||||
);
|
||||
});
|
||||
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
||||
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));
|
||||
const assertion = new FunctionAssertion<[], {}, void>(exampleContract, 'emptyRevert');
|
||||
await assertion.executeAsync([], {});
|
||||
});
|
||||
|
||||
@@ -65,7 +59,8 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
||||
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
||||
let sideEffectTarget = ZERO_AMOUNT;
|
||||
const assertion = new FunctionAssertion<[BigNumber], BigNumber, BigNumber>(
|
||||
exampleContract.returnInteger.bind(exampleContract),
|
||||
exampleContract,
|
||||
'returnInteger',
|
||||
{
|
||||
before: async (_args: [BigNumber], _txData: Partial<TxData>) => {
|
||||
return randomInput;
|
||||
@@ -86,19 +81,16 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
||||
|
||||
it('should pass the result from the function call to "after"', async () => {
|
||||
let sideEffectTarget = ZERO_AMOUNT;
|
||||
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(
|
||||
exampleContract.returnInteger.bind(exampleContract),
|
||||
{
|
||||
after: async (
|
||||
_beforeInfo: any,
|
||||
result: FunctionResult,
|
||||
_args: [BigNumber],
|
||||
_txData: Partial<TxData>,
|
||||
) => {
|
||||
sideEffectTarget = result.data;
|
||||
},
|
||||
const assertion = new FunctionAssertion<[BigNumber], void, BigNumber>(exampleContract, 'returnInteger', {
|
||||
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], {});
|
||||
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
||||
@@ -106,21 +98,13 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
||||
|
||||
it('should pass the receipt from the function call to "after"', async () => {
|
||||
let sideEffectTarget: TransactionReceiptWithDecodedLogs;
|
||||
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 assertion = new FunctionAssertion<[string], void, void>(exampleContract, 'emitEvent', {
|
||||
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], {});
|
||||
@@ -135,19 +119,11 @@ 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<[string], void, void>(
|
||||
exampleContract.stringRevert.bind(exampleContract),
|
||||
{
|
||||
after: async (
|
||||
_beforeInfo: any,
|
||||
result: FunctionResult,
|
||||
_args: [string],
|
||||
_txData: Partial<TxData>,
|
||||
) => {
|
||||
sideEffectTarget = result.data;
|
||||
},
|
||||
const assertion = new FunctionAssertion<[string], void, void>(exampleContract, 'stringRevert', {
|
||||
after: async (_beforeInfo: any, result: FunctionResult, _args: [string], _txData: Partial<TxData>) => {
|
||||
sideEffectTarget = result.data;
|
||||
},
|
||||
);
|
||||
});
|
||||
const message = 'error message';
|
||||
await assertion.executeAsync([message], {});
|
||||
|
||||
|
||||
@@ -7,3 +7,13 @@ export function shortZip<T1, T2>(a: T1[], b: T2[]): Array<[T1, T2]> {
|
||||
const minLength = Math.min(a.length, b.length);
|
||||
return _.zip(a.slice(0, minLength), b.slice(0, minLength)) as Array<[T1, T2]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the keys in a deeply nested object. Adapted from https://stackoverflow.com/a/39126851
|
||||
*/
|
||||
export function replaceKeysDeep(obj: {}, mapKeys: (key: string) => string | void) {
|
||||
return _.transform(obj, function(result, value, key) {
|
||||
var currentKey = mapKeys(key) || key;
|
||||
result[currentKey] = _.isObject(value) ? replaceKeysDeep(value, mapKeys) : value;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user