diff options
author | Fabio Berger <me@fabioberger.com> | 2018-07-19 22:38:33 +0800 |
---|---|---|
committer | Fabio Berger <me@fabioberger.com> | 2018-07-19 22:38:33 +0800 |
commit | b6de0bdd43d53204fbbe9a13d44658963a7f2725 (patch) | |
tree | 45a7084ff4de40deab8b79fc689e4718e365e7b2 /packages/order-watcher | |
parent | a2b62fd8085df2121424fd8a9defee42879971ce (diff) | |
parent | 886a03fdcd4893a57f88fa407de94852cb0a2285 (diff) | |
download | dexon-0x-contracts-b6de0bdd43d53204fbbe9a13d44658963a7f2725.tar dexon-0x-contracts-b6de0bdd43d53204fbbe9a13d44658963a7f2725.tar.gz dexon-0x-contracts-b6de0bdd43d53204fbbe9a13d44658963a7f2725.tar.bz2 dexon-0x-contracts-b6de0bdd43d53204fbbe9a13d44658963a7f2725.tar.lz dexon-0x-contracts-b6de0bdd43d53204fbbe9a13d44658963a7f2725.tar.xz dexon-0x-contracts-b6de0bdd43d53204fbbe9a13d44658963a7f2725.tar.zst dexon-0x-contracts-b6de0bdd43d53204fbbe9a13d44658963a7f2725.zip |
Merge branch 'v2-prototype' into update-lerna
* v2-prototype: (48 commits)
Update CHANGELOG
Rename call data schema id to CallData. Check for TypedArray when hashing data in order-utils crypto
Fix broken links in sol-cov documentation
Fix 0x.js ts warnings
Update yarn.lock
Fix 0x.js ts warnings
Fix 0x.js tests on CI
Fix a bad merge
Update package versions
Merge
Update changelogs
Add a test for ERC721 Allowance
Use allowance instead of approval for all in fill-scenarios
Upgrade sha3 to 1.2.2 to work with node v10
Check if the token doesn't exist before minting in fill scenarios
Make downlevelIteration a global config
Fix tests descriptions
DRY up the code in order-watcher collision-resistant abi decoder
Await transactions in fillScenarios
Rename decodeAssetData to decodeAssetDataOrThrow
...
Diffstat (limited to 'packages/order-watcher')
30 files changed, 1289 insertions, 2266 deletions
diff --git a/packages/order-watcher/CHANGELOG.json b/packages/order-watcher/CHANGELOG.json index e747a2129..a8f935f6d 100644 --- a/packages/order-watcher/CHANGELOG.json +++ b/packages/order-watcher/CHANGELOG.json @@ -1,5 +1,14 @@ [ { + "timestamp": 1531919263, + "version": "0.0.8", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { "version": "0.0.7", "changes": [ { diff --git a/packages/order-watcher/CHANGELOG.md b/packages/order-watcher/CHANGELOG.md index c14b66e39..cd00c5f14 100644 --- a/packages/order-watcher/CHANGELOG.md +++ b/packages/order-watcher/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v0.0.8 - _July 18, 2018_ + + * Dependencies updated + ## v0.0.7 - _July 9, 2018_ * Switch out simple getLogs polling with ethereumjs-blockstream (#825) diff --git a/packages/order-watcher/package.json b/packages/order-watcher/package.json index 30cd90ef9..65ead4511 100644 --- a/packages/order-watcher/package.json +++ b/packages/order-watcher/package.json @@ -1,6 +1,6 @@ { "name": "@0xproject/order-watcher", - "version": "0.0.7", + "version": "0.0.8", "description": "An order watcher daemon that watches for order validity", "keywords": [ "0x", @@ -47,11 +47,11 @@ "node": ">=6.0.0" }, "devDependencies": { - "@0xproject/abi-gen": "^0.3.3", - "@0xproject/dev-utils": "^0.4.5", - "@0xproject/migrations": "^0.0.9", + "@0xproject/abi-gen": "^0.3.4", + "@0xproject/dev-utils": "^0.4.6", + "@0xproject/migrations": "^0.0.10", "@0xproject/monorepo-scripts": "^0.2.2", - "@0xproject/sol-compiler": "^0.5.3", + "@0xproject/sol-compiler": "^0.5.4", "@0xproject/tslint-config": "^0.4.21", "@types/bintrees": "^1.0.2", "@types/lodash": "4.14.104", @@ -77,16 +77,16 @@ "typescript": "2.7.1" }, "dependencies": { - "@0xproject/assert": "0.2.13", - "@0xproject/base-contract": "^0.3.5", - "@0xproject/contract-wrappers": "0.0.5", - "@0xproject/fill-scenarios": "^0.0.5", - "@0xproject/json-schemas": "0.8.2", - "@0xproject/order-utils": "^0.0.8", - "@0xproject/types": "^0.8.2", - "@0xproject/typescript-typings": "^0.4.2", - "@0xproject/utils": "^0.7.2", - "@0xproject/web3-wrapper": "^0.7.2", + "@0xproject/assert": "0.3.0", + "@0xproject/base-contract": "0.3.6", + "@0xproject/contract-wrappers": "0.1.1", + "@0xproject/fill-scenarios": "1.0.0", + "@0xproject/json-schemas": "1.0.0", + "@0xproject/order-utils": "1.0.0", + "@0xproject/types": "1.0.0", + "@0xproject/typescript-typings": "^0.4.3", + "@0xproject/utils": "^0.7.3", + "@0xproject/web3-wrapper": "^0.7.3", "ethereumjs-blockstream": "5.0.0", "ethereum-types": "^0.0.2", "bintrees": "^1.0.2", diff --git a/packages/order-watcher/src/artifacts.ts b/packages/order-watcher/src/artifacts.ts index 13587984c..4732fb2b5 100644 --- a/packages/order-watcher/src/artifacts.ts +++ b/packages/order-watcher/src/artifacts.ts @@ -1,18 +1,13 @@ import { Artifact } from '@0xproject/types'; -import * as DummyToken from './compact_artifacts/DummyToken.json'; -import * as EtherToken from './compact_artifacts/EtherToken.json'; +import * as ERC20Token from './compact_artifacts/ERC20Token.json'; +import * as ERC721Token from './compact_artifacts/ERC721Token.json'; import * as Exchange from './compact_artifacts/Exchange.json'; -import * as Token from './compact_artifacts/Token.json'; -import * as TokenRegistry from './compact_artifacts/TokenRegistry.json'; -import * as TokenTransferProxy from './compact_artifacts/TokenTransferProxy.json'; -import * as ZRX from './compact_artifacts/ZRX.json'; +import * as WETH9 from './compact_artifacts/WETH9.json'; + export const artifacts = { - ZRX: (ZRX as any) as Artifact, - DummyToken: (DummyToken as any) as Artifact, - Token: (Token as any) as Artifact, + ERC20Token: (ERC20Token as any) as Artifact, + ERC721Token: (ERC721Token as any) as Artifact, Exchange: (Exchange as any) as Artifact, - EtherToken: (EtherToken as any) as Artifact, - TokenRegistry: (TokenRegistry as any) as Artifact, - TokenTransferProxy: (TokenTransferProxy as any) as Artifact, + EtherToken: (WETH9 as any) as Artifact, }; diff --git a/packages/order-watcher/src/compact_artifacts/DummyToken.json b/packages/order-watcher/src/compact_artifacts/DummyToken.json deleted file mode 100644 index f64a8cd3d..000000000 --- a/packages/order-watcher/src/compact_artifacts/DummyToken.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "contract_name": "DummyToken", - "abi": [ - { - "constant": false, - "inputs": [ - { - "name": "_target", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "setBalance", - "outputs": [], - "payable": false, - "type": "function" - } - ] -} diff --git a/packages/order-watcher/src/compact_artifacts/ERC20Token.json b/packages/order-watcher/src/compact_artifacts/ERC20Token.json new file mode 100644 index 000000000..8d60fa324 --- /dev/null +++ b/packages/order-watcher/src/compact_artifacts/ERC20Token.json @@ -0,0 +1,49 @@ +{ + "contract_name": "ERC20Token", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": true, + "name": "_spender", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + } + ] +} diff --git a/packages/order-watcher/src/compact_artifacts/ERC721Token.json b/packages/order-watcher/src/compact_artifacts/ERC721Token.json new file mode 100644 index 000000000..5934376d3 --- /dev/null +++ b/packages/order-watcher/src/compact_artifacts/ERC721Token.json @@ -0,0 +1,71 @@ +{ + "contract_name": "ERC721Token", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": true, + "name": "_approved", + "type": "address" + }, + { + "indexed": false, + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": true, + "name": "_operator", + "type": "address" + }, + { + "indexed": false, + "name": "_approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + } + ] +} diff --git a/packages/order-watcher/src/compact_artifacts/EtherToken.json b/packages/order-watcher/src/compact_artifacts/EtherToken.json deleted file mode 100644 index 26cca57cd..000000000 --- a/packages/order-watcher/src/compact_artifacts/EtherToken.json +++ /dev/null @@ -1,287 +0,0 @@ -{ - "contract_name": "EtherToken", - "abi": [ - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_from", - "type": "address" - }, - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "amount", - "type": "uint256" - } - ], - "name": "withdraw", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "name": "", - "type": "uint8" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "deposit", - "outputs": [], - "payable": true, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - }, - { - "name": "_spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "payable": true, - "type": "fallback" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_owner", - "type": "address" - }, - { - "indexed": true, - "name": "_spender", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_owner", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Deposit", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_owner", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Withdrawal", - "type": "event" - } - ], - "networks": { - "1": { - "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "3": { - "address": "0xc00fd9820cd2898cc4c054b7bf142de637ad129a" - }, - "4": { - "address": "0xc778417e063141139fce010982780140aa0cd5ab" - }, - "42": { - "address": "0x653e49e301e508a13237c0ddc98ae7d4cd2667a1" - }, - "50": { - "address": "0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c" - } - } -} diff --git a/packages/order-watcher/src/compact_artifacts/Exchange.json b/packages/order-watcher/src/compact_artifacts/Exchange.json index af8db7360..4e319f7cf 100644 --- a/packages/order-watcher/src/compact_artifacts/Exchange.json +++ b/packages/order-watcher/src/compact_artifacts/Exchange.json @@ -2,527 +2,65 @@ "contract_name": "Exchange", "abi": [ { - "constant": true, - "inputs": [ - { - "name": "numerator", - "type": "uint256" - }, - { - "name": "denominator", - "type": "uint256" - }, - { - "name": "target", - "type": "uint256" - } - ], - "name": "isRoundingError", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "name": "filled", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "name": "cancelled", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5][]" - }, - { - "name": "orderValues", - "type": "uint256[6][]" - }, - { - "name": "fillTakerTokenAmount", - "type": "uint256" - }, - { - "name": "shouldThrowOnInsufficientBalanceOrAllowance", - "type": "bool" - }, - { - "name": "v", - "type": "uint8[]" - }, - { - "name": "r", - "type": "bytes32[]" - }, - { - "name": "s", - "type": "bytes32[]" - } - ], - "name": "fillOrdersUpTo", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5]" - }, - { - "name": "orderValues", - "type": "uint256[6]" - }, - { - "name": "cancelTakerTokenAmount", - "type": "uint256" - } - ], - "name": "cancelOrder", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "ZRX_TOKEN_CONTRACT", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5][]" - }, - { - "name": "orderValues", - "type": "uint256[6][]" - }, - { - "name": "fillTakerTokenAmounts", - "type": "uint256[]" - }, - { - "name": "v", - "type": "uint8[]" - }, - { - "name": "r", - "type": "bytes32[]" - }, - { - "name": "s", - "type": "bytes32[]" - } - ], - "name": "batchFillOrKillOrders", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5]" - }, - { - "name": "orderValues", - "type": "uint256[6]" - }, - { - "name": "fillTakerTokenAmount", - "type": "uint256" - }, - { - "name": "v", - "type": "uint8" - }, - { - "name": "r", - "type": "bytes32" - }, - { - "name": "s", - "type": "bytes32" - } - ], - "name": "fillOrKillOrder", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "orderHash", - "type": "bytes32" - } - ], - "name": "getUnavailableTakerTokenAmount", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "signer", - "type": "address" - }, - { - "name": "hash", - "type": "bytes32" - }, - { - "name": "v", - "type": "uint8" - }, - { - "name": "r", - "type": "bytes32" - }, - { - "name": "s", - "type": "bytes32" - } - ], - "name": "isValidSignature", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "numerator", - "type": "uint256" - }, - { - "name": "denominator", - "type": "uint256" - }, - { - "name": "target", - "type": "uint256" - } - ], - "name": "getPartialAmount", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "TOKEN_TRANSFER_PROXY_CONTRACT", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5][]" - }, - { - "name": "orderValues", - "type": "uint256[6][]" - }, - { - "name": "fillTakerTokenAmounts", - "type": "uint256[]" - }, - { - "name": "shouldThrowOnInsufficientBalanceOrAllowance", - "type": "bool" - }, - { - "name": "v", - "type": "uint8[]" - }, - { - "name": "r", - "type": "bytes32[]" - }, - { - "name": "s", - "type": "bytes32[]" - } - ], - "name": "batchFillOrders", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5][]" - }, - { - "name": "orderValues", - "type": "uint256[6][]" - }, - { - "name": "cancelTakerTokenAmounts", - "type": "uint256[]" - } - ], - "name": "batchCancelOrders", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5]" - }, - { - "name": "orderValues", - "type": "uint256[6]" - }, - { - "name": "fillTakerTokenAmount", - "type": "uint256" - }, - { - "name": "shouldThrowOnInsufficientBalanceOrAllowance", - "type": "bool" - }, - { - "name": "v", - "type": "uint8" - }, - { - "name": "r", - "type": "bytes32" - }, - { - "name": "s", - "type": "bytes32" - } - ], - "name": "fillOrder", - "outputs": [ - { - "name": "filledTakerTokenAmount", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "orderAddresses", - "type": "address[5]" - }, - { - "name": "orderValues", - "type": "uint256[6]" - } - ], - "name": "getOrderHash", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "EXTERNAL_QUERY_GAS_LIMIT", - "outputs": [ - { - "name": "", - "type": "uint16" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "VERSION", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "type": "function" - }, - { - "inputs": [ - { - "name": "_zrxToken", - "type": "address" - }, - { - "name": "_tokenTransferProxy", - "type": "address" - } - ], - "payable": false, - "type": "constructor" - }, - { "anonymous": false, "inputs": [ { "indexed": true, - "name": "maker", - "type": "address" - }, - { - "indexed": false, - "name": "taker", + "name": "makerAddress", "type": "address" }, { "indexed": true, - "name": "feeRecipient", + "name": "feeRecipientAddress", "type": "address" }, { "indexed": false, - "name": "makerToken", + "name": "takerAddress", "type": "address" }, { "indexed": false, - "name": "takerToken", + "name": "senderAddress", "type": "address" }, { "indexed": false, - "name": "filledMakerTokenAmount", + "name": "makerAssetFilledAmount", "type": "uint256" }, { "indexed": false, - "name": "filledTakerTokenAmount", + "name": "takerAssetFilledAmount", "type": "uint256" }, { "indexed": false, - "name": "paidMakerFee", + "name": "makerFeePaid", "type": "uint256" }, { "indexed": false, - "name": "paidTakerFee", + "name": "takerFeePaid", "type": "uint256" }, { "indexed": true, - "name": "tokens", + "name": "orderHash", "type": "bytes32" }, { "indexed": false, - "name": "orderHash", - "type": "bytes32" + "name": "makerAssetData", + "type": "bytes" + }, + { + "indexed": false, + "name": "takerAssetData", + "type": "bytes" } ], - "name": "LogFill", + "name": "Fill", "type": "event" }, { @@ -530,46 +68,36 @@ "inputs": [ { "indexed": true, - "name": "maker", + "name": "makerAddress", "type": "address" }, { "indexed": true, - "name": "feeRecipient", + "name": "feeRecipientAddress", "type": "address" }, { "indexed": false, - "name": "makerToken", + "name": "senderAddress", "type": "address" }, { - "indexed": false, - "name": "takerToken", - "type": "address" - }, - { - "indexed": false, - "name": "cancelledMakerTokenAmount", - "type": "uint256" + "indexed": true, + "name": "orderHash", + "type": "bytes32" }, { "indexed": false, - "name": "cancelledTakerTokenAmount", - "type": "uint256" - }, - { - "indexed": true, - "name": "tokens", - "type": "bytes32" + "name": "makerAssetData", + "type": "bytes" }, { "indexed": false, - "name": "orderHash", - "type": "bytes32" + "name": "takerAssetData", + "type": "bytes" } ], - "name": "LogCancel", + "name": "Cancel", "type": "event" }, { @@ -577,16 +105,21 @@ "inputs": [ { "indexed": true, - "name": "errorId", - "type": "uint8" + "name": "makerAddress", + "type": "address" }, { "indexed": true, - "name": "orderHash", - "type": "bytes32" + "name": "senderAddress", + "type": "address" + }, + { + "indexed": false, + "name": "orderEpoch", + "type": "uint256" } ], - "name": "LogError", + "name": "CancelUpTo", "type": "event" } ], diff --git a/packages/order-watcher/src/compact_artifacts/Token.json b/packages/order-watcher/src/compact_artifacts/Token.json deleted file mode 100644 index 3b5a86ae0..000000000 --- a/packages/order-watcher/src/compact_artifacts/Token.json +++ /dev/null @@ -1,172 +0,0 @@ -{ - "contract_name": "Token", - "abi": [ - { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "name": "success", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "name": "supply", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_from", - "type": "address" - }, - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "success", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "balance", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "name": "success", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - }, - { - "name": "_spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "name": "remaining", - "type": "uint256" - } - ], - "payable": false, - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_from", - "type": "address" - }, - { - "indexed": true, - "name": "_to", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "_owner", - "type": "address" - }, - { - "indexed": true, - "name": "_spender", - "type": "address" - }, - { - "indexed": false, - "name": "_value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - } - ] -} diff --git a/packages/order-watcher/src/compact_artifacts/TokenRegistry.json b/packages/order-watcher/src/compact_artifacts/TokenRegistry.json deleted file mode 100644 index 0f583628c..000000000 --- a/packages/order-watcher/src/compact_artifacts/TokenRegistry.json +++ /dev/null @@ -1,547 +0,0 @@ -{ - "contract_name": "TokenRegistry", - "abi": [ - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_index", - "type": "uint256" - } - ], - "name": "removeToken", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_name", - "type": "string" - } - ], - "name": "getTokenAddressByName", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_symbol", - "type": "string" - } - ], - "name": "getTokenAddressBySymbol", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_swarmHash", - "type": "bytes" - } - ], - "name": "setTokenSwarmHash", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_token", - "type": "address" - } - ], - "name": "getTokenMetaData", - "outputs": [ - { - "name": "", - "type": "address" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "uint8" - }, - { - "name": "", - "type": "bytes" - }, - { - "name": "", - "type": "bytes" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "owner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_name", - "type": "string" - }, - { - "name": "_symbol", - "type": "string" - }, - { - "name": "_decimals", - "type": "uint8" - }, - { - "name": "_ipfsHash", - "type": "bytes" - }, - { - "name": "_swarmHash", - "type": "bytes" - } - ], - "name": "addToken", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_name", - "type": "string" - } - ], - "name": "setTokenName", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "tokens", - "outputs": [ - { - "name": "token", - "type": "address" - }, - { - "name": "name", - "type": "string" - }, - { - "name": "symbol", - "type": "string" - }, - { - "name": "decimals", - "type": "uint8" - }, - { - "name": "ipfsHash", - "type": "bytes" - }, - { - "name": "swarmHash", - "type": "bytes" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "tokenAddresses", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_name", - "type": "string" - } - ], - "name": "getTokenByName", - "outputs": [ - { - "name": "", - "type": "address" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "uint8" - }, - { - "name": "", - "type": "bytes" - }, - { - "name": "", - "type": "bytes" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getTokenAddresses", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_ipfsHash", - "type": "bytes" - } - ], - "name": "setTokenIpfsHash", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_symbol", - "type": "string" - } - ], - "name": "getTokenBySymbol", - "outputs": [ - { - "name": "", - "type": "address" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "string" - }, - { - "name": "", - "type": "uint8" - }, - { - "name": "", - "type": "bytes" - }, - { - "name": "", - "type": "bytes" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_token", - "type": "address" - }, - { - "name": "_symbol", - "type": "string" - } - ], - "name": "setTokenSymbol", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "name", - "type": "string" - }, - { - "indexed": false, - "name": "symbol", - "type": "string" - }, - { - "indexed": false, - "name": "decimals", - "type": "uint8" - }, - { - "indexed": false, - "name": "ipfsHash", - "type": "bytes" - }, - { - "indexed": false, - "name": "swarmHash", - "type": "bytes" - } - ], - "name": "LogAddToken", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "name", - "type": "string" - }, - { - "indexed": false, - "name": "symbol", - "type": "string" - }, - { - "indexed": false, - "name": "decimals", - "type": "uint8" - }, - { - "indexed": false, - "name": "ipfsHash", - "type": "bytes" - }, - { - "indexed": false, - "name": "swarmHash", - "type": "bytes" - } - ], - "name": "LogRemoveToken", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "oldName", - "type": "string" - }, - { - "indexed": false, - "name": "newName", - "type": "string" - } - ], - "name": "LogTokenNameChange", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "oldSymbol", - "type": "string" - }, - { - "indexed": false, - "name": "newSymbol", - "type": "string" - } - ], - "name": "LogTokenSymbolChange", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "oldIpfsHash", - "type": "bytes" - }, - { - "indexed": false, - "name": "newIpfsHash", - "type": "bytes" - } - ], - "name": "LogTokenIpfsHashChange", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "token", - "type": "address" - }, - { - "indexed": false, - "name": "oldSwarmHash", - "type": "bytes" - }, - { - "indexed": false, - "name": "newSwarmHash", - "type": "bytes" - } - ], - "name": "LogTokenSwarmHashChange", - "type": "event" - } - ], - "networks": { - "1": { - "address": "0x926a74c5c36adf004c87399e65f75628b0f98d2c" - }, - "3": { - "address": "0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed" - }, - "4": { - "address": "0x4e9aad8184de8833365fea970cd9149372fdf1e6" - }, - "42": { - "address": "0xf18e504561f4347bea557f3d4558f559dddbae7f" - }, - "50": { - "address": "0x0b1ba0af832d7c05fd64161e0db78e85978e8082" - } - } -} diff --git a/packages/order-watcher/src/compact_artifacts/TokenTransferProxy.json b/packages/order-watcher/src/compact_artifacts/TokenTransferProxy.json deleted file mode 100644 index 8cf551ddb..000000000 --- a/packages/order-watcher/src/compact_artifacts/TokenTransferProxy.json +++ /dev/null @@ -1,187 +0,0 @@ -{ - "contract_name": "TokenTransferProxy", - "abi": [ - { - "constant": false, - "inputs": [ - { - "name": "token", - "type": "address" - }, - { - "name": "from", - "type": "address" - }, - { - "name": "to", - "type": "address" - }, - { - "name": "value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "target", - "type": "address" - } - ], - "name": "addAuthorizedAddress", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "uint256" - } - ], - "name": "authorities", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "target", - "type": "address" - } - ], - "name": "removeAuthorizedAddress", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "owner", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "", - "type": "address" - } - ], - "name": "authorized", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "getAuthorizedAddresses", - "outputs": [ - { - "name": "", - "type": "address[]" - } - ], - "payable": false, - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "payable": false, - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "target", - "type": "address" - }, - { - "indexed": true, - "name": "caller", - "type": "address" - } - ], - "name": "LogAuthorizedAddressAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "target", - "type": "address" - }, - { - "indexed": true, - "name": "caller", - "type": "address" - } - ], - "name": "LogAuthorizedAddressRemoved", - "type": "event" - } - ], - "networks": { - "1": { - "address": "0x8da0d80f5007ef1e431dd2127178d224e32c2ef4" - }, - "3": { - "address": "0x4e9aad8184de8833365fea970cd9149372fdf1e6" - }, - "4": { - "address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d" - }, - "42": { - "address": "0x087eed4bc1ee3de49befbd66c662b434b15d49d4" - }, - "50": { - "address": "0x1dc4c1cefef38a777b15aa20260a54e584b16c48" - } - } -} diff --git a/packages/order-watcher/src/compact_artifacts/WETH9.json b/packages/order-watcher/src/compact_artifacts/WETH9.json new file mode 100644 index 000000000..f635276a1 --- /dev/null +++ b/packages/order-watcher/src/compact_artifacts/WETH9.json @@ -0,0 +1,100 @@ +{ + "contract_name": "WETH9", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": true, + "name": "_spender", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": false, + "name": "_value", + "type": "uint256" + } + ], + "name": "Withdrawal", + "type": "event" + } + ], + "networks": { + "1": { + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "3": { + "address": "0xc00fd9820cd2898cc4c054b7bf142de637ad129a" + }, + "4": { + "address": "0xc778417e063141139fce010982780140aa0cd5ab" + }, + "42": { + "address": "0x653e49e301e508a13237c0ddc98ae7d4cd2667a1" + }, + "50": { + "address": "0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c" + } + } +} diff --git a/packages/order-watcher/src/compact_artifacts/ZRX.json b/packages/order-watcher/src/compact_artifacts/ZRX.json deleted file mode 100644 index e40b8f268..000000000 --- a/packages/order-watcher/src/compact_artifacts/ZRX.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "contract_name": "ZRX", - "networks": { - "1": { - "address": "0xe41d2489571d322189246dafa5ebde1f4699f498" - }, - "3": { - "address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d" - }, - "4": { - "address": "0x00f58d6d585f84b2d7267940cede30ce2fe6eae8" - }, - "42": { - "address": "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570" - }, - "50": { - "address": "0x1d7022f5b17d2f8b695918fb48fa1089c9f85401" - } - } -} diff --git a/packages/order-watcher/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts b/packages/order-watcher/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts new file mode 100644 index 000000000..a1de22a5e --- /dev/null +++ b/packages/order-watcher/src/fetchers/asset_balance_and_proxy_allowance_fetcher.ts @@ -0,0 +1,74 @@ +// tslint:disable:no-unnecessary-type-assertion +import { BlockParamLiteral, ERC20TokenWrapper, ERC721TokenWrapper } from '@0xproject/contract-wrappers'; +import { AbstractBalanceAndProxyAllowanceFetcher, assetDataUtils } from '@0xproject/order-utils'; +import { AssetProxyId, ERC20AssetData, ERC721AssetData } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; + +export class AssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher { + private readonly _erc20Token: ERC20TokenWrapper; + private readonly _erc721Token: ERC721TokenWrapper; + private readonly _stateLayer: BlockParamLiteral; + constructor(erc20Token: ERC20TokenWrapper, erc721Token: ERC721TokenWrapper, stateLayer: BlockParamLiteral) { + this._erc20Token = erc20Token; + this._erc721Token = erc721Token; + this._stateLayer = stateLayer; + } + public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { + const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); + if (decodedAssetData.assetProxyId === AssetProxyId.ERC20) { + const decodedERC20AssetData = decodedAssetData as ERC20AssetData; + const balance = await this._erc20Token.getBalanceAsync(decodedERC20AssetData.tokenAddress, userAddress, { + defaultBlock: this._stateLayer, + }); + return balance; + } else { + const decodedERC721AssetData = decodedAssetData as ERC721AssetData; + const tokenOwner = await this._erc721Token.getOwnerOfAsync( + decodedERC721AssetData.tokenAddress, + decodedERC721AssetData.tokenId, + { + defaultBlock: this._stateLayer, + }, + ); + const balance = tokenOwner === userAddress ? new BigNumber(1) : new BigNumber(0); + return balance; + } + } + public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { + const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); + if (decodedAssetData.assetProxyId === AssetProxyId.ERC20) { + const decodedERC20AssetData = decodedAssetData as ERC20AssetData; + const proxyAllowance = await this._erc20Token.getProxyAllowanceAsync( + decodedERC20AssetData.tokenAddress, + userAddress, + { + defaultBlock: this._stateLayer, + }, + ); + return proxyAllowance; + } else { + const decodedERC721AssetData = decodedAssetData as ERC721AssetData; + + const isApprovedForAll = await this._erc721Token.isProxyApprovedForAllAsync( + decodedERC721AssetData.tokenAddress, + userAddress, + { + defaultBlock: this._stateLayer, + }, + ); + if (isApprovedForAll) { + return new BigNumber(this._erc20Token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + } else { + const isApproved = await this._erc721Token.isProxyApprovedAsync( + decodedERC721AssetData.tokenAddress, + decodedERC721AssetData.tokenId, + { + defaultBlock: this._stateLayer, + }, + ); + const proxyAllowance = isApproved ? new BigNumber(1) : new BigNumber(0); + return proxyAllowance; + } + } + } +} diff --git a/packages/order-watcher/src/fetchers/order_filled_cancelled_fetcher.ts b/packages/order-watcher/src/fetchers/order_filled_cancelled_fetcher.ts new file mode 100644 index 000000000..bfad1a48c --- /dev/null +++ b/packages/order-watcher/src/fetchers/order_filled_cancelled_fetcher.ts @@ -0,0 +1,27 @@ +// tslint:disable:no-unnecessary-type-assertion +import { BlockParamLiteral, ExchangeWrapper } from '@0xproject/contract-wrappers'; +import { AbstractOrderFilledCancelledFetcher } from '@0xproject/order-utils'; +import { BigNumber } from '@0xproject/utils'; + +export class OrderFilledCancelledFetcher implements AbstractOrderFilledCancelledFetcher { + private readonly _exchange: ExchangeWrapper; + private readonly _stateLayer: BlockParamLiteral; + constructor(exchange: ExchangeWrapper, stateLayer: BlockParamLiteral) { + this._exchange = exchange; + this._stateLayer = stateLayer; + } + public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> { + const filledTakerAmount = this._exchange.getFilledTakerAssetAmountAsync(orderHash, { + defaultBlock: this._stateLayer, + }); + return filledTakerAmount; + } + public async isOrderCancelledAsync(orderHash: string): Promise<boolean> { + const isCancelled = await this._exchange.isCancelledAsync(orderHash); + return isCancelled; + } + public getZRXAssetData(): string { + const zrxAssetData = this._exchange.getZRXAssetData(); + return zrxAssetData; + } +} diff --git a/packages/order-watcher/src/index.ts b/packages/order-watcher/src/index.ts index 390003b1d..5f84554c8 100644 --- a/packages/order-watcher/src/index.ts +++ b/packages/order-watcher/src/index.ts @@ -4,4 +4,5 @@ export { OrderStateValid, OrderStateInvalid, OrderState } from '@0xproject/types export { OnOrderStateChangeCallback, OrderWatcherConfig } from './types'; -export { BlockParamLiteral, BlockParam, Order, Provider, SignedOrder } from '@0xproject/types'; +export { Order, SignedOrder } from '@0xproject/types'; +export { BlockParamLiteral, BlockParam, Provider } from 'ethereum-types'; diff --git a/packages/order-watcher/src/order_watcher/collision_resistant_abi_decoder.ts b/packages/order-watcher/src/order_watcher/collision_resistant_abi_decoder.ts new file mode 100644 index 000000000..e13663c7a --- /dev/null +++ b/packages/order-watcher/src/order_watcher/collision_resistant_abi_decoder.ts @@ -0,0 +1,54 @@ +import { AbiDecoder } from '@0xproject/utils'; +import { ContractAbi, DecodedLogArgs, LogEntry, LogWithDecodedArgs, RawLog } from 'ethereum-types'; + +const TOKEN_TYPE_COLLISION = `Token can't be marked as ERC20 and ERC721 at the same time`; + +/** + * ERC20 and ERC721 have some events with different args but colliding signature. + * For exmaple: + * Transfer(_from address, _to address, _value uint256) + * Transfer(_from address, _to address, _tokenId uint256) + * Both have the signature: + * Transfer(address,address,uint256) + * + * In order to correctly decode those events we need to know the token type by address in advance. + * You can pass it by calling `this.addERC20Token(address)` or `this.addERC721Token(address)` + */ +export class CollisionResistanceAbiDecoder { + private readonly _erc20AbiDecoder: AbiDecoder; + private readonly _erc721AbiDecoder: AbiDecoder; + private readonly _restAbiDecoder: AbiDecoder; + private readonly _knownERC20Tokens = new Set(); + private readonly _knownERC721Tokens = new Set(); + constructor(erc20Abi: ContractAbi, erc721Abi: ContractAbi, abis: ContractAbi[]) { + this._erc20AbiDecoder = new AbiDecoder([erc20Abi]); + this._erc721AbiDecoder = new AbiDecoder([erc721Abi]); + this._restAbiDecoder = new AbiDecoder(abis); + } + public tryToDecodeLogOrNoop<ArgsType extends DecodedLogArgs>(log: LogEntry): LogWithDecodedArgs<ArgsType> | RawLog { + if (this._knownERC20Tokens.has(log.address)) { + const maybeDecodedERC20Log = this._erc20AbiDecoder.tryToDecodeLogOrNoop(log); + return maybeDecodedERC20Log; + } else if (this._knownERC721Tokens.has(log.address)) { + const maybeDecodedERC721Log = this._erc721AbiDecoder.tryToDecodeLogOrNoop(log); + return maybeDecodedERC721Log; + } else { + const maybeDecodedLog = this._restAbiDecoder.tryToDecodeLogOrNoop(log); + return maybeDecodedLog; + } + } + // Hints the ABI decoder that a particular token address is ERC20 and events from it should be decoded as ERC20 events + public addERC20Token(address: string): void { + if (this._knownERC721Tokens.has(address)) { + throw new Error(TOKEN_TYPE_COLLISION); + } + this._knownERC20Tokens.add(address); + } + // Hints the ABI decoder that a particular token address is ERC721 and events from it should be decoded as ERC721 events + public addERC721Token(address: string): void { + if (this._knownERC20Tokens.has(address)) { + throw new Error(TOKEN_TYPE_COLLISION); + } + this._knownERC721Tokens.add(address); + } +} 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 new file mode 100644 index 000000000..cc70bd5d7 --- /dev/null +++ b/packages/order-watcher/src/order_watcher/dependent_order_hashes_tracker.ts @@ -0,0 +1,230 @@ +// tslint:disable:no-unnecessary-type-assertion +import { assetDataUtils, orderHashUtils } from '@0xproject/order-utils'; +import { AssetProxyId, ERC20AssetData, ERC721AssetData, SignedOrder } from '@0xproject/types'; +import { BigNumber } from '@0xproject/utils'; +import * as _ from 'lodash'; + +export interface OrderHashesByMakerAddress { + [makerAddress: string]: Set<string>; +} + +export interface OrderHashesByERC20ByMakerAddress { + [makerAddress: string]: { + [erc20TokenAddress: string]: Set<string>; + }; +} + +export interface OrderHashesByERC721AddressByTokenIdByMakerAddress { + [makerAddress: string]: { + [erc721TokenAddress: string]: { + // Ideally erc721TokenId should be a BigNumber, but it's not a valid index type so we just convert it to a string before using it as an index + [erc721TokenId: string]: Set<string>; + }; + }; +} + +/** + */ +export class DependentOrderHashesTracker { + private readonly _zrxTokenAddress: string; + // `_orderHashesByMakerAddress` is redundant and could be generated from + // `_orderHashesByERC20ByMakerAddress` and `_orderHashesByERC721AddressByTokenIdByMakerAddress` + // on the fly by merging all the entries together but it's more complex and computationally heavy. + // We might change that in future if we're move memory-constrained. + private readonly _orderHashesByMakerAddress: OrderHashesByMakerAddress = {}; + private readonly _orderHashesByERC20ByMakerAddress: OrderHashesByERC20ByMakerAddress = {}; + private readonly _orderHashesByERC721AddressByTokenIdByMakerAddress: OrderHashesByERC721AddressByTokenIdByMakerAddress = {}; + constructor(zrxTokenAddress: string) { + this._zrxTokenAddress = zrxTokenAddress; + } + public getDependentOrderHashesByERC721ByMaker(makerAddress: string, tokenAddress: string): string[] { + const orderHashSets = _.values( + this._orderHashesByERC721AddressByTokenIdByMakerAddress[makerAddress][tokenAddress], + ); + const orderHashList = _.reduce( + orderHashSets, + (accumulator, orderHashSet) => [...accumulator, ...orderHashSet], + [] as string[], + ); + const uniqueOrderHashList = _.uniq(orderHashList); + return uniqueOrderHashList; + } + public getDependentOrderHashesByMaker(makerAddress: string): string[] { + const dependentOrderHashes = Array.from(this._orderHashesByMakerAddress[makerAddress]); + return dependentOrderHashes; + } + public getDependentOrderHashesByAssetDataByMaker(makerAddress: string, assetData: string): string[] { + const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData); + const dependentOrderHashes = + decodedAssetData.assetProxyId === AssetProxyId.ERC20 + ? this._getDependentOrderHashesByERC20AssetData(makerAddress, assetData) + : this._getDependentOrderHashesByERC721AssetData(makerAddress, assetData); + 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._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._removeFromERC20DependentOrderhashes(signedOrder, this._zrxTokenAddress); + this._removeFromMakerDependentOrderhashes(signedOrder); + } + private _getDependentOrderHashesByERC20AssetData(makerAddress: string, erc20AssetData: string): string[] { + const tokenAddress = assetDataUtils.decodeERC20AssetData(erc20AssetData).tokenAddress; + let dependentOrderHashes: string[] = []; + if ( + !_.isUndefined(this._orderHashesByERC20ByMakerAddress[makerAddress]) && + !_.isUndefined(this._orderHashesByERC20ByMakerAddress[makerAddress][tokenAddress]) + ) { + dependentOrderHashes = Array.from(this._orderHashesByERC20ByMakerAddress[makerAddress][tokenAddress]); + } + return dependentOrderHashes; + } + private _getDependentOrderHashesByERC721AssetData(makerAddress: string, erc721AssetData: string): string[] { + const tokenAddress = assetDataUtils.decodeERC721AssetData(erc721AssetData).tokenAddress; + const tokenId = assetDataUtils.decodeERC721AssetData(erc721AssetData).tokenId; + let dependentOrderHashes: string[] = []; + if ( + !_.isUndefined(this._orderHashesByERC721AddressByTokenIdByMakerAddress[makerAddress]) && + !_.isUndefined(this._orderHashesByERC721AddressByTokenIdByMakerAddress[makerAddress][tokenAddress]) && + !_.isUndefined( + this._orderHashesByERC721AddressByTokenIdByMakerAddress[makerAddress][tokenAddress][tokenId.toString()], + ) + ) { + dependentOrderHashes = Array.from( + this._orderHashesByERC721AddressByTokenIdByMakerAddress[makerAddress][tokenAddress][tokenId.toString()], + ); + } + return dependentOrderHashes; + } + private _addToERC20DependentOrderHashes(signedOrder: SignedOrder, erc20TokenAddress: string): void { + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + if (_.isUndefined(this._orderHashesByERC20ByMakerAddress[signedOrder.makerAddress])) { + this._orderHashesByERC20ByMakerAddress[signedOrder.makerAddress] = {}; + } + if (_.isUndefined(this._orderHashesByERC20ByMakerAddress[signedOrder.makerAddress][erc20TokenAddress])) { + this._orderHashesByERC20ByMakerAddress[signedOrder.makerAddress][erc20TokenAddress] = new Set(); + } + this._orderHashesByERC20ByMakerAddress[signedOrder.makerAddress][erc20TokenAddress].add(orderHash); + } + private _addToERC721DependentOrderHashes( + signedOrder: SignedOrder, + erc721TokenAddress: string, + tokenId: BigNumber, + ): void { + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + if (_.isUndefined(this._orderHashesByERC721AddressByTokenIdByMakerAddress[signedOrder.makerAddress])) { + this._orderHashesByERC721AddressByTokenIdByMakerAddress[signedOrder.makerAddress] = {}; + } + + if ( + _.isUndefined( + this._orderHashesByERC721AddressByTokenIdByMakerAddress[signedOrder.makerAddress][erc721TokenAddress], + ) + ) { + this._orderHashesByERC721AddressByTokenIdByMakerAddress[signedOrder.makerAddress][erc721TokenAddress] = {}; + } + + if ( + _.isUndefined( + this._orderHashesByERC721AddressByTokenIdByMakerAddress[signedOrder.makerAddress][erc721TokenAddress][ + tokenId.toString() + ], + ) + ) { + this._orderHashesByERC721AddressByTokenIdByMakerAddress[signedOrder.makerAddress][erc721TokenAddress][ + tokenId.toString() + ] = new Set(); + } + + this._orderHashesByERC721AddressByTokenIdByMakerAddress[signedOrder.makerAddress][erc721TokenAddress][ + tokenId.toString() + ].add(orderHash); + } + private _addToMakerDependentOrderHashes(signedOrder: SignedOrder): void { + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + if (_.isUndefined(this._orderHashesByMakerAddress[signedOrder.makerAddress])) { + this._orderHashesByMakerAddress[signedOrder.makerAddress] = new Set(); + } + this._orderHashesByMakerAddress[signedOrder.makerAddress].add(orderHash); + } + private _removeFromERC20DependentOrderhashes(signedOrder: SignedOrder, erc20TokenAddress: string): void { + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + this._orderHashesByERC20ByMakerAddress[signedOrder.makerAddress][erc20TokenAddress].delete(orderHash); + + if (_.isEmpty(this._orderHashesByERC20ByMakerAddress[signedOrder.makerAddress][erc20TokenAddress])) { + delete this._orderHashesByERC20ByMakerAddress[signedOrder.makerAddress][erc20TokenAddress]; + } + + if (_.isEmpty(this._orderHashesByERC20ByMakerAddress[signedOrder.makerAddress])) { + delete this._orderHashesByERC20ByMakerAddress[signedOrder.makerAddress]; + } + } + private _removeFromERC721DependentOrderhashes( + signedOrder: SignedOrder, + erc721TokenAddress: string, + tokenId: BigNumber, + ): void { + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + this._orderHashesByERC721AddressByTokenIdByMakerAddress[signedOrder.makerAddress][erc721TokenAddress][ + tokenId.toString() + ].delete(orderHash); + + if ( + _.isEmpty( + this._orderHashesByERC721AddressByTokenIdByMakerAddress[signedOrder.makerAddress][erc721TokenAddress][ + tokenId.toString() + ], + ) + ) { + delete this._orderHashesByERC721AddressByTokenIdByMakerAddress[signedOrder.makerAddress][ + erc721TokenAddress + ][tokenId.toString()]; + } + + if ( + _.isEmpty( + this._orderHashesByERC721AddressByTokenIdByMakerAddress[signedOrder.makerAddress][erc721TokenAddress], + ) + ) { + delete this._orderHashesByERC721AddressByTokenIdByMakerAddress[signedOrder.makerAddress][ + erc721TokenAddress + ]; + } + + if (_.isEmpty(this._orderHashesByERC721AddressByTokenIdByMakerAddress[signedOrder.makerAddress])) { + delete this._orderHashesByERC721AddressByTokenIdByMakerAddress[signedOrder.makerAddress]; + } + } + private _removeFromMakerDependentOrderhashes(signedOrder: SignedOrder): void { + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + this._orderHashesByMakerAddress[signedOrder.makerAddress].delete(orderHash); + + if (_.isEmpty(this._orderHashesByMakerAddress[signedOrder.makerAddress])) { + delete this._orderHashesByMakerAddress[signedOrder.makerAddress]; + } + } +} diff --git a/packages/order-watcher/src/order_watcher/event_watcher.ts b/packages/order-watcher/src/order_watcher/event_watcher.ts index 8ad52989b..9509c75de 100644 --- a/packages/order-watcher/src/order_watcher/event_watcher.ts +++ b/packages/order-watcher/src/order_watcher/event_watcher.ts @@ -1,6 +1,6 @@ -import { BlockParamLiteral, LogEntry } from '@0xproject/types'; import { intervalUtils, logUtils } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { BlockParamLiteral, LogEntry, Provider } from 'ethereum-types'; import { Block, BlockAndLogStreamer, Log } from 'ethereumjs-blockstream'; import * as _ from 'lodash'; @@ -20,21 +20,21 @@ enum LogEventState { */ export class EventWatcher { private readonly _web3Wrapper: Web3Wrapper; + private readonly _stateLayer: BlockParamLiteral; + private readonly _isVerbose: boolean; private _blockAndLogStreamerIfExists: BlockAndLogStreamer<Block, Log> | undefined; private _blockAndLogStreamIntervalIfExists?: NodeJS.Timer; private _onLogAddedSubscriptionToken: string | undefined; private _onLogRemovedSubscriptionToken: string | undefined; private readonly _pollingIntervalMs: number; - private readonly _stateLayer: BlockParamLiteral; - private readonly _isVerbose: boolean; constructor( - web3Wrapper: Web3Wrapper, + provider: Provider, pollingIntervalIfExistsMs: undefined | number, stateLayer: BlockParamLiteral, isVerbose: boolean, ) { this._isVerbose = isVerbose; - this._web3Wrapper = web3Wrapper; + this._web3Wrapper = new Web3Wrapper(provider); this._stateLayer = stateLayer; this._pollingIntervalMs = _.isUndefined(pollingIntervalIfExistsMs) ? DEFAULT_EVENT_POLLING_INTERVAL_MS diff --git a/packages/order-watcher/src/order_watcher/expiration_watcher.ts b/packages/order-watcher/src/order_watcher/expiration_watcher.ts index c1f34d13d..6eadf14c7 100644 --- a/packages/order-watcher/src/order_watcher/expiration_watcher.ts +++ b/packages/order-watcher/src/order_watcher/expiration_watcher.ts @@ -68,8 +68,8 @@ export class ExpirationWatcher { private _pruneExpiredOrders(callback: (orderHash: string) => void): void { const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs(); while (true) { - const hasTrakedOrders = this._orderHashByExpirationRBTree.size === 0; - if (hasTrakedOrders) { + const hasNoTrackedOrders = this._orderHashByExpirationRBTree.size === 0; + if (hasNoTrackedOrders) { break; } const nextOrderHashToExpire = this._orderHashByExpirationRBTree.min(); diff --git a/packages/order-watcher/src/order_watcher/order_watcher.ts b/packages/order-watcher/src/order_watcher/order_watcher.ts index 765747e35..2dfbd4230 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher.ts @@ -1,55 +1,53 @@ +// tslint:disable:no-unnecessary-type-assertion import { - BalanceAndProxyAllowanceLazyStore, ContractWrappers, - OrderFilledCancelledLazyStore, + ERC20TokenApprovalEventArgs, + ERC20TokenEventArgs, + ERC20TokenEvents, + ERC20TokenTransferEventArgs, + ERC721TokenApprovalEventArgs, + ERC721TokenApprovalForAllEventArgs, + ERC721TokenEventArgs, + ERC721TokenEvents, + ERC721TokenTransferEventArgs, + ExchangeCancelEventArgs, + ExchangeCancelUpToEventArgs, + ExchangeEventArgs, + ExchangeEvents, + ExchangeFillEventArgs, + WETH9DepositEventArgs, + WETH9EventArgs, + WETH9Events, + WETH9WithdrawalEventArgs, } from '@0xproject/contract-wrappers'; import { schemas } from '@0xproject/json-schemas'; -import { getOrderHashHex, OrderStateUtils } from '@0xproject/order-utils'; import { - BlockParamLiteral, - ExchangeContractErrs, - LogEntryEvent, - LogWithDecodedArgs, - OrderState, - Provider, - SignedOrder, -} from '@0xproject/types'; + assetDataUtils, + BalanceAndProxyAllowanceLazyStore, + OrderFilledCancelledLazyStore, + orderHashUtils, + OrderStateUtils, +} from '@0xproject/order-utils'; +import { AssetProxyId, ExchangeContractErrs, OrderState, SignedOrder } from '@0xproject/types'; import { errorUtils, intervalUtils } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { BlockParamLiteral, LogEntryEvent, LogWithDecodedArgs, Provider } from 'ethereum-types'; import * as _ from 'lodash'; import { artifacts } from '../artifacts'; -import { - EtherTokenDepositEventArgs, - EtherTokenEventArgs, - EtherTokenEvents, - EtherTokenWithdrawalEventArgs, -} from '../generated_contract_wrappers/ether_token'; -import { - ExchangeEventArgs, - ExchangeEvents, - ExchangeLogCancelEventArgs, - ExchangeLogFillEventArgs, -} from '../generated_contract_wrappers/exchange'; -import { - TokenApprovalEventArgs, - TokenEventArgs, - TokenEvents, - TokenTransferEventArgs, -} from '../generated_contract_wrappers/token'; +import { AssetBalanceAndProxyAllowanceFetcher } from '../fetchers/asset_balance_and_proxy_allowance_fetcher'; +import { OrderFilledCancelledFetcher } from '../fetchers/order_filled_cancelled_fetcher'; +import { orderWatcherPartialConfigSchema } from '../schemas/order_watcher_partial_config_schema'; import { OnOrderStateChangeCallback, OrderWatcherConfig, OrderWatcherError } from '../types'; import { assert } from '../utils/assert'; +import { CollisionResistanceAbiDecoder } from './collision_resistant_abi_decoder'; +import { DependentOrderHashesTracker } from './dependent_order_hashes_tracker'; import { EventWatcher } from './event_watcher'; import { ExpirationWatcher } from './expiration_watcher'; -type ContractEventArgs = EtherTokenEventArgs | ExchangeEventArgs | TokenEventArgs; +const MILLISECONDS_IN_A_SECOND = 1000; -interface DependentOrderHashes { - [makerAddress: string]: { - [makerToken: string]: Set<string>; - }; -} +type ContractEventArgs = WETH9EventArgs | ExchangeEventArgs | ERC20TokenEventArgs | ERC721TokenEventArgs; interface OrderByOrderHash { [orderHash: string]: SignedOrder; @@ -59,8 +57,14 @@ interface OrderStateByOrderHash { [orderHash: string]: OrderState; } -// tslint:disable-next-line:custom-no-magic-numbers -const DEFAULT_CLEANUP_JOB_INTERVAL_MS = 1000 * 60 * 60; // 1h +const DEFAULT_ORDER_WATCHER_CONFIG: OrderWatcherConfig = { + orderExpirationCheckingIntervalMs: 50, + eventPollingIntervalMs: 200, + expirationMarginMs: 0, + // tslint:disable-next-line:custom-no-magic-numbers + cleanupJobIntervalMs: 1000 * 60 * 60, // 1h + isVerbose: true, +}; const STATE_LAYER = BlockParamLiteral.Latest; /** @@ -70,69 +74,86 @@ const STATE_LAYER = BlockParamLiteral.Latest; * the order should be deemed invalid. */ export class OrderWatcher { - private readonly _contractWrappers: ContractWrappers; + private readonly _dependentOrderHashesTracker: DependentOrderHashesTracker; private readonly _orderStateByOrderHashCache: OrderStateByOrderHash = {}; private readonly _orderByOrderHash: OrderByOrderHash = {}; - private readonly _dependentOrderHashes: DependentOrderHashes = {}; - private _callbackIfExists?: OnOrderStateChangeCallback; private readonly _eventWatcher: EventWatcher; - private readonly _web3Wrapper: Web3Wrapper; + private readonly _provider: Provider; + private readonly _collisionResistantAbiDecoder: CollisionResistanceAbiDecoder; private readonly _expirationWatcher: ExpirationWatcher; private readonly _orderStateUtils: OrderStateUtils; private readonly _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore; private readonly _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore; private readonly _cleanupJobInterval: number; private _cleanupJobIntervalIdIfExists?: NodeJS.Timer; - constructor(provider: Provider, networkId: number, config?: OrderWatcherConfig) { - this._web3Wrapper = new Web3Wrapper(provider); - const artifactJSONs = _.values(artifacts); - const abiArrays = _.map(artifactJSONs, artifact => artifact.abi); - _.forEach(abiArrays, abi => { - this._web3Wrapper.abiDecoder.addABI(abi); - }); - this._contractWrappers = new ContractWrappers(provider, { networkId }); - const pollingIntervalIfExistsMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs; - const isVerbose = !_.isUndefined(config) && !_.isUndefined(config.isVerbose) ? config.isVerbose : false; - this._eventWatcher = new EventWatcher(this._web3Wrapper, pollingIntervalIfExistsMs, STATE_LAYER, isVerbose); - this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( - this._contractWrappers.token, - STATE_LAYER, + private _callbackIfExists?: OnOrderStateChangeCallback; + constructor( + provider: Provider, + networkId: number, + partialConfig: Partial<OrderWatcherConfig> = DEFAULT_ORDER_WATCHER_CONFIG, + ) { + assert.isWeb3Provider('provider', provider); + assert.isNumber('networkId', networkId); + assert.doesConformToSchema('partialConfig', partialConfig, orderWatcherPartialConfigSchema); + const config = { + ...DEFAULT_ORDER_WATCHER_CONFIG, + ...partialConfig, + }; + + this._provider = provider; + this._collisionResistantAbiDecoder = new CollisionResistanceAbiDecoder( + artifacts.ERC20Token.abi, + artifacts.ERC721Token.abi, + [artifacts.EtherToken.abi, artifacts.Exchange.abi], ); - this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore( - this._contractWrappers.exchange, + const contractWrappers = new ContractWrappers(provider, { networkId }); + this._eventWatcher = new EventWatcher(provider, config.eventPollingIntervalMs, STATE_LAYER, config.isVerbose); + const balanceAndProxyAllowanceFetcher = new AssetBalanceAndProxyAllowanceFetcher( + contractWrappers.erc20Token, + contractWrappers.erc721Token, STATE_LAYER, ); - this._orderStateUtils = new OrderStateUtils( - this._balanceAndProxyAllowanceLazyStore, - this._orderFilledCancelledLazyStore, + this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( + balanceAndProxyAllowanceFetcher, ); - const orderExpirationCheckingIntervalMsIfExists = _.isUndefined(config) - ? undefined - : config.orderExpirationCheckingIntervalMs; + const orderFilledCancelledFetcher = new OrderFilledCancelledFetcher(contractWrappers.exchange, STATE_LAYER); + this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(orderFilledCancelledFetcher); + this._orderStateUtils = new OrderStateUtils(balanceAndProxyAllowanceFetcher, orderFilledCancelledFetcher); const expirationMarginIfExistsMs = _.isUndefined(config) ? undefined : config.expirationMarginMs; this._expirationWatcher = new ExpirationWatcher( expirationMarginIfExistsMs, - orderExpirationCheckingIntervalMsIfExists, + config.orderExpirationCheckingIntervalMs, ); - this._cleanupJobInterval = - _.isUndefined(config) || _.isUndefined(config.cleanupJobIntervalMs) - ? DEFAULT_CLEANUP_JOB_INTERVAL_MS - : config.cleanupJobIntervalMs; + this._cleanupJobInterval = config.cleanupJobIntervalMs; + const zrxTokenAddress = assetDataUtils.decodeERC20AssetData(orderFilledCancelledFetcher.getZRXAssetData()) + .tokenAddress; + this._dependentOrderHashesTracker = new DependentOrderHashesTracker(zrxTokenAddress); } /** * Add an order to the orderWatcher. Before the order is added, it's * signature is verified. * @param signedOrder The order you wish to start watching. */ - public addOrder(signedOrder: SignedOrder): void { + public async addOrderAsync(signedOrder: SignedOrder): Promise<void> { assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema); - const orderHash = getOrderHashHex(signedOrder); - assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker); - this._orderByOrderHash[orderHash] = signedOrder; - this._addToDependentOrderHashes(signedOrder, orderHash); - const milisecondsInASecond = 1000; - const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(milisecondsInASecond); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + await assert.isValidSignatureAsync(this._provider, orderHash, signedOrder.signature, signedOrder.makerAddress); + + const expirationUnixTimestampMs = signedOrder.expirationTimeSeconds.times(MILLISECONDS_IN_A_SECOND); this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs); + + this._orderByOrderHash[orderHash] = signedOrder; + 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); + } + }); } /** * Removes an order from the orderWatcher @@ -144,16 +165,10 @@ export class OrderWatcher { if (_.isUndefined(signedOrder)) { return; // noop } + this._dependentOrderHashesTracker.removeFromDependentOrderHashes(signedOrder); delete this._orderByOrderHash[orderHash]; - delete this._orderStateByOrderHashCache[orderHash]; - const zrxTokenAddress = this._orderFilledCancelledLazyStore.getZRXTokenAddress(); - - this._removeFromDependentOrderHashes(signedOrder.maker, zrxTokenAddress, orderHash); - if (zrxTokenAddress !== signedOrder.makerTokenAddress) { - this._removeFromDependentOrderHashes(signedOrder.maker, signedOrder.makerTokenAddress, orderHash); - } - this._expirationWatcher.removeOrder(orderHash); + delete this._orderStateByOrderHashCache[orderHash]; } /** * Starts an orderWatcher subscription. The callback will be called every time a watched order's @@ -202,21 +217,27 @@ export class OrderWatcher { const signedOrder = this._orderByOrderHash[orderHash]; this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(orderHash); - this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(orderHash); + this._orderFilledCancelledLazyStore.deleteIsCancelled(orderHash); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.makerTokenAddress, signedOrder.maker); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.makerTokenAddress, signedOrder.maker); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.takerTokenAddress, signedOrder.taker); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.takerTokenAddress, signedOrder.taker); + 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, + ); - const zrxTokenAddress = this._getZRXTokenAddress(); + const zrxAssetData = this._orderFilledCancelledLazyStore.getZRXAssetData(); if (!signedOrder.makerFee.isZero()) { - this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.maker); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.maker); + this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxAssetData, signedOrder.makerAddress); + this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxAssetData, signedOrder.makerAddress); } if (!signedOrder.takerFee.isZero()) { - this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.taker); - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.taker); + this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxAssetData, signedOrder.takerAddress); + this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxAssetData, signedOrder.takerAddress); } } private _onOrderExpired(orderHash: string): void { @@ -239,89 +260,122 @@ export class OrderWatcher { } return; } - const log = logIfExists as LogEntryEvent; // At this moment we are sure that no error occured and log is defined. - const maybeDecodedLog = this._web3Wrapper.abiDecoder.tryToDecodeLogOrNoop<ContractEventArgs>(log); + const maybeDecodedLog = this._collisionResistantAbiDecoder.tryToDecodeLogOrNoop<ContractEventArgs>( + // At this moment we are sure that no error occured and log is defined. + logIfExists as LogEntryEvent, + ); const isLogDecoded = !_.isUndefined(((maybeDecodedLog as any) as LogWithDecodedArgs<ContractEventArgs>).event); if (!isLogDecoded) { return; // noop } const decodedLog = (maybeDecodedLog as any) as LogWithDecodedArgs<ContractEventArgs>; - let makerToken: string; - let makerAddress: string; switch (decodedLog.event) { - case TokenEvents.Approval: { - // Invalidate cache - // tslint:disable-next-line:no-unnecessary-type-assertion - const args = decodedLog.args as TokenApprovalEventArgs; - this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(decodedLog.address, args._owner); - // Revalidate orders - makerToken = decodedLog.address; - makerAddress = args._owner; - if ( - !_.isUndefined(this._dependentOrderHashes[makerAddress]) && - !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) - ) { - const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); + case ERC20TokenEvents.Approval: + case ERC721TokenEvents.Approval: { + // ERC20 and ERC721 Transfer events have the same name so we need to distinguish them by args + if (!_.isUndefined(decodedLog.args._value)) { + // ERC20 + // Invalidate cache + const args = decodedLog.args as ERC20TokenApprovalEventArgs; + const tokenAssetData = assetDataUtils.encodeERC20AssetData(decodedLog.address); + this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(tokenAssetData, args._owner); + // Revalidate orders + const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( + args._owner, + tokenAssetData, + ); await this._emitRevalidateOrdersAsync(orderHashes); + break; + } else { + // ERC721 + // Invalidate cache + const args = decodedLog.args as ERC721TokenApprovalEventArgs; + const tokenAssetData = assetDataUtils.encodeERC721AssetData(decodedLog.address, args._tokenId); + this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(tokenAssetData, args._owner); + // Revalidate orders + const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( + args._owner, + tokenAssetData, + ); + await this._emitRevalidateOrdersAsync(orderHashes); + break; } - break; } - case TokenEvents.Transfer: { - // Invalidate cache - // tslint:disable-next-line:no-unnecessary-type-assertion - const args = decodedLog.args as TokenTransferEventArgs; - this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._from); - this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._to); - // Revalidate orders - makerToken = decodedLog.address; - makerAddress = args._from; - if ( - !_.isUndefined(this._dependentOrderHashes[makerAddress]) && - !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) - ) { - const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); + case ERC20TokenEvents.Transfer: + case ERC721TokenEvents.Transfer: { + // ERC20 and ERC721 Transfer events have the same name so we need to distinguish them by args + if (!_.isUndefined(decodedLog.args._value)) { + // ERC20 + // 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); + // Revalidate orders + const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( + args._from, + tokenAssetData, + ); await this._emitRevalidateOrdersAsync(orderHashes); + break; + } else { + // ERC721 + // 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); + // Revalidate orders + const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( + args._from, + tokenAssetData, + ); + await this._emitRevalidateOrdersAsync(orderHashes); + break; } + } + case ERC721TokenEvents.ApprovalForAll: { + // Invalidate cache + const args = decodedLog.args as ERC721TokenApprovalForAllEventArgs; + const tokenAddress = decodedLog.address; + this._balanceAndProxyAllowanceLazyStore.deleteAllERC721ProxyAllowance(tokenAddress, args._owner); + // Revalidate orders + const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByERC721ByMaker( + args._owner, + tokenAddress, + ); + await this._emitRevalidateOrdersAsync(orderHashes); break; } - case EtherTokenEvents.Deposit: { + case WETH9Events.Deposit: { // Invalidate cache - // tslint:disable-next-line:no-unnecessary-type-assertion - const args = decodedLog.args as EtherTokenDepositEventArgs; - this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner); + const args = decodedLog.args as WETH9DepositEventArgs; + const tokenAssetData = assetDataUtils.encodeERC20AssetData(decodedLog.address); + this._balanceAndProxyAllowanceLazyStore.deleteBalance(tokenAssetData, args._owner); // Revalidate orders - makerToken = decodedLog.address; - makerAddress = args._owner; - if ( - !_.isUndefined(this._dependentOrderHashes[makerAddress]) && - !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) - ) { - const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); - await this._emitRevalidateOrdersAsync(orderHashes); - } + const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( + args._owner, + tokenAssetData, + ); + await this._emitRevalidateOrdersAsync(orderHashes); break; } - case EtherTokenEvents.Withdrawal: { + case WETH9Events.Withdrawal: { // Invalidate cache - // tslint:disable-next-line:no-unnecessary-type-assertion - const args = decodedLog.args as EtherTokenWithdrawalEventArgs; - this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner); + const args = decodedLog.args as WETH9WithdrawalEventArgs; + const tokenAssetData = assetDataUtils.encodeERC20AssetData(decodedLog.address); + this._balanceAndProxyAllowanceLazyStore.deleteBalance(tokenAssetData, args._owner); // Revalidate orders - makerToken = decodedLog.address; - makerAddress = args._owner; - if ( - !_.isUndefined(this._dependentOrderHashes[makerAddress]) && - !_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken]) - ) { - const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]); - await this._emitRevalidateOrdersAsync(orderHashes); - } + const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByAssetDataByMaker( + args._owner, + tokenAssetData, + ); + await this._emitRevalidateOrdersAsync(orderHashes); break; } - case ExchangeEvents.LogFill: { + case ExchangeEvents.Fill: { // Invalidate cache - // tslint:disable-next-line:no-unnecessary-type-assertion - const args = decodedLog.args as ExchangeLogFillEventArgs; + const args = decodedLog.args as ExchangeFillEventArgs; this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(args.orderHash); // Revalidate orders const orderHash = args.orderHash; @@ -331,11 +385,10 @@ export class OrderWatcher { } break; } - case ExchangeEvents.LogCancel: { + case ExchangeEvents.Cancel: { // Invalidate cache - // tslint:disable-next-line:no-unnecessary-type-assertion - const args = decodedLog.args as ExchangeLogCancelEventArgs; - this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(args.orderHash); + const args = decodedLog.args as ExchangeCancelEventArgs; + this._orderFilledCancelledLazyStore.deleteIsCancelled(args.orderHash); // Revalidate orders const orderHash = args.orderHash; const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]); @@ -344,8 +397,16 @@ export class OrderWatcher { } break; } - case ExchangeEvents.LogError: - return; // noop + case ExchangeEvents.CancelUpTo: { + // TODO(logvinov): Do it smarter and actually look at the salt and order epoch + // Invalidate cache + const args = decodedLog.args as ExchangeCancelUpToEventArgs; + this._orderFilledCancelledLazyStore.deleteAllIsCancelled(); + // Revalidate orders + const orderHashes = this._dependentOrderHashesTracker.getDependentOrderHashesByMaker(args.makerAddress); + await this._emitRevalidateOrdersAsync(orderHashes); + break; + } default: throw errorUtils.spawnSwitchErr('decodedLog.event', decodedLog.event); @@ -356,7 +417,7 @@ export class OrderWatcher { const signedOrder = this._orderByOrderHash[orderHash]; // Most of these calls will never reach the network because the data is fetched from stores // and only updated when cache is invalidated - const orderState = await this._orderStateUtils.getOrderStateAsync(signedOrder); + const orderState = await this._orderStateUtils.getOpenOrderStateAsync(signedOrder); if (_.isUndefined(this._callbackIfExists)) { break; // Unsubscribe was called } @@ -369,31 +430,4 @@ export class OrderWatcher { this._callbackIfExists(null, orderState); } } - private _addToDependentOrderHashes(signedOrder: SignedOrder, orderHash: string): void { - if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker])) { - this._dependentOrderHashes[signedOrder.maker] = {}; - } - if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress])) { - this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress] = new Set(); - } - this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress].add(orderHash); - const zrxTokenAddress = this._getZRXTokenAddress(); - if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress])) { - this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress] = new Set(); - } - this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress].add(orderHash); - } - private _removeFromDependentOrderHashes(makerAddress: string, tokenAddress: string, orderHash: string): void { - this._dependentOrderHashes[makerAddress][tokenAddress].delete(orderHash); - if (this._dependentOrderHashes[makerAddress][tokenAddress].size === 0) { - delete this._dependentOrderHashes[makerAddress][tokenAddress]; - } - if (_.isEmpty(this._dependentOrderHashes[makerAddress])) { - delete this._dependentOrderHashes[makerAddress]; - } - } - private _getZRXTokenAddress(): string { - const zrxTokenAddress = this._orderFilledCancelledLazyStore.getZRXTokenAddress(); - return zrxTokenAddress; - } } diff --git a/packages/order-watcher/src/schemas/order_watcher_partial_config_schema.ts b/packages/order-watcher/src/schemas/order_watcher_partial_config_schema.ts new file mode 100644 index 000000000..bc7d40524 --- /dev/null +++ b/packages/order-watcher/src/schemas/order_watcher_partial_config_schema.ts @@ -0,0 +1,13 @@ +export const orderWatcherPartialConfigSchema = { + id: '/OrderWatcherPartialConfigSchema', + properties: { + stateLayer: { $ref: '/BlockParam' }, + orderExpirationCheckingIntervalMs: { type: 'number' }, + eventPollingIntervalMs: { type: 'number' }, + expirationMarginMs: { type: 'number' }, + cleanupJobIntervalMs: { type: 'number' }, + isVerbose: { type: 'boolean' }, + }, + type: 'object', + required: [], +}; diff --git a/packages/order-watcher/src/types.ts b/packages/order-watcher/src/types.ts index fd71267a2..27d892985 100644 --- a/packages/order-watcher/src/types.ts +++ b/packages/order-watcher/src/types.ts @@ -1,4 +1,5 @@ -import { LogEntryEvent, OrderState } from '@0xproject/types'; +import { OrderState } from '@0xproject/types'; +import { LogEntryEvent } from 'ethereum-types'; export enum OrderWatcherError { SubscriptionAlreadyPresent = 'SUBSCRIPTION_ALREADY_PRESENT', @@ -13,13 +14,14 @@ export type EventWatcherCallback = (err: null | Error, log?: LogEntryEvent) => v * expirationMarginMs: Amount of time before order expiry that you'd like to be notified * of an orders expiration. Default=0. * cleanupJobIntervalMs: How often to run a cleanup job which revalidates all the orders. Default=1hr. + * isVerbose: Weather the order watcher should be verbose. Default=true. */ export interface OrderWatcherConfig { - orderExpirationCheckingIntervalMs?: number; - eventPollingIntervalMs?: number; - expirationMarginMs?: number; - cleanupJobIntervalMs?: number; - isVerbose?: boolean; + orderExpirationCheckingIntervalMs: number; + eventPollingIntervalMs: number; + expirationMarginMs: number; + cleanupJobIntervalMs: number; + isVerbose: boolean; } export type OnOrderStateChangeCallback = (err: Error | null, orderState?: OrderState) => void; diff --git a/packages/order-watcher/src/utils/assert.ts b/packages/order-watcher/src/utils/assert.ts index 4a1441474..a891a60d2 100644 --- a/packages/order-watcher/src/utils/assert.ts +++ b/packages/order-watcher/src/utils/assert.ts @@ -5,13 +5,19 @@ import { Schema } from '@0xproject/json-schemas'; import { ECSignature } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; // tslint:enable:no-unused-variable +import { Provider } from 'ethereum-types'; -import { isValidSignature } from '@0xproject/order-utils'; +import { isValidSignatureAsync } from '@0xproject/order-utils'; export const assert = { ...sharedAssert, - isValidSignature(orderHash: string, ecSignature: ECSignature, signerAddress: string): void { - const isValid = isValidSignature(orderHash, ecSignature, signerAddress); + async isValidSignatureAsync( + provider: Provider, + orderHash: string, + signature: string, + signerAddress: string, + ): Promise<void> { + const isValid = await isValidSignatureAsync(provider, orderHash, signature, signerAddress); assert.assert(isValid, `Expected order with hash '${orderHash}' to have a valid signature`); }, }; diff --git a/packages/order-watcher/test/expiration_watcher_test.ts b/packages/order-watcher/test/expiration_watcher_test.ts index dfd3556bc..ea9923487 100644 --- a/packages/order-watcher/test/expiration_watcher_test.ts +++ b/packages/order-watcher/test/expiration_watcher_test.ts @@ -1,8 +1,9 @@ import { ContractWrappers } from '@0xproject/contract-wrappers'; +import { tokenUtils } from '@0xproject/contract-wrappers/lib/test/utils/token_utils'; import { BlockchainLifecycle, callbackErrorReporter } from '@0xproject/dev-utils'; import { FillScenarios } from '@0xproject/fill-scenarios'; -import { getOrderHashHex } from '@0xproject/order-utils'; -import { DoneCallback, Token } from '@0xproject/types'; +import { assetDataUtils, orderHashUtils } from '@0xproject/order-utils'; +import { DoneCallback } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import * as _ from 'lodash'; @@ -14,7 +15,6 @@ 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(); @@ -23,15 +23,16 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const MILISECONDS_IN_SECOND = 1000; describe('ExpirationWatcher', () => { - let contractWrappers: ContractWrappers; - let tokenUtils: TokenUtils; - let tokens: Token[]; + const config = { + networkId: constants.TESTRPC_NETWORK_ID, + }; + const contractWrappers = new ContractWrappers(provider, config); let userAddresses: string[]; let zrxTokenAddress: string; let fillScenarios: FillScenarios; - let exchangeContractAddress: string; - let makerTokenAddress: string; - let takerTokenAddress: string; + const exchangeContractAddress = contractWrappers.exchange.getContractAddress(); + let makerAssetData: string; + let takerAssetData: string; let coinbase: string; let makerAddress: string; let takerAddress: string; @@ -41,21 +42,26 @@ describe('ExpirationWatcher', () => { 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(); + await blockchainLifecycle.startAsync(); 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); + zrxTokenAddress = tokenUtils.getProtocolTokenAddress(); + fillScenarios = new FillScenarios( + provider, + userAddresses, + zrxTokenAddress, + exchangeContractAddress, + contractWrappers.erc20Proxy.getContractAddress(), + contractWrappers.erc721Proxy.getContractAddress(), + ); [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; - tokens = await contractWrappers.tokenRegistry.getTokensAsync(); - const [makerToken, takerToken] = tokenUtils.getDummyTokens(); - makerTokenAddress = makerToken.address; - takerTokenAddress = takerToken.address; + const [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + [makerAssetData, takerAssetData] = [ + assetDataUtils.encodeERC20AssetData(makerTokenAddress), + assetDataUtils.encodeERC20AssetData(takerTokenAddress), + ]; + }); + after(async () => { + await blockchainLifecycle.revertAsync(); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -75,15 +81,15 @@ describe('ExpirationWatcher', () => { const orderLifetimeSec = 60; const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec); const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, + makerAssetData, + takerAssetData, makerAddress, takerAddress, fillableAmount, expirationUnixTimestampSec, ); - const orderHash = getOrderHashHex(signedOrder); - expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(MILISECONDS_IN_SECOND)); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + expirationWatcher.addOrder(orderHash, signedOrder.expirationTimeSeconds.times(MILISECONDS_IN_SECOND)); const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done)((hash: string) => { expect(hash).to.be.equal(orderHash); expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.gte(expirationUnixTimestampSec); @@ -97,15 +103,15 @@ describe('ExpirationWatcher', () => { const orderLifetimeSec = 60; const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec); const signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, + makerAssetData, + takerAssetData, makerAddress, takerAddress, fillableAmount, expirationUnixTimestampSec, ); - const orderHash = getOrderHashHex(signedOrder); - expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(MILISECONDS_IN_SECOND)); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + expirationWatcher.addOrder(orderHash, signedOrder.expirationTimeSeconds.times(MILISECONDS_IN_SECOND)); const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done)(async (_hash: string) => { done(new Error('Emitted expiration went before the order actually expired')); }); @@ -122,31 +128,25 @@ describe('ExpirationWatcher', () => { const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime); const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime); const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, + makerAssetData, + takerAssetData, makerAddress, takerAddress, fillableAmount, order1ExpirationUnixTimestampSec, ); const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, + makerAssetData, + takerAssetData, makerAddress, takerAddress, fillableAmount, order2ExpirationUnixTimestampSec, ); - const orderHash1 = getOrderHashHex(signedOrder1); - const orderHash2 = getOrderHashHex(signedOrder2); - expirationWatcher.addOrder( - orderHash2, - signedOrder2.expirationUnixTimestampSec.times(MILISECONDS_IN_SECOND), - ); - expirationWatcher.addOrder( - orderHash1, - signedOrder1.expirationUnixTimestampSec.times(MILISECONDS_IN_SECOND), - ); + const orderHash1 = orderHashUtils.getOrderHashHex(signedOrder1); + const orderHash2 = orderHashUtils.getOrderHashHex(signedOrder2); + expirationWatcher.addOrder(orderHash2, signedOrder2.expirationTimeSeconds.times(MILISECONDS_IN_SECOND)); + expirationWatcher.addOrder(orderHash1, signedOrder1.expirationTimeSeconds.times(MILISECONDS_IN_SECOND)); const expirationOrder = [orderHash1, orderHash2]; const expectToBeCalledOnce = false; const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done, expectToBeCalledOnce)( @@ -169,31 +169,25 @@ describe('ExpirationWatcher', () => { const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime); const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime); const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, + makerAssetData, + takerAssetData, makerAddress, takerAddress, fillableAmount, order1ExpirationUnixTimestampSec, ); const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync( - makerTokenAddress, - takerTokenAddress, + makerAssetData, + takerAssetData, makerAddress, takerAddress, fillableAmount, order2ExpirationUnixTimestampSec, ); - const orderHash1 = getOrderHashHex(signedOrder1); - const orderHash2 = getOrderHashHex(signedOrder2); - expirationWatcher.addOrder( - orderHash1, - signedOrder1.expirationUnixTimestampSec.times(MILISECONDS_IN_SECOND), - ); - expirationWatcher.addOrder( - orderHash2, - signedOrder2.expirationUnixTimestampSec.times(MILISECONDS_IN_SECOND), - ); + const orderHash1 = orderHashUtils.getOrderHashHex(signedOrder1); + const orderHash2 = orderHashUtils.getOrderHashHex(signedOrder2); + expirationWatcher.addOrder(orderHash1, signedOrder1.expirationTimeSeconds.times(MILISECONDS_IN_SECOND)); + expirationWatcher.addOrder(orderHash2, signedOrder2.expirationTimeSeconds.times(MILISECONDS_IN_SECOND)); const expirationOrder = orderHash1 < orderHash2 ? [orderHash1, orderHash2] : [orderHash2, orderHash1]; const expectToBeCalledOnce = false; const callbackAsync = callbackErrorReporter.reportNoErrorCallbackErrors(done, expectToBeCalledOnce)( diff --git a/packages/order-watcher/test/global_hooks.ts b/packages/order-watcher/test/global_hooks.ts index 8ff4a120f..f64f1df78 100644 --- a/packages/order-watcher/test/global_hooks.ts +++ b/packages/order-watcher/test/global_hooks.ts @@ -1,5 +1,5 @@ import { devConstants } from '@0xproject/dev-utils'; -import { runV1MigrationsAsync } from '@0xproject/migrations'; +import { runV2MigrationsAsync } from '@0xproject/migrations'; import { provider } from './utils/web3_wrapper'; @@ -12,6 +12,6 @@ before('migrate contracts', async function(): Promise<void> { gas: devConstants.GAS_LIMIT, from: devConstants.TESTRPC_FIRST_ADDRESS, }; - const artifactsDir = `../migrations/artifacts/1.0.0`; - await runV1MigrationsAsync(provider, artifactsDir, txDefaults); + const artifactsDir = `../migrations/artifacts/2.0.0`; + await runV2MigrationsAsync(provider, artifactsDir, txDefaults); }); diff --git a/packages/order-watcher/test/order_watcher_test.ts b/packages/order-watcher/test/order_watcher_test.ts index 8e9223efe..00962bed0 100644 --- a/packages/order-watcher/test/order_watcher_test.ts +++ b/packages/order-watcher/test/order_watcher_test.ts @@ -1,8 +1,9 @@ // tslint:disable:no-unnecessary-type-assertion import { ContractWrappers } from '@0xproject/contract-wrappers'; +import { tokenUtils } from '@0xproject/contract-wrappers/lib/test/utils/token_utils'; import { BlockchainLifecycle, callbackErrorReporter } from '@0xproject/dev-utils'; import { FillScenarios } from '@0xproject/fill-scenarios'; -import { getOrderHashHex } from '@0xproject/order-utils'; +import { assetDataUtils, orderHashUtils } from '@0xproject/order-utils'; import { DoneCallback, ExchangeContractErrs, @@ -10,7 +11,6 @@ import { OrderStateInvalid, OrderStateValid, SignedOrder, - Token, } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; @@ -18,12 +18,15 @@ import * as chai from 'chai'; import * as _ from 'lodash'; import 'mocha'; +import { + DependentOrderHashesTracker, + OrderHashesByERC20ByMakerAddress, +} from '../src/order_watcher/dependent_order_hashes_tracker'; 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; @@ -33,37 +36,49 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); describe('OrderWatcher', () => { - let contractWrappers: ContractWrappers; - let tokens: Token[]; - let tokenUtils: TokenUtils; + const networkId = constants.TESTRPC_NETWORK_ID; + const config = { networkId }; + const contractWrappers = new ContractWrappers(provider, config); 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 makerAssetData: string; + let takerAssetData: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let makerAddress: string; + let takerAddress: string; + let coinbase: string; + let feeRecipient: string; let signedOrder: SignedOrder; let orderWatcher: OrderWatcher; const decimals = constants.ZRX_DECIMALS; const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); before(async () => { - const networkId = await web3Wrapper.getNetworkIdAsync(); - const config = { - networkId, - }; - contractWrappers = new ContractWrappers(provider, config); - orderWatcher = new OrderWatcher(provider, networkId); - exchangeContractAddress = contractWrappers.exchange.getContractAddress(); + await blockchainLifecycle.startAsync(); 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(); + zrxTokenAddress = tokenUtils.getProtocolTokenAddress(); + exchangeContractAddress = contractWrappers.exchange.getContractAddress(); + fillScenarios = new FillScenarios( + provider, + userAddresses, + zrxTokenAddress, + exchangeContractAddress, + contractWrappers.erc20Proxy.getContractAddress(), + contractWrappers.erc721Proxy.getContractAddress(), + ); + [coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses; + [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + [makerAssetData, takerAssetData] = [ + assetDataUtils.encodeERC20AssetData(makerTokenAddress), + assetDataUtils.encodeERC20AssetData(takerTokenAddress), + ]; + const orderWatcherConfig = {}; + orderWatcher = new OrderWatcher(provider, networkId, orderWatcherConfig); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -74,35 +89,40 @@ describe('OrderWatcher', () => { describe('#removeOrder', async () => { it('should successfully remove existing order', async () => { signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, fillableAmount, ); - const orderHash = getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + await orderWatcher.addOrderAsync(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); + const dependentOrderHashesTracker = (orderWatcher as any) + ._dependentOrderHashesTracker as DependentOrderHashesTracker; + let orderHashesByERC20ByMakerAddress: OrderHashesByERC20ByMakerAddress = (dependentOrderHashesTracker as any) + ._orderHashesByERC20ByMakerAddress; + expect(orderHashesByERC20ByMakerAddress[signedOrder.makerAddress][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(); + orderHashesByERC20ByMakerAddress = (dependentOrderHashesTracker as any)._orderHashesByERC20ByMakerAddress; + expect(orderHashesByERC20ByMakerAddress[signedOrder.makerAddress]).to.be.undefined(); }); it('should no-op when removing a non-existing order', async () => { signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, fillableAmount, ); - const orderHash = getOrderHashHex(signedOrder); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); const nonExistentOrderHash = `0x${orderHash .substr(2) .split('') @@ -123,20 +143,20 @@ describe('OrderWatcher', () => { describe('tests with cleanup', async () => { afterEach(async () => { orderWatcher.unsubscribe(); - const orderHash = getOrderHashHex(signedOrder); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); orderWatcher.removeOrder(orderHash); }); - it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => { + it('should emit orderStateInvalid when makerAddress allowance set to 0 for watched order', (done: DoneCallback) => { (async () => { signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, fillableAmount, ); - const orderHash = getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + 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; @@ -144,28 +164,32 @@ describe('OrderWatcher', () => { expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance); }); orderWatcher.subscribe(callback); - await contractWrappers.token.setProxyAllowanceAsync(makerToken.address, maker, new BigNumber(0)); + await contractWrappers.erc20Token.setProxyAllowanceAsync( + makerTokenAddress, + makerAddress, + 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, + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, fillableAmount, ); - orderWatcher.addOrder(signedOrder); + 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 = taker; + const anyRecipient = takerAddress; const transferAmount = new BigNumber(2); - await contractWrappers.token.transferAsync( - makerToken.address, + await contractWrappers.erc20Token.transferAsync( + makerTokenAddress, notTheMaker, anyRecipient, transferAmount, @@ -175,17 +199,17 @@ describe('OrderWatcher', () => { }, TIMEOUT_MS); })().catch(done); }); - it('should emit orderStateInvalid when maker moves balance backing watched order', (done: DoneCallback) => { + it('should emit orderStateInvalid when makerAddress moves balance backing watched order', (done: DoneCallback) => { (async () => { signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, fillableAmount, ); - const orderHash = getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + 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; @@ -193,22 +217,27 @@ describe('OrderWatcher', () => { 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); + const anyRecipient = takerAddress; + const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress); + await contractWrappers.erc20Token.transferAsync( + makerTokenAddress, + makerAddress, + 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, + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, fillableAmount, ); - const orderHash = getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.false(); @@ -218,29 +247,23 @@ describe('OrderWatcher', () => { }); orderWatcher.subscribe(callback); - const shouldThrowOnInsufficientBalanceOrAllowance = true; - await contractWrappers.exchange.fillOrderAsync( - signedOrder, - fillableAmount, - shouldThrowOnInsufficientBalanceOrAllowance, - taker, - ); + await contractWrappers.exchange.fillOrderAsync(signedOrder, fillableAmount, takerAddress); })().catch(done); }); it('should emit orderStateValid when watched order partially filled', (done: DoneCallback) => { (async () => { signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, fillableAmount, ); - const makerBalance = await contractWrappers.token.getBalanceAsync(makerToken.address, maker); + const makerBalance = await contractWrappers.erc20Token.getBalanceAsync(makerTokenAddress, makerAddress); const fillAmountInBaseUnits = new BigNumber(2); - const orderHash = getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.true(); @@ -249,22 +272,16 @@ describe('OrderWatcher', () => { const orderRelevantState = validOrderState.orderRelevantState; const remainingMakerBalance = makerBalance.sub(fillAmountInBaseUnits); const remainingFillable = fillableAmount.minus(fillAmountInBaseUnits); - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal( remainingFillable, ); - expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + expect(orderRelevantState.remainingFillableTakerAssetAmount).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, - ); + await contractWrappers.exchange.fillOrderAsync(signedOrder, fillAmountInBaseUnits, takerAddress); })().catch(done); }); it('should trigger the callback when orders backing ZRX allowance changes', (done: DoneCallback) => { @@ -272,19 +289,23 @@ describe('OrderWatcher', () => { const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals); signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerToken.address, - takerToken.address, + makerAssetData, + takerAssetData, makerFee, takerFee, - maker, - taker, + makerAddress, + takerAddress, fillableAmount, - taker, + takerAddress, ); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)(); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); orderWatcher.subscribe(callback); - await contractWrappers.token.setProxyAllowanceAsync(zrxTokenAddress, maker, new BigNumber(0)); + await contractWrappers.erc20Token.setProxyAllowanceAsync( + zrxTokenAddress, + makerAddress, + new BigNumber(0), + ); })().catch(done); }); describe('remainingFillable(M|T)akerTokenAmount', () => { @@ -293,65 +314,59 @@ describe('OrderWatcher', () => { 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, + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, makerFillableAmount, takerFillableAmount, ); const fillAmountInBaseUnits = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); - const orderHash = getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + await orderWatcher.addOrderAsync(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( + expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal( Web3Wrapper.toBaseUnitAmount(new BigNumber(16), decimals), ); - expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + expect(orderRelevantState.remainingFillableTakerAssetAmount).to.be.bignumber.equal( Web3Wrapper.toBaseUnitAmount(new BigNumber(8), decimals), ); }); orderWatcher.subscribe(callback); - const shouldThrowOnInsufficientBalanceOrAllowance = true; - await contractWrappers.exchange.fillOrderAsync( - signedOrder, - fillAmountInBaseUnits, - shouldThrowOnInsufficientBalanceOrAllowance, - taker, - ); + await contractWrappers.exchange.fillOrderAsync(signedOrder, fillAmountInBaseUnits, takerAddress); })().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, + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, fillableAmount, ); const changedMakerApprovalAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(3), decimals); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { const validOrderState = orderState as OrderStateValid; const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal( changedMakerApprovalAmount, ); - expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + expect(orderRelevantState.remainingFillableTakerAssetAmount).to.be.bignumber.equal( changedMakerApprovalAmount, ); }); orderWatcher.subscribe(callback); - await contractWrappers.token.setProxyAllowanceAsync( - makerToken.address, - maker, + await contractWrappers.erc20Token.setProxyAllowanceAsync( + makerTokenAddress, + makerAddress, changedMakerApprovalAmount, ); })().catch(done); @@ -359,83 +374,53 @@ describe('OrderWatcher', () => { it('should equal balance amount when balance amount is lowest', (done: DoneCallback) => { (async () => { signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, fillableAmount, ); - const makerBalance = await contractWrappers.token.getBalanceAsync(makerToken.address, maker); + const makerBalance = await contractWrappers.erc20Token.getBalanceAsync( + makerTokenAddress, + makerAddress, + ); const remainingAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), decimals); const transferAmount = makerBalance.sub(remainingAmount); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(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( + expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal( remainingAmount, ); - expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal( + expect(orderRelevantState.remainingFillableTakerAssetAmount).to.be.bignumber.equal( remainingAmount, ); }); orderWatcher.subscribe(callback); - await contractWrappers.token.transferAsync( - makerToken.address, - maker, + await contractWrappers.erc20Token.transferAsync( + makerTokenAddress, + makerAddress, 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, + makerAssetData, + takerAssetData, makerFee, takerFee, - maker, - taker, + makerAddress, + takerAddress, fillableAmount, feeRecipient, ); @@ -444,20 +429,24 @@ describe('OrderWatcher', () => { const remainingTokenAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(4), decimals); const transferTokenAmount = makerFee.sub(remainingTokenAmount); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { const validOrderState = orderState as OrderStateValid; const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal( remainingFeeAmount, ); }); orderWatcher.subscribe(callback); - await contractWrappers.token.setProxyAllowanceAsync(zrxTokenAddress, maker, remainingFeeAmount); - await contractWrappers.token.transferAsync( - makerToken.address, - maker, + await contractWrappers.erc20Token.setProxyAllowanceAsync( + zrxTokenAddress, + makerAddress, + remainingFeeAmount, + ); + await contractWrappers.erc20Token.transferAsync( + makerTokenAddress, + makerAddress, constants.NULL_ADDRESS, transferTokenAmount, ); @@ -467,31 +456,30 @@ describe('OrderWatcher', () => { (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, + makerAssetData, + takerAssetData, makerFee, takerFee, - maker, - taker, + makerAddress, + takerAddress, fillableAmount, feeRecipient, ); - orderWatcher.addOrder(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { const validOrderState = orderState as OrderStateValid; const orderRelevantState = validOrderState.orderRelevantState; - expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal( + expect(orderRelevantState.remainingFillableMakerAssetAmount).to.be.bignumber.equal( fillableAmount, ); }); orderWatcher.subscribe(callback); - await contractWrappers.token.setProxyAllowanceAsync( - makerToken.address, - maker, + await contractWrappers.erc20Token.setProxyAllowanceAsync( + makerTokenAddress, + makerAddress, Web3Wrapper.toBaseUnitAmount(new BigNumber(100), decimals), ); })().catch(done); @@ -500,38 +488,37 @@ describe('OrderWatcher', () => { it('should emit orderStateInvalid when watched order cancelled', (done: DoneCallback) => { (async () => { signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerToken.address, - takerToken.address, - maker, - taker, + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, fillableAmount, ); - const orderHash = getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + 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); + expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError); }); orderWatcher.subscribe(callback); - - await contractWrappers.exchange.cancelOrderAsync(signedOrder, fillableAmount); + await contractWrappers.exchange.cancelOrderAsync(signedOrder); })().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, + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, fillableAmount, ); - const orderHash = getOrderHashHex(signedOrder); - orderWatcher.addOrder(signedOrder); + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + await orderWatcher.addOrderAsync(signedOrder); const callback = callbackErrorReporter.reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.false(); @@ -540,36 +527,108 @@ describe('OrderWatcher', () => { expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError); }); orderWatcher.subscribe(callback); - await contractWrappers.exchange.cancelOrderAsync( + await contractWrappers.exchange.fillOrderAsync( signedOrder, fillableAmount.minus(remainingFillableAmountInBaseUnits), + takerAddress, ); })().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); + describe('erc721', () => { + let makerErc721AssetData: string; + let makerErc721TokenAddress: string; + const tokenId = new BigNumber(42); + [makerErc721TokenAddress] = tokenUtils.getDummyERC721TokenAddresses(); + makerErc721AssetData = assetDataUtils.encodeERC721AssetData(makerErc721TokenAddress, tokenId); + const fillableErc721Amount = new BigNumber(1); + it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerErc721AssetData, + 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 set to 0 for watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerErc721AssetData, + 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 NFT backing watched order', (done: DoneCallback) => { + (async () => { + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerErc721AssetData, + 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); + }); }); }); }); // tslint:disable:max-file-line-count diff --git a/packages/order-watcher/test/utils/web3_wrapper.ts b/packages/order-watcher/test/utils/web3_wrapper.ts index f7d11f138..ab801fa7f 100644 --- a/packages/order-watcher/test/utils/web3_wrapper.ts +++ b/packages/order-watcher/test/utils/web3_wrapper.ts @@ -1,6 +1,6 @@ import { web3Factory } from '@0xproject/dev-utils'; -import { Provider } from '@0xproject/types'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { Provider } from 'ethereum-types'; const provider: Provider = web3Factory.getRpcProvider({ shouldUseInProcessGanache: true }); const web3Wrapper = new Web3Wrapper(provider); diff --git a/packages/order-watcher/tslint.json b/packages/order-watcher/tslint.json index ffaefe83a..059573ce7 100644 --- a/packages/order-watcher/tslint.json +++ b/packages/order-watcher/tslint.json @@ -1,3 +1,6 @@ { + "rules": { + "prefer-readonly": true + }, "extends": ["@0xproject/tslint-config"] } |