200 lines
9.1 KiB
TypeScript
200 lines
9.1 KiB
TypeScript
import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils';
|
|
import { BlockchainLifecycle, callbackErrorReporter } from '@0x/dev-utils';
|
|
import { FillScenarios } from '@0x/fill-scenarios';
|
|
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
|
|
import { DoneCallback } from '@0x/types';
|
|
import { BigNumber } from '@0x/utils';
|
|
import * as chai from 'chai';
|
|
import * as _ from 'lodash';
|
|
import 'mocha';
|
|
import * as Sinon from 'sinon';
|
|
|
|
import { ExpirationWatcher } from '../src/order_watcher/expiration_watcher';
|
|
import { utils } from '../src/utils/utils';
|
|
|
|
import { chaiSetup } from './utils/chai_setup';
|
|
import { migrateOnceAsync } from './utils/migrate';
|
|
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
|
|
|
chaiSetup.configure();
|
|
const expect = chai.expect;
|
|
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
|
const MILISECONDS_IN_SECOND = 1000;
|
|
|
|
describe('ExpirationWatcher', () => {
|
|
let userAddresses: string[];
|
|
let fillScenarios: FillScenarios;
|
|
let makerAssetData: string;
|
|
let takerAssetData: string;
|
|
let coinbase: string;
|
|
let makerAddress: string;
|
|
let takerAddress: string;
|
|
let feeRecipient: string;
|
|
const fillableAmount = new BigNumber(5);
|
|
let currentUnixTimestampSec: BigNumber;
|
|
let timer: Sinon.SinonFakeTimers;
|
|
let expirationWatcher: ExpirationWatcher;
|
|
before(async () => {
|
|
const contractAddresses = await migrateOnceAsync();
|
|
await blockchainLifecycle.startAsync();
|
|
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
|
fillScenarios = new FillScenarios(
|
|
provider,
|
|
userAddresses,
|
|
contractAddresses.zrxToken,
|
|
contractAddresses.exchange,
|
|
contractAddresses.erc20Proxy,
|
|
contractAddresses.erc721Proxy,
|
|
);
|
|
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
|
|
const [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
|
|
[makerAssetData, takerAssetData] = [
|
|
assetDataUtils.encodeERC20AssetData(makerTokenAddress),
|
|
assetDataUtils.encodeERC20AssetData(takerTokenAddress),
|
|
];
|
|
});
|
|
after(async () => {
|
|
await blockchainLifecycle.revertAsync();
|
|
});
|
|
beforeEach(async () => {
|
|
await blockchainLifecycle.startAsync();
|
|
const sinonTimerConfig = { shouldAdvanceTime: true } as any;
|
|
// This constructor has incorrect types
|
|
timer = Sinon.useFakeTimers(sinonTimerConfig);
|
|
currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
|
|
expirationWatcher = new ExpirationWatcher();
|
|
});
|
|
afterEach(async () => {
|
|
await blockchainLifecycle.revertAsync();
|
|
timer.restore();
|
|
expirationWatcher.unsubscribe();
|
|
});
|
|
it('correctly emits events when order expires', (done: DoneCallback) => {
|
|
(async () => {
|
|
const orderLifetimeSec = 60;
|
|
const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec);
|
|
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
expirationUnixTimestampSec,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
expirationWatcher.addOrder(orderHash, signedOrder.expirationTimeSeconds.times(MILISECONDS_IN_SECOND));
|
|
const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done)((hash: string) => {
|
|
expect(hash).to.be.equal(orderHash);
|
|
expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.gte(expirationUnixTimestampSec);
|
|
});
|
|
expirationWatcher.subscribe(callbackAsync);
|
|
timer.tick(orderLifetimeSec * MILISECONDS_IN_SECOND);
|
|
})().catch(done);
|
|
});
|
|
it("doesn't emit events before order expires", (done: DoneCallback) => {
|
|
(async () => {
|
|
const orderLifetimeSec = 60;
|
|
const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec);
|
|
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
expirationUnixTimestampSec,
|
|
);
|
|
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
|
|
expirationWatcher.addOrder(orderHash, signedOrder.expirationTimeSeconds.times(MILISECONDS_IN_SECOND));
|
|
const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done)(async (_hash: string) => {
|
|
done(new Error('Emitted expiration went before the order actually expired'));
|
|
});
|
|
expirationWatcher.subscribe(callbackAsync);
|
|
const notEnoughTime = orderLifetimeSec - 1;
|
|
timer.tick(notEnoughTime * MILISECONDS_IN_SECOND);
|
|
done();
|
|
})().catch(done);
|
|
});
|
|
it('emits events in correct order', (done: DoneCallback) => {
|
|
(async () => {
|
|
const order1Lifetime = 60;
|
|
const order2Lifetime = 120;
|
|
const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime);
|
|
const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime);
|
|
const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
order1ExpirationUnixTimestampSec,
|
|
);
|
|
const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
order2ExpirationUnixTimestampSec,
|
|
);
|
|
const orderHash1 = orderHashUtils.getOrderHashHex(signedOrder1);
|
|
const orderHash2 = orderHashUtils.getOrderHashHex(signedOrder2);
|
|
expirationWatcher.addOrder(orderHash2, signedOrder2.expirationTimeSeconds.times(MILISECONDS_IN_SECOND));
|
|
expirationWatcher.addOrder(orderHash1, signedOrder1.expirationTimeSeconds.times(MILISECONDS_IN_SECOND));
|
|
const expirationOrder = [orderHash1, orderHash2];
|
|
const expectToBeCalledOnce = false;
|
|
const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done, expectToBeCalledOnce)(
|
|
(hash: string) => {
|
|
const orderHash = expirationOrder.shift();
|
|
expect(hash).to.be.equal(orderHash);
|
|
if (_.isEmpty(expirationOrder)) {
|
|
done();
|
|
}
|
|
},
|
|
);
|
|
expirationWatcher.subscribe(callbackAsync);
|
|
timer.tick(order2Lifetime * MILISECONDS_IN_SECOND);
|
|
})().catch(done);
|
|
});
|
|
it('emits events in correct order when expirations are equal', (done: DoneCallback) => {
|
|
(async () => {
|
|
const order1Lifetime = 60;
|
|
const order2Lifetime = 60;
|
|
const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime);
|
|
const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime);
|
|
const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
order1ExpirationUnixTimestampSec,
|
|
);
|
|
const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync(
|
|
makerAssetData,
|
|
takerAssetData,
|
|
makerAddress,
|
|
takerAddress,
|
|
fillableAmount,
|
|
order2ExpirationUnixTimestampSec,
|
|
);
|
|
const orderHash1 = orderHashUtils.getOrderHashHex(signedOrder1);
|
|
const orderHash2 = orderHashUtils.getOrderHashHex(signedOrder2);
|
|
expirationWatcher.addOrder(orderHash1, signedOrder1.expirationTimeSeconds.times(MILISECONDS_IN_SECOND));
|
|
expirationWatcher.addOrder(orderHash2, signedOrder2.expirationTimeSeconds.times(MILISECONDS_IN_SECOND));
|
|
const expirationOrder = orderHash1 < orderHash2 ? [orderHash1, orderHash2] : [orderHash2, orderHash1];
|
|
const expectToBeCalledOnce = false;
|
|
const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done, expectToBeCalledOnce)(
|
|
(hash: string) => {
|
|
const orderHash = expirationOrder.shift();
|
|
expect(hash).to.be.equal(orderHash);
|
|
if (_.isEmpty(expirationOrder)) {
|
|
done();
|
|
}
|
|
},
|
|
);
|
|
expirationWatcher.subscribe(callbackAsync);
|
|
timer.tick(order2Lifetime * MILISECONDS_IN_SECOND);
|
|
})().catch(done);
|
|
});
|
|
});
|