diff options
-rw-r--r-- | src/0x.js.ts | 23 | ||||
-rw-r--r-- | src/artifacts/EtherToken.json | 2 | ||||
-rw-r--r-- | src/artifacts/Mintable.json | 2 | ||||
-rw-r--r-- | src/artifacts/Proxy.json | 2 | ||||
-rw-r--r-- | src/artifacts/Token.json | 2 | ||||
-rw-r--r-- | src/artifacts/TokenRegistry.json | 2 | ||||
-rw-r--r-- | src/contract_wrappers/exchange_wrapper.ts | 85 | ||||
-rw-r--r-- | src/contract_wrappers/token_wrapper.ts | 4 | ||||
-rw-r--r-- | src/types.ts | 54 | ||||
-rw-r--r-- | src/utils/assert.ts | 4 | ||||
-rw-r--r-- | src/utils/utils.ts | 7 | ||||
-rw-r--r-- | test/0x.js_test.ts | 6 | ||||
-rw-r--r-- | test/exchange_wrapper_test.ts | 166 | ||||
-rw-r--r-- | test/utils/fill_scenarios.ts | 22 |
14 files changed, 354 insertions, 27 deletions
diff --git a/src/0x.js.ts b/src/0x.js.ts index 7cf313666..850827fee 100644 --- a/src/0x.js.ts +++ b/src/0x.js.ts @@ -17,7 +17,7 @@ import {TokenRegistryWrapper} from './contract_wrappers/token_registry_wrapper'; import {ecSignatureSchema} from './schemas/ec_signature_schema'; import {TokenWrapper} from './contract_wrappers/token_wrapper'; import {SolidityTypes, ECSignature, ZeroExError} from './types'; -import {Order} from './types'; +import {Order, SignedOrder} from './types'; import {orderSchema} from './schemas/order_schemas'; import * as ExchangeArtifacts from './artifacts/Exchange.json'; @@ -69,11 +69,16 @@ export class ZeroEx { const salt = randomNumber.times(factor).round(); return salt; } - /** Checks if order hash is valid */ - public static isValidOrderHash(orderHash: string): boolean { - assert.isString('orderHash', orderHash); - const isValid = /^0x[0-9A-F]{64}$/i.test(orderHash); - return isValid; + /** + * Checks if the supplied hex encoded order hash is valid. + * Note: Valid means it has the expected format, not that an order with the orderHash exists. + */ + public static isValidOrderHash(orderHashHex: string): boolean { + // Since this method can be called to check if any arbitrary string conforms to an orderHash's + // format, we only assert that we were indeed passed a string. + assert.isString('orderHashHex', orderHashHex); + const isValidOrderHash = utils.isValidOrderHash(orderHashHex); + return isValidOrderHash; } /** * A unit amount is defined as the amount of a token above the specified decimal places (integer part). @@ -110,9 +115,9 @@ export class ZeroEx { /** * Sets a new provider for the web3 instance used by 0x.js */ - public setProvider(provider: Web3.Provider) { + public async setProviderAsync(provider: Web3.Provider) { this.web3Wrapper.setProvider(provider); - this.exchange.invalidateContractInstance(); + await this.exchange.invalidateContractInstanceAsync(); this.tokenRegistry.invalidateContractInstance(); this.token.invalidateContractInstances(); } @@ -132,7 +137,7 @@ export class ZeroEx { /** * Computes the orderHash for a given order and returns it as a hex encoded string. */ - public async getOrderHashHexAsync(order: Order): Promise<string> { + public async getOrderHashHexAsync(order: Order|SignedOrder): Promise<string> { const exchangeContractAddr = await this.getExchangeAddressAsync(); assert.doesConformToSchema('order', SchemaValidator.convertToJSONSchemaCompatibleObject(order as object), diff --git a/src/artifacts/EtherToken.json b/src/artifacts/EtherToken.json index 0593cdc95..ae79d6bf5 100644 --- a/src/artifacts/EtherToken.json +++ b/src/artifacts/EtherToken.json @@ -336,4 +336,4 @@ }, "schema_version": "0.0.5", "updated_at": 1495042008609 -}
\ No newline at end of file +} diff --git a/src/artifacts/Mintable.json b/src/artifacts/Mintable.json index 026f294f2..b508eaee7 100644 --- a/src/artifacts/Mintable.json +++ b/src/artifacts/Mintable.json @@ -186,4 +186,4 @@ "networks": {}, "schema_version": "0.0.5", "updated_at": 1495030728122 -}
\ No newline at end of file +} diff --git a/src/artifacts/Proxy.json b/src/artifacts/Proxy.json index 0d6faef35..d02804096 100644 --- a/src/artifacts/Proxy.json +++ b/src/artifacts/Proxy.json @@ -264,4 +264,4 @@ }, "schema_version": "0.0.5", "updated_at": 1495042008598 -}
\ No newline at end of file +} diff --git a/src/artifacts/Token.json b/src/artifacts/Token.json index 3bbcead41..4843cc2ca 100644 --- a/src/artifacts/Token.json +++ b/src/artifacts/Token.json @@ -173,4 +173,4 @@ "networks": {}, "schema_version": "0.0.5", "updated_at": 1495030728125 -}
\ No newline at end of file +} diff --git a/src/artifacts/TokenRegistry.json b/src/artifacts/TokenRegistry.json index f9f4fafbc..6ecdfa079 100644 --- a/src/artifacts/TokenRegistry.json +++ b/src/artifacts/TokenRegistry.json @@ -999,4 +999,4 @@ }, "schema_version": "0.0.5", "updated_at": 1495042008599 -}
\ No newline at end of file +} diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts index 4aa532bdd..b67fd33ac 100644 --- a/src/contract_wrappers/exchange_wrapper.ts +++ b/src/contract_wrappers/exchange_wrapper.ts @@ -1,4 +1,6 @@ import * as _ from 'lodash'; +import * as BigNumber from 'bignumber.js'; +import promisify = require('es6-promisify'); import {Web3Wrapper} from '../web3_wrapper'; import { ECSignature, @@ -9,9 +11,17 @@ import { OrderAddresses, SignedOrder, ContractEvent, + ZeroExError, + ExchangeEvents, + SubscriptionOpts, + IndexFilterValues, + CreateContractEvent, + ContractEventObj, + EventCallback, ContractResponse, } from '../types'; import {assert} from '../utils/assert'; +import {utils} from '../utils/utils'; import {ContractWrapper} from './contract_wrapper'; import * as ExchangeArtifacts from '../artifacts/Exchange.json'; import {ecSignatureSchema} from '../schemas/ec_signature_schema'; @@ -30,12 +40,15 @@ export class ExchangeWrapper extends ContractWrapper { [ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.FILL_BALANCE_ALLOWANCE_ERROR, }; private exchangeContractIfExists?: ExchangeContract; + private exchangeLogEventObjs: ContractEventObj[]; private tokenWrapper: TokenWrapper; constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper) { super(web3Wrapper); this.tokenWrapper = tokenWrapper; + this.exchangeLogEventObjs = []; } - public invalidateContractInstance(): void { + public async invalidateContractInstanceAsync(): Promise<void> { + await this.stopWatchingExchangeLogEventsAsync(); delete this.exchangeContractIfExists; } public async isValidSignatureAsync(dataHex: string, ecSignature: ECSignature, @@ -60,6 +73,44 @@ export class ExchangeWrapper extends ContractWrapper { return isValidSignature; } /** + * Returns the unavailable takerAmount of an order. Unavailable amount is defined as the total + * amount that has been filled or cancelled. The remaining takerAmount can be calculated by + * subtracting the unavailable amount from the total order takerAmount. + */ + public async getUnavailableTakerAmountAsync(orderHashHex: string): Promise<BigNumber.BigNumber> { + assert.isValidOrderHash('orderHashHex', orderHashHex); + + const exchangeContract = await this.getExchangeContractAsync(); + let unavailableAmountInBaseUnits = await exchangeContract.getUnavailableValueT.call(orderHashHex); + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber + unavailableAmountInBaseUnits = new BigNumber(unavailableAmountInBaseUnits); + return unavailableAmountInBaseUnits; + } + /** + * Retrieve the takerAmount of an order that has already been filled. + */ + public async getFilledTakerAmountAsync(orderHashHex: string): Promise<BigNumber.BigNumber> { + assert.isValidOrderHash('orderHashHex', orderHashHex); + + const exchangeContract = await this.getExchangeContractAsync(); + let fillAmountInBaseUnits = await exchangeContract.filled.call(orderHashHex); + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber + fillAmountInBaseUnits = new BigNumber(fillAmountInBaseUnits); + return fillAmountInBaseUnits; + } + /** + * Retrieve the takerAmount of an order that has been cancelled. + */ + public async getCanceledTakerAmountAsync(orderHashHex: string): Promise<BigNumber.BigNumber> { + assert.isValidOrderHash('orderHashHex', orderHashHex); + + const exchangeContract = await this.getExchangeContractAsync(); + let cancelledAmountInBaseUnits = await exchangeContract.cancelled.call(orderHashHex); + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber + cancelledAmountInBaseUnits = new BigNumber(cancelledAmountInBaseUnits); + return cancelledAmountInBaseUnits; + } + /** * Fills a signed order with a fillAmount denominated in baseUnits of the taker token. * Since the order in which transactions are included in the next block is indeterminate, race-conditions * could arise where a users balance or allowance changes before the fillOrder executes. Because of this, @@ -121,6 +172,38 @@ export class ExchangeWrapper extends ContractWrapper { ); this.throwErrorLogsAsErrors(response.logs); } + /** + * Subscribe to an event type emitted by the Exchange smart contract + */ + public async subscribeAsync(eventName: ExchangeEvents, subscriptionOpts: SubscriptionOpts, + indexFilterValues: IndexFilterValues, callback: EventCallback) { + const exchangeContract = await this.getExchangeContractAsync(); + let createLogEvent: CreateContractEvent; + switch (eventName) { + case ExchangeEvents.LogFill: + createLogEvent = exchangeContract.LogFill; + break; + case ExchangeEvents.LogError: + createLogEvent = exchangeContract.LogError; + break; + case ExchangeEvents.LogCancel: + createLogEvent = exchangeContract.LogCancel; + break; + default: + utils.spawnSwitchErr('ExchangeEvents', eventName); + return; + } + + const logEventObj: ContractEventObj = createLogEvent(indexFilterValues, subscriptionOpts); + logEventObj.watch(callback); + this.exchangeLogEventObjs.push(logEventObj); + } + private async stopWatchingExchangeLogEventsAsync() { + for (const logEventObj of this.exchangeLogEventObjs) { + await promisify(logEventObj.stopWatching, logEventObj)(); + } + this.exchangeLogEventObjs = []; + } private async validateFillOrderAndThrowIfInvalidAsync(signedOrder: SignedOrder, fillTakerAmount: BigNumber.BigNumber, senderAddress: string): Promise<void> { diff --git a/src/contract_wrappers/token_wrapper.ts b/src/contract_wrappers/token_wrapper.ts index cedbfbdae..69bcc9024 100644 --- a/src/contract_wrappers/token_wrapper.ts +++ b/src/contract_wrappers/token_wrapper.ts @@ -28,8 +28,7 @@ export class TokenWrapper extends ContractWrapper { const tokenContract = await this.getTokenContractAsync(tokenAddress); let balance = await tokenContract.balanceOf.call(ownerAddress); - // The BigNumber instance returned by Web3 is of a much older version then our own, we therefore - // should always re-instantiate the returned BigNumber after retrieval. + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber balance = new BigNumber(balance); return balance; } @@ -44,6 +43,7 @@ export class TokenWrapper extends ContractWrapper { const tokenContract = await this.getTokenContractAsync(tokenAddress); const proxyAddress = await this.getProxyAddressAsync(); let allowanceInBaseUnits = await tokenContract.allowance.call(ownerAddress, proxyAddress); + // Wrap BigNumbers returned from web3 with our own (later) version of BigNumber allowanceInBaseUnits = new BigNumber(allowanceInBaseUnits); return allowanceInBaseUnits; } diff --git a/src/types.ts b/src/types.ts index 3da24abc1..b5430a783 100644 --- a/src/types.ts +++ b/src/types.ts @@ -35,6 +35,9 @@ export type OrderValues = [BigNumber.BigNumber, BigNumber.BigNumber, BigNumber.B export interface ExchangeContract { isValidSignature: any; + getUnavailableValueT: { + call: (orderHash: string) => BigNumber.BigNumber; + }; isRoundingError: { call: (takerTokenAmount: BigNumber.BigNumber, fillTakerAmount: BigNumber.BigNumber, makerTokenAmount: BigNumber.BigNumber, txOpts: TxOpts) => Promise<boolean>; @@ -45,6 +48,28 @@ export interface ExchangeContract { estimateGas: (orderAddresses: OrderAddresses, orderValues: OrderValues, fillAmount: BigNumber.BigNumber, shouldCheckTransfer: boolean, v: number, r: string, s: string, txOpts: TxOpts) => number; }; + filled: { + call: (orderHash: string) => BigNumber.BigNumber; + }; + cancelled: { + call: (orderHash: string) => BigNumber.BigNumber; + }; +} + +export type EventCallbackAsync = (err: Error, event: ContractEvent) => Promise<void>; +export type EventCallbackSync = (err: Error, event: ContractEvent) => void; +export type EventCallback = EventCallbackSync|EventCallbackAsync; +export interface ContractEventObj { + watch: (eventWatch: EventCallback) => void; + stopWatching: () => void; +} +export type CreateContractEvent = (indexFilterValues: IndexFilterValues, + subscriptionOpts: SubscriptionOpts) => ContractEventObj; +export interface ExchangeContract { + isValidSignature: any; + LogFill: CreateContractEvent; + LogCancel: CreateContractEvent; + LogError: CreateContractEvent; ZRX: { call: () => Promise<string>; }; @@ -108,6 +133,13 @@ export interface ContractResponse { } export interface ContractEvent { + logIndex: number; + transactionIndex: number; + transactionHash: string; + blockHash: string; + blockNumber: number; + address: string; + type: string; event: string; args: any; } @@ -145,3 +177,25 @@ export interface TxOpts { from: string; gas?: number; } + +export interface TokenAddressBySymbol { + [symbol: string]: string; +} + +export const ExchangeEvents = strEnum([ + 'LogFill', + 'LogCancel', + 'LogError', +]); +export type ExchangeEvents = keyof typeof ExchangeEvents; + +export interface IndexFilterValues { + [index: string]: any; +} + +export interface SubscriptionOpts { + fromBlock: string|number; + toBlock: string|number; +} + +export type DoneCallback = (err?: Error) => void; diff --git a/src/utils/assert.ts b/src/utils/assert.ts index aeed1c6dc..406f2b149 100644 --- a/src/utils/assert.ts +++ b/src/utils/assert.ts @@ -2,6 +2,7 @@ import * as _ from 'lodash'; import * as BigNumber from 'bignumber.js'; import * as Web3 from 'web3'; import {SchemaValidator} from './schema_validator'; +import {utils} from './utils'; const HEX_REGEX = /^0x[0-9A-F]*$/i; @@ -27,6 +28,9 @@ export const assert = { isNumber(variableName: string, value: number): void { this.assert(_.isFinite(value), this.typeAssertionMessage(variableName, 'number', value)); }, + isValidOrderHash(variableName: string, value: string): void { + this.assert(utils.isValidOrderHash(value), this.typeAssertionMessage(variableName, 'orderHash', value)); + }, isBoolean(variableName: string, value: boolean): void { this.assert(_.isBoolean(value), this.typeAssertionMessage(variableName, 'boolean', value)); }, diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 336eaf7bb..114b46f6c 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -18,4 +18,11 @@ export const utils = { isParityNode(nodeVersion: string): boolean { return _.includes(nodeVersion, 'Parity'); }, + isValidOrderHash(orderHashHex: string) { + const isValid = /^0x[0-9A-F]{64}$/i.test(orderHashHex); + return isValid; + }, + spawnSwitchErr(name: string, value: any) { + return new Error(`Unexpected switch value: ${value} encountered for ${name}`); + }, }; diff --git a/test/0x.js_test.ts b/test/0x.js_test.ts index 9196d3df3..8686b42eb 100644 --- a/test/0x.js_test.ts +++ b/test/0x.js_test.ts @@ -8,7 +8,7 @@ import * as Sinon from 'sinon'; import {ZeroEx} from '../src/0x.js'; import {constants} from './utils/constants'; import {web3Factory} from './utils/web3_factory'; -import {Order} from '../src/types'; +import {Order, DoneCallback} from '../src/types'; chai.config.includeStack = true; chai.use(ChaiBigNumber()); @@ -17,7 +17,7 @@ const expect = chai.expect; describe('ZeroEx library', () => { describe('#setProvider', () => { - it('overrides the provider in the nested web3 instance and invalidates contractInstances', async () => { + it('overrides provider in nested web3s and invalidates contractInstances', async () => { const web3 = web3Factory.create(); const zeroEx = new ZeroEx(web3); // Instantiate the contract instances with the current provider @@ -29,7 +29,7 @@ describe('ZeroEx library', () => { const newProvider = web3Factory.getRpcProvider(); // Add property to newProvider so that we can differentiate it from old provider (newProvider as any).zeroExTestId = 1; - zeroEx.setProvider(newProvider); + await zeroEx.setProviderAsync(newProvider); // Check that contractInstances with old provider are removed after provider update expect((zeroEx.exchange as any).exchangeContractIfExists).to.be.undefined(); diff --git a/test/exchange_wrapper_test.ts b/test/exchange_wrapper_test.ts index 607cdf052..ab6f2df67 100644 --- a/test/exchange_wrapper_test.ts +++ b/test/exchange_wrapper_test.ts @@ -9,7 +9,15 @@ import promisify = require('es6-promisify'); import {web3Factory} from './utils/web3_factory'; import {ZeroEx} from '../src/0x.js'; import {BlockchainLifecycle} from './utils/blockchain_lifecycle'; -import {ExchangeContractErrs, SignedOrder, Token} from '../src/types'; +import { + Token, + SignedOrder, + SubscriptionOpts, + ExchangeEvents, + ContractEvent, + DoneCallback, + ExchangeContractErrs, +} from '../src/types'; import {FillScenarios} from './utils/fill_scenarios'; import {TokenUtils} from './utils/token_utils'; @@ -19,14 +27,24 @@ chai.use(ChaiBigNumber()); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(); +const NON_EXISTENT_ORDER_HASH = '0x79370342234e7acd6bbeac335bd3bb1d368383294b64b8160a00f4060e4d3777'; + describe('ExchangeWrapper', () => { let web3: Web3; let zeroEx: ZeroEx; let userAddresses: string[]; + let tokenUtils: TokenUtils; + let tokens: Token[]; + let fillScenarios: FillScenarios; + let zrxTokenAddress: string; before(async () => { web3 = web3Factory.create(); zeroEx = new ZeroEx(web3); userAddresses = await promisify(web3.eth.getAccounts)(); + tokens = await zeroEx.tokenRegistry.getTokensAsync(); + tokenUtils = new TokenUtils(tokens); + zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; + fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -105,26 +123,20 @@ describe('ExchangeWrapper', () => { }); }); describe('#fillOrderAsync', () => { - let tokens: Token[]; let makerTokenAddress: string; let takerTokenAddress: string; - let fillScenarios: FillScenarios; let coinbase: string; let makerAddress: string; let takerAddress: string; let feeRecipient: string; - let zrxTokenAddress: string; const fillTakerAmount = new BigNumber(5); const shouldCheckTransfer = false; before(async () => { [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; tokens = await zeroEx.tokenRegistry.getTokensAsync(); - const tokenUtils = new TokenUtils(tokens); const [makerToken, takerToken] = tokenUtils.getNonProtocolTokens(); makerTokenAddress = makerToken.address; takerTokenAddress = takerToken.address; - zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address; - fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress); }); afterEach('reset default account', () => { zeroEx.setTransactionSenderAccount(userAddresses[0]); @@ -326,4 +338,144 @@ describe('ExchangeWrapper', () => { }); }); }); + describe('tests that require partially filled order', () => { + let makerTokenAddress: string; + let takerTokenAddress: string; + let takerAddress: string; + let fillableAmount: BigNumber.BigNumber; + let partialFillAmount: BigNumber.BigNumber; + let signedOrder: SignedOrder; + before(() => { + takerAddress = userAddresses[1]; + const [makerToken, takerToken] = tokens; + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + beforeEach(async () => { + fillableAmount = new BigNumber(5); + partialFillAmount = new BigNumber(2); + signedOrder = await fillScenarios.createPartiallyFilledSignedOrderAsync( + makerTokenAddress, takerTokenAddress, takerAddress, fillableAmount, partialFillAmount, + ); + }); + describe('#getUnavailableTakerAmountAsync', () => { + it ('should throw if passed an invalid orderHash', async () => { + const invalidOrderHashHex = '0x123'; + expect(zeroEx.exchange.getUnavailableTakerAmountAsync(invalidOrderHashHex)).to.be.rejected(); + }); + it ('should return zero if passed a valid but non-existent orderHash', async () => { + const unavailableValueT = await zeroEx.exchange.getUnavailableTakerAmountAsync(NON_EXISTENT_ORDER_HASH); + expect(unavailableValueT).to.be.bignumber.equal(0); + }); + it ('should return the unavailableValueT for a valid and partially filled orderHash', async () => { + const orderHash = await zeroEx.getOrderHashHexAsync(signedOrder); + const unavailableValueT = await zeroEx.exchange.getUnavailableTakerAmountAsync(orderHash); + expect(unavailableValueT).to.be.bignumber.equal(partialFillAmount); + }); + }); + describe('#getFilledTakerAmountAsync', () => { + it ('should throw if passed an invalid orderHash', async () => { + const invalidOrderHashHex = '0x123'; + expect(zeroEx.exchange.getFilledTakerAmountAsync(invalidOrderHashHex)).to.be.rejected(); + }); + it ('should return zero if passed a valid but non-existent orderHash', async () => { + const filledValueT = await zeroEx.exchange.getFilledTakerAmountAsync(NON_EXISTENT_ORDER_HASH); + expect(filledValueT).to.be.bignumber.equal(0); + }); + it ('should return the filledValueT for a valid and partially filled orderHash', async () => { + const orderHash = await zeroEx.getOrderHashHexAsync(signedOrder); + const filledValueT = await zeroEx.exchange.getFilledTakerAmountAsync(orderHash); + expect(filledValueT).to.be.bignumber.equal(partialFillAmount); + }); + }); + describe('#getCanceledTakerAmountAsync', () => { + it ('should throw if passed an invalid orderHash', async () => { + const invalidOrderHashHex = '0x123'; + expect(zeroEx.exchange.getCanceledTakerAmountAsync(invalidOrderHashHex)).to.be.rejected(); + }); + it ('should return zero if passed a valid but non-existent orderHash', async () => { + const cancelledValueT = await zeroEx.exchange.getCanceledTakerAmountAsync(NON_EXISTENT_ORDER_HASH); + expect(cancelledValueT).to.be.bignumber.equal(0); + }); + it ('should return the cancelledValueT for a valid and partially filled orderHash', async () => { + const orderHash = await zeroEx.getOrderHashHexAsync(signedOrder); + const cancelledValueT = await zeroEx.exchange.getCanceledTakerAmountAsync(orderHash); + expect(cancelledValueT).to.be.bignumber.equal(0); + }); + }); + }); + describe('#subscribeAsync', () => { + const indexFilterValues = {}; + const shouldCheckTransfer = false; + let makerTokenAddress: string; + let takerTokenAddress: string; + let coinbase: string; + let takerAddress: string; + let makerAddress: string; + let fillableAmount: BigNumber.BigNumber; + let signedOrder: SignedOrder; + before(() => { + [coinbase, makerAddress, takerAddress] = userAddresses; + const [makerToken, takerToken] = tokens; + makerTokenAddress = makerToken.address; + takerTokenAddress = takerToken.address; + }); + beforeEach(async () => { + fillableAmount = new BigNumber(5); + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount, + ); + }); + afterEach(async () => { + (zeroEx.exchange as any).stopWatchingExchangeLogEventsAsync(); + }); + // Hack: Mocha does not allow a test to be both async and have a `done` callback + // Since we need to await the receipt of the event in the `subscribeAsync` callback, + // we do need both. A hack is to make the top-level a sync fn w/ a done callback and then + // wrap the rest of the test in an async block + // Source: https://github.com/mochajs/mocha/issues/2407 + it ('Should receive the LogFill event when an order is filled', (done: DoneCallback) => { + (async () => { + const subscriptionOpts: SubscriptionOpts = { + fromBlock: 0, + toBlock: 'latest', + }; + await zeroEx.exchange.subscribeAsync(ExchangeEvents.LogFill, subscriptionOpts, + indexFilterValues, (err: Error, event: ContractEvent) => { + expect(err).to.be.null(); + expect(event).to.not.be.undefined(); + done(); + }); + const fillTakerAmountInBaseUnits = new BigNumber(1); + zeroEx.setTransactionSenderAccount(takerAddress); + await zeroEx.exchange.fillOrderAsync(signedOrder, fillTakerAmountInBaseUnits, shouldCheckTransfer); + })(); + }); + it('Outstanding subscriptions are cancelled when zeroEx.setProviderAsync called', (done: DoneCallback) => { + (async () => { + const subscriptionOpts: SubscriptionOpts = { + fromBlock: 0, + toBlock: 'latest', + }; + await zeroEx.exchange.subscribeAsync(ExchangeEvents.LogFill, subscriptionOpts, + indexFilterValues, (err: Error, event: ContractEvent) => { + done(new Error('Expected this subscription to have been cancelled')); + }); + + const newProvider = web3Factory.getRpcProvider(); + await zeroEx.setProviderAsync(newProvider); + + await zeroEx.exchange.subscribeAsync(ExchangeEvents.LogFill, subscriptionOpts, + indexFilterValues, (err: Error, event: ContractEvent) => { + expect(err).to.be.null(); + expect(event).to.not.be.undefined(); + done(); + }); + + const fillTakerAmountInBaseUnits = new BigNumber(1); + zeroEx.setTransactionSenderAccount(takerAddress); + await zeroEx.exchange.fillOrderAsync(signedOrder, fillTakerAmountInBaseUnits, shouldCheckTransfer); + })(); + }); + }); }); diff --git a/test/utils/fill_scenarios.ts b/test/utils/fill_scenarios.ts index a44d6b18a..ea5eb77eb 100644 --- a/test/utils/fill_scenarios.ts +++ b/test/utils/fill_scenarios.ts @@ -51,6 +51,28 @@ export class FillScenarios { makerFillableAmount, takerFillableAmount, feeRecepient, expirationUnixTimestampSec, ); } + public async createPartiallyFilledSignedOrderAsync(makerTokenAddress: string, takerTokenAddress: string, + takerAddress: string, fillableAmount: BigNumber.BigNumber, + partialFillAmount: BigNumber.BigNumber) { + const prevSenderAccount = await this.zeroEx.getTransactionSenderAccountIfExistsAsync(); + const [makerAddress] = this.userAddresses; + await this.zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, fillableAmount); + await this.zeroEx.token.transferAsync(takerTokenAddress, makerAddress, takerAddress, fillableAmount); + await this.zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, fillableAmount); + + const signedOrder = await this.createAsymmetricFillableSignedOrderAsync( + makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, + fillableAmount, fillableAmount, + ); + + this.zeroEx.setTransactionSenderAccount(takerAddress); + const shouldCheckTransfer = false; + await this.zeroEx.exchange.fillOrderAsync(signedOrder, partialFillAmount, shouldCheckTransfer); + + // Re-set sender account so as to avoid introducing side-effects + this.zeroEx.setTransactionSenderAccount(prevSenderAccount as string); + return signedOrder; + } private async createAsymmetricFillableSignedOrderWithFeesAsync( makerTokenAddress: string, takerTokenAddress: string, makerFee: BigNumber.BigNumber, takerFee: BigNumber.BigNumber, |