diff options
Diffstat (limited to 'packages/order-watcher')
-rw-r--r-- | packages/order-watcher/CHANGELOG.json | 34 | ||||
-rw-r--r-- | packages/order-watcher/CHANGELOG.md | 8 | ||||
-rw-r--r-- | packages/order-watcher/Dockerfile | 13 | ||||
-rw-r--r-- | packages/order-watcher/README.md | 20 | ||||
-rw-r--r-- | packages/order-watcher/package.json | 31 | ||||
-rw-r--r-- | packages/order-watcher/src/index.ts | 1 | ||||
-rw-r--r-- | packages/order-watcher/src/order_watcher/dependent_order_hashes_tracker.ts | 62 | ||||
-rw-r--r-- | packages/order-watcher/src/order_watcher/order_watcher.ts | 93 | ||||
-rw-r--r-- | packages/order-watcher/src/server.ts | 44 | ||||
-rw-r--r-- | packages/order-watcher/test/order_watcher_test.ts | 208 | ||||
-rw-r--r-- | packages/order-watcher/test/order_watcher_web_socket_server_test.ts | 38 |
11 files changed, 438 insertions, 114 deletions
diff --git a/packages/order-watcher/CHANGELOG.json b/packages/order-watcher/CHANGELOG.json index 304dc45fd..e98a36719 100644 --- a/packages/order-watcher/CHANGELOG.json +++ b/packages/order-watcher/CHANGELOG.json @@ -1,10 +1,19 @@ [ { + "version": "2.4.0", + "changes": [ + { + "note": "Add support for `MultiAssetProxy`", + "pr": 1363 + } + ], + "timestamp": 1547040760 + }, + { "version": "2.3.0", "changes": [ { - "note": - "Added a WebSocket interface to OrderWatcher so that it can be used by a client written in any language", + "note": "Added a WebSocket interface to OrderWatcher so that it can be used by a client written in any language", "pr": 1427 } ] @@ -49,8 +58,7 @@ "version": "2.2.4", "changes": [ { - "note": - "Fix the bug when order watcher was throwing an error on order removal when maker token was ZRX", + "note": "Fix the bug when order watcher was throwing an error on order removal when maker token was ZRX", "pr": 1259 } ], @@ -64,8 +72,7 @@ "pr": 1227 }, { - "note": - "Fix the bug when order watcher was trying to convert undefined to an object in case of CancelUpTo event" + "note": "Fix the bug when order watcher was trying to convert undefined to an object in case of CancelUpTo event" } ], "timestamp": 1542134075 @@ -96,8 +103,7 @@ "pr": 1118 }, { - "note": - "Updated to use new modularized artifacts and the latest version of @0xproject/contract-wrappers. Constructor has a new optional `contractAddresses` parameter.", + "note": "Updated to use new modularized artifacts and the latest version of @0xproject/contract-wrappers. Constructor has a new optional `contractAddresses` parameter.", "pr": 1105 } ], @@ -126,18 +132,15 @@ "version": "2.0.0", "changes": [ { - "note": - "Fixes dropped events issue by fetching logs by blockHash instead of blockNumber. Support for fetching by blockHash was added in Geth > v1.8.13 and Parity > v2.1.0. Infura works too.", + "note": "Fixes dropped events issue by fetching logs by blockHash instead of blockNumber. Support for fetching by blockHash was added in Geth > v1.8.13 and Parity > v2.1.0. Infura works too.", "pr": 1080 }, { - "note": - "Fix misunderstanding about blockstream interface callbacks and pass the raw JSON RPC responses to it", + "note": "Fix misunderstanding about blockstream interface callbacks and pass the raw JSON RPC responses to it", "pr": 1080 }, { - "note": - "Add `transactionHash` to `OrderState` emitted by `OrderWatcher` subscriptions if the order's state change originated from a transaction.", + "note": "Add `transactionHash` to `OrderState` emitted by `OrderWatcher` subscriptions if the order's state change originated from a transaction.", "pr": 1087 } ], @@ -201,8 +204,7 @@ "version": "1.0.1-rc.4", "changes": [ { - "note": - "Export types: `ExchangeContractErrs`, `OrderRelevantState`, `JSONRPCRequestPayload`, `JSONRPCErrorCallback` and `JSONRPCResponsePayload`", + "note": "Export types: `ExchangeContractErrs`, `OrderRelevantState`, `JSONRPCRequestPayload`, `JSONRPCErrorCallback` and `JSONRPCResponsePayload`", "pr": 924 }, { diff --git a/packages/order-watcher/CHANGELOG.md b/packages/order-watcher/CHANGELOG.md index 4e49b4637..600b9fa6f 100644 --- a/packages/order-watcher/CHANGELOG.md +++ b/packages/order-watcher/CHANGELOG.md @@ -5,6 +5,14 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.4.0 - _January 9, 2019_ + + * Add support for `MultiAssetProxy` (#1363) + +## v2.3.0 - _Invalid date_ + + * Added a WebSocket interface to OrderWatcher so that it can be used by a client written in any language (#1427) + ## v2.2.8 - _December 13, 2018_ * Dependencies updated diff --git a/packages/order-watcher/Dockerfile b/packages/order-watcher/Dockerfile new file mode 100644 index 000000000..3ffa1b72f --- /dev/null +++ b/packages/order-watcher/Dockerfile @@ -0,0 +1,13 @@ +FROM node + +WORKDIR /order-watcher + +COPY package.json . +RUN npm i +RUN npm install forever -g + +COPY . . + +EXPOSE 8080 + +CMD ["forever", "./lib/src/server.js"] diff --git a/packages/order-watcher/README.md b/packages/order-watcher/README.md index 385fe4715..a841775b6 100644 --- a/packages/order-watcher/README.md +++ b/packages/order-watcher/README.md @@ -36,7 +36,7 @@ If your project is in [TypeScript](https://www.typescriptlang.org/), add the fol **Environmental Variables** Several environmental variables can be set to configure the server: -* `ORDER_WATCHER_HTTP_PORT` specifies the port that the http server will listen on +- `ORDER_WATCHER_HTTP_PORT` specifies the port that the http server will listen on and accept connections from. When this is not set, we default to 8080. **Requests** @@ -58,10 +58,10 @@ wsClient = create_connection("ws://127.0.0.1:8080") With the connection established, you prepare the payload for your request. The payload is a json object with a format established by the [JSON RPC specification](https://www.jsonrpc.org/specification): -* `id`: All requests require you to specify a numerical `id`. When the server responds to the request, the response will have the same `id` as the one supplied with your request. -* `jsonrpc`: This is always the string `'2.0'`. -* `method`: This specifies the OrderWatcher method you want to call. I.e., `'ADD_ORDER'`, `'REMOVE_ORDER'` or `'GET_STATS'`. -* `params`: These contain the parameters needed by OrderWatcher to execute the method you called. For `ADD_ORDER`, provide `{ signedOrder: <your signedOrder> }`. For `REMOVE_ORDER`, provide `{ orderHash: <your orderHash> }`. For `GET_STATS`, no parameters are needed, so you may leave this empty. +- `id`: All requests require you to specify a numerical `id`. When the server responds to the request, the response will have the same `id` as the one supplied with your request. +- `jsonrpc`: This is always the string `'2.0'`. +- `method`: This specifies the OrderWatcher method you want to call. I.e., `'ADD_ORDER'`, `'REMOVE_ORDER'` or `'GET_STATS'`. +- `params`: These contain the parameters needed by OrderWatcher to execute the method you called. For `ADD_ORDER`, provide `{ signedOrder: <your signedOrder> }`. For `REMOVE_ORDER`, provide `{ orderHash: <your orderHash> }`. For `GET_STATS`, no parameters are needed, so you may leave this empty. Next, convert the payload to a string and send it through the connection. In Javascript: @@ -92,11 +92,11 @@ wsClient.send(json.dumps(remove_order_payload)); **Response** The server responds to all requests in a similar format. In the data field, you'll find another object containing the following fields: -* `id`: The id corresponding to the request that the server is responding to. `UPDATE` responses are not based on any requests so the `id` field is omitted`. -* `jsonrpc`: Always `'2.0'`. -* `method`: The method the server is responding to. Eg. `ADD_ORDER`. When order states change the server may also initiate a response. In this case, method will be listed as `UPDATE`. -* `result`: This field varies based on the method. `UPDATE` responses contain the new order state. `GET_STATS` responses contain the current order count. When there are errors, this field is omitted. -* `error`: When there is an error executing a request, the [JSON RPC](https://www.jsonrpc.org/specification) error object is listed here. When the server responds successfully, this field is omitted. +- `id`: The id corresponding to the request that the server is responding to. `UPDATE` responses are not based on any requests so the `id` field is omitted`. +- `jsonrpc`: Always `'2.0'`. +- `method`: The method the server is responding to. Eg. `ADD_ORDER`. When order states change the server may also initiate a response. In this case, method will be listed as `UPDATE`. +- `result`: This field varies based on the method. `UPDATE` responses contain the new order state. `GET_STATS` responses contain the current order count. When there are errors, this field is omitted. +- `error`: When there is an error executing a request, the [JSON RPC](https://www.jsonrpc.org/specification) error object is listed here. When the server responds successfully, this field is omitted. In Javascript, the responses can be parsed using the `onmessage` callback: diff --git a/packages/order-watcher/package.json b/packages/order-watcher/package.json index 16a46294e..5919759dd 100644 --- a/packages/order-watcher/package.json +++ b/packages/order-watcher/package.json @@ -1,6 +1,6 @@ { "name": "@0x/order-watcher", - "version": "2.2.8", + "version": "2.4.0", "description": "An order watcher daemon that watches for order validity", "keywords": [ "0x", @@ -33,8 +33,9 @@ "node": ">=6.0.0" }, "devDependencies": { - "@0x/dev-utils": "^1.0.21", - "@0x/migrations": "^2.2.2", + "@0x/dev-utils": "^1.0.22", + "@0x/migrations": "^2.3.0", + "@0x/subproviders": "^2.1.9", "@0x/tslint-config": "^2.0.0", "@types/bintrees": "^1.0.2", "@types/lodash": "4.14.104", @@ -57,19 +58,19 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/abi-gen-wrappers": "^2.0.2", - "@0x/assert": "^1.0.20", - "@0x/base-contract": "^3.0.10", - "@0x/contract-addresses": "^2.0.0", - "@0x/contract-artifacts": "^1.1.2", - "@0x/contract-wrappers": "^4.1.3", - "@0x/fill-scenarios": "^1.0.16", - "@0x/json-schemas": "^2.1.4", - "@0x/order-utils": "^3.0.7", - "@0x/types": "^1.4.1", + "@0x/abi-gen-wrappers": "^2.1.0", + "@0x/assert": "^1.0.21", + "@0x/base-contract": "^3.0.11", + "@0x/contract-addresses": "^2.1.0", + "@0x/contract-artifacts": "^1.2.0", + "@0x/contract-wrappers": "^4.2.0", + "@0x/fill-scenarios": "^1.1.0", + "@0x/json-schemas": "^2.1.5", + "@0x/order-utils": "^3.1.0", + "@0x/types": "^1.5.0", "@0x/typescript-typings": "^3.0.6", - "@0x/utils": "^2.0.8", - "@0x/web3-wrapper": "^3.2.1", + "@0x/utils": "^2.1.1", + "@0x/web3-wrapper": "^3.2.2", "bintrees": "^1.0.2", "ethereum-types": "^1.1.4", "ethereumjs-blockstream": "6.0.0", diff --git a/packages/order-watcher/src/index.ts b/packages/order-watcher/src/index.ts index e275a0c6a..1f4e5eff1 100644 --- a/packages/order-watcher/src/index.ts +++ b/packages/order-watcher/src/index.ts @@ -7,6 +7,7 @@ export { OrderStateInvalid, OrderState, ExchangeContractErrs, + ObjectMap, OrderRelevantState, Stats, } from '@0x/types'; diff --git a/packages/order-watcher/src/order_watcher/dependent_order_hashes_tracker.ts b/packages/order-watcher/src/order_watcher/dependent_order_hashes_tracker.ts index a956a94db..d1085014c 100644 --- a/packages/order-watcher/src/order_watcher/dependent_order_hashes_tracker.ts +++ b/packages/order-watcher/src/order_watcher/dependent_order_hashes_tracker.ts @@ -1,6 +1,5 @@ -// tslint:disable:no-unnecessary-type-assertion import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; -import { AssetProxyId, ERC20AssetData, ERC721AssetData, SignedOrder } from '@0x/types'; +import { AssetProxyId, SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; @@ -62,35 +61,18 @@ export class DependentOrderHashesTracker { return dependentOrderHashes; } public addToDependentOrderHashes(signedOrder: SignedOrder): void { - const decodedMakerAssetData = assetDataUtils.decodeAssetDataOrThrow(signedOrder.makerAssetData); - if (decodedMakerAssetData.assetProxyId === AssetProxyId.ERC20) { - this._addToERC20DependentOrderHashes(signedOrder, (decodedMakerAssetData as ERC20AssetData).tokenAddress); - } else { - this._addToERC721DependentOrderHashes( - signedOrder, - (decodedMakerAssetData as ERC721AssetData).tokenAddress, - (decodedMakerAssetData as ERC721AssetData).tokenId, - ); - } + this._addAssetDataToDependentOrderHashes(signedOrder, signedOrder.makerAssetData); this._addToERC20DependentOrderHashes(signedOrder, this._zrxTokenAddress); this._addToMakerDependentOrderHashes(signedOrder); } public removeFromDependentOrderHashes(signedOrder: SignedOrder): void { - const decodedMakerAssetData = assetDataUtils.decodeAssetDataOrThrow(signedOrder.makerAssetData); - if (decodedMakerAssetData.assetProxyId === AssetProxyId.ERC20) { - this._removeFromERC20DependentOrderhashes( - signedOrder, - (decodedMakerAssetData as ERC20AssetData).tokenAddress, - ); - } else { - this._removeFromERC721DependentOrderhashes( - signedOrder, - (decodedMakerAssetData as ERC721AssetData).tokenAddress, - (decodedMakerAssetData as ERC721AssetData).tokenId, - ); - } + this._removeAssetDataFromDependentOrderHashes(signedOrder, signedOrder.makerAssetData); // If makerToken === ZRX then we already removed it and we don't need to remove it again. - if ((decodedMakerAssetData as ERC20AssetData).tokenAddress !== this._zrxTokenAddress) { + const decodedMakerAssetData = assetDataUtils.decodeAssetDataOrThrow(signedOrder.makerAssetData); + if ( + assetDataUtils.isERC20AssetData(decodedMakerAssetData) && + decodedMakerAssetData.tokenAddress !== this._zrxTokenAddress + ) { this._removeFromERC20DependentOrderhashes(signedOrder, this._zrxTokenAddress); } this._removeFromMakerDependentOrderhashes(signedOrder); @@ -167,6 +149,18 @@ export class DependentOrderHashesTracker { tokenId.toString() ].add(orderHash); } + private _addAssetDataToDependentOrderHashes(signedOrder: SignedOrder, assetData: string): void { + const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); + if (assetDataUtils.isERC20AssetData(decodedAssetData)) { + this._addToERC20DependentOrderHashes(signedOrder, decodedAssetData.tokenAddress); + } else if (assetDataUtils.isERC721AssetData(decodedAssetData)) { + this._addToERC721DependentOrderHashes(signedOrder, decodedAssetData.tokenAddress, decodedAssetData.tokenId); + } else if (assetDataUtils.isMultiAssetData(decodedAssetData)) { + _.each(decodedAssetData.nestedAssetData, nestedAssetDataElement => + this._addAssetDataToDependentOrderHashes(signedOrder, nestedAssetDataElement), + ); + } + } private _addToMakerDependentOrderHashes(signedOrder: SignedOrder): void { const orderHash = orderHashUtils.getOrderHashHex(signedOrder); if (_.isUndefined(this._orderHashesByMakerAddress[signedOrder.makerAddress])) { @@ -230,4 +224,20 @@ export class DependentOrderHashesTracker { delete this._orderHashesByMakerAddress[signedOrder.makerAddress]; } } + private _removeAssetDataFromDependentOrderHashes(signedOrder: SignedOrder, assetData: string): void { + const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); + if (assetDataUtils.isERC20AssetData(decodedAssetData)) { + this._removeFromERC20DependentOrderhashes(signedOrder, decodedAssetData.tokenAddress); + } else if (assetDataUtils.isERC721AssetData(decodedAssetData)) { + this._removeFromERC721DependentOrderhashes( + signedOrder, + decodedAssetData.tokenAddress, + decodedAssetData.tokenId, + ); + } else if (assetDataUtils.isMultiAssetData(decodedAssetData)) { + _.each(decodedAssetData.nestedAssetData, nestedAssetDataElement => + this._removeAssetDataFromDependentOrderHashes(signedOrder, nestedAssetDataElement), + ); + } + } } diff --git a/packages/order-watcher/src/order_watcher/order_watcher.ts b/packages/order-watcher/src/order_watcher/order_watcher.ts index 96c5ca7b4..a06fd0cfe 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher.ts @@ -161,14 +161,7 @@ export class OrderWatcher { this._dependentOrderHashesTracker.addToDependentOrderHashes(signedOrder); const orderAssetDatas = [signedOrder.makerAssetData, signedOrder.takerAssetData]; - _.each(orderAssetDatas, assetData => { - const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); - if (decodedAssetData.assetProxyId === AssetProxyId.ERC20) { - this._collisionResistantAbiDecoder.addERC20Token(decodedAssetData.tokenAddress); - } else if (decodedAssetData.assetProxyId === AssetProxyId.ERC721) { - this._collisionResistantAbiDecoder.addERC721Token(decodedAssetData.tokenAddress); - } - }); + _.each(orderAssetDatas, assetData => this._addAssetDataToAbiDecoder(assetData)); } /** * Removes an order from the orderWatcher @@ -236,31 +229,71 @@ export class OrderWatcher { await this._emitRevalidateOrdersAsync([orderHash]); } } + private _addAssetDataToAbiDecoder(assetData: string): void { + const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); + if (assetDataUtils.isERC20AssetData(decodedAssetData)) { + this._collisionResistantAbiDecoder.addERC20Token(decodedAssetData.tokenAddress); + } else if (assetDataUtils.isERC721AssetData(decodedAssetData)) { + this._collisionResistantAbiDecoder.addERC721Token(decodedAssetData.tokenAddress); + } else if (assetDataUtils.isMultiAssetData(decodedAssetData)) { + _.each(decodedAssetData.nestedAssetData, nestedAssetDataElement => + this._addAssetDataToAbiDecoder(nestedAssetDataElement), + ); + } + } + private _deleteLazyStoreBalance(assetData: string, userAddress: string): void { + const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData); + switch (assetProxyId) { + case AssetProxyId.ERC20: + case AssetProxyId.ERC721: + this._balanceAndProxyAllowanceLazyStore.deleteBalance(assetData, userAddress); + break; + case AssetProxyId.MultiAsset: + const decodedAssetData = assetDataUtils.decodeMultiAssetData(assetData); + _.each(decodedAssetData.nestedAssetData, nestedAssetDataElement => + this._deleteLazyStoreBalance(nestedAssetDataElement, userAddress), + ); + break; + default: + break; + } + } + private _deleteLazyStoreProxyAllowance(assetData: string, userAddress: string): void { + const assetProxyId = assetDataUtils.decodeAssetProxyId(assetData); + switch (assetProxyId) { + case AssetProxyId.ERC20: + case AssetProxyId.ERC721: + this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(assetData, userAddress); + break; + case AssetProxyId.MultiAsset: + const decodedAssetData = assetDataUtils.decodeMultiAssetData(assetData); + _.each(decodedAssetData.nestedAssetData, nestedAssetDataElement => + this._deleteLazyStoreProxyAllowance(nestedAssetDataElement, userAddress), + ); + break; + default: + break; + } + } private _cleanupOrderRelatedState(orderHash: string): void { const signedOrder = this._orderByOrderHash[orderHash]; this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(orderHash); this._orderFilledCancelledLazyStore.deleteIsCancelled(orderHash); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.makerAssetData, signedOrder.makerAddress); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance( - signedOrder.makerAssetData, - signedOrder.makerAddress, - ); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.takerAssetData, signedOrder.takerAddress); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance( - signedOrder.takerAssetData, - signedOrder.takerAddress, - ); + this._deleteLazyStoreBalance(signedOrder.makerAssetData, signedOrder.makerAddress); + this._deleteLazyStoreProxyAllowance(signedOrder.makerAssetData, signedOrder.makerAddress); + this._deleteLazyStoreBalance(signedOrder.takerAssetData, signedOrder.takerAddress); + this._deleteLazyStoreProxyAllowance(signedOrder.takerAssetData, signedOrder.takerAddress); const zrxAssetData = this._orderFilledCancelledLazyStore.getZRXAssetData(); if (!signedOrder.makerFee.isZero()) { - this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxAssetData, signedOrder.makerAddress); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxAssetData, signedOrder.makerAddress); + this._deleteLazyStoreBalance(zrxAssetData, signedOrder.makerAddress); + this._deleteLazyStoreProxyAllowance(zrxAssetData, signedOrder.makerAddress); } if (!signedOrder.takerFee.isZero()) { - this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxAssetData, signedOrder.takerAddress); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxAssetData, signedOrder.takerAddress); + this._deleteLazyStoreBalance(zrxAssetData, signedOrder.takerAddress); + this._deleteLazyStoreProxyAllowance(zrxAssetData, signedOrder.takerAddress); } } private _onOrderExpired(orderHash: string): void { @@ -302,7 +335,7 @@ export class OrderWatcher { // Invalidate cache const args = decodedLog.args as ERC20TokenApprovalEventArgs; const tokenAssetData = assetDataUtils.encodeERC20AssetData(decodedLog.address); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(tokenAssetData, args._owner); + this._deleteLazyStoreProxyAllowance(tokenAssetData, args._owner); // Revalidate orders const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( args._owner, @@ -315,7 +348,7 @@ export class OrderWatcher { // Invalidate cache const args = decodedLog.args as ERC721TokenApprovalEventArgs; const tokenAssetData = assetDataUtils.encodeERC721AssetData(decodedLog.address, args._tokenId); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(tokenAssetData, args._owner); + this._deleteLazyStoreProxyAllowance(tokenAssetData, args._owner); // Revalidate orders const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( args._owner, @@ -333,8 +366,8 @@ export class OrderWatcher { // Invalidate cache const args = decodedLog.args as ERC20TokenTransferEventArgs; const tokenAssetData = assetDataUtils.encodeERC20AssetData(decodedLog.address); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(tokenAssetData, args._from); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(tokenAssetData, args._to); + this._deleteLazyStoreBalance(tokenAssetData, args._from); + this._deleteLazyStoreBalance(tokenAssetData, args._to); // Revalidate orders const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( args._from, @@ -347,8 +380,8 @@ export class OrderWatcher { // Invalidate cache const args = decodedLog.args as ERC721TokenTransferEventArgs; const tokenAssetData = assetDataUtils.encodeERC721AssetData(decodedLog.address, args._tokenId); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(tokenAssetData, args._from); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(tokenAssetData, args._to); + this._deleteLazyStoreBalance(tokenAssetData, args._from); + this._deleteLazyStoreBalance(tokenAssetData, args._to); // Revalidate orders const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( args._from, @@ -375,7 +408,7 @@ export class OrderWatcher { // Invalidate cache const args = decodedLog.args as WETH9DepositEventArgs; const tokenAssetData = assetDataUtils.encodeERC20AssetData(decodedLog.address); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(tokenAssetData, args._owner); + this._deleteLazyStoreBalance(tokenAssetData, args._owner); // Revalidate orders const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( args._owner, @@ -388,7 +421,7 @@ export class OrderWatcher { // Invalidate cache const args = decodedLog.args as WETH9WithdrawalEventArgs; const tokenAssetData = assetDataUtils.encodeERC20AssetData(decodedLog.address); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(tokenAssetData, args._owner); + this._deleteLazyStoreBalance(tokenAssetData, args._owner); // Revalidate orders const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( args._owner, diff --git a/packages/order-watcher/src/server.ts b/packages/order-watcher/src/server.ts new file mode 100644 index 000000000..1d31e87ab --- /dev/null +++ b/packages/order-watcher/src/server.ts @@ -0,0 +1,44 @@ +import { getContractAddressesForNetworkOrThrow } from '@0x/contract-addresses'; +import { RPCSubprovider, Web3ProviderEngine } from '@0x/subproviders'; +import * as _ from 'lodash'; + +import { OrderWatcherWebSocketServer } from './order_watcher/order_watcher_web_socket_server'; + +const GANACHE_NETWORK_ID = 50; +const DEFAULT_RPC_URL = 'http://localhost:8545'; + +const provider = new Web3ProviderEngine(); +const jsonRpcUrl = process.env.JSON_RPC_URL || DEFAULT_RPC_URL; +const rpcSubprovider = new RPCSubprovider(jsonRpcUrl); +provider.addProvider(rpcSubprovider); +provider.start(); + +const networkId = process.env.NETWORK_ID !== undefined ? _.parseInt(process.env.NETWORK_ID) : GANACHE_NETWORK_ID; + +const contractAddressesString = process.env.contractAddresses; +const contractAddressesIfExists = + contractAddressesString === undefined + ? getContractAddressesForNetworkOrThrow(networkId) + : JSON.parse(contractAddressesString); + +const orderWatcherConfig: any = { + isVerbose: process.env.IS_VERBOSE === 'true', +}; +const orderExpirationCheckingIntervalMs = process.env.ORDER_EXPIRATION_CHECKING_INTERVAL_MS; +if (orderExpirationCheckingIntervalMs !== undefined) { + orderWatcherConfig.orderExpirationCheckingIntervalMs = _.parseInt(orderExpirationCheckingIntervalMs); +} +const eventPollingIntervalMs = process.env.EVENT_POLLING_INTERVAL_MS; +if (eventPollingIntervalMs !== undefined) { + orderWatcherConfig.eventPollingIntervalMs = _.parseInt(eventPollingIntervalMs); +} +const expirationMarginMs = process.env.EXPIRATION_MARGIN_MS; +if (expirationMarginMs !== undefined) { + orderWatcherConfig.expirationMarginMs = _.parseInt(expirationMarginMs); +} +const cleanupJobIntervalMs = process.env.CLEANUP_JOB_INTERVAL_MS; +if (cleanupJobIntervalMs !== undefined) { + orderWatcherConfig.cleanupJobIntervalMs = _.parseInt(cleanupJobIntervalMs); +} +const wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddressesIfExists, orderWatcherConfig); +wsServer.start(); diff --git a/packages/order-watcher/test/order_watcher_test.ts b/packages/order-watcher/test/order_watcher_test.ts index 271e5dec5..41dc884d5 100644 --- a/packages/order-watcher/test/order_watcher_test.ts +++ b/packages/order-watcher/test/order_watcher_test.ts @@ -675,5 +675,213 @@ describe('OrderWatcher', () => { })().catch(done); }); }); + describe('multiAsset', async () => { + const tokenId = new BigNumber(42); + const [makerErc721TokenAddress] = tokenUtils.getDummyERC721TokenAddresses(); + const makerErc721AssetData = assetDataUtils.encodeERC721AssetData(makerErc721TokenAddress, tokenId); + const fillableErc721Amount = new BigNumber(1); + const [makerErc20TokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + const makerErc20AssetData = assetDataUtils.encodeERC20AssetData(makerErc20TokenAddress); + const fillableErc20Amount = new BigNumber(2); + const multiAssetAmounts = [fillableErc721Amount, fillableErc20Amount]; + const nestedAssetData = [makerErc721AssetData, makerErc20AssetData]; + const makerMultiAssetData = assetDataUtils.encodeMultiAssetData(multiAssetAmounts, nestedAssetData); + it('should emit orderStateInvalid when maker allowance of ERC721 token set to 0 for watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerMultiAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableErc721Amount, + ); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + await orderWatcher.addOrderAsync(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.erc721Token.setApprovalAsync( + makerErc721TokenAddress, + constants.NULL_ADDRESS, + tokenId, + ); + })().catch(done); + }); + it('should emit orderStateInvalid when maker allowance for all of ERC721 token set to 0 for watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerMultiAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableErc721Amount, + ); + await contractWrappers.erc721Token.setApprovalAsync( + makerErc721TokenAddress, + constants.NULL_ADDRESS, + tokenId, + ); + let isApproved = true; + await contractWrappers.erc721Token.setProxyApprovalForAllAsync( + makerErc721TokenAddress, + makerAddress, + isApproved, + ); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + await orderWatcher.addOrderAsync(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); + isApproved = false; + await contractWrappers.erc721Token.setProxyApprovalForAllAsync( + makerErc721TokenAddress, + makerAddress, + isApproved, + ); + })().catch(done); + }); + it('should emit orderStateInvalid when maker moves ERC721 backing watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerMultiAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableErc721Amount, + ); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + await orderWatcher.addOrderAsync(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); + await contractWrappers.erc721Token.transferFromAsync( + makerErc721TokenAddress, + coinbase, + makerAddress, + tokenId, + ); + })().catch(done); + }); + it('should emit orderStateInvalid when maker allowance of ERC20 token set to 0 for watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerMultiAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableErc721Amount, + ); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + await orderWatcher.addOrderAsync(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.erc20Token.setProxyAllowanceAsync( + makerErc20TokenAddress, + makerAddress, + new BigNumber(0), + ); + })().catch(done); + }); + it('should not emit an orderState event when irrelevant ERC20 Transfer event received', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerMultiAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + await orderWatcher.addOrderAsync(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 = takerAddress; + const transferAmount = new BigNumber(2); + await contractWrappers.erc20Token.transferAsync( + makerTokenAddress, + notTheMaker, + anyRecipient, + transferAmount, + ); + setTimeout(() => { + done(); + }, TIMEOUT_MS); + })().catch(done); + }); + it('should emit orderStateInvalid when makerAddress moves ERC20 balance backing watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerMultiAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + await orderWatcher.addOrderAsync(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 = takerAddress; + const makerBalance = await contractWrappers.erc20Token.getBalanceAsync( + makerTokenAddress, + makerAddress, + ); + await contractWrappers.erc20Token.transferAsync( + makerTokenAddress, + makerAddress, + anyRecipient, + makerBalance, + ); + })().catch(done); + }); + // TODO(abandeali1): The following test will fail until the MAP has been deployed and activated. + it.skip('should emit orderStateInvalid when watched order fully filled', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerMultiAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + await orderWatcher.addOrderAsync(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.fillOrderAsync(signedOrder, fillableAmount, takerAddress); + })().catch(done); + }); + }); }); }); // tslint:disable:max-file-line-count diff --git a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts index 578e0de61..36135f65c 100644 --- a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts +++ b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts @@ -1,9 +1,10 @@ +import { ContractAddresses } from '@0x/contract-addresses'; import { ContractWrappers } from '@0x/contract-wrappers'; import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils'; import { BlockchainLifecycle } from '@0x/dev-utils'; import { FillScenarios } from '@0x/fill-scenarios'; import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; -import { ExchangeContractErrs, OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; +import { ExchangeContractErrs, OrderStateInvalid, SignedOrder } from '@0x/types'; import { BigNumber, logUtils } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; @@ -26,7 +27,7 @@ interface WsMessage { data: string; } -describe.only('OrderWatcherWebSocketServer', async () => { +describe('OrderWatcherWebSocketServer', async () => { let contractWrappers: ContractWrappers; let wsServer: OrderWatcherWebSocketServer; let wsClient: WebSocket.w3cwebsocket; @@ -44,14 +45,16 @@ describe.only('OrderWatcherWebSocketServer', async () => { let orderHash: string; let addOrderPayload: AddOrderRequest; let removeOrderPayload: RemoveOrderRequest; + let networkId: number; + let contractAddresses: ContractAddresses; const decimals = constants.ZRX_DECIMALS; const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); before(async () => { // Set up constants - const contractAddresses = await migrateOnceAsync(); + contractAddresses = await migrateOnceAsync(); await blockchainLifecycle.startAsync(); - const networkId = constants.TESTRPC_NETWORK_ID; + networkId = constants.TESTRPC_NETWORK_ID; const config = { networkId, contractAddresses, @@ -93,17 +96,16 @@ describe.only('OrderWatcherWebSocketServer', async () => { method: OrderWatcherMethod.RemoveOrder, params: { orderHash }, }; - - // Prepare OrderWatcher WebSocket server - const orderWatcherConfig = { - isVerbose: true, - }; - wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); }); after(async () => { await blockchainLifecycle.revertAsync(); }); beforeEach(async () => { + // Prepare OrderWatcher WebSocket server + const orderWatcherConfig = { + isVerbose: true, + }; + wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); wsServer.start(); await blockchainLifecycle.startAsync(); wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); @@ -260,7 +262,9 @@ describe.only('OrderWatcherWebSocketServer', async () => { id: 1, jsonrpc: '2.0', method: 'ADD_ORDER', - signedOrder: nonZeroMakerFeeSignedOrder, + params: { + signedOrder: nonZeroMakerFeeSignedOrder, + }, }; // Set up a second client and have it add the order @@ -278,15 +282,15 @@ describe.only('OrderWatcherWebSocketServer', async () => { // Check that both clients receive the emitted event by awaiting the onMessageAsync promises let updateMsg = await clientOneOnMessagePromise; let updateData = JSON.parse(updateMsg.data); - let orderState = updateData.result as OrderStateValid; - expect(orderState.isValid).to.be.true(); - expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); + let orderState = updateData.result as OrderStateInvalid; + expect(orderState.isValid).to.be.false(); + expect(orderState.error).to.be.eq('INSUFFICIENT_MAKER_FEE_ALLOWANCE'); updateMsg = await clientTwoOnMessagePromise; updateData = JSON.parse(updateMsg.data); - orderState = updateData.result as OrderStateValid; - expect(orderState.isValid).to.be.true(); - expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); + orderState = updateData.result as OrderStateInvalid; + expect(orderState.isValid).to.be.false(); + expect(orderState.error).to.be.eq('INSUFFICIENT_MAKER_FEE_ALLOWANCE'); wsClientTwo.close(); logUtils.log(`${new Date()} [Client] Closed.`); |