diff options
author | Fabio Berger <me@fabioberger.com> | 2018-05-10 02:36:28 +0800 |
---|---|---|
committer | Fabio Berger <me@fabioberger.com> | 2018-05-10 02:36:28 +0800 |
commit | 209266dbed9d7d038c90c2da8d9b99acab77c80c (patch) | |
tree | cfefd5ab15bc237716b15687629e0f41362a8e86 /packages/order-watcher/test | |
parent | 69a6166b6a1d39afc24b8dd950ec5d8539a03420 (diff) | |
download | dexon-0x-contracts-209266dbed9d7d038c90c2da8d9b99acab77c80c.tar dexon-0x-contracts-209266dbed9d7d038c90c2da8d9b99acab77c80c.tar.gz dexon-0x-contracts-209266dbed9d7d038c90c2da8d9b99acab77c80c.tar.bz2 dexon-0x-contracts-209266dbed9d7d038c90c2da8d9b99acab77c80c.tar.lz dexon-0x-contracts-209266dbed9d7d038c90c2da8d9b99acab77c80c.tar.xz dexon-0x-contracts-209266dbed9d7d038c90c2da8d9b99acab77c80c.tar.zst dexon-0x-contracts-209266dbed9d7d038c90c2da8d9b99acab77c80c.zip |
Split 0x.js into contract-wrappers, order-watcher but keep 0x.js as a unifying library with the same interface
Diffstat (limited to 'packages/order-watcher/test')
-rw-r--r-- | packages/order-watcher/test/event_watcher_test.ts | 126 | ||||
-rw-r--r-- | packages/order-watcher/test/expiration_watcher_test.ts | 200 | ||||
-rw-r--r-- | packages/order-watcher/test/global_hooks.ts | 7 | ||||
-rw-r--r-- | packages/order-watcher/test/order_watcher_test.ts | 574 | ||||
-rw-r--r-- | packages/order-watcher/test/remaining_fillable_calculator_test.ts | 234 | ||||
-rw-r--r-- | packages/order-watcher/test/utils/chai_setup.ts | 13 | ||||
-rw-r--r-- | packages/order-watcher/test/utils/constants.ts | 5 | ||||
-rw-r--r-- | packages/order-watcher/test/utils/deployer.ts | 18 | ||||
-rw-r--r-- | packages/order-watcher/test/utils/token_utils.ts | 34 | ||||
-rw-r--r-- | packages/order-watcher/test/utils/web3_wrapper.ts | 9 |
10 files changed, 1220 insertions, 0 deletions
diff --git a/packages/order-watcher/test/event_watcher_test.ts b/packages/order-watcher/test/event_watcher_test.ts new file mode 100644 index 000000000..b4eca315e --- /dev/null +++ b/packages/order-watcher/test/event_watcher_test.ts @@ -0,0 +1,126 @@ +import { callbackErrorReporter, web3Factory } from '@0xproject/dev-utils'; +import { DoneCallback, LogEntry, LogEntryEvent } from '@0xproject/types'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import * as chai from 'chai'; +import * as _ from 'lodash'; +import 'mocha'; +import * as Sinon from 'sinon'; + +import { EventWatcher } from '../src/order_watcher/event_watcher'; + +import { chaiSetup } from './utils/chai_setup'; +import { provider } from './utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe('EventWatcher', () => { + let stubs: Sinon.SinonStub[] = []; + let eventWatcher: EventWatcher; + let web3Wrapper: Web3Wrapper; + const logA: LogEntry = { + address: '0x71d271f8b14adef568f8f28f1587ce7271ac4ca5', + blockHash: null, + blockNumber: null, + data: '', + logIndex: null, + topics: [], + transactionHash: '0x004881d38cd4a8f72f1a0d68c8b9b8124504706041ff37019c1d1ed6bfda8e17', + transactionIndex: 0, + }; + const logB: LogEntry = { + address: '0x8d12a197cb00d4747a1fe03395095ce2a5cc6819', + blockHash: null, + blockNumber: null, + data: '', + logIndex: null, + topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'], + transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25', + transactionIndex: 0, + }; + const logC: LogEntry = { + address: '0x1d271f8b174adef58f1587ce68f8f27271ac4ca5', + blockHash: null, + blockNumber: null, + data: '', + logIndex: null, + topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'], + transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25', + transactionIndex: 0, + }; + before(async () => { + const pollingIntervalMs = 10; + web3Wrapper = new Web3Wrapper(provider); + eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalMs); + }); + afterEach(() => { + // clean up any stubs after the test has completed + _.each(stubs, s => s.restore()); + stubs = []; + eventWatcher.unsubscribe(); + }); + it('correctly emits initial log events', (done: DoneCallback) => { + const logs: LogEntry[] = [logA, logB]; + const expectedLogEvents = [ + { + removed: false, + ...logA, + }, + { + removed: false, + ...logB, + }, + ]; + const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync'); + getLogsStub.onCall(0).returns(logs); + stubs.push(getLogsStub); + const expectedToBeCalledOnce = false; + const callback = callbackErrorReporter.reportNodeCallbackErrors(done, expectedToBeCalledOnce)( + (event: LogEntryEvent) => { + const expectedLogEvent = expectedLogEvents.shift(); + expect(event).to.be.deep.equal(expectedLogEvent); + if (_.isEmpty(expectedLogEvents)) { + done(); + } + }, + ); + eventWatcher.subscribe(callback); + }); + it('correctly computes the difference and emits only changes', (done: DoneCallback) => { + const initialLogs: LogEntry[] = [logA, logB]; + const changedLogs: LogEntry[] = [logA, logC]; + const expectedLogEvents = [ + { + removed: false, + ...logA, + }, + { + removed: false, + ...logB, + }, + { + removed: true, + ...logB, + }, + { + removed: false, + ...logC, + }, + ]; + const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync'); + getLogsStub.onCall(0).returns(initialLogs); + getLogsStub.onCall(1).returns(changedLogs); + stubs.push(getLogsStub); + const expectedToBeCalledOnce = false; + const callback = callbackErrorReporter.reportNodeCallbackErrors(done, expectedToBeCalledOnce)( + (event: LogEntryEvent) => { + const expectedLogEvent = expectedLogEvents.shift(); + expect(event).to.be.deep.equal(expectedLogEvent); + if (_.isEmpty(expectedLogEvents)) { + done(); + } + }, + ); + eventWatcher.subscribe(callback); + }); +}); diff --git a/packages/order-watcher/test/expiration_watcher_test.ts b/packages/order-watcher/test/expiration_watcher_test.ts new file mode 100644 index 000000000..0a2524d78 --- /dev/null +++ b/packages/order-watcher/test/expiration_watcher_test.ts @@ -0,0 +1,200 @@ +import { ContractWrappers } from '@0xproject/contract-wrappers'; +import { BlockchainLifecycle, callbackErrorReporter, devConstants } from '@0xproject/dev-utils'; +import { FillScenarios } from '@0xproject/fill-scenarios'; +import { getOrderHashHex } from '@0xproject/order-utils'; +import { DoneCallback, Token } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; +import * as _ from 'lodash'; +import 'mocha'; +import * as Sinon from 'sinon'; + +import { artifacts } from '../src/artifacts'; +import { ExpirationWatcher } from '../src/order_watcher/expiration_watcher'; +import { utils } from '../src/utils/utils'; + +import { chaiSetup } from './utils/chai_setup'; +import { constants } from './utils/constants'; +import { TokenUtils } from './utils/token_utils'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('ExpirationWatcher', () => { + let contractWrappers: ContractWrappers; + let tokenUtils: TokenUtils; + let tokens: Token[]; + let userAddresses: string[]; + let zrxTokenAddress: string; + let fillScenarios: FillScenarios; + let exchangeContractAddress: string; + let makerTokenAddress: string; + let takerTokenAddress: 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 config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + contractWrappers = new ContractWrappers(provider, config); + exchangeContractAddress = contractWrappers.exchange.getContractAddress(); + userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + tokens = await contractWrappers.tokenRegistry.getTokensAsync(); + tokenUtils = new TokenUtils(tokens); + zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; + fillScenarios = new FillScenarios(provider, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); + [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; + tokens = await contractWrappers.tokenRegistry.getTokensAsync(); + const [makerToken, takerToken] = tokenUtils.getDummyTokens(); + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + 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( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + expirationUnixTimestampSec, + ); + const orderHash = getOrderHashHex(signedOrder); + expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000)); + 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 * 1000); + })().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( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + expirationUnixTimestampSec, + ); + const orderHash = getOrderHashHex(signedOrder); + expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000)); + 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 * 1000); + 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( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + order1ExpirationUnixTimestampSec, + ); + const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + order2ExpirationUnixTimestampSec, + ); + const orderHash1 = getOrderHashHex(signedOrder1); + const orderHash2 = getOrderHashHex(signedOrder2); + expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000)); + expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000)); + 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 * 1000); + })().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( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + order1ExpirationUnixTimestampSec, + ); + const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, + takerTokenAddress, + makerAddress, + takerAddress, + fillableAmount, + order2ExpirationUnixTimestampSec, + ); + const orderHash1 = getOrderHashHex(signedOrder1); + const orderHash2 = getOrderHashHex(signedOrder2); + expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000)); + expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000)); + 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 * 1000); + })().catch(done); + }); +}); diff --git a/packages/order-watcher/test/global_hooks.ts b/packages/order-watcher/test/global_hooks.ts new file mode 100644 index 000000000..e3c986524 --- /dev/null +++ b/packages/order-watcher/test/global_hooks.ts @@ -0,0 +1,7 @@ +import { runMigrationsAsync } from '@0xproject/migrations'; + +import { deployer } from './utils/deployer'; + +before('migrate contracts', async () => { + await runMigrationsAsync(deployer); +}); diff --git a/packages/order-watcher/test/order_watcher_test.ts b/packages/order-watcher/test/order_watcher_test.ts new file mode 100644 index 000000000..8c9249f58 --- /dev/null +++ b/packages/order-watcher/test/order_watcher_test.ts @@ -0,0 +1,574 @@ +import { ContractWrappers } from '@0xproject/contract-wrappers'; +import { BlockchainLifecycle, callbackErrorReporter, devConstants } from '@0xproject/dev-utils'; +import { FillScenarios } from '@0xproject/fill-scenarios'; +import { getOrderHashHex } from '@0xproject/order-utils'; +import { + DoneCallback, + ExchangeContractErrs, + OrderState, + OrderStateInvalid, + OrderStateValid, + SignedOrder, + Token, +} from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import * as chai from 'chai'; +import * as _ from 'lodash'; +import 'mocha'; + +import { OrderWatcher } from '../src/order_watcher/order_watcher'; +import { OrderWatcherError } from '../src/types'; + +import { chaiSetup } from './utils/chai_setup'; +import { constants } from './utils/constants'; +import { TokenUtils } from './utils/token_utils'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; + +const TIMEOUT_MS = 150; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('OrderWatcher', () => { + let contractWrappers: ContractWrappers; + let tokens: Token[]; + let tokenUtils: TokenUtils; + let fillScenarios: FillScenarios; + let userAddresses: string[]; + let zrxTokenAddress: string; + let exchangeContractAddress: string; + let makerToken: Token; + let takerToken: Token; + let maker: string; + let taker: string; + let signedOrder: SignedOrder; + let orderWatcher: OrderWatcher; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + const decimals = constants.ZRX_DECIMALS; + const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); + before(async () => { + contractWrappers = new ContractWrappers(provider, config); + const networkId = await web3Wrapper.getNetworkIdAsync(); + orderWatcher = new OrderWatcher(provider, constants.TESTRPC_NETWORK_ID); + exchangeContractAddress = contractWrappers.exchange.getContractAddress(); + userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + [, maker, taker] = userAddresses; + tokens = await contractWrappers.tokenRegistry.getTokensAsync(); + tokenUtils = new TokenUtils(tokens); + zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; + fillScenarios = new FillScenarios(provider, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress); + await fillScenarios.initTokenBalancesAsync(); + [makerToken, takerToken] = tokenUtils.getDummyTokens(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('#removeOrder', async () => { + it('should successfully remove existing order', async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = getOrderHashHex(signedOrder); + orderWatcher.addOrder(signedOrder); + expect((orderWatcher as any)._orderByOrderHash).to.include({ + [orderHash]: signedOrder, + }); + let dependentOrderHashes = (orderWatcher as any)._dependentOrderHashes; + expect(dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress]).to.have.keys(orderHash); + orderWatcher.removeOrder(orderHash); + expect((orderWatcher as any)._orderByOrderHash).to.not.include({ + [orderHash]: signedOrder, + }); + dependentOrderHashes = (orderWatcher as any)._dependentOrderHashes; + expect(dependentOrderHashes[signedOrder.maker]).to.be.undefined(); + }); + it('should no-op when removing a non-existing order', async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = getOrderHashHex(signedOrder); + const nonExistentOrderHash = `0x${orderHash + .substr(2) + .split('') + .reverse() + .join('')}`; + orderWatcher.removeOrder(nonExistentOrderHash); + }); + }); + describe('#subscribe', async () => { + afterEach(async () => { + orderWatcher.unsubscribe(); + }); + it('should fail when trying to subscribe twice', async () => { + orderWatcher.subscribe(_.noop); + expect(() => orderWatcher.subscribe(_.noop)).to.throw(OrderWatcherError.SubscriptionAlreadyPresent); + }); + }); + describe('tests with cleanup', async () => { + afterEach(async () => { + orderWatcher.unsubscribe(); + const orderHash = getOrderHashHex(signedOrder); + orderWatcher.removeOrder(orderHash); + }); + it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = getOrderHashHex(signedOrder); + orderWatcher.addOrder(signedOrder); + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance); + }); + orderWatcher.subscribe(callback); + await contractWrappers.token.setProxyAllowanceAsync(makerToken.address, maker, new BigNumber(0)); + })().catch(done); + }); + it('should not emit an orderState event when irrelevant Transfer event received', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + orderWatcher.addOrder(signedOrder); + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + throw new Error('OrderState callback fired for irrelevant order'); + }); + orderWatcher.subscribe(callback); + const notTheMaker = userAddresses[0]; + const anyRecipient = taker; + const transferAmount = new BigNumber(2); + await contractWrappers.token.transferAsync( + makerToken.address, + notTheMaker, + anyRecipient, + transferAmount, + ); + setTimeout(() => { + done(); + }, TIMEOUT_MS); + })().catch(done); + }); + it('should emit orderStateInvalid when maker moves balance backing watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = getOrderHashHex(signedOrder); + orderWatcher.addOrder(signedOrder); + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance); + }); + orderWatcher.subscribe(callback); + const anyRecipient = taker; + const makerBalance = await contractWrappers.token.getBalanceAsync(makerToken.address, maker); + await contractWrappers.token.transferAsync(makerToken.address, maker, anyRecipient, makerBalance); + })().catch(done); + }); + it('should emit orderStateInvalid when watched order fully filled', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = getOrderHashHex(signedOrder); + orderWatcher.addOrder(signedOrder); + + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero); + }); + orderWatcher.subscribe(callback); + + const shouldThrowOnInsufficientBalanceOrAllowance = true; + await contractWrappers.exchange.fillOrderAsync( + signedOrder, + fillableAmount, + shouldThrowOnInsufficientBalanceOrAllowance, + taker, + ); + })().catch(done); + }); + it('should emit orderStateValid when watched order partially filled', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + + const makerBalance = await contractWrappers.token.getBalanceAsync(makerToken.address, maker); + const fillAmountInBaseUnits = new BigNumber(2); + const orderHash = getOrderHashHex(signedOrder); + orderWatcher.addOrder(signedOrder); + + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.true(); + const validOrderState = orderState as OrderStateValid; + expect(validOrderState.orderHash).to.be.equal(orderHash); + const orderRelevantState = validOrderState.orderRelevantState; + const remainingMakerBalance = makerBalance.sub(fillAmountInBaseUnits); + const remainingFillable = fillableAmount.minus(fillAmountInBaseUnits); + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + remainingFillable, + ); + expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + remainingFillable, + ); + expect(orderRelevantState.makerBalance).to.be.bignumber.equal(remainingMakerBalance); + }); + orderWatcher.subscribe(callback); + const shouldThrowOnInsufficientBalanceOrAllowance = true; + await contractWrappers.exchange.fillOrderAsync( + signedOrder, + fillAmountInBaseUnits, + shouldThrowOnInsufficientBalanceOrAllowance, + taker, + ); + })().catch(done); + }); + it('should trigger the callback when orders backing ZRX allowance changes', (done: DoneCallback) => { + (async () => { + const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18); + const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18); + signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerToken.address, + takerToken.address, + makerFee, + takerFee, + maker, + taker, + fillableAmount, + taker, + ); + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)(); + orderWatcher.addOrder(signedOrder); + orderWatcher.subscribe(callback); + await contractWrappers.token.setProxyAllowanceAsync(zrxTokenAddress, maker, new BigNumber(0)); + })().catch(done); + }); + describe('remainingFillable(M|T)akerTokenAmount', () => { + it('should calculate correct remaining fillable', (done: DoneCallback) => { + (async () => { + const takerFillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10), decimals); + const makerFillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(20), decimals); + signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + makerFillableAmount, + takerFillableAmount, + ); + const fillAmountInBaseUnits = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); + const orderHash = getOrderHashHex(signedOrder); + orderWatcher.addOrder(signedOrder); + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.true(); + const validOrderState = orderState as OrderStateValid; + expect(validOrderState.orderHash).to.be.equal(orderHash); + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + Web3Wrapper.toBaseUnitAmount(new BigNumber(16), decimals), + ); + expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + Web3Wrapper.toBaseUnitAmount(new BigNumber(8), decimals), + ); + }); + orderWatcher.subscribe(callback); + const shouldThrowOnInsufficientBalanceOrAllowance = true; + await contractWrappers.exchange.fillOrderAsync( + signedOrder, + fillAmountInBaseUnits, + shouldThrowOnInsufficientBalanceOrAllowance, + taker, + ); + })().catch(done); + }); + it('should equal approved amount when approved amount is lowest', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + + const changedMakerApprovalAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(3), decimals); + orderWatcher.addOrder(signedOrder); + + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + const validOrderState = orderState as OrderStateValid; + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + changedMakerApprovalAmount, + ); + expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + changedMakerApprovalAmount, + ); + }); + orderWatcher.subscribe(callback); + await contractWrappers.token.setProxyAllowanceAsync( + makerToken.address, + maker, + changedMakerApprovalAmount, + ); + })().catch(done); + }); + it('should equal balance amount when balance amount is lowest', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + + const makerBalance = await contractWrappers.token.getBalanceAsync(makerToken.address, maker); + + const remainingAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), decimals); + const transferAmount = makerBalance.sub(remainingAmount); + orderWatcher.addOrder(signedOrder); + + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.true(); + const validOrderState = orderState as OrderStateValid; + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + remainingAmount, + ); + expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + remainingAmount, + ); + }); + orderWatcher.subscribe(callback); + await contractWrappers.token.transferAsync( + makerToken.address, + maker, + constants.NULL_ADDRESS, + transferAmount, + ); + })().catch(done); + }); + it('should equal remaining amount when partially cancelled and order has fees', (done: DoneCallback) => { + (async () => { + const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals); + const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); + const feeRecipient = taker; + signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerToken.address, + takerToken.address, + makerFee, + takerFee, + maker, + taker, + fillableAmount, + feeRecipient, + ); + + const remainingTokenAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(4), decimals); + const transferTokenAmount = makerFee.sub(remainingTokenAmount); + orderWatcher.addOrder(signedOrder); + + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.true(); + const validOrderState = orderState as OrderStateValid; + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + remainingTokenAmount, + ); + }); + orderWatcher.subscribe(callback); + await contractWrappers.exchange.cancelOrderAsync(signedOrder, transferTokenAmount); + })().catch(done); + }); + it('should equal ratio amount when fee balance is lowered', (done: DoneCallback) => { + (async () => { + const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals); + const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); + const feeRecipient = taker; + signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerToken.address, + takerToken.address, + makerFee, + takerFee, + maker, + taker, + fillableAmount, + feeRecipient, + ); + + const remainingFeeAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(3), decimals); + + const remainingTokenAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(4), decimals); + const transferTokenAmount = makerFee.sub(remainingTokenAmount); + orderWatcher.addOrder(signedOrder); + + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + const validOrderState = orderState as OrderStateValid; + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + remainingFeeAmount, + ); + }); + orderWatcher.subscribe(callback); + await contractWrappers.token.setProxyAllowanceAsync(zrxTokenAddress, maker, remainingFeeAmount); + await contractWrappers.token.transferAsync( + makerToken.address, + maker, + constants.NULL_ADDRESS, + transferTokenAmount, + ); + })().catch(done); + }); + it('should calculate full amount when all available and non-divisible', (done: DoneCallback) => { + (async () => { + const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals); + const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); + const feeRecipient = taker; + signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerToken.address, + takerToken.address, + makerFee, + takerFee, + maker, + taker, + fillableAmount, + feeRecipient, + ); + + orderWatcher.addOrder(signedOrder); + + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + const validOrderState = orderState as OrderStateValid; + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + fillableAmount, + ); + }); + orderWatcher.subscribe(callback); + await contractWrappers.token.setProxyAllowanceAsync( + makerToken.address, + maker, + Web3Wrapper.toBaseUnitAmount(new BigNumber(100), decimals), + ); + })().catch(done); + }); + }); + it('should emit orderStateInvalid when watched order cancelled', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = getOrderHashHex(signedOrder); + orderWatcher.addOrder(signedOrder); + + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero); + }); + orderWatcher.subscribe(callback); + + await contractWrappers.exchange.cancelOrderAsync(signedOrder, fillableAmount); + })().catch(done); + }); + it('should emit orderStateInvalid when within rounding error range', (done: DoneCallback) => { + (async () => { + const remainingFillableAmountInBaseUnits = new BigNumber(100); + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + const orderHash = getOrderHashHex(signedOrder); + orderWatcher.addOrder(signedOrder); + + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.false(); + const invalidOrderState = orderState as OrderStateInvalid; + expect(invalidOrderState.orderHash).to.be.equal(orderHash); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError); + }); + orderWatcher.subscribe(callback); + await contractWrappers.exchange.cancelOrderAsync( + signedOrder, + fillableAmount.minus(remainingFillableAmountInBaseUnits), + ); + })().catch(done); + }); + it('should emit orderStateValid when watched order partially cancelled', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerToken.address, + takerToken.address, + maker, + taker, + fillableAmount, + ); + + const cancelAmountInBaseUnits = new BigNumber(2); + const orderHash = getOrderHashHex(signedOrder); + orderWatcher.addOrder(signedOrder); + + const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { + expect(orderState.isValid).to.be.true(); + const validOrderState = orderState as OrderStateValid; + expect(validOrderState.orderHash).to.be.equal(orderHash); + const orderRelevantState = validOrderState.orderRelevantState; + expect(orderRelevantState.cancelledTakerTokenAmount).to.be.bignumber.equal(cancelAmountInBaseUnits); + }); + orderWatcher.subscribe(callback); + await contractWrappers.exchange.cancelOrderAsync(signedOrder, cancelAmountInBaseUnits); + })().catch(done); + }); + }); +}); // tslint:disable:max-file-line-count diff --git a/packages/order-watcher/test/remaining_fillable_calculator_test.ts b/packages/order-watcher/test/remaining_fillable_calculator_test.ts new file mode 100644 index 000000000..7ec3f1ebc --- /dev/null +++ b/packages/order-watcher/test/remaining_fillable_calculator_test.ts @@ -0,0 +1,234 @@ +import { ECSignature, SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import * as chai from 'chai'; +import 'mocha'; + +import { RemainingFillableCalculator } from '@0xproject/order-utils'; + +import { chaiSetup } from './utils/chai_setup'; + +chaiSetup.configure(); +const expect = chai.expect; + +describe('RemainingFillableCalculator', () => { + let calculator: RemainingFillableCalculator; + let signedOrder: SignedOrder; + let transferrableMakerTokenAmount: BigNumber; + let transferrableMakerFeeTokenAmount: BigNumber; + let remainingMakerTokenAmount: BigNumber; + let makerAmount: BigNumber; + let takerAmount: BigNumber; + let makerFeeAmount: BigNumber; + let isMakerTokenZRX: boolean; + const makerToken: string = '0x1'; + const takerToken: string = '0x2'; + const decimals: number = 4; + const zero: BigNumber = new BigNumber(0); + const zeroAddress = '0x0'; + const signature: ECSignature = { v: 27, r: '', s: '' }; + beforeEach(async () => { + [makerAmount, takerAmount, makerFeeAmount] = [ + Web3Wrapper.toBaseUnitAmount(new BigNumber(50), decimals), + Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals), + Web3Wrapper.toBaseUnitAmount(new BigNumber(1), decimals), + ]; + [transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount] = [ + Web3Wrapper.toBaseUnitAmount(new BigNumber(50), decimals), + Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals), + ]; + }); + function buildSignedOrder(): SignedOrder { + return { + ecSignature: signature, + exchangeContractAddress: zeroAddress, + feeRecipient: zeroAddress, + maker: zeroAddress, + taker: zeroAddress, + makerFee: makerFeeAmount, + takerFee: zero, + makerTokenAmount: makerAmount, + takerTokenAmount: takerAmount, + makerTokenAddress: makerToken, + takerTokenAddress: takerToken, + salt: zero, + expirationUnixTimestampSec: zero, + }; + } + describe('Maker token is NOT ZRX', () => { + before(async () => { + isMakerTokenZRX = false; + }); + it('calculates the correct amount when unfilled and funds available', () => { + signedOrder = buildSignedOrder(); + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); + }); + it('calculates the correct amount when partially filled and funds available', () => { + signedOrder = buildSignedOrder(); + remainingMakerTokenAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), decimals); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); + }); + it('calculates the amount to be 0 when all fee funds are transferred', () => { + signedOrder = buildSignedOrder(); + transferrableMakerFeeTokenAmount = zero; + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero); + }); + it('calculates the correct amount when balance is less than remaining fillable', () => { + signedOrder = buildSignedOrder(); + const partiallyFilledAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); + remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount); + transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount); + }); + describe('Order to Fee Ratio is < 1', () => { + beforeEach(async () => { + [makerAmount, takerAmount, makerFeeAmount] = [ + Web3Wrapper.toBaseUnitAmount(new BigNumber(3), decimals), + Web3Wrapper.toBaseUnitAmount(new BigNumber(6), decimals), + Web3Wrapper.toBaseUnitAmount(new BigNumber(6), decimals), + ]; + }); + it('calculates the correct amount when funds unavailable', () => { + signedOrder = buildSignedOrder(); + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + const transferredAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); + transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount); + }); + }); + describe('Ratio is not evenly divisble', () => { + beforeEach(async () => { + [makerAmount, takerAmount, makerFeeAmount] = [ + Web3Wrapper.toBaseUnitAmount(new BigNumber(3), decimals), + Web3Wrapper.toBaseUnitAmount(new BigNumber(7), decimals), + Web3Wrapper.toBaseUnitAmount(new BigNumber(7), decimals), + ]; + }); + it('calculates the correct amount when funds unavailable', () => { + signedOrder = buildSignedOrder(); + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + const transferredAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); + transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + const calculatedFillableAmount = calculator.computeRemainingMakerFillable(); + expect(calculatedFillableAmount.lessThanOrEqualTo(transferrableMakerTokenAmount)).to.be.true(); + expect(calculatedFillableAmount).to.be.bignumber.greaterThan(new BigNumber(0)); + const orderToFeeRatio = signedOrder.makerTokenAmount.dividedBy(signedOrder.makerFee); + const calculatedFeeAmount = calculatedFillableAmount.dividedBy(orderToFeeRatio); + expect(calculatedFeeAmount).to.be.bignumber.lessThan(transferrableMakerFeeTokenAmount); + }); + }); + }); + describe('Maker Token is ZRX', () => { + before(async () => { + isMakerTokenZRX = true; + }); + it('calculates the correct amount when unfilled and funds available', () => { + signedOrder = buildSignedOrder(); + transferrableMakerTokenAmount = makerAmount.plus(makerFeeAmount); + transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount; + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); + }); + it('calculates the correct amount when partially filled and funds available', () => { + signedOrder = buildSignedOrder(); + remainingMakerTokenAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), decimals); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount); + }); + it('calculates the amount to be 0 when all fee funds are transferred', () => { + signedOrder = buildSignedOrder(); + transferrableMakerTokenAmount = zero; + transferrableMakerFeeTokenAmount = zero; + remainingMakerTokenAmount = signedOrder.makerTokenAmount; + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero); + }); + it('calculates the correct amount when balance is less than remaining fillable', () => { + signedOrder = buildSignedOrder(); + const partiallyFilledAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); + remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount); + transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount); + transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount; + + const orderToFeeRatio = signedOrder.makerTokenAmount.dividedToIntegerBy(signedOrder.makerFee); + const expectedFillableAmount = new BigNumber(450980); + calculator = new RemainingFillableCalculator( + signedOrder, + isMakerTokenZRX, + transferrableMakerTokenAmount, + transferrableMakerFeeTokenAmount, + remainingMakerTokenAmount, + ); + const calculatedFillableAmount = calculator.computeRemainingMakerFillable(); + const numberOfFillsInRatio = calculatedFillableAmount.dividedToIntegerBy(orderToFeeRatio); + const calculatedFillableAmountPlusFees = calculatedFillableAmount.plus(numberOfFillsInRatio); + expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(transferrableMakerTokenAmount); + expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(remainingMakerTokenAmount); + expect(calculatedFillableAmount).to.be.bignumber.equal(expectedFillableAmount); + expect(numberOfFillsInRatio.decimalPlaces()).to.be.equal(0); + }); + }); +}); diff --git a/packages/order-watcher/test/utils/chai_setup.ts b/packages/order-watcher/test/utils/chai_setup.ts new file mode 100644 index 000000000..078edd309 --- /dev/null +++ b/packages/order-watcher/test/utils/chai_setup.ts @@ -0,0 +1,13 @@ +import * as chai from 'chai'; +import chaiAsPromised = require('chai-as-promised'); +import ChaiBigNumber = require('chai-bignumber'); +import * as dirtyChai from 'dirty-chai'; + +export const chaiSetup = { + configure() { + chai.config.includeStack = true; + chai.use(ChaiBigNumber()); + chai.use(dirtyChai); + chai.use(chaiAsPromised); + }, +}; diff --git a/packages/order-watcher/test/utils/constants.ts b/packages/order-watcher/test/utils/constants.ts new file mode 100644 index 000000000..78037647c --- /dev/null +++ b/packages/order-watcher/test/utils/constants.ts @@ -0,0 +1,5 @@ +export const constants = { + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', + TESTRPC_NETWORK_ID: 50, + ZRX_DECIMALS: 18, +}; diff --git a/packages/order-watcher/test/utils/deployer.ts b/packages/order-watcher/test/utils/deployer.ts new file mode 100644 index 000000000..b092322e2 --- /dev/null +++ b/packages/order-watcher/test/utils/deployer.ts @@ -0,0 +1,18 @@ +import { Deployer } from '@0xproject/deployer'; +import { devConstants } from '@0xproject/dev-utils'; +import * as path from 'path'; + +import { constants } from './constants'; + +import { provider } from './web3_wrapper'; + +const artifactsDir = path.resolve('test', 'artifacts'); +const deployerOpts = { + artifactsDir, + provider, + networkId: constants.TESTRPC_NETWORK_ID, + defaults: { + gas: devConstants.GAS_ESTIMATE, + }, +}; +export const deployer = new Deployer(deployerOpts); diff --git a/packages/order-watcher/test/utils/token_utils.ts b/packages/order-watcher/test/utils/token_utils.ts new file mode 100644 index 000000000..e1191b5bb --- /dev/null +++ b/packages/order-watcher/test/utils/token_utils.ts @@ -0,0 +1,34 @@ +import { Token } from '@0xproject/types'; +import * as _ from 'lodash'; + +import { InternalOrderWatcherError } from '../../src/types'; + +const PROTOCOL_TOKEN_SYMBOL = 'ZRX'; +const WETH_TOKEN_SYMBOL = 'WETH'; + +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(InternalOrderWatcherError.ZrxNotInTokenRegistry); + } + return zrxToken; + } + public getWethTokenOrThrow(): Token { + const wethToken = _.find(this._tokens, { symbol: WETH_TOKEN_SYMBOL }); + if (_.isUndefined(wethToken)) { + throw new Error(InternalOrderWatcherError.WethNotInTokenRegistry); + } + return wethToken; + } + public getDummyTokens(): Token[] { + const dummyTokens = _.filter(this._tokens, token => { + return !_.includes([PROTOCOL_TOKEN_SYMBOL, WETH_TOKEN_SYMBOL], token.symbol); + }); + return dummyTokens; + } +} diff --git a/packages/order-watcher/test/utils/web3_wrapper.ts b/packages/order-watcher/test/utils/web3_wrapper.ts new file mode 100644 index 000000000..b0ccfa546 --- /dev/null +++ b/packages/order-watcher/test/utils/web3_wrapper.ts @@ -0,0 +1,9 @@ +import { devConstants, web3Factory } from '@0xproject/dev-utils'; +import { Provider } from '@0xproject/types'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; + +const web3 = web3Factory.create({ shouldUseInProcessGanache: true }); +const provider: Provider = web3.currentProvider; +const web3Wrapper = new Web3Wrapper(web3.currentProvider); + +export { provider, web3Wrapper }; |