298 lines
14 KiB
TypeScript
298 lines
14 KiB
TypeScript
import { BlockchainLifecycle } from 'dev-utils-deprecated';
|
|
import { LimitOrderFields } from '@0x/protocol-utils';
|
|
import Web3ProviderEngine from 'web3-provider-engine';
|
|
import { BigNumber } from '@0x/utils';
|
|
import { toBuffer } from 'ethereumjs-util';
|
|
import * as Mocha from 'mocha';
|
|
import { expect } from 'chai';
|
|
import { Connection } from 'typeorm';
|
|
|
|
// See https://github.com/0xProject/protocol/blob/34bbdc9c0f5812103d0e3917fd3933e3b510eb84/contracts/test-utils/src/constants.ts#L8-L29
|
|
const TESTRPC_PRIVATE_KEYS_STRINGS = [
|
|
'0xf2f48ee19680706196e2e339e5da3491186e0c4c5030670656b0e0164837257d',
|
|
'0x5d862464fe9303452126c8bc94274b8c5f9874cbd219789b3eb2128075a76f72',
|
|
'0xdf02719c4df8b9b8ac7f551fcb5d9ef48fa27eef7a66453879f4d8fdc6e78fb1',
|
|
'0xff12e391b79415e941a94de3bf3a9aee577aed0731e297d5cfa0b8a1e02fa1d0',
|
|
'0x752dd9cf65e68cfaba7d60225cbdbc1f4729dd5e5507def72815ed0d8abc6249',
|
|
'0xefb595a0178eb79a8df953f87c5148402a224cdf725e88c0146727c6aceadccd',
|
|
'0x83c6d2cc5ddcf9711a6d59b417dc20eb48afd58d45290099e5987e3d768f328f',
|
|
'0xbb2d3f7c9583780a7d3904a2f55d792707c345f21de1bacb2d389934d82796b2',
|
|
'0xb2fd4d29c1390b71b8795ae81196bfd60293adf99f9d32a0aff06288fcdac55f',
|
|
'0x23cb7121166b9a2f93ae0b7c05bde02eae50d64449b2cbb42bc84e9d38d6cc89',
|
|
'0x5ad34d7f8704ed33ab9e8dc30a76a8c48060649204c1f7b21b973235bba8092f',
|
|
'0xf18b03c1ae8e3876d76f20c7a5127a169dd6108c55fe9ce78bc7a91aca67dee3',
|
|
'0x4ccc4e7d7843e0701295e8fd671332a0e2f1e92d0dab16e8792e91cb0b719c9d',
|
|
'0xd7638ae813450e710e6f1b09921cc1593181073ce2099fb418fc03a933c7f41f',
|
|
'0xbc7bbca8ca15eb567be60df82e4452b13072dcb60db89747e3c85df63d8270ca',
|
|
'0x55131517839bf782e6e573bc3ac8f262efd2b6cb0ac86e8f147db26fcbdb15a5',
|
|
'0x6c2b5a16e327e0c4e7fafca5ae35616141de81f77da66ee0857bc3101d446e68',
|
|
'0xfd79b71625eec963e6ec42e9b5b10602c938dfec29cbbc7d17a492dd4f403859',
|
|
'0x3003eace3d4997c52ba69c2ca97a6b5d0d1216d894035a97071590ee284c1023',
|
|
'0x84a8bb71450a1b82be2b1cdd25d079cbf23dc8054e94c47ad14510aa967f45de',
|
|
];
|
|
|
|
const TESTRPC_PRIVATE_KEYS = TESTRPC_PRIVATE_KEYS_STRINGS.map((privateKeyString) => toBuffer(privateKeyString));
|
|
|
|
// Helps with printing test case results
|
|
const { color, symbols } = Mocha.reporters.Base;
|
|
|
|
import { DEFAULT_PAGE, DEFAULT_PER_PAGE } from '../src/constants';
|
|
import { getDBConnectionOrThrow } from '../src/db_connection';
|
|
import { OrderWatcherSignedOrderEntity, PersistentSignedOrderV4Entity } from '../src/entities';
|
|
import { OrderBookService } from '../src/services/orderbook_service';
|
|
import { OrderEventEndState, PaginatedCollection, SRAOrder, SRAOrderMetaData } from '../src/types';
|
|
import { orderUtils } from '../src/utils/order_utils';
|
|
|
|
import { CHAIN_ID, getProvider } from './constants';
|
|
import { setupDependenciesAsync, teardownDependenciesAsync } from './utils/deployment';
|
|
import { MockOrderWatcher } from './utils/mock_order_watcher';
|
|
import { getRandomLimitOrder } from './utils/orders';
|
|
import { Web3Wrapper } from '@0x/web3-wrapper';
|
|
|
|
const SUITE_NAME = 'OrderbookService';
|
|
|
|
const EMPTY_PAGINATED_RESPONSE = {
|
|
perPage: DEFAULT_PER_PAGE,
|
|
page: DEFAULT_PAGE,
|
|
total: 0,
|
|
records: [],
|
|
};
|
|
|
|
const TOMORROW = new BigNumber(Date.now() + 24 * 3600);
|
|
|
|
async function saveSignedOrdersAsync(connection: Connection, orders: SRAOrder[]): Promise<void> {
|
|
await connection.getRepository(OrderWatcherSignedOrderEntity).save(orders.map(orderUtils.serializeOrder));
|
|
}
|
|
|
|
async function savePersistentOrdersAsync(connection: Connection, orders: SRAOrder[]): Promise<void> {
|
|
await connection.getRepository(PersistentSignedOrderV4Entity).save(orders.map(orderUtils.serializePersistentOrder));
|
|
}
|
|
|
|
async function deleteSignedOrdersAsync(connection: Connection, orderHashes: string[]): Promise<void> {
|
|
try {
|
|
await connection.manager.delete(OrderWatcherSignedOrderEntity, orderHashes);
|
|
} catch (e) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
async function deletePersistentOrdersAsync(connection: Connection, orderHashes: string[]): Promise<void> {
|
|
try {
|
|
await connection.manager.delete(PersistentSignedOrderV4Entity, orderHashes);
|
|
} catch (e) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
async function newSRAOrderAsync(
|
|
privateKey: string,
|
|
params: Partial<LimitOrderFields>,
|
|
metadata?: Partial<SRAOrderMetaData>,
|
|
): Promise<SRAOrder> {
|
|
const limitOrder = getRandomLimitOrder({
|
|
expiry: TOMORROW,
|
|
chainId: CHAIN_ID,
|
|
...params,
|
|
});
|
|
const apiOrder: SRAOrder = {
|
|
order: {
|
|
...limitOrder,
|
|
signature: limitOrder.getSignatureWithKey(privateKey),
|
|
},
|
|
metaData: {
|
|
orderHash: limitOrder.getHash(),
|
|
remainingFillableTakerAmount: limitOrder.takerAmount,
|
|
state: undefined,
|
|
...metadata,
|
|
},
|
|
};
|
|
return apiOrder;
|
|
}
|
|
|
|
describe(SUITE_NAME, () => {
|
|
let makerAddress: string;
|
|
|
|
let blockchainLifecycle: BlockchainLifecycle;
|
|
let provider: Web3ProviderEngine;
|
|
|
|
let orderBookService: OrderBookService;
|
|
let privateKey: string;
|
|
|
|
let connection: Connection;
|
|
|
|
before(async () => {
|
|
await setupDependenciesAsync(SUITE_NAME);
|
|
connection = await getDBConnectionOrThrow();
|
|
await connection.runMigrations();
|
|
orderBookService = new OrderBookService(connection, new MockOrderWatcher(connection));
|
|
provider = getProvider();
|
|
const web3Wrapper = new Web3Wrapper(provider);
|
|
blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
|
|
|
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
|
[makerAddress] = accounts;
|
|
|
|
const privateKeyBuf = TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
|
|
privateKey = `0x${privateKeyBuf.toString('hex')}`;
|
|
await blockchainLifecycle.startAsync();
|
|
});
|
|
|
|
after(async () => {
|
|
await teardownDependenciesAsync(SUITE_NAME);
|
|
});
|
|
|
|
describe('getOrdersAsync', () => {
|
|
it.skip(`ran getOrdersAsync test cases`, async () => {
|
|
// Test case interface
|
|
type GetOrdersTestCase = [
|
|
SRAOrder[], // orders to save in the SignedOrder cache
|
|
SRAOrder[], // orders to save in the PersistentOrder cache
|
|
Partial<PaginatedCollection<SRAOrder>>, // expected response; missing fields will be filled in with defaults
|
|
Partial<Parameters<typeof orderBookService.getOrdersAsync>>, // params to pass to getOrdersAsync; using [] means default empty filter
|
|
string, // optional description to print out with the test case
|
|
];
|
|
|
|
/** Define All Test Cases Here */
|
|
const testCases: GetOrdersTestCase[] = await Promise.all([
|
|
[[], [], {}, [], `should return empty response when no orders`],
|
|
await (async () => {
|
|
const description = `should return orders in the cache when no filters`;
|
|
const apiOrder = await newSRAOrderAsync(privateKey, {});
|
|
const expected = {
|
|
records: [apiOrder],
|
|
};
|
|
return [[apiOrder], [], expected, [], description] as GetOrdersTestCase;
|
|
})(),
|
|
await (async () => {
|
|
const description = `should de-duplicate signed orders and persistent orders`;
|
|
const apiOrder = await newSRAOrderAsync(privateKey, {});
|
|
const expected = {
|
|
records: [apiOrder],
|
|
};
|
|
return [[apiOrder], [apiOrder], expected, [], description] as GetOrdersTestCase;
|
|
})(),
|
|
await (async () => {
|
|
const description = `should return persistent orders that are NOT in the signed orders cache`;
|
|
const apiOrder = await newSRAOrderAsync(privateKey, {}, { state: OrderEventEndState.Cancelled });
|
|
const expected = {
|
|
records: [apiOrder],
|
|
};
|
|
const params = [
|
|
DEFAULT_PAGE,
|
|
DEFAULT_PER_PAGE,
|
|
{ maker: apiOrder.order.maker },
|
|
{ isUnfillable: true },
|
|
] as Parameters<typeof orderBookService.getOrdersAsync>;
|
|
return [[], [apiOrder], expected, params, description] as GetOrdersTestCase;
|
|
})(),
|
|
]);
|
|
/** End Test Cases */
|
|
|
|
// Generic test runner
|
|
function runTestCaseForGetOrdersFilters(
|
|
orders: SRAOrder[],
|
|
persistentOrders: SRAOrder[],
|
|
expectedResponse: PaginatedCollection<SRAOrder>,
|
|
description: string,
|
|
): (params: Parameters<typeof orderBookService.getOrdersAsync>) => Promise<void> {
|
|
const indent = ' ';
|
|
return async (args: Parameters<typeof orderBookService.getOrdersAsync>) => {
|
|
try {
|
|
// setup
|
|
await Promise.all([
|
|
saveSignedOrdersAsync(connection, orders),
|
|
savePersistentOrdersAsync(connection, persistentOrders),
|
|
]);
|
|
const results = await orderBookService.getOrdersAsync(...args);
|
|
// clean non-deterministic field
|
|
expectedResponse.records.forEach((o, i) => {
|
|
o.metaData.createdAt = results.records[i].metaData.createdAt;
|
|
});
|
|
// assertion
|
|
expect(expectedResponse).deep.equal(results);
|
|
|
|
// cleanup
|
|
const deletePromise = async (_orders: SRAOrder[], isPersistent: boolean) => {
|
|
const deleteFn = isPersistent ? deletePersistentOrdersAsync : deleteSignedOrdersAsync;
|
|
return _orders.length > 0
|
|
? deleteFn(
|
|
connection,
|
|
_orders.map((o) => o.metaData.orderHash),
|
|
)
|
|
: Promise.resolve();
|
|
};
|
|
await Promise.all([deletePromise(orders, false), deletePromise(persistentOrders, true)]);
|
|
// If anything went wrong, the test failed
|
|
} catch (e) {
|
|
console.log(indent, color('bright fail', `${symbols.err}`), color('fail', description));
|
|
throw e;
|
|
}
|
|
// Otherwise, succeeded
|
|
console.log(indent, color('checkmark', `${symbols.ok}`), color('pass', description));
|
|
};
|
|
}
|
|
|
|
// Run the tests synchronously; fill in default values
|
|
for (const [i, _test] of testCases.entries()) {
|
|
const test = fillInDefaultTestCaseValues(_test, i);
|
|
await runTestCaseForGetOrdersFilters(
|
|
test[0],
|
|
test[1],
|
|
test[2] as PaginatedCollection<SRAOrder>,
|
|
test[4],
|
|
)(test[3] as Parameters<typeof orderBookService.getOrdersAsync>);
|
|
}
|
|
function fillInDefaultTestCaseValues(test: GetOrdersTestCase, i: number): GetOrdersTestCase {
|
|
// expected orderbook response
|
|
test[2] = { ...EMPTY_PAGINATED_RESPONSE, ...test[2] };
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- TODO: fix me!
|
|
test[2] = { ...test[2], total: test[2].records!.length };
|
|
// test description
|
|
test[4] = test[4] || `Test Case #${i}`;
|
|
// params for getOrdersAsync
|
|
test[3][0] = test[3][0] || DEFAULT_PAGE;
|
|
test[3][1] = test[3][1] || DEFAULT_PER_PAGE;
|
|
test[3][2] = test[3][2] || {};
|
|
test[3][3] = test[3][3] || {};
|
|
return test;
|
|
}
|
|
});
|
|
});
|
|
describe('addOrdersAsync, addPersistentOrdersAsync', () => {
|
|
before(async () => {
|
|
// await connection.runMigrations();
|
|
});
|
|
beforeEach(async () => {
|
|
await blockchainLifecycle.startAsync();
|
|
});
|
|
afterEach(async () => {
|
|
await blockchainLifecycle.revertAsync();
|
|
});
|
|
it('should post orders to order watcher', async () => {
|
|
const apiOrder = await newSRAOrderAsync(privateKey, {});
|
|
await orderBookService.addOrdersAsync([apiOrder.order]);
|
|
|
|
// should not save to persistent orders table
|
|
const result = await connection.manager.find(PersistentSignedOrderV4Entity, {
|
|
hash: apiOrder.metaData.orderHash,
|
|
});
|
|
expect(result).to.deep.equal([]);
|
|
|
|
await deleteSignedOrdersAsync(connection, [apiOrder.metaData.orderHash]);
|
|
});
|
|
it('should find persistent orders after posting them', async () => {
|
|
const apiOrder = await newSRAOrderAsync(privateKey, {});
|
|
await orderBookService.addPersistentOrdersAsync([apiOrder.order]);
|
|
|
|
const result = await connection.manager.find(PersistentSignedOrderV4Entity, {
|
|
hash: apiOrder.metaData.orderHash,
|
|
});
|
|
const expected = orderUtils.serializePersistentOrder(apiOrder);
|
|
expected.createdAt = result[0].createdAt; // createdAt is saved in the PersistentOrders table directly
|
|
expect(result).to.deep.equal([expected]);
|
|
|
|
await deletePersistentOrdersAsync(connection, [apiOrder.metaData.orderHash]);
|
|
});
|
|
});
|
|
});
|