From c4ee2d73865a1444c079b9e2836b7630a0adf03e Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 12 Nov 2017 22:17:18 -0500 Subject: Switch over to Lerna + Yarn Workspaces setup for a mono-repo approach --- packages/0x.js/test/utils/blockchain_lifecycle.ts | 26 +++++ packages/0x.js/test/utils/chai_setup.ts | 13 +++ packages/0x.js/test/utils/constants.ts | 8 ++ packages/0x.js/test/utils/fill_scenarios.ts | 114 +++++++++++++++++++++ packages/0x.js/test/utils/order_factory.ts | 42 ++++++++ .../0x.js/test/utils/report_callback_errors.ts | 14 +++ packages/0x.js/test/utils/rpc.ts | 57 +++++++++++ packages/0x.js/test/utils/token_utils.ts | 24 +++++ packages/0x.js/test/utils/web3_factory.ts | 31 ++++++ 9 files changed, 329 insertions(+) create mode 100644 packages/0x.js/test/utils/blockchain_lifecycle.ts create mode 100644 packages/0x.js/test/utils/chai_setup.ts create mode 100644 packages/0x.js/test/utils/constants.ts create mode 100644 packages/0x.js/test/utils/fill_scenarios.ts create mode 100644 packages/0x.js/test/utils/order_factory.ts create mode 100644 packages/0x.js/test/utils/report_callback_errors.ts create mode 100644 packages/0x.js/test/utils/rpc.ts create mode 100644 packages/0x.js/test/utils/token_utils.ts create mode 100644 packages/0x.js/test/utils/web3_factory.ts (limited to 'packages/0x.js/test/utils') diff --git a/packages/0x.js/test/utils/blockchain_lifecycle.ts b/packages/0x.js/test/utils/blockchain_lifecycle.ts new file mode 100644 index 000000000..9a44ccd6f --- /dev/null +++ b/packages/0x.js/test/utils/blockchain_lifecycle.ts @@ -0,0 +1,26 @@ +import {RPC} from './rpc'; + +export class BlockchainLifecycle { + private rpc: RPC; + private snapshotIdsStack: number[]; + constructor() { + this.rpc = new RPC(); + this.snapshotIdsStack = []; + } + // TODO: In order to run these tests on an actual node, we should check if we are running against + // TestRPC, if so, use snapshots, otherwise re-deploy contracts before every test + public async startAsync(): Promise { + const snapshotId = await this.rpc.takeSnapshotAsync(); + this.snapshotIdsStack.push(snapshotId); + } + public async revertAsync(): Promise { + const snapshotId = this.snapshotIdsStack.pop() as number; + const didRevert = await this.rpc.revertSnapshotAsync(snapshotId); + if (!didRevert) { + throw new Error(`Snapshot with id #${snapshotId} failed to revert`); + } + } + public async mineABlock(): Promise { + await this.rpc.mineBlockAsync(); + } +} diff --git a/packages/0x.js/test/utils/chai_setup.ts b/packages/0x.js/test/utils/chai_setup.ts new file mode 100644 index 000000000..c18988106 --- /dev/null +++ b/packages/0x.js/test/utils/chai_setup.ts @@ -0,0 +1,13 @@ +import * as chai from 'chai'; +import * as dirtyChai from 'dirty-chai'; +import ChaiBigNumber = require('chai-bignumber'); +import chaiAsPromised = require('chai-as-promised'); + +export const chaiSetup = { + configure() { + chai.config.includeStack = true; + chai.use(ChaiBigNumber()); + chai.use(dirtyChai); + chai.use(chaiAsPromised); + }, +}; diff --git a/packages/0x.js/test/utils/constants.ts b/packages/0x.js/test/utils/constants.ts new file mode 100644 index 000000000..c7d3aebca --- /dev/null +++ b/packages/0x.js/test/utils/constants.ts @@ -0,0 +1,8 @@ +export const constants = { + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', + RPC_HOST: 'localhost', + RPC_PORT: 8545, + TESTRPC_NETWORK_ID: 50, + KOVAN_RPC_URL: 'https://kovan.infura.io', + ROPSTEN_RPC_URL: 'https://ropsten.infura.io', +}; diff --git a/packages/0x.js/test/utils/fill_scenarios.ts b/packages/0x.js/test/utils/fill_scenarios.ts new file mode 100644 index 000000000..a0632b12c --- /dev/null +++ b/packages/0x.js/test/utils/fill_scenarios.ts @@ -0,0 +1,114 @@ +import BigNumber from 'bignumber.js'; +import {ZeroEx, Token, SignedOrder} from '../../src'; +import {orderFactory} from '../utils/order_factory'; +import {constants} from './constants'; + +export class FillScenarios { + private zeroEx: ZeroEx; + private userAddresses: string[]; + private tokens: Token[]; + private coinbase: string; + private zrxTokenAddress: string; + private exchangeContractAddress: string; + constructor(zeroEx: ZeroEx, userAddresses: string[], + tokens: Token[], zrxTokenAddress: string, exchangeContractAddress: string) { + this.zeroEx = zeroEx; + this.userAddresses = userAddresses; + this.tokens = tokens; + this.coinbase = userAddresses[0]; + this.zrxTokenAddress = zrxTokenAddress; + this.exchangeContractAddress = exchangeContractAddress; + } + public async createFillableSignedOrderAsync(makerTokenAddress: string, takerTokenAddress: string, + makerAddress: string, takerAddress: string, + fillableAmount: BigNumber, + expirationUnixTimestampSec?: BigNumber): + Promise { + return this.createAsymmetricFillableSignedOrderAsync( + makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, + fillableAmount, fillableAmount, expirationUnixTimestampSec, + ); + } + public async createFillableSignedOrderWithFeesAsync( + makerTokenAddress: string, takerTokenAddress: string, + makerFee: BigNumber, takerFee: BigNumber, + makerAddress: string, takerAddress: string, + fillableAmount: BigNumber, + feeRecepient: string, expirationUnixTimestampSec?: BigNumber, + ): Promise { + return this.createAsymmetricFillableSignedOrderWithFeesAsync( + makerTokenAddress, takerTokenAddress, makerFee, takerFee, makerAddress, takerAddress, + fillableAmount, fillableAmount, feeRecepient, expirationUnixTimestampSec, + ); + } + public async createAsymmetricFillableSignedOrderAsync( + makerTokenAddress: string, takerTokenAddress: string, makerAddress: string, takerAddress: string, + makerFillableAmount: BigNumber, takerFillableAmount: BigNumber, + expirationUnixTimestampSec?: BigNumber): Promise { + const makerFee = new BigNumber(0); + const takerFee = new BigNumber(0); + const feeRecepient = constants.NULL_ADDRESS; + return this.createAsymmetricFillableSignedOrderWithFeesAsync( + makerTokenAddress, takerTokenAddress, makerFee, takerFee, makerAddress, takerAddress, + makerFillableAmount, takerFillableAmount, feeRecepient, expirationUnixTimestampSec, + ); + } + public async createPartiallyFilledSignedOrderAsync(makerTokenAddress: string, takerTokenAddress: string, + takerAddress: string, fillableAmount: BigNumber, + partialFillAmount: BigNumber) { + const [makerAddress] = this.userAddresses; + const signedOrder = await this.createAsymmetricFillableSignedOrderAsync( + makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, + fillableAmount, fillableAmount, + ); + const shouldThrowOnInsufficientBalanceOrAllowance = false; + await this.zeroEx.exchange.fillOrderAsync( + signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, + ); + return signedOrder; + } + private async createAsymmetricFillableSignedOrderWithFeesAsync( + makerTokenAddress: string, takerTokenAddress: string, + makerFee: BigNumber, takerFee: BigNumber, + makerAddress: string, takerAddress: string, + makerFillableAmount: BigNumber, takerFillableAmount: BigNumber, + feeRecepient: string, expirationUnixTimestampSec?: BigNumber): Promise { + + await Promise.all([ + this.increaseBalanceAndAllowanceAsync(makerTokenAddress, makerAddress, makerFillableAmount), + this.increaseBalanceAndAllowanceAsync(takerTokenAddress, takerAddress, takerFillableAmount), + ]); + await Promise.all([ + this.increaseBalanceAndAllowanceAsync(this.zrxTokenAddress, makerAddress, makerFee), + this.increaseBalanceAndAllowanceAsync(this.zrxTokenAddress, takerAddress, takerFee), + ]); + + const signedOrder = await orderFactory.createSignedOrderAsync(this.zeroEx, + makerAddress, takerAddress, makerFee, takerFee, + makerFillableAmount, makerTokenAddress, takerFillableAmount, takerTokenAddress, + this.exchangeContractAddress, feeRecepient, expirationUnixTimestampSec); + return signedOrder; + } + private async increaseBalanceAndAllowanceAsync( + tokenAddress: string, address: string, amount: BigNumber): Promise { + if (amount.isZero() || address === ZeroEx.NULL_ADDRESS) { + return; // noop + } + await Promise.all([ + this.increaseBalanceAsync(tokenAddress, address, amount), + this.increaseAllowanceAsync(tokenAddress, address, amount), + ]); + } + private async increaseBalanceAsync( + tokenAddress: string, address: string, amount: BigNumber): Promise { + await this.zeroEx.token.transferAsync(tokenAddress, this.coinbase, address, amount); + } + private async increaseAllowanceAsync( + tokenAddress: string, address: string, amount: BigNumber): Promise { + const oldMakerAllowance = await this.zeroEx.token.getProxyAllowanceAsync(tokenAddress, address); + const newMakerAllowance = oldMakerAllowance.plus(amount); + await this.zeroEx.token.setProxyAllowanceAsync( + tokenAddress, address, newMakerAllowance, + ); + } +} diff --git a/packages/0x.js/test/utils/order_factory.ts b/packages/0x.js/test/utils/order_factory.ts new file mode 100644 index 000000000..6086e09f7 --- /dev/null +++ b/packages/0x.js/test/utils/order_factory.ts @@ -0,0 +1,42 @@ +import * as _ from 'lodash'; +import BigNumber from 'bignumber.js'; +import {ZeroEx, SignedOrder} from '../../src'; + +export const orderFactory = { + async createSignedOrderAsync( + zeroEx: ZeroEx, + maker: string, + taker: string, + makerFee: BigNumber, + takerFee: BigNumber, + makerTokenAmount: BigNumber, + makerTokenAddress: string, + takerTokenAmount: BigNumber, + takerTokenAddress: string, + exchangeContractAddress: string, + feeRecipient: string, + expirationUnixTimestampSec?: BigNumber): Promise { + const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite + expirationUnixTimestampSec = _.isUndefined(expirationUnixTimestampSec) ? + defaultExpirationUnixTimestampSec : + expirationUnixTimestampSec; + const order = { + maker, + taker, + makerFee, + takerFee, + makerTokenAmount, + takerTokenAmount, + makerTokenAddress, + takerTokenAddress, + salt: ZeroEx.generatePseudoRandomSalt(), + exchangeContractAddress, + feeRecipient, + expirationUnixTimestampSec, + }; + const orderHash = ZeroEx.getOrderHashHex(order); + const ecSignature = await zeroEx.signOrderHashAsync(orderHash, maker); + const signedOrder: SignedOrder = _.assign(order, {ecSignature}); + return signedOrder; + }, +}; diff --git a/packages/0x.js/test/utils/report_callback_errors.ts b/packages/0x.js/test/utils/report_callback_errors.ts new file mode 100644 index 000000000..d471b2af2 --- /dev/null +++ b/packages/0x.js/test/utils/report_callback_errors.ts @@ -0,0 +1,14 @@ +import { DoneCallback } from '../../src/types'; + +export const reportCallbackErrors = (done: DoneCallback) => { + return (f: (...args: any[]) => void) => { + const wrapped = (...args: any[]) => { + try { + f(...args); + } catch (err) { + done(err); + } + }; + return wrapped; + }; +}; diff --git a/packages/0x.js/test/utils/rpc.ts b/packages/0x.js/test/utils/rpc.ts new file mode 100644 index 000000000..299e72e79 --- /dev/null +++ b/packages/0x.js/test/utils/rpc.ts @@ -0,0 +1,57 @@ +import * as ethUtil from 'ethereumjs-util'; +import * as request from 'request-promise-native'; +import {constants} from './constants'; + +export class RPC { + private host: string; + private port: number; + private id: number; + constructor() { + this.host = constants.RPC_HOST; + this.port = constants.RPC_PORT; + this.id = 0; + } + public async takeSnapshotAsync(): Promise { + const method = 'evm_snapshot'; + const params: any[] = []; + const payload = this.toPayload(method, params); + const snapshotIdHex = await this.sendAsync(payload); + const snapshotId = ethUtil.bufferToInt(ethUtil.toBuffer(snapshotIdHex)); + return snapshotId; + } + public async revertSnapshotAsync(snapshotId: number): Promise { + const method = 'evm_revert'; + const params = [snapshotId]; + const payload = this.toPayload(method, params); + const didRevert = await this.sendAsync(payload); + return didRevert; + } + public async mineBlockAsync(): Promise { + const method = 'evm_mine'; + const params: any[] = []; + const payload = this.toPayload(method, params); + await this.sendAsync(payload); + } + private toPayload(method: string, params: any[] = []): string { + const payload = JSON.stringify({ + id: this.id, + method, + params, + }); + this.id += 1; + return payload; + } + private async sendAsync(payload: string): Promise { + const opts = { + method: 'POST', + uri: `http://${this.host}:${this.port}`, + body: payload, + headers: { + 'content-type': 'application/json', + }, + }; + const bodyString = await request(opts); + const body = JSON.parse(bodyString); + return body.result; + } +} diff --git a/packages/0x.js/test/utils/token_utils.ts b/packages/0x.js/test/utils/token_utils.ts new file mode 100644 index 000000000..51cb9411c --- /dev/null +++ b/packages/0x.js/test/utils/token_utils.ts @@ -0,0 +1,24 @@ +import * as _ from 'lodash'; +import {Token, InternalZeroExError} from '../../src/types'; + +const PROTOCOL_TOKEN_SYMBOL = 'ZRX'; + +export class TokenUtils { + private tokens: Token[]; + constructor(tokens: Token[]) { + this.tokens = tokens; + } + public getProtocolTokenOrThrow(): Token { + const zrxToken = _.find(this.tokens, {symbol: PROTOCOL_TOKEN_SYMBOL}); + if (_.isUndefined(zrxToken)) { + throw new Error(InternalZeroExError.ZrxNotInTokenRegistry); + } + return zrxToken; + } + public getNonProtocolTokens(): Token[] { + const nonProtocolTokens = _.filter(this.tokens, token => { + return token.symbol !== PROTOCOL_TOKEN_SYMBOL; + }); + return nonProtocolTokens; + } +} diff --git a/packages/0x.js/test/utils/web3_factory.ts b/packages/0x.js/test/utils/web3_factory.ts new file mode 100644 index 000000000..b20070c74 --- /dev/null +++ b/packages/0x.js/test/utils/web3_factory.ts @@ -0,0 +1,31 @@ +// HACK: web3 injects XMLHttpRequest into the global scope and ProviderEngine checks XMLHttpRequest +// to know whether it is running in a browser or node environment. We need it to be undefined since +// we are not running in a browser env. +// Filed issue: https://github.com/ethereum/web3.js/issues/844 +(global as any).XMLHttpRequest = undefined; +import ProviderEngine = require('web3-provider-engine'); +import RpcSubprovider = require('web3-provider-engine/subproviders/rpc'); +import * as Web3 from 'web3'; +import {constants} from './constants'; +import {EmptyWalletSubProvider} from '../../src/subproviders/empty_wallet_subprovider'; + +export const web3Factory = { + create(hasAddresses: boolean = true): Web3 { + const provider = this.getRpcProvider(hasAddresses); + const web3 = new Web3(); + web3.setProvider(provider); + return web3; + }, + getRpcProvider(hasAddresses: boolean = true): Web3.Provider { + const provider = new ProviderEngine(); + const rpcUrl = `http://${constants.RPC_HOST}:${constants.RPC_PORT}`; + if (!hasAddresses) { + provider.addProvider(new EmptyWalletSubProvider()); + } + provider.addProvider(new RpcSubprovider({ + rpcUrl, + })); + provider.start(); + return provider; + }, +}; -- cgit