Compare commits

...

279 Commits

Author SHA1 Message Date
Leonid Logvinov
32e8e52ad7 0.22.1 2017-10-19 17:09:30 +03:00
Leonid Logvinov
77b3a43e17 Update CHANGELOG 2017-10-19 17:09:23 +03:00
Leonid
ddec91ba52 Merge pull request #195 from 0xProject/minification
Reduce final bundle size by 11% (82kB)
2017-10-19 16:31:06 +03:00
Leonid Logvinov
0329b36430 Fix linter errors 2017-10-19 12:55:14 +03:00
Leonid Logvinov
f62dc0f46c Update 0x-json-schemas 2017-10-19 12:44:00 +03:00
Leonid Logvinov
f64638173a Transform lodash-es to commonjs module format 2017-10-19 00:31:10 +03:00
Leonid Logvinov
14a0dcecf5 Fix tests 2017-10-18 23:38:28 +03:00
Leonid Logvinov
e17cca9834 Remove unused parts from artifacts 2017-10-18 17:46:37 +03:00
Leonid Logvinov
9b0f68f9a9 Include only used lodash functions 2017-10-18 17:45:39 +03:00
Leonid Logvinov
f4eb73ca7c Upgrade 0x-json-schemas to the version that doesn't depend on lodash 2017-10-18 17:19:56 +03:00
Leonid Logvinov
4ea6ebb0ae Uprade ethereumjs-blockstream, cause new version doesn't include source maps and is significantly smaller 2017-10-18 17:18:44 +03:00
Leonid Logvinov
8931f2e736 0.22.0 2017-10-16 12:03:31 +03:00
Leonid Logvinov
7ae78ca3c8 Update CHANGELOG 2017-10-16 12:03:20 +03:00
Leonid
d24eeec1a1 Merge pull request #187 from apackin/setFillOrKillToUseRequestInterface
[WIP] Use OrderFillRequest interface for batchFillOrKill
2017-10-16 11:41:04 +03:00
Leonid
a798f32cc8 Merge branch 'development' into setFillOrKillToUseRequestInterface 2017-10-16 11:40:20 +03:00
Leonid Logvinov
df5fe4a84f 0.21.4 2017-10-13 19:00:06 +03:00
Leonid Logvinov
9aef222f79 Add changes to CHANGELOG 2017-10-13 18:59:52 +03:00
Leonid
5591378245 Merge pull request #194 from 0xProject/feature/type-safe-subscriptions
Make logs fetching and subscriptions more type-safe
2017-10-13 17:16:11 +03:00
Leonid Logvinov
dde2268f9f Remove unused code 2017-10-13 13:03:00 +03:00
Leonid Logvinov
0eaca6c691 Make logs fetching and sunscriptions more type-safe 2017-10-13 12:52:59 +03:00
Leonid Logvinov
ba654c04a0 0.21.3 2017-10-12 17:56:13 +03:00
Leonid
f4fbac2694 Merge pull request #193 from 0xProject/fix/allowance-error
Fix an issue causing fills to throw `INSUFFICIENT_TAKER_ALLOWANCE`
2017-10-12 17:51:37 +03:00
Leonid Logvinov
b86f6322e1 Update CHANGELOG 2017-10-12 17:08:46 +03:00
Leonid Logvinov
74c6be3698 Pass correct parameters to validation simulation 2017-10-12 17:02:18 +03:00
Leonid Logvinov
d114613384 Add a regression test 2017-10-12 17:01:55 +03:00
Leonid Logvinov
c23ea1e688 0.21.2 2017-10-11 18:49:26 +03:00
Leonid
f9d4799ebe Merge pull request #190 from 0xProject/fix/export-contract-event-arg
Export ContractEventArg
2017-10-11 18:37:48 +03:00
Leonid Logvinov
9ec4f6dcab Update CHANGELOG 2017-10-11 18:09:10 +03:00
Leonid Logvinov
bcdd063d70 Export ContractEventArg 2017-10-11 18:03:25 +03:00
Leonid Logvinov
22bc7cd692 0.21.1 2017-10-11 15:12:02 +03:00
Leonid
080fe38d1c Merge pull request #189 from 0xProject/fix/getLogsParamsSerialization
Fix a bug in logs fetching
2017-10-11 15:11:40 +03:00
Leonid Logvinov
df32756556 Update CHANGELOG 2017-10-11 14:58:58 +03:00
Leonid Logvinov
d02b7c5fdf Fix a bug in logs fetching 2017-10-11 14:54:49 +03:00
Assaf
c8b54f3bac Use OrderFillRequest interface for batchFillOrKill 2017-10-10 19:52:43 -04:00
Leonid Logvinov
233f97891c 0.21.0 2017-10-10 15:26:46 +03:00
Leonid
056e0f26ab Merge pull request #185 from 0xProject/fix/batch-validation
Fix batch validation
2017-10-10 14:15:57 +03:00
Leonid Logvinov
bda979a6c7 Change tests to test that allowance is checked first 2017-10-10 11:58:18 +03:00
Leonid Logvinov
cfae1a8dfd Throw allowance errors first 2017-10-10 11:48:42 +03:00
Leonid Logvinov
468a20c9ea Remove unused check 2017-10-10 11:48:05 +03:00
Leonid Logvinov
f24c94f1a8 Fix the comment 2017-10-10 11:47:39 +03:00
Leonid Logvinov
ac27937a9c Fix the comment 2017-10-10 11:47:01 +03:00
Leonid Logvinov
2b82354617 Remove redundant constructor 2017-10-10 11:46:33 +03:00
Leonid Logvinov
3fa98ec00e Assign to a variable before assigning 2017-10-10 11:45:57 +03:00
Leonid Logvinov
052fd5783f Change string enum value 2017-10-10 11:44:27 +03:00
Leonid Logvinov
63aa3d0659 Fix CHANGELOG comments 2017-10-10 11:43:55 +03:00
Leonid Logvinov
a4af1065ed Update CHANGELOG 2017-10-09 13:23:51 +03:00
Leonid Logvinov
ef8b2875cf Fix the comment 2017-10-09 13:20:19 +03:00
Leonid Logvinov
1424a7302a Implement transfer Emulator and rewrite tests 2017-10-09 13:07:48 +03:00
Leonid Logvinov
54ac354809 Add types for TradeSide and TransferType 2017-10-06 15:54:17 +03:00
Leonid
f38d2f80a6 Merge pull request #182 from 0xProject/feature/ethereumjs-blockstream
Rewrite subscriptions
2017-10-06 15:24:42 +03:00
Leonid Logvinov
0c112a2a1c Add a hex prefix 2017-10-06 15:13:31 +03:00
Leonid Logvinov
81297b44c6 Add undefined check 2017-10-06 13:15:11 +03:00
Leonid Logvinov
a2cc127ea9 Fix comments 2017-10-06 13:10:45 +03:00
Leonid Logvinov
cfa75ed36c Add a comment 2017-10-06 13:09:14 +03:00
Leonid Logvinov
498cf5333d Fix a typo 2017-10-06 13:08:07 +03:00
Leonid Logvinov
292aab9b18 Use BlockParamLiteral types 2017-10-06 13:07:01 +03:00
Leonid Logvinov
637183e4b2 introduce BlockParamLiteral 2017-10-06 13:04:51 +03:00
Leonid Logvinov
44e2929a4c Check for blockAndLogStreamer to be undefined instead of th filters object to be empty 2017-10-06 13:01:08 +03:00
Leonid Logvinov
1043def46c Install js-sha3 and use it for keccak256 2017-10-06 12:58:17 +03:00
Leonid Logvinov
6af2ba5cff Remove _activeFilters 2017-10-06 12:30:27 +03:00
Leonid
cd5327bc31 Merge pull request #184 from 0xProject/greenkeeper/typedoc-0.9.0
Update typedoc to the latest version 🚀
2017-10-06 12:11:05 +03:00
greenkeeper[bot]
977fe0f8ef chore(package): update typedoc to version 0.9.0 2017-10-06 04:16:50 +00:00
Leonid Logvinov
a406b4d134 Remove unused imports 2017-10-05 17:34:30 +03:00
Leonid Logvinov
1414b8ee8b Add type assertions for callback parameters 2017-10-05 16:32:01 +03:00
Leonid Logvinov
209c31f361 Remove TODOs 2017-10-05 16:28:20 +03:00
Leonid Logvinov
721d969a85 Make it possible to have multiple layers of snapshots 2017-10-05 15:56:28 +03:00
Leonid Logvinov
7bcedc27b8 Update CHANGELOG 2017-10-05 15:43:46 +03:00
Leonid Logvinov
553cbb25f4 Fix comments 2017-10-05 15:35:38 +03:00
Leonid Logvinov
118381c1d1 Move more logic into _stopBlockAndLogStream and _startBlockAndLogStream 2017-10-05 15:35:38 +03:00
Leonid Logvinov
f2100fa36d Remove missing comment 2017-10-05 15:35:38 +03:00
Leonid Logvinov
a537b2e40c Add missing comment 2017-10-05 15:35:37 +03:00
Leonid Logvinov
1b6d3b0f0b Add missing comments 2017-10-05 15:35:37 +03:00
Leonid Logvinov
7dd6352393 Implement subscriptions based on ethereumjs-blockstream 2017-10-05 15:35:37 +03:00
Leonid Logvinov
e37a3155cd Instantiate logAndBlockStreamer 2017-10-05 15:35:37 +03:00
Leonid Logvinov
542cf7b1cb Fix CHANGELOG formatting 2017-10-05 14:55:32 +03:00
Leonid Logvinov
60de7ecc41 0.20.0 2017-10-05 14:52:16 +03:00
Leonid Logvinov
624d108124 Revert "0.20.0"
This reverts commit acdc65c895.
2017-10-05 14:51:54 +03:00
Leonid
46f4c56a3d Merge pull request #183 from 0xProject/fix/getLogs-assertions
Add assertions
2017-10-05 14:49:04 +03:00
Leonid Logvinov
49a50efa9f Add assertion for tokenAddress 2017-10-05 14:43:04 +03:00
Leonid Logvinov
80cbdf469e Add assertions 2017-10-05 14:41:42 +03:00
Leonid Logvinov
acdc65c895 0.20.0 2017-10-05 09:48:27 +03:00
Leonid
0eb7b81636 Merge pull request #181 from 0xProject/feature/order-validation-zrx
Fees validations when one of the tokens is ZRX
2017-10-05 09:47:55 +03:00
Leonid Logvinov
0594667d36 Small reordering 2017-10-05 09:46:54 +03:00
Leonid Logvinov
cd16b35814 Fix a typo 2017-10-05 09:46:09 +03:00
Leonid
5bc7d716e0 Merge pull request #177 from 0xProject/greenkeeper/mocha-4.0.0
Update mocha to the latest version 🚀
2017-10-04 15:42:14 +03:00
Leonid Logvinov
3ec2402a98 Exit after running the tests 2017-10-04 15:16:51 +03:00
greenkeeper[bot]
04978f93d5 chore(package): update mocha to version 4.0.0 2017-10-04 15:11:37 +03:00
Leonid
0fa978c959 Merge pull request #179 from 0xProject/greenkeeper/source-map-support-0.5.0
Update source-map-support to the latest version 🚀
2017-10-04 15:10:38 +03:00
greenkeeper[bot]
f70cef081c chore(package): update source-map-support to version 0.5.0 2017-10-04 15:10:17 +03:00
Leonid Logvinov
b4717b8526 Fix tests 2017-10-04 15:06:38 +03:00
Leonid Logvinov
074040daf5 Add changes to CHANGELOG 2017-10-04 15:01:51 +03:00
Leonid Logvinov
0caab98399 Fi fees validation is one of the tokens transfered is 0x 2017-10-04 14:58:08 +03:00
Leonid Logvinov
8b7caef0db Fix an issue when validation failed, but contract call will succeed 2017-10-04 14:31:09 +03:00
Leonid
836d9be7fe Merge pull request #178 from 0xProject/feature/getLogs
Add zeroEx.getLogsAsync
2017-10-04 14:30:36 +03:00
Leonid Logvinov
e5bdf60460 Move ZRX_NOT_IN_TOKEN_REGISTRY to InternalZeroExError 2017-10-04 14:25:15 +03:00
Leonid Logvinov
11c48ced00 Reduce nesting 2017-10-04 14:23:42 +03:00
Leonid Logvinov
cc3871aca5 Use find 2017-10-04 14:22:47 +03:00
Leonid Logvinov
8fb5e87243 Allign brackets 2017-10-04 14:22:17 +03:00
Leonid Logvinov
504beeb2f3 Add filtering by topic 2017-10-04 13:30:00 +03:00
Leonid Logvinov
9af47eb063 Use a ternary and add a comment 2017-10-04 12:59:46 +03:00
Leonid Logvinov
944f51d66c Use SolidityTypes 2017-10-04 12:55:16 +03:00
Leonid Logvinov
499e60c4a3 Use 0x.length instead of 2 2017-10-04 12:48:45 +03:00
Leonid Logvinov
a6f4f83b5b Add a comment 2017-10-04 11:55:29 +03:00
Leonid Logvinov
2b9418b700 Fix a typo 2017-10-04 11:54:34 +03:00
Leonid Logvinov
aa995ff994 Use _.find instead of _.filter 2017-10-04 11:53:44 +03:00
Leonid Logvinov
d1e4f6efdd Move NoAbiDecoder to InternalZeroExErrors 2017-10-04 11:53:02 +03:00
Leonid Logvinov
f65bfc1ab1 Extract topics to its variable 2017-10-04 11:51:36 +03:00
Leonid Logvinov
f26d49f077 Use Noop instead of NoOp 2017-10-04 11:48:53 +03:00
Leonid Logvinov
ad7ce6c916 Update CHANGELOG 2017-10-04 11:14:20 +03:00
Leonid Logvinov
7c49224c7b Don't export RawLog 2017-10-04 11:14:20 +03:00
Leonid Logvinov
451ded4963 Add tests for zeroEx.exchange.getLogsAsync 2017-10-04 11:14:20 +03:00
Leonid Logvinov
837618c7a0 Implement zeroEx.exchange.getLogsAsync 2017-10-04 11:14:20 +03:00
Leonid Logvinov
e6c138be5a Add _getLogsAsync on contract_wrapper 2017-10-04 11:14:19 +03:00
Leonid Logvinov
087645e59f Add tests for zeroEx.token.getLogsAsync 2017-10-04 11:14:19 +03:00
Leonid Logvinov
0a12fa7f4e Implement getLogsAsync on token contract 2017-10-04 11:14:19 +03:00
Leonid Logvinov
87374d7f46 Refactor abi decoder 2017-10-04 11:14:19 +03:00
Leonid Logvinov
efa85f844b Add tryToDecodeLogOrNoOp and _getEventSignatureFromAbiByName on contract_wrapper 2017-10-04 11:14:19 +03:00
Leonid Logvinov
db08896274 Remove old tests 2017-10-04 11:14:19 +03:00
Leonid Logvinov
44abf283ec Add keccak256 on web3_wrapper 2017-10-04 11:14:19 +03:00
Leonid Logvinov
671bc7c917 Add NO_ABI_DECODER and ContractEvents 2017-10-04 11:14:19 +03:00
Leonid Logvinov
6bbdc98ba2 Move log decoding to AbiDecoder 2017-10-04 11:14:19 +03:00
Leonid Logvinov
a9681072ee Factor out tryToDecodeLogOrNoOp 2017-10-04 11:14:19 +03:00
Leonid Logvinov
16af052a16 Don't text for an exact block hash 2017-10-04 11:14:19 +03:00
Leonid Logvinov
40e0706954 Add changes to CHANGELOG 2017-10-04 11:14:19 +03:00
Leonid Logvinov
b859f4b8ab Add tests for zeroEx.getLogsAsync 2017-10-04 11:14:19 +03:00
Leonid Logvinov
835c17c961 Add zeroEx.getLogsAsync 2017-10-04 11:14:19 +03:00
Leonid Logvinov
7b545aa0e0 Re-export new types 2017-10-04 11:14:19 +03:00
Leonid Logvinov
ea08fc8642 Add getLogsAsync to web3_wrapper 2017-10-04 11:14:19 +03:00
Leonid Logvinov
5410924810 Add type aliases for web3 types 2017-10-04 11:14:19 +03:00
Leonid Logvinov
5d21d10437 Update web3-typescript-typings 2017-10-04 11:14:18 +03:00
Leonid
5d554ab882 Merge pull request #180 from 0xProject/greenkeeper/0x-json-schemas-0.6.0
Update 0x-json-schemas to the latest version 🚀
2017-10-04 11:12:15 +03:00
greenkeeper[bot]
c2ce8732e7 fix(package): update 0x-json-schemas to version 0.6.0 2017-10-04 06:49:52 +00:00
Leonid Logvinov
76c8d7108f 0.19.0 2017-09-29 16:21:22 +02:00
Leonid Logvinov
024bc1756e Fix types 2017-09-29 16:21:08 +02:00
Leonid Logvinov
0cf5cc778a Update CHANGELOG 2017-09-29 15:42:40 +02:00
Leonid
ed4536f57f Merge pull request #175 from 0xProject/fix/set-interval-bug
Fix the bug in transaction mined awaiting
2017-09-29 15:27:51 +02:00
Leonid Logvinov
5bc33257b6 Fix the bug in transaction mined awaiting 2017-09-29 15:22:01 +02:00
Leonid
0c3a14b662 Merge pull request #169 from 0xProject/greenkeeper/sinon-4.0.0
Update sinon to the latest version 🚀
2017-09-29 11:51:41 +02:00
Leonid
4b7b46071f Merge pull request #174 from 0xProject/greenkeeper/coveralls-3.0.0
Update coveralls to the latest version 🚀
2017-09-29 11:51:13 +02:00
Leonid
cbc9e87d65 Merge pull request #173 from 0xProject/feature/ropsten-support
Add Ropsten support
2017-09-28 18:18:19 +02:00
greenkeeper[bot]
1c10440a15 chore(package): update coveralls to version 3.0.0 2017-09-28 16:11:29 +00:00
Brandon Millman
a0af271996 Merge pull request #172 from 0xProject/bmillman_optional_validation
Add OrderTransactionOpts to enable optional validation to exchange_wr…
2017-09-28 09:06:23 -07:00
Brandon Millman
d21fbbc4c8 Fixed nits 2017-09-28 08:44:29 -07:00
Leonid Logvinov
db419ffcc7 Add tests for ropsten artifacts 2017-09-28 14:42:48 +02:00
Leonid Logvinov
b537636b42 Update CHANGELOG 2017-09-28 14:38:51 +02:00
Leonid Logvinov
4e0b4415f0 Add Ropsten artifacts 2017-09-28 14:36:58 +02:00
Brandon Millman
333665370f Add tests 2017-09-27 17:12:38 -07:00
Brandon Millman
1d4506427f Add OrderTransactionOpts to enable optional validation to exchange_wrapper 2017-09-27 13:28:11 -07:00
Fabio Berger
be12c5b538 0.18.0 2017-09-26 16:24:13 +02:00
Fabio Berger
5449fc5093 0.17.0 2017-09-26 16:23:48 +02:00
Fabio Berger
a87dd7af7d Update changelog 2017-09-26 16:23:29 +02:00
Fabio Berger
949fc2fc82 Merge pull request #170 from 0xProject/addOrderValidation
Add validateOrderFillableOrThrowAsync Method
2017-09-26 16:07:42 +02:00
Fabio Berger
5f7afce49d fix test 2017-09-26 15:58:08 +02:00
Fabio Berger
f8d5b72367 fix test 2017-09-26 15:57:36 +02:00
Fabio Berger
ad6e848821 fix merge issue 2017-09-26 15:37:14 +02:00
Fabio Berger
9ee88ba06d Updated changelog 2017-09-26 15:30:54 +02:00
Fabio Berger
deac665b42 Merge branch 'development' into addOrderValidation
* development:
  Update comment
  Add a test for getZRXTokenAddressAsync
  Document changes in CHANGELOG
  Make getZRXTokenAddressAsync public
2017-09-26 15:29:44 +02:00
Fabio Berger
fbac611337 improve comment 2017-09-26 15:29:08 +02:00
Fabio Berger
9886383638 Fix linter issue 2017-09-26 15:28:07 +02:00
Fabio Berger
5bea6ff581 Add success test and regression test for previous bug where comparing makerTokenAmount with a takerTokenAmount 2017-09-26 15:27:59 +02:00
Leonid
3c40526bff Merge pull request #171 from 0xProject/feature/zrx-getter
Make getZRXTokenAddressAsync public
2017-09-26 15:07:14 +02:00
Leonid Logvinov
8a29f12a61 Update comment 2017-09-26 15:02:12 +02:00
Fabio Berger
e704aea643 Add tests for validateOrderFillableOrThrowAsync 2017-09-26 12:39:17 +02:00
Fabio Berger
25116940c0 Refactor our logic checking fillAmountNotZero and expiry 2017-09-26 12:39:05 +02:00
Fabio Berger
2148eb6d99 make opts optional 2017-09-26 12:32:49 +02:00
Leonid Logvinov
ee0adc8a7e Add a test for getZRXTokenAddressAsync 2017-09-26 11:57:32 +02:00
Fabio Berger
d3f729dd71 rename validateOrderFillableThrowIfNotFillableAsync to validateOrderFillableOrThrowAsync 2017-09-26 11:55:07 +02:00
Fabio Berger
9effd57229 Fix bug where we were accidentally comparing a makerTokenAmount to a takerTokenAmount 2017-09-26 11:51:51 +02:00
Leonid Logvinov
e347297aaa Document changes in CHANGELOG 2017-09-26 11:46:31 +02:00
Leonid Logvinov
68c240aa4d Make getZRXTokenAddressAsync public 2017-09-26 11:44:33 +02:00
Fabio Berger
5e6c4e0ec3 improve comment 2017-09-26 11:38:37 +02:00
Fabio Berger
5e92ca039c Add validateOrderFillableThrowIfNotFillableAsync to public methods in order to validate orders in an orderbook without a specific taker in mind 2017-09-26 11:01:33 +02:00
greenkeeper[bot]
d1ce78a64f chore(package): update sinon to version 4.0.0 2017-09-26 08:27:04 +00:00
Leonid Logvinov
0a19a7e8d1 0.16.0 2017-09-20 15:05:43 +02:00
Leonid
d424933d70 Merge pull request #165 from 0xProject/feature/configurable-addresses
Allow users to pass contract addresses as a config
2017-09-20 14:59:48 +02:00
Leonid Logvinov
91679caf93 Update release date 2017-09-20 14:59:21 +02:00
Leonid Logvinov
1efcc5ad62 Fix the typo 2017-09-20 14:57:33 +02:00
Leonid Logvinov
96d853fc4b Add a comment for ZeroExConfig type 2017-09-19 15:09:05 +02:00
Leonid Logvinov
0f942f95f0 Move lower-casing logic 2017-09-19 14:56:41 +02:00
Leonid Logvinov
f1cb3a4f37 Fix a comment 2017-09-19 14:53:58 +02:00
Leonid Logvinov
66db021900 Postfix variable names with 'ifExists' 2017-09-19 14:53:12 +02:00
Leonid Logvinov
e7af0eb20c Prefix HACK comment with HACK 2017-09-19 14:43:42 +02:00
Leonid Logvinov
5e40f76996 Postfix variable names with 'ifExists' 2017-09-19 14:43:14 +02:00
Leonid Logvinov
903e2f4f8a Verify ZeroExConfig 2017-09-19 14:13:06 +02:00
Leonid Logvinov
4aeb6226d5 Add changes to the CHANGELOG 2017-09-19 13:29:15 +02:00
Leonid Logvinov
e19d5b3c78 Fix a typo 2017-09-18 17:26:30 +02:00
Leonid Logvinov
946978e454 Add explanatory comment 2017-09-18 17:24:54 +02:00
Leonid Logvinov
40a0d345b5 Add tests for contracts address config 2017-09-18 16:54:24 +02:00
Leonid Logvinov
504e7a25a5 Make contract addresses configurable 2017-09-18 15:55:26 +02:00
Leonid Logvinov
8db90538a1 Fetch tokenTransferProxy address from exchange contract 2017-09-18 15:18:04 +02:00
Leonid Logvinov
fe9f692a4f 0.15.0 2017-09-08 16:34:19 +02:00
Leonid
e9448953ac Merge pull request #161 from 0xProject/feature/defaultBlock
Add a possibility to specify defaultBlock when calling const blockchain methods
2017-09-08 16:33:45 +02:00
Leonid Logvinov
217c16027e Replace placeholders with actual coments 2017-09-08 16:24:07 +02:00
Leonid Logvinov
be0b9a1d7c Add a comment for MethodOpts 2017-09-08 14:02:03 +02:00
Leonid Logvinov
fdf54668ae Fix CHANGELOG comment 2017-09-08 14:00:39 +02:00
Leonid Logvinov
1d64b542d8 Rename CallOpts to MethodOpts 2017-09-08 13:56:10 +02:00
Leonid Logvinov
e60153a4fb Update CHANGELOG.md 2017-09-08 12:28:18 +02:00
Leonid Logvinov
762d02b2e0 Allow user to specify defaultBlock when calling const exchange methods 2017-09-08 12:25:05 +02:00
Leonid Logvinov
1dcfd4102b Allow user to specify defaultBlock when calling const token methods 2017-09-08 12:03:04 +02:00
Leonid Logvinov
aaae22642e Define CallOpts type 2017-09-08 12:02:37 +02:00
Leonid Logvinov
6999e15972 0.14.2 2017-09-07 21:10:23 +02:00
Leonid
1501b47c10 Merge pull request #160 from 0xProject/fix/bignumber-types
Add bignumber.js types as a dependency
2017-09-07 21:09:30 +02:00
Leonid Logvinov
fbb4e844a7 Update CHANGELOG 2017-09-07 21:05:35 +02:00
Leonid Logvinov
a705a7d612 Make bignumber.js types normal dependency 2017-09-07 20:55:21 +02:00
Leonid Logvinov
a0a39fa4dd 0.14.1 2017-09-07 20:32:40 +02:00
Leonid Logvinov
36de4c7b0f Add release note to CHANGELOG 2017-09-07 20:32:29 +02:00
Leonid
4e22c289af Merge pull request #159 from 0xProject/fix/types
Move Aftifact type definition to 'types'
2017-09-07 20:31:20 +02:00
Leonid Logvinov
af217e316a Move Aftifact type definition to 'types' 2017-09-07 20:28:45 +02:00
Leonid Logvinov
b5435b6ab8 0.14.0 2017-09-06 18:34:30 +02:00
Leonid Logvinov
c7418e131f Fix version number in CHANGELOG 2017-09-06 18:34:05 +02:00
Leonid
c567249819 Merge pull request #157 from 0xProject/feature/throw-for-exchange-errors
Feature/throw for exchange errors
2017-09-06 18:32:02 +02:00
Leonid Logvinov
d2dc4d18be Add zeroEx.exchange.throwLogErrorsAsErrors to CHANGELOG 2017-09-06 18:27:25 +02:00
Leonid Logvinov
67ed341f24 Use LogError type 2017-09-06 18:26:17 +02:00
Leonid Logvinov
0b6e874a0d Fix a comment 2017-09-06 18:25:03 +02:00
Leonid Logvinov
88791f732f Make intervalUtils an object instead of a class and make instance variable local 2017-09-06 18:22:28 +02:00
Leonid Logvinov
b1feb5ac29 Fix a bug in intervalUtils 2017-09-06 17:49:51 +02:00
Leonid Logvinov
873aa26f63 Update CHANGELOG 2017-09-06 17:45:10 +02:00
Leonid Logvinov
a57b22a6bc Fix overlapping async intervals issue 2017-09-06 17:41:40 +02:00
Leonid Logvinov
7c61b09dce Add zeroEx.exchange.throwLogErrorsAsErrors 2017-09-06 17:29:15 +02:00
Leonid Logvinov
b38aff8808 Fix log decoder to return correct types 2017-09-06 17:26:55 +02:00
Leonid Logvinov
912d15cb73 0.13.0 2017-09-06 15:35:15 +02:00
Leonid Logvinov
9dc13360c9 Revert "0.13.0"
This reverts commit 92eb68bf2c.
2017-09-06 15:35:03 +02:00
Leonid
cb44b77d0b Merge pull request #156 from 0xProject/feature/custom-abi-decoder
Custom abi decoder
2017-09-06 15:26:29 +02:00
Leonid Logvinov
1baf065317 Use startsWith instead of includes 2017-09-06 15:26:15 +02:00
Leonid Logvinov
88c96c7052 Use template strings 2017-09-06 15:15:00 +02:00
Leonid Logvinov
9680cc1270 Use _.includes instead of indexOf 2017-09-06 15:14:36 +02:00
Leonid Logvinov
68d051d4a7 Rename methodID to methodId 2017-09-06 15:11:41 +02:00
Leonid Logvinov
f1ed572819 Remove redundant js prefix 2017-09-06 15:11:08 +02:00
Leonid Logvinov
9ae56485a9 Fix type error 2017-09-06 15:10:59 +02:00
Leonid Logvinov
a7ba16ef4a Handle the case, when it's not possible to decode args 2017-09-06 14:31:52 +02:00
Leonid Logvinov
35f3396295 Implement custom ABI decoder 2017-09-06 14:29:52 +02:00
Leonid Logvinov
10817aa337 Add types for web3/lib/solidity/coder.js 2017-09-06 14:29:09 +02:00
Leonid Logvinov
542aae6cd9 Remove abi-decoder 2017-09-06 14:28:29 +02:00
Leonid Logvinov
92eb68bf2c 0.13.0 2017-09-06 13:02:31 +02:00
Leonid Logvinov
aa7d10e510 Update web-typescript-typings 2017-09-06 13:01:33 +02:00
Leonid Logvinov
7377df8a4d Define sendAsync on HDWalletProvider 2017-09-06 13:00:13 +02:00
Leonid Logvinov
70a7f02d0f Fix import in order_validation_utils 2017-09-06 12:51:47 +02:00
Leonid Logvinov
5fb4b54153 Specify the release date 2017-09-06 12:48:47 +02:00
Leonid Logvinov
c64154e33a Add missing separators in CHANGELOG 2017-09-06 10:47:31 +02:00
Leonid
07da617c05 Merge pull request #152 from 0xProject/fix/signature-verification
Add signature verification as a part of order validation
2017-09-06 10:39:43 +02:00
Leonid
35c133caed Merge branch 'development' into fix/signature-verification 2017-09-06 10:35:19 +02:00
Leonid Logvinov
18a52a1ea7 Update CHANGELOG 2017-09-06 10:34:31 +02:00
Leonid Logvinov
501f054d51 Add signature verification as a part of order validation and tests for it 2017-09-06 10:33:34 +02:00
Leonid
f0a5ad2d20 Merge pull request #151 from 0xProject/feature/remove-truffle-contracts
Remove truffle contracts dependency
2017-09-06 10:28:37 +02:00
Leonid Logvinov
258b4fac31 Fix a typo in test name 2017-09-06 10:26:22 +02:00
Leonid Logvinov
8ebc724379 Add lifecycle methods 2017-09-06 00:40:22 +02:00
Leonid Logvinov
df904f80e3 Add test for logs decoding in awaitTransactionMinedAsync 2017-09-06 00:18:07 +02:00
Leonid Logvinov
e6e12e946e Update CHANGELOG 2017-09-05 18:59:46 +02:00
Leonid Logvinov
2fd5f2781b Add forgotten artifacts file 2017-09-05 18:59:18 +02:00
Leonid Logvinov
2f97ddb727 Fix the return types and export the required public types 2017-09-05 18:50:22 +02:00
Leonid Logvinov
a7b2131db7 Decode logs args in awaitTransactionMinedAsync 2017-09-05 18:45:20 +02:00
Leonid Logvinov
f057267955 Update json-schemas 2017-09-05 16:45:40 +02:00
Leonid Logvinov
b0547819fd Define AbiType 2017-09-05 15:44:42 +02:00
Leonid Logvinov
dff63f9b89 Use AbiType 2017-09-05 15:34:52 +02:00
Leonid Logvinov
ee00769be1 Use schema validation to distinguish txData argument 2017-09-05 15:29:29 +02:00
Leonid Logvinov
ec22097efb Don't override function arguments 2017-09-05 13:49:47 +02:00
Leonid Logvinov
bc5fd316df Cast to Artifat type 2017-09-05 13:47:17 +02:00
Leonid Logvinov
92b101fac8 Remove unused code 2017-09-05 13:46:13 +02:00
Leonid Logvinov
78c46d7cc9 Change the order of default overriding 2017-09-05 13:45:32 +02:00
Leonid Logvinov
9f12ef61b0 Rename x.call -> x.callAsync x() -> x.sendTransactionAsync() x.estimateGas() -> x.estimateGasAsync() 2017-09-05 13:43:46 +02:00
Leonid Logvinov
e05dfab1fc Add explaining comment 2017-09-05 13:14:27 +02:00
Leonid Logvinov
b5c6c91962 Increase the default polling interval to 1000 2017-09-05 12:52:44 +02:00
Leonid Logvinov
f6a945dfe4 Fix the comment at awaitTransactionMinedAsync 2017-09-05 12:52:00 +02:00
Leonid Logvinov
a12df1c73a Fix gasPrice regression 2017-09-05 11:38:28 +02:00
Leonid Logvinov
876032a8a7 Update CHANGELOG 2017-09-05 10:42:00 +02:00
Leonid Logvinov
96d2a55eff Add TransationReceipt as a public exported type 2017-09-05 10:29:51 +02:00
Leonid Logvinov
5d57a2f0e9 Increase timeout 2017-09-05 10:22:22 +02:00
Leonid Logvinov
2b547f94a4 Change non-exhange contracts to also return txHash 2017-09-05 10:07:16 +02:00
Leonid Logvinov
c9e490bdae Implement zeroEx.awaitTransactionMined 2017-09-04 19:08:14 +02:00
Leonid Logvinov
6325a03818 Temporarily remove web3_beta, cause it breaks installation and tests 2017-09-04 18:48:18 +02:00
Leonid Logvinov
2577d8f662 Use Web3.ContractInstance type 2017-09-04 18:23:12 +02:00
Leonid Logvinov
1ad395cf86 Make the functions immidiately return txHash instead of awaiting for a transaction to be mined 2017-09-04 18:14:48 +02:00
Leonid Logvinov
1c2d4cbb1a Fix tests descriptions 2017-09-04 16:57:22 +02:00
Leonid Logvinov
9818eb2835 Use custom contract abstraction 2017-09-04 14:45:01 +02:00
Leonid Logvinov
59fed02a8b Remove truffle-contract from deps 2017-09-04 12:07:17 +02:00
Fabio Berger
0275ac9dad Merge pull request #146 from 0xProject/greenkeeper/0x-json-schemas-0.4.0
Update 0x-json-schemas to the latest version 🚀
2017-09-03 10:15:30 +02:00
Leonid Logvinov
62452db5d8 0.12.1 2017-09-02 05:18:31 +02:00
Leonid Logvinov
792646888a Update CHANGELOG version 2017-09-02 05:18:24 +02:00
greenkeeper[bot]
66066b9722 fix(package): update 0x-json-schemas to version 0.4.0 2017-08-31 12:37:57 +00:00
52 changed files with 12778 additions and 3171 deletions

View File

@@ -1,6 +1,100 @@
# CHANGELOG
v0.12.0 - _September 2, 2017_
v0.22.1 - _October 19, 2017_
------------------------
* Reduced bundle size by 11% 82kB (#195)
v0.22.0 - _October 16, 2017_
------------------------
* Started using `OrderFillRequest` interface instead of `OrderFillOrKillRequest` interface for `zeroEx.exchange.batchFillOrKill` (#187)
* Removed `OrderFillOrKillRequest` (#187)
v0.21.4 - _October 13, 2017_
------------------------
* Made 0x.js more type-safe by making `getLogsAsync` and `subscribe/subscribeAsync` generics parametrized with arg type (#194)
v0.21.3 - _October 12, 2017_
------------------------
* Fixed a bug causing order fills to throw `INSUFFICIENT_TAKER_ALLOWANCE` (#193)
v0.21.2 - _October 11, 2017_
------------------------
* Exported `ContractEventArg` as a public type (#190)
v0.21.1 - _October 11, 2017_
------------------------
* Fixed a bug in subscriptions (#189)
v0.21.0 - _October 10, 2017_
------------------------
* Complete rewrite of subscription logic (#182)
* Subscriptions no longer return historical logs. If you want them - use `getLogsAsync`
* Subscriptions now use [ethereumjs-blockstream](https://github.com/ethereumjs/ethereumjs-blockstream) under the hood
* Subscriptions correctly handle block re-orgs (forks)
* Subscriptions correctly backfill logs (connection problems)
* They no longer setup filters on the underlying nodes, so you can use them with infura without a filter Subprovider
* Removed `ContractEventEmitter` and added `LogEvent`
* Renamed `zeroEx.token.subscribeAsync` to `zeroEx.token.subscribe`
* Added `zeroEx.token.unsubscribe` and `zeroEx.exchange.unsubscribe`
* Renamed `zeroEx.exchange.stopWatchingAllEventsAsync` to `zeroEx.exhange.unsubscribeAll`
* Renamed `zeroEx.token.stopWatchingAllEventsAsync` to `zeroEx.token.unsubscribeAll`
* Fixed the batch fills validation by emulating all balance & proxy allowance changes (#185)
v0.20.0 - _October 5, 2017_
------------------------
* Add `zeroEx.token.getLogsAsync` (#178)
* Add `zeroEx.exchange.getLogsAsync` (#178)
* Fixed fees validation when one of the tokens transferred is ZRX (#181)
v0.19.0 - _September 29, 2017_
------------------------
* Made order validation optional (#172)
* Added Ropsten testnet support (#173)
* Fixed a bug causing awaitTransactionMinedAsync to DDos backend nodes (#175)
v0.18.0 - _September 26, 2017_
------------------------
* Added `zeroEx.exchange.validateOrderFillableOrThrowAsync` to simplify orderbook pruning (#170)
v0.17.0 - _September 26, 2017_
------------------------
* Made `zeroEx.exchange.getZRXTokenAddressAsync` public (#171)
v0.16.0 - _September 20, 2017_
------------------------
* Added the ability to specify custom contract addresses to be used with 0x.js (#165)
* ZeroExConfig.exchangeContractAddress
* ZeroExConfig.tokenRegistryContractAddress
* ZeroExConfig.etherTokenContractAddress
* Added `zeroEx.tokenRegistry.getContractAddressAsync` (#165)
v0.15.0 - _September 8, 2017_
------------------------
* Added the ability to specify a historical `blockNumber` at which to query the blockchain's state when calling a token or exchange method (#161)
v0.14.2 - _September 7, 2017_
------------------------
* Fixed an issue with bignumber.js types not found (#160)
v0.14.1 - _September 7, 2017_
------------------------
* Fixed an issue with Artifact type not found (#159)
v0.14.0 - _September 6, 2017_
------------------------
* Added `zeroEx.exchange.throwLogErrorsAsErrors` method to public interface (#157)
* Fixed an issue with overlapping async intervals in `zeroEx.awaitTransactionMinedAsync` (#157)
* Fixed an issue with log decoder returning `BigNumber`s as `strings` (#157)
v0.13.0 - _September 6, 2017_
------------------------
* Made all the functions submitting transactions to the network to immediately return transaction hash (#151)
* Added `zeroEx.awaitTransactionMinedAsync` (#151)
* Added `TransactionReceiptWithDecodedLogs`, `LogWithDecodedArgs`, `DecodedLogArgs` to public types (#151)
* Added signature validation to `validateFillOrderThrowIfInvalidAsync` (#152)
v0.12.1 - _September 2, 2017_
------------------------
* Added the support for web3@1.x.x provider (#142)
* Added the optional `zeroExConfig` parameter to the constructor of `ZeroEx` (#139)
* Added the ability to specify `gasPrice` when instantiating `ZeroEx` (#139)

10156
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "0x.js",
"version": "0.12.0",
"version": "0.22.1",
"description": "A javascript library for interacting with the 0x protocol",
"keywords": [
"0x.js",
@@ -36,7 +36,7 @@
"pretest:umd": "run-s clean build:umd:dev build:commonjs",
"substitute_umd_bundle": "npm run remove_src_files_not_used_by_tests; shx mv _bundles/* lib/src",
"remove_src_files_not_used_by_tests": "find ./lib/src \\( -path ./lib/src/utils -o -path ./lib/src/subproviders -o -path ./lib/src/schemas -o -path \"./lib/src/types.*\" \\) -prune -o -type f -print | xargs rm",
"run_mocha": "mocha lib/test/**/*_test.js --timeout 4000 --bail"
"run_mocha": "mocha lib/test/**/*_test.js --timeout 5000 --bail --exit"
},
"config": {
"artifacts": "TokenTransferProxy Exchange TokenRegistry Token EtherToken",
@@ -51,12 +51,13 @@
"node": ">=6.0.0"
},
"devDependencies": {
"@types/bignumber.js": "^4.0.2",
"@types/jsonschema": "^1.1.1",
"@types/lodash": "^4.14.64",
"@types/lodash": "^4.14.77",
"@types/lodash-es": "^4.14.7",
"@types/mocha": "^2.2.41",
"@types/node": "^8.0.1",
"@types/sinon": "^2.2.2",
"@types/uuid": "^3.4.2",
"awesome-typescript-loader": "^3.1.3",
"bignumber.js": "^4.0.2",
"chai": "^4.0.1",
@@ -65,42 +66,44 @@
"chai-bignumber": "^2.0.1",
"chai-typescript-typings": "^0.0.0",
"copyfiles": "^1.2.0",
"coveralls": "^2.13.1",
"coveralls": "^3.0.0",
"dirty-chai": "^2.0.1",
"ethereumjs-testrpc": "4.0.1",
"json-loader": "^0.5.4",
"mocha": "^3.4.1",
"mocha": "^4.0.0",
"npm-run-all": "^4.0.2",
"nyc": "^11.0.1",
"opn-cli": "^3.1.0",
"request": "^2.81.0",
"request-promise-native": "^1.0.4",
"shx": "^0.2.2",
"sinon": "^3.0.0",
"source-map-support": "^0.4.15",
"sinon": "^4.0.0",
"source-map-support": "^0.5.0",
"truffle-hdwallet-provider": "^0.0.3",
"tslint": "^5.3.2",
"tslint-config-0xproject": "^0.0.2",
"typedoc": "^0.8.0",
"typedoc": "^0.9.0",
"types-bn": "^0.0.1",
"types-ethereumjs-util": "^0.0.5",
"types-ethereumjs-util": "0xProject/types-ethereumjs-util",
"typescript": "^2.4.1",
"web3_beta": "ethereum/web3.js#1.0",
"web3-provider-engine": "^13.0.1",
"web3-typescript-typings": "^0.3.2",
"web3-typescript-typings": "^0.6.2",
"webpack": "^3.1.0"
},
"dependencies": {
"0x-json-schemas": "^0.3.0",
"0x-json-schemas": "^0.6.5",
"@types/bignumber.js": "^4.0.2",
"bignumber.js": "^4.0.2",
"compare-versions": "^3.0.1",
"es6-promisify": "^5.0.0",
"ethereumjs-abi": "^0.6.4",
"ethereumjs-blockstream": "^2.0.7",
"ethereumjs-util": "^5.1.1",
"find-versions": "^2.0.0",
"lodash": "^4.17.4",
"js-sha3": "^0.6.1",
"lodash-es": "^4.17.4",
"publish-release": "^1.3.3",
"truffle-contract": "^2.0.1",
"uuid": "^3.1.0",
"web3": "^0.20.0"
}
}

View File

@@ -1,22 +1,31 @@
import * as _ from 'lodash';
import {_} from './utils/lodash';
import * as BigNumber from 'bignumber.js';
import {SchemaValidator, schemas} from '0x-json-schemas';
import {bigNumberConfigs} from './bignumber_config';
import * as ethUtil from 'ethereumjs-util';
import contract = require('truffle-contract');
import findVersions = require('find-versions');
import compareVersions = require('compare-versions');
import {Web3Wrapper} from './web3_wrapper';
import {constants} from './utils/constants';
import {utils} from './utils/utils';
import {signatureUtils} from './utils/signature_utils';
import {assert} from './utils/assert';
import {AbiDecoder} from './utils/abi_decoder';
import {intervalUtils} from './utils/interval_utils';
import {artifacts} from './artifacts';
import {ExchangeWrapper} from './contract_wrappers/exchange_wrapper';
import {TokenRegistryWrapper} from './contract_wrappers/token_registry_wrapper';
import {EtherTokenWrapper} from './contract_wrappers/ether_token_wrapper';
import {TokenWrapper} from './contract_wrappers/token_wrapper';
import {TokenTransferProxyWrapper} from './contract_wrappers/token_transfer_proxy_wrapper';
import {ECSignature, ZeroExError, Order, SignedOrder, Web3Provider, ZeroExConfig} from './types';
import {
ECSignature,
ZeroExError,
Order,
SignedOrder,
Web3Provider,
ZeroExConfig,
TransactionReceiptWithDecodedLogs,
} from './types';
import {zeroExConfigSchema} from './schemas/zero_ex_config_schema';
// Customize our BigNumber instances
bigNumberConfigs.configure();
@@ -57,6 +66,7 @@ export class ZeroEx {
*/
public proxy: TokenTransferProxyWrapper;
private _web3Wrapper: Web3Wrapper;
private _abiDecoder: AbiDecoder;
/**
* Verifies that the elliptic curve signature `signature` was generated
* by signing `data` with the private key corresponding to the `signerAddress` address.
@@ -164,19 +174,45 @@ export class ZeroEx {
*/
constructor(provider: Web3Provider, config?: ZeroExConfig) {
assert.isWeb3Provider('provider', provider);
if (!_.isUndefined(config)) {
assert.doesConformToSchema('config', config, zeroExConfigSchema);
}
if (_.isUndefined((provider as any).sendAsync)) {
// Web3@1.0 provider doesn't support synchronous http requests,
// so it only has an async `send` method, instead of a `send` and `sendAsync` in web3@0.x.x`
// We re-assign the send method so that Web3@1.0 providers work with 0x.js
(provider as any).sendAsync = (provider as any).send;
}
this._web3Wrapper = new Web3Wrapper(provider);
const artifactJSONs = _.values(artifacts);
const abiArrays = _.map(artifactJSONs, artifact => artifact.abi);
this._abiDecoder = new AbiDecoder(abiArrays);
const gasPrice = _.isUndefined(config) ? undefined : config.gasPrice;
this.token = new TokenWrapper(this._web3Wrapper, gasPrice);
this.proxy = new TokenTransferProxyWrapper(this._web3Wrapper, gasPrice);
this.exchange = new ExchangeWrapper(this._web3Wrapper, this.token, gasPrice);
this.tokenRegistry = new TokenRegistryWrapper(this._web3Wrapper, gasPrice);
this.etherToken = new EtherTokenWrapper(this._web3Wrapper, this.token, gasPrice);
const defaults = {
gasPrice,
};
this._web3Wrapper = new Web3Wrapper(provider, defaults);
this.token = new TokenWrapper(
this._web3Wrapper,
this._abiDecoder,
this._getTokenTransferProxyAddressAsync.bind(this),
);
const exchageContractAddressIfExists = _.isUndefined(config) ? undefined : config.exchangeContractAddress;
this.exchange = new ExchangeWrapper(
this._web3Wrapper,
this._abiDecoder,
this.token,
exchageContractAddressIfExists,
);
this.proxy = new TokenTransferProxyWrapper(
this._web3Wrapper,
this._getTokenTransferProxyAddressAsync.bind(this),
);
const tokenRegistryContractAddressIfExists = _.isUndefined(config) ?
undefined :
config.tokenRegistryContractAddress;
this.tokenRegistry = new TokenRegistryWrapper(this._web3Wrapper, tokenRegistryContractAddressIfExists);
const etherTokenContractAddressIfExists = _.isUndefined(config) ? undefined : config.etherTokenContractAddress;
this.etherToken = new EtherTokenWrapper(this._web3Wrapper, this.token, etherTokenContractAddressIfExists);
}
/**
* Sets a new web3 provider for 0x.js. Updating the provider will stop all
@@ -249,4 +285,42 @@ export class ZeroEx {
throw new Error(ZeroExError.InvalidSignature);
}
/**
* Waits for a transaction to be mined and returns the transaction receipt.
* @param txHash Transaction hash
* @param pollingIntervalMs How often (in ms) should we check if the transaction is mined.
* @return Transaction receipt with decoded log args.
*/
public async awaitTransactionMinedAsync(
txHash: string, pollingIntervalMs: number = 1000): Promise<TransactionReceiptWithDecodedLogs> {
const txReceiptPromise = new Promise(
(resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => {
const intervalId = intervalUtils.setAsyncExcludingInterval(async () => {
const transactionReceipt = await this._web3Wrapper.getTransactionReceiptAsync(txHash);
if (!_.isNull(transactionReceipt)) {
intervalUtils.clearAsyncExcludingInterval(intervalId);
const logsWithDecodedArgs = _.map(
transactionReceipt.logs,
this._abiDecoder.tryToDecodeLogOrNoop.bind(this._abiDecoder),
);
const transactionReceiptWithDecodedLogArgs: TransactionReceiptWithDecodedLogs = {
...transactionReceipt,
logs: logsWithDecodedArgs,
};
resolve(transactionReceiptWithDecodedLogArgs);
}
}, pollingIntervalMs);
});
return txReceiptPromise;
}
/*
* HACK: `TokenWrapper` needs a token transfer proxy address. `TokenTransferProxy` address is fetched from
* an `ExchangeWrapper`. `ExchangeWrapper` needs `TokenWrapper` to validate orders, creating a dependency cycle.
* In order to break this - we create this function here and pass it as a parameter to the `TokenWrapper`
* and `ProxyWrapper`.
*/
private async _getTokenTransferProxyAddressAsync(): Promise<string> {
const tokenTransferProxyAddress = await (this.exchange as any)._getTokenTransferProxyAddressAsync();
return tokenTransferProxyAddress;
}
}

14
src/artifacts.ts Normal file
View File

@@ -0,0 +1,14 @@
import {Artifact} from './types';
import * as TokenArtifact from './artifacts/Token.json';
import * as ExchangeArtifact from './artifacts/Exchange.json';
import * as EtherTokenArtifact from './artifacts/EtherToken.json';
import * as TokenRegistryArtifact from './artifacts/TokenRegistry.json';
import * as TokenTransferProxyArtifact from './artifacts/TokenTransferProxy.json';
export const artifacts = {
TokenArtifact: TokenArtifact as any as Artifact,
ExchangeArtifact: ExchangeArtifact as any as Artifact,
EtherTokenArtifact: EtherTokenArtifact as any as Artifact,
TokenRegistryArtifact: TokenRegistryArtifact as any as Artifact,
TokenTransferProxyArtifact: TokenTransferProxyArtifact as any as Artifact,
};

View File

@@ -233,162 +233,20 @@
"type": "event"
}
],
"unlinked_binary": "0x6060604052341561000c57fe5b5b6107598061001c6000396000f300606060405236156100935763ffffffff60e060020a60003504166306fdde0381146100a4578063095ea7b31461013457806318160ddd1461016757806323b872dd146101895780632e1a7d4d146101c2578063313ce567146101d757806370a08231146101fd57806395d89b411461022b578063a9059cbb146102bb578063d0e30db0146102ee578063dd62ed3e146102f8575b6100a25b61009f61032c565b5b565b005b34156100ac57fe5b6100b461037b565b6040805160208082528351818301528351919283929083019185019080838382156100fa575b8051825260208311156100fa57601f1990920191602091820191016100da565b505050905090810190601f1680156101265780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561013c57fe5b610153600160a060020a03600435166024356103a3565b604080519115158252519081900360200190f35b341561016f57fe5b61017761040e565b60408051918252519081900360200190f35b341561019157fe5b610153600160a060020a0360043581169060243516604435610414565b604080519115158252519081900360200190f35b34156101ca57fe5b6100a2600435610537565b005b34156101df57fe5b6101e76105b8565b6040805160ff9092168252519081900360200190f35b341561020557fe5b610177600160a060020a03600435166105bd565b60408051918252519081900360200190f35b341561023357fe5b6100b46105dc565b6040805160208082528351818301528351919283929083019185019080838382156100fa575b8051825260208311156100fa57601f1990920191602091820191016100da565b505050905090810190601f1680156101265780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34156102c357fe5b610153600160a060020a03600435166024356105fd565b604080519115158252519081900360200190f35b6100a261032c565b005b341561030057fe5b610177600160a060020a03600435811690602435166106af565b60408051918252519081900360200190f35b600160a060020a03331660009081526020819052604090205461034f90346106dc565b600160a060020a03331660009081526020819052604090205560025461037590346106dc565b6002555b565b60408051808201909152600b815260a960020a6a22ba3432b9102a37b5b2b702602082015281565b600160a060020a03338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60025481565b600160a060020a03808416600081815260016020908152604080832033909516835293815283822054928252819052918220548390108015906104575750828110155b801561047d5750600160a060020a03841660009081526020819052604090205483810110155b1561052957600160a060020a03808516600090815260208190526040808220805487019055918716815220805484900390556000198110156104e757600160a060020a03808616600090815260016020908152604080832033909416835292905220805484900390555b83600160a060020a031685600160a060020a031660008051602061070e833981519152856040518082815260200191505060405180910390a36001915061052e565b600091505b5b509392505050565b600160a060020a03331660009081526020819052604090205461055a90826106f6565b600160a060020a03331660009081526020819052604090205560025461058090826106f6565b600255604051600160a060020a0333169082156108fc029083906000818181858888f1935050505015156105b45760006000fd5b5b50565b601281565b600160a060020a0381166000908152602081905260409020545b919050565b604080518082019091526004815260e360020a630ae8aa8902602082015281565b600160a060020a0333166000908152602081905260408120548290108015906106405750600160a060020a03831660009081526020819052604090205482810110155b156106a057600160a060020a03338116600081815260208181526040808320805488900390559387168083529184902080548701905583518681529351919360008051602061070e833981519152929081900390910190a3506001610408565b506000610408565b5b92915050565b600160a060020a038083166000908152600160209081526040808320938516835292905220545b92915050565b6000828201838110156106eb57fe5b8091505b5092915050565b60008282111561070257fe5b508082035b929150505600ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa165627a7a72305820ec42c469bb8ddd5de28c55b9cc393c812397c063a57fb88926e3f6de246318b70029",
"networks": {
"1": {
"links": {},
"events": {
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
"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"
},
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925": {
"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"
}
},
"updated_at": 1502488087000,
"address": "0x2956356cd2a2bf3202f771f50d3d14a367b48070"
},
"3": {
"address": "0xc00fd9820cd2898cc4c054b7bf142de637ad129a"
},
"42": {
"links": {},
"events": {
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
"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"
},
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925": {
"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"
}
},
"updated_at": 1502391794392,
"address": "0x05d090b51c40b020eab3bfcb6a2dff130df22e9c"
},
"50": {
"links": {},
"events": {
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
"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"
},
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925": {
"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"
}
},
"updated_at": 1503318938233,
"address": "0x48bacb9266a570d521063ef5dd96e61686dbe788"
}
},
"schema_version": "0.0.5",
"updated_at": 1503318938233
}
}

File diff suppressed because one or more lines are too long

View File

@@ -169,8 +169,7 @@
"type": "event"
}
],
"unlinked_binary": "0x6060604052341561000c57fe5b5b6101e08061001c6000396000f3006060604052361561005c5763ffffffff60e060020a600035041663095ea7b3811461005e57806318160ddd1461009157806323b872dd146100b357806370a08231146100ec578063a9059cbb1461005e578063dd62ed3e1461014d575bfe5b341561006657fe5b61007d600160a060020a0360043516602435610181565b604080519115158252519081900360200190f35b341561009957fe5b6100a161018a565b60408051918252519081900360200190f35b34156100bb57fe5b61007d600160a060020a0360043581169060243516604435610190565b604080519115158252519081900360200190f35b34156100f457fe5b6100a1600160a060020a036004351661019a565b60408051918252519081900360200190f35b341561006657fe5b61007d600160a060020a0360043516602435610181565b604080519115158252519081900360200190f35b341561015557fe5b6100a1600160a060020a0360043581169060243516610181565b60408051918252519081900360200190f35b60005b92915050565b60005b90565b60005b9392505050565b60005b919050565b60005b92915050565b60005b929150505600a165627a7a723058202e3f7ac17048343c0d0ea24fccb64620577374eeeed61539e543df4025d7d0db0029",
"networks": {},
"schema_version": "0.0.5",
"updated_at": 1503317882695
}
}

File diff suppressed because one or more lines are too long

View File

@@ -167,132 +167,7 @@
"type": "event"
}
],
"unlinked_binary": "0x60606040525b60008054600160a060020a03191633600160a060020a03161790555b5b6106e6806100316000396000f300606060405236156100725763ffffffff60e060020a60003504166315dacbea811461007457806342f1181e146100b3578063494503d4146100d157806370712939146101005780638da5cb5b1461011e578063b91816111461014a578063d39de6e91461017a578063f2fde38b146101e5575bfe5b341561007c57fe5b61009f600160a060020a0360043581169060243581169060443516606435610203565b604080519115158252519081900360200190f35b34156100bb57fe5b6100cf600160a060020a03600435166102ae565b005b34156100d957fe5b6100e4600435610390565b60408051600160a060020a039092168252519081900360200190f35b341561010857fe5b6100cf600160a060020a03600435166103c2565b005b341561012657fe5b6100e461055a565b60408051600160a060020a039092168252519081900360200190f35b341561015257fe5b61009f600160a060020a0360043516610569565b604080519115158252519081900360200190f35b341561018257fe5b61018a61057e565b60408051602080825283518183015283519192839290830191858101910280838382156101d2575b8051825260208311156101d257601f1990920191602091820191016101b2565b5050509050019250505060405180910390f35b34156101ed57fe5b6100cf600160a060020a03600435166105e7565b005b600160a060020a03331660009081526001602052604081205460ff16151561022b5760006000fd5b6040805160006020918201819052825160e060020a6323b872dd028152600160a060020a0388811660048301528781166024830152604482018790529351938916936323b872dd9360648084019491938390030190829087803b151561028d57fe5b6102c65a03f1151561029b57fe5b5050604051519150505b5b949350505050565b60005433600160a060020a039081169116146102ca5760006000fd5b600160a060020a038116600090815260016020526040902054819060ff16156102f35760006000fd5b600160a060020a0382166000908152600160208190526040909120805460ff191682179055600280549091810161032a8382610633565b916000526020600020900160005b81546101009190910a600160a060020a0381810219909216868316918202179092556040513390911692507f94bb87f4c15c4587ff559a7584006fa01ddf9299359be6b512b94527aa961aca90600090a35b5b505b50565b600280548290811061039e57fe5b906000526020600020900160005b915054906101000a9004600160a060020a031681565b6000805433600160a060020a039081169116146103df5760006000fd5b600160a060020a038216600090815260016020526040902054829060ff1615156104095760006000fd5b600160a060020a0383166000908152600160205260408120805460ff1916905591505b6002548210156105195782600160a060020a031660028381548110151561044f57fe5b906000526020600020900160005b9054906101000a9004600160a060020a0316600160a060020a0316141561050d5760028054600019810190811061049057fe5b906000526020600020900160005b9054906101000a9004600160a060020a03166002838154811015156104bf57fe5b906000526020600020900160005b6101000a815481600160a060020a030219169083600160a060020a0316021790555060016002818180549050039150816105079190610633565b50610519565b5b60019091019061042c565b604051600160a060020a0333811691908516907ff5b347a1e40749dd050f5f07fbdbeb7e3efa9756903044dd29401fd1d4bb4a1c90600090a35b5b505b5050565b600054600160a060020a031681565b60016020526000908152604090205460ff1681565b610586610687565b60028054806020026020016040519081016040528092919081815260200182805480156105dc57602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116105be575b505050505090505b90565b60005433600160a060020a039081169116146106035760006000fd5b600160a060020a0381161561038d5760008054600160a060020a031916600160a060020a0383161790555b5b5b50565b81548183558181151161055357600083815260209020610553918101908301610699565b5b505050565b81548183558181151161055357600083815260209020610553918101908301610699565b5b505050565b60408051602081019091526000815290565b6105e491905b808211156106b3576000815560010161069f565b5090565b905600a165627a7a72305820d2924957bb88a128789172e164d874fe5445218fc2dde2f5eb265839a1f341a20029",
"networks": {
"1": {
"links": {},
"events": {
"0x94bb87f4c15c4587ff559a7584006fa01ddf9299359be6b512b94527aa961aca": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressAdded",
"type": "event"
},
"0xf5b347a1e40749dd050f5f07fbdbeb7e3efa9756903044dd29401fd1d4bb4a1c": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressRemoved",
"type": "event"
}
},
"updated_at": 1502478966000,
"address": "0x8da0d80f5007ef1e431dd2127178d224e32c2ef4"
},
"42": {
"links": {},
"events": {
"0x94bb87f4c15c4587ff559a7584006fa01ddf9299359be6b512b94527aa961aca": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressAdded",
"type": "event"
},
"0xf5b347a1e40749dd050f5f07fbdbeb7e3efa9756903044dd29401fd1d4bb4a1c": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressRemoved",
"type": "event"
}
},
"updated_at": 1502391794384,
"address": "0x087eed4bc1ee3de49befbd66c662b434b15d49d4"
},
"50": {
"links": {},
"events": {
"0x94bb87f4c15c4587ff559a7584006fa01ddf9299359be6b512b94527aa961aca": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressAdded",
"type": "event"
},
"0xf5b347a1e40749dd050f5f07fbdbeb7e3efa9756903044dd29401fd1d4bb4a1c": {
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressRemoved",
"type": "event"
}
},
"updated_at": 1503318938227,
"address": "0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c"
}
},
"networks": {},
"schema_version": "0.0.5",
"updated_at": 1503318938227
}
}

80
src/contract.ts Normal file
View File

@@ -0,0 +1,80 @@
import * as Web3 from 'web3';
import {_} from './utils/lodash';
import promisify = require('es6-promisify');
import {SchemaValidator, schemas} from '0x-json-schemas';
import {AbiType} from './types';
export class Contract implements Web3.ContractInstance {
public address: string;
public abi: Web3.ContractAbi;
private contract: Web3.ContractInstance;
private defaults: Partial<Web3.TxData>;
private validator: SchemaValidator;
// This class instance is going to be populated with functions and events depending on the ABI
// and we don't know their types in advance
[name: string]: any;
constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<Web3.TxData>) {
this.contract = web3ContractInstance;
this.address = web3ContractInstance.address;
this.abi = web3ContractInstance.abi;
this.defaults = defaults;
this.populateEvents();
this.populateFunctions();
this.validator = new SchemaValidator();
}
private populateFunctions(): void {
const functionsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Function);
_.forEach(functionsAbi, (functionAbi: Web3.MethodAbi) => {
if (functionAbi.constant) {
const cbStyleCallFunction = this.contract[functionAbi.name].call;
this[functionAbi.name] = {
callAsync: promisify(cbStyleCallFunction, this.contract),
};
} else {
const cbStyleFunction = this.contract[functionAbi.name];
const cbStyleEstimateGasFunction = this.contract[functionAbi.name].estimateGas;
this[functionAbi.name] = {
estimateGasAsync: promisify(cbStyleEstimateGasFunction, this.contract),
sendTransactionAsync: this.promisifyWithDefaultParams(cbStyleFunction),
};
}
});
}
private populateEvents(): void {
const eventsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Event);
_.forEach(eventsAbi, (eventAbi: Web3.EventAbi) => {
this[eventAbi.name] = this.contract[eventAbi.name];
});
}
private promisifyWithDefaultParams(fn: (...args: any[]) => void): (...args: any[]) => Promise<any> {
const promisifiedWithDefaultParams = (...args: any[]) => {
const promise = new Promise((resolve, reject) => {
const lastArg = args[args.length - 1];
let txData: Partial<Web3.TxData> = {};
if (this.isTxData(lastArg)) {
txData = args.pop();
}
txData = {
...this.defaults,
...txData,
};
const callback = (err: Error, data: any) => {
if (_.isNull(err)) {
resolve(data);
} else {
reject(err);
}
};
args.push(txData);
args.push(callback);
fn.apply(this.contract, args);
});
return promise;
};
return promisifiedWithDefaultParams;
}
private isTxData(lastArg: any): boolean {
const isValid = this.validator.isValid(lastArg, schemas.txDataSchema);
return isValid;
}
}

View File

@@ -1,53 +1,132 @@
import * as _ from 'lodash';
import contract = require('truffle-contract');
import {_} from '../utils/lodash';
import * as Web3 from 'web3';
import {BlockAndLogStreamer, Block} from 'ethereumjs-blockstream';
import {Web3Wrapper} from '../web3_wrapper';
import {ZeroExError, Artifact, ContractInstance} from '../types';
import {utils} from '../utils/utils';
import {AbiDecoder} from '../utils/abi_decoder';
import {
ZeroExError,
InternalZeroExError,
Artifact,
LogWithDecodedArgs,
RawLog,
ContractEvents,
SubscriptionOpts,
IndexedFilterValues,
EventCallback,
BlockParamLiteral,
ContractEventArgs,
} from '../types';
import {constants} from '../utils/constants';
import {intervalUtils} from '../utils/interval_utils';
import {filterUtils} from '../utils/filter_utils';
export class ContractWrapper {
protected _web3Wrapper: Web3Wrapper;
private _gasPrice?: BigNumber.BigNumber;
constructor(web3Wrapper: Web3Wrapper, gasPrice?: BigNumber.BigNumber) {
private _abiDecoder?: AbiDecoder;
private _blockAndLogStreamer: BlockAndLogStreamer|undefined;
private _blockAndLogStreamInterval: NodeJS.Timer;
private _filters: {[filterToken: string]: Web3.FilterObject};
private _filterCallbacks: {[filterToken: string]: EventCallback<ContractEventArgs>};
private _onLogAddedSubscriptionToken: string|undefined;
private _onLogRemovedSubscriptionToken: string|undefined;
constructor(web3Wrapper: Web3Wrapper, abiDecoder?: AbiDecoder) {
this._web3Wrapper = web3Wrapper;
this._gasPrice = gasPrice;
this._abiDecoder = abiDecoder;
this._filters = {};
this._filterCallbacks = {};
this._blockAndLogStreamer = undefined;
this._onLogAddedSubscriptionToken = undefined;
this._onLogRemovedSubscriptionToken = undefined;
}
protected async _instantiateContractIfExistsAsync(artifact: Artifact, address?: string): Promise<ContractInstance> {
const c = await contract(artifact);
c.defaults({
gasPrice: this._gasPrice,
protected _subscribe<ArgsType extends ContractEventArgs>(
address: string, eventName: ContractEvents, indexFilterValues: IndexedFilterValues, abi: Web3.ContractAbi,
callback: EventCallback<ArgsType>): string {
const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi);
if (_.isUndefined(this._blockAndLogStreamer)) {
this._startBlockAndLogStream();
}
const filterToken = filterUtils.generateUUID();
this._filters[filterToken] = filter;
this._filterCallbacks[filterToken] = callback;
return filterToken;
}
protected _unsubscribe(filterToken: string): void {
if (_.isUndefined(this._filters[filterToken])) {
throw new Error(ZeroExError.SubscriptionNotFound);
}
delete this._filters[filterToken];
delete this._filterCallbacks[filterToken];
if (_.isEmpty(this._filters)) {
this._stopBlockAndLogStream();
}
}
protected async _getLogsAsync<ArgsType extends ContractEventArgs>(
address: string, eventName: ContractEvents, subscriptionOpts: SubscriptionOpts,
indexFilterValues: IndexedFilterValues, abi: Web3.ContractAbi): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi, subscriptionOpts);
const logs = await this._web3Wrapper.getLogsAsync(filter);
const logsWithDecodedArguments = _.map(logs, this._tryToDecodeLogOrNoop.bind(this));
return logsWithDecodedArguments;
}
protected _tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>(
log: Web3.LogEntry): LogWithDecodedArgs<ArgsType>|RawLog {
if (_.isUndefined(this._abiDecoder)) {
throw new Error(InternalZeroExError.NoAbiDecoder);
}
const logWithDecodedArgs = this._abiDecoder.tryToDecodeLogOrNoop(log);
return logWithDecodedArgs;
}
protected async _instantiateContractIfExistsAsync<ContractType extends Web3.ContractInstance>(
artifact: Artifact, addressIfExists?: string): Promise<ContractType> {
const contractInstance =
await this._web3Wrapper.getContractInstanceFromArtifactAsync<ContractType>(artifact, addressIfExists);
return contractInstance;
}
private _onLogStateChanged<ArgsType extends ContractEventArgs>(removed: boolean, log: Web3.LogEntry): void {
_.forEach(this._filters, (filter: Web3.FilterObject, filterToken: string) => {
if (filterUtils.matchesFilter(log, filter)) {
const decodedLog = this._tryToDecodeLogOrNoop(log) as LogWithDecodedArgs<ArgsType>;
const logEvent = {
...decodedLog,
removed,
};
this._filterCallbacks[filterToken](logEvent);
}
});
const providerObj = this._web3Wrapper.getCurrentProvider();
c.setProvider(providerObj);
const networkIdIfExists = await this._web3Wrapper.getNetworkIdIfExistsAsync();
const artifactNetworkConfigs = _.isUndefined(networkIdIfExists) ?
undefined :
artifact.networks[networkIdIfExists];
let contractAddress;
if (!_.isUndefined(address)) {
contractAddress = address;
} else if (!_.isUndefined(artifactNetworkConfigs)) {
contractAddress = artifactNetworkConfigs.address.toLowerCase();
}
if (!_.isUndefined(contractAddress)) {
const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(contractAddress);
if (!doesContractExist) {
throw new Error(ZeroExError.ContractDoesNotExist);
}
}
try {
const contractInstance = _.isUndefined(address) ? await c.deployed() : await c.at(address);
return contractInstance;
} catch (err) {
const errMsg = `${err}`;
if (_.includes(errMsg, 'not been deployed to detected network')) {
throw new Error(ZeroExError.ContractDoesNotExist);
} else {
utils.consoleLog(`Notice: Error encountered: ${err} ${err.stack}`);
throw new Error(ZeroExError.UnhandledError);
}
}
private _startBlockAndLogStream(): void {
this._blockAndLogStreamer = new BlockAndLogStreamer(
this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper),
this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper),
);
const catchAllLogFilter = {};
this._blockAndLogStreamer.addLogFilter(catchAllLogFilter);
this._blockAndLogStreamInterval = intervalUtils.setAsyncExcludingInterval(
this._reconcileBlockAsync.bind(this), constants.DEFAULT_BLOCK_POLLING_INTERVAL,
);
let removed = false;
this._onLogAddedSubscriptionToken = this._blockAndLogStreamer.subscribeToOnLogAdded(
this._onLogStateChanged.bind(this, removed),
);
removed = true;
this._onLogRemovedSubscriptionToken = this._blockAndLogStreamer.subscribeToOnLogRemoved(
this._onLogStateChanged.bind(this, removed),
);
}
private _stopBlockAndLogStream(): void {
(this._blockAndLogStreamer as BlockAndLogStreamer).unsubscribeFromOnLogAdded(
this._onLogAddedSubscriptionToken as string);
(this._blockAndLogStreamer as BlockAndLogStreamer).unsubscribeFromOnLogRemoved(
this._onLogRemovedSubscriptionToken as string);
intervalUtils.clearAsyncExcludingInterval(this._blockAndLogStreamInterval);
delete this._blockAndLogStreamer;
}
private async _reconcileBlockAsync(): Promise<void> {
const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest);
// We need to coerce to Block type cause Web3.Block includes types for mempool blocks
if (!_.isUndefined(this._blockAndLogStreamer)) {
// If we clear the interval while fetching the block - this._blockAndLogStreamer will be undefined
this._blockAndLogStreamer.reconcileNewBlock(latestBlock as any as Block);
}
}
}

View File

@@ -1,10 +1,10 @@
import * as _ from 'lodash';
import {_} from '../utils/lodash';
import {Web3Wrapper} from '../web3_wrapper';
import {ContractWrapper} from './contract_wrapper';
import {TokenWrapper} from './token_wrapper';
import {EtherTokenContract, ZeroExError} from '../types';
import {assert} from '../utils/assert';
import * as EtherTokenArtifacts from '../artifacts/EtherToken.json';
import {artifacts} from '../artifacts';
/**
* This class includes all the functionality related to interacting with a wrapped Ether ERC20 token contract.
@@ -13,9 +13,11 @@ import * as EtherTokenArtifacts from '../artifacts/EtherToken.json';
export class EtherTokenWrapper extends ContractWrapper {
private _etherTokenContractIfExists?: EtherTokenContract;
private _tokenWrapper: TokenWrapper;
constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper, gasPrice?: BigNumber.BigNumber) {
super(web3Wrapper, gasPrice);
private _contractAddressIfExists?: string;
constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper, contractAddressIfExists?: string) {
super(web3Wrapper);
this._tokenWrapper = tokenWrapper;
this._contractAddressIfExists = contractAddressIfExists;
}
/**
* Deposit ETH into the Wrapped ETH smart contract and issues the equivalent number of wrapped ETH tokens
@@ -23,8 +25,9 @@ export class EtherTokenWrapper extends ContractWrapper {
* for ETH.
* @param amountInWei Amount of ETH in Wei the caller wishes to deposit.
* @param depositor The hex encoded user Ethereum address that would like to make the deposit.
* @return Transaction hash.
*/
public async depositAsync(amountInWei: BigNumber.BigNumber, depositor: string): Promise<void> {
public async depositAsync(amountInWei: BigNumber.BigNumber, depositor: string): Promise<string> {
assert.isBigNumber('amountInWei', amountInWei);
await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper);
@@ -32,18 +35,20 @@ export class EtherTokenWrapper extends ContractWrapper {
assert.assert(ethBalanceInWei.gte(amountInWei), ZeroExError.InsufficientEthBalanceForDeposit);
const wethContract = await this._getEtherTokenContractAsync();
await wethContract.deposit({
const txHash = await wethContract.deposit.sendTransactionAsync({
from: depositor,
value: amountInWei,
});
return txHash;
}
/**
* Withdraw ETH to the withdrawer's address from the wrapped ETH smart contract in exchange for the
* equivalent number of wrapped ETH tokens.
* @param amountInWei Amount of ETH in Wei the caller wishes to withdraw.
* @param withdrawer The hex encoded user Ethereum address that would like to make the withdrawl.
* @return Transaction hash.
*/
public async withdrawAsync(amountInWei: BigNumber.BigNumber, withdrawer: string): Promise<void> {
public async withdrawAsync(amountInWei: BigNumber.BigNumber, withdrawer: string): Promise<string> {
assert.isBigNumber('amountInWei', amountInWei);
await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper);
@@ -52,9 +57,10 @@ export class EtherTokenWrapper extends ContractWrapper {
assert.assert(WETHBalanceInBaseUnits.gte(amountInWei), ZeroExError.InsufficientWEthBalanceForWithdrawal);
const wethContract = await this._getEtherTokenContractAsync();
await wethContract.withdraw(amountInWei, {
const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, {
from: withdrawer,
});
return txHash;
}
/**
* Retrieves the Wrapped Ether token contract address
@@ -71,7 +77,9 @@ export class EtherTokenWrapper extends ContractWrapper {
if (!_.isUndefined(this._etherTokenContractIfExists)) {
return this._etherTokenContractIfExists;
}
const contractInstance = await this._instantiateContractIfExistsAsync((EtherTokenArtifacts as any));
const contractInstance = await this._instantiateContractIfExistsAsync<EtherTokenContract>(
artifacts.EtherTokenArtifact, this._contractAddressIfExists,
);
this._etherTokenContractIfExists = contractInstance as EtherTokenContract;
return this._etherTokenContractIfExists;
}

View File

@@ -1,7 +1,7 @@
import * as _ from 'lodash';
import {_} from '../utils/lodash';
import * as Web3 from 'web3';
import * as BigNumber from 'bignumber.js';
import {SchemaValidator, schemas} from '0x-json-schemas';
import promisify = require('es6-promisify');
import {schemas} from '0x-json-schemas';
import {Web3Wrapper} from '../web3_wrapper';
import {
ECSignature,
@@ -12,37 +12,46 @@ import {
OrderValues,
OrderAddresses,
Order,
OrderFillOrKillRequest,
SignedOrder,
ContractEvent,
ExchangeEvents,
ContractEventEmitter,
SubscriptionOpts,
IndexedFilterValues,
CreateContractEvent,
ContractEventObj,
ContractResponse,
OrderCancellationRequest,
OrderFillRequest,
LogErrorContractEventArgs,
LogFillContractEventArgs,
LogCancelContractEventArgs,
LogWithDecodedArgs,
MethodOpts,
ValidateOrderFillableOpts,
OrderTransactionOpts,
RawLog,
EventCallback,
ExchangeContractEventArgs,
DecodedLogArgs,
} from '../types';
import {assert} from '../utils/assert';
import {utils} from '../utils/utils';
import {eventUtils} from '../utils/event_utils';
import {OrderValidationUtils} from '../utils/order_validation_utils';
import {ContractWrapper} from './contract_wrapper';
import {constants} from '../utils/constants';
import {TokenWrapper} from './token_wrapper';
import {decorators} from '../utils/decorators';
import * as ExchangeArtifacts from '../artifacts/Exchange.json';
import {AbiDecoder} from '../utils/abi_decoder';
import {ExchangeTransferSimulator} from '../utils/exchange_transfer_simulator';
import {artifacts} from '../artifacts';
const SHOULD_VALIDATE_BY_DEFAULT = true;
/**
* This class includes all the functionality related to calling methods and subscribing to
* events of the 0x Exchange smart contract.
*/
export class ExchangeWrapper extends ContractWrapper {
private _exchangeContractIfExists?: ExchangeContract;
private _activeSubscriptions: string[];
private _orderValidationUtils: OrderValidationUtils;
private _tokenWrapper: TokenWrapper;
private _exchangeContractErrCodesToMsg = {
[ExchangeContractErrCodes.ERROR_FILL_EXPIRED]: ExchangeContractErrs.OrderFillExpired,
[ExchangeContractErrCodes.ERROR_CANCEL_EXPIRED]: ExchangeContractErrs.OrderFillExpired,
@@ -51,10 +60,7 @@ export class ExchangeWrapper extends ContractWrapper {
[ExchangeContractErrCodes.ERROR_FILL_TRUNCATION]: ExchangeContractErrs.OrderFillRoundingError,
[ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.FillBalanceAllowanceError,
};
private _exchangeContractIfExists?: ExchangeContract;
private _exchangeLogEventEmitters: ContractEventEmitter[];
private _orderValidationUtils: OrderValidationUtils;
private _tokenWrapper: TokenWrapper;
private _contractAddressIfExists?: string;
private static _getOrderAddressesAndValues(order: Order): [OrderAddresses, OrderValues] {
const orderAddresses: OrderAddresses = [
order.maker,
@@ -73,11 +79,13 @@ export class ExchangeWrapper extends ContractWrapper {
];
return [orderAddresses, orderValues];
}
constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper, gasPrice?: BigNumber.BigNumber) {
super(web3Wrapper, gasPrice);
constructor(web3Wrapper: Web3Wrapper, abiDecoder: AbiDecoder,
tokenWrapper: TokenWrapper, contractAddressIfExists?: string) {
super(web3Wrapper, abiDecoder);
this._tokenWrapper = tokenWrapper;
this._orderValidationUtils = new OrderValidationUtils(tokenWrapper, this);
this._exchangeLogEventEmitters = [];
this._activeSubscriptions = [];
this._contractAddressIfExists = contractAddressIfExists;
}
/**
* Returns the unavailable takerAmount of an order. Unavailable amount is defined as the total
@@ -85,13 +93,18 @@ export class ExchangeWrapper extends ContractWrapper {
* subtracting the unavailable amount from the total order takerAmount.
* @param orderHash The hex encoded orderHash for which you would like to retrieve the
* unavailable takerAmount.
* @param methodOpts Optional arguments this method accepts.
* @return The amount of the order (in taker tokens) that has either been filled or canceled.
*/
public async getUnavailableTakerAmountAsync(orderHash: string): Promise<BigNumber.BigNumber> {
public async getUnavailableTakerAmountAsync(orderHash: string,
methodOpts?: MethodOpts): Promise<BigNumber.BigNumber> {
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
const exchangeContract = await this._getExchangeContractAsync();
let unavailableTakerTokenAmount = await exchangeContract.getUnavailableTakerTokenAmount.call(orderHash);
const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
let unavailableTakerTokenAmount = await exchangeContract.getUnavailableTakerTokenAmount.callAsync(
orderHash, defaultBlock,
);
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
unavailableTakerTokenAmount = new BigNumber(unavailableTakerTokenAmount);
return unavailableTakerTokenAmount;
@@ -99,13 +112,15 @@ export class ExchangeWrapper extends ContractWrapper {
/**
* Retrieve the takerAmount of an order that has already been filled.
* @param orderHash The hex encoded orderHash for which you would like to retrieve the filled takerAmount.
* @param methodOpts Optional arguments this method accepts.
* @return The amount of the order (in taker tokens) that has already been filled.
*/
public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber.BigNumber> {
public async getFilledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber.BigNumber> {
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
const exchangeContract = await this._getExchangeContractAsync();
let fillAmountInBaseUnits = await exchangeContract.filled.call(orderHash);
const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
let fillAmountInBaseUnits = await exchangeContract.filled.callAsync(orderHash, defaultBlock);
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
fillAmountInBaseUnits = new BigNumber(fillAmountInBaseUnits);
return fillAmountInBaseUnits;
@@ -114,13 +129,15 @@ export class ExchangeWrapper extends ContractWrapper {
* Retrieve the takerAmount of an order that has been cancelled.
* @param orderHash The hex encoded orderHash for which you would like to retrieve the
* cancelled takerAmount.
* @param methodOpts Optional arguments this method accepts.
* @return The amount of the order (in taker tokens) that has been cancelled.
*/
public async getCanceledTakerAmountAsync(orderHash: string): Promise<BigNumber.BigNumber> {
public async getCanceledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber.BigNumber> {
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
const exchangeContract = await this._getExchangeContractAsync();
let cancelledAmountInBaseUnits = await exchangeContract.cancelled.call(orderHash);
const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
let cancelledAmountInBaseUnits = await exchangeContract.cancelled.callAsync(orderHash, defaultBlock);
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
cancelledAmountInBaseUnits = new BigNumber(cancelledAmountInBaseUnits);
return cancelledAmountInBaseUnits;
@@ -141,23 +158,33 @@ export class ExchangeWrapper extends ContractWrapper {
* @param takerAddress The user Ethereum address who would like to fill this order.
* Must be available via the supplied Web3.Provider
* passed to 0x.js.
* @return The amount of the order that was filled (in taker token baseUnits).
* @param orderTransactionOpts Optional arguments this method accepts.
* @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
takerAddress: string): Promise<BigNumber.BigNumber> {
takerAddress: string,
orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
const exchangeInstance = await this._getExchangeContractAsync();
await this.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress);
const shouldValidate = _.isUndefined(orderTransactionOpts) ?
SHOULD_VALIDATE_BY_DEFAULT :
orderTransactionOpts.shouldValidate;
if (shouldValidate) {
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
}
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder);
const gas = await exchangeInstance.fillOrder.estimateGas(
const gas = await exchangeInstance.fillOrder.estimateGasAsync(
orderAddresses,
orderValues,
fillTakerTokenAmount,
@@ -169,7 +196,7 @@ export class ExchangeWrapper extends ContractWrapper {
from: takerAddress,
},
);
const response: ContractResponse = await exchangeInstance.fillOrder(
const txHash: string = await exchangeInstance.fillOrder.sendTransactionAsync(
orderAddresses,
orderValues,
fillTakerTokenAmount,
@@ -182,10 +209,7 @@ export class ExchangeWrapper extends ContractWrapper {
gas,
},
);
this._throwErrorLogsAsErrors(response.logs);
const logFillArgs = response.logs[0].args as LogFillContractEventArgs;
const filledTakerTokenAmount = new BigNumber(logFillArgs.filledTakerTokenAmount);
return filledTakerTokenAmount;
return txHash;
}
/**
* Sequentially and atomically fills signedOrders up to the specified takerTokenFillAmount.
@@ -201,12 +225,14 @@ export class ExchangeWrapper extends ContractWrapper {
* @param takerAddress The user Ethereum address who would like to fill these
* orders. Must be available via the supplied Web3.Provider
* passed to 0x.js.
* @return The amount of the orders that was filled (in taker token baseUnits).
* @param orderTransactionOpts Optional arguments this method accepts.
* @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async fillOrdersUpToAsync(signedOrders: SignedOrder[], fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
takerAddress: string): Promise<BigNumber.BigNumber> {
takerAddress: string,
orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
const takerTokenAddresses = _.map(signedOrders, signedOrder => signedOrder.takerTokenAddress);
assert.hasAtMostOneUniqueValue(takerTokenAddresses,
@@ -217,12 +243,21 @@ export class ExchangeWrapper extends ContractWrapper {
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
for (const signedOrder of signedOrders) {
await this.validateFillOrderThrowIfInvalidAsync(
signedOrder, fillTakerTokenAmount, takerAddress);
const shouldValidate = _.isUndefined(orderTransactionOpts) ?
SHOULD_VALIDATE_BY_DEFAULT :
orderTransactionOpts.shouldValidate;
if (shouldValidate) {
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
for (const signedOrder of signedOrders) {
await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
}
}
if (_.isEmpty(signedOrders)) {
return new BigNumber(0); // no-op
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
}
const orderAddressesValuesAndSignatureArray = _.map(signedOrders, signedOrder => {
@@ -239,7 +274,7 @@ export class ExchangeWrapper extends ContractWrapper {
);
const exchangeInstance = await this._getExchangeContractAsync();
const gas = await exchangeInstance.fillOrdersUpTo.estimateGas(
const gas = await exchangeInstance.fillOrdersUpTo.estimateGasAsync(
orderAddressesArray,
orderValuesArray,
fillTakerTokenAmount,
@@ -251,7 +286,7 @@ export class ExchangeWrapper extends ContractWrapper {
from: takerAddress,
},
);
const response: ContractResponse = await exchangeInstance.fillOrdersUpTo(
const txHash = await exchangeInstance.fillOrdersUpTo.sendTransactionAsync(
orderAddressesArray,
orderValuesArray,
fillTakerTokenAmount,
@@ -264,13 +299,7 @@ export class ExchangeWrapper extends ContractWrapper {
gas,
},
);
this._throwErrorLogsAsErrors(response.logs);
let filledTakerTokenAmount = new BigNumber(0);
_.each(response.logs, log => {
filledTakerTokenAmount = filledTakerTokenAmount.plus(
(log.args as LogFillContractEventArgs).filledTakerTokenAmount);
});
return filledTakerTokenAmount;
return txHash;
}
/**
* Batch version of fillOrderAsync.
@@ -288,11 +317,14 @@ export class ExchangeWrapper extends ContractWrapper {
* @param takerAddress The user Ethereum address who would like to fill
* these orders. Must be available via the supplied
* Web3.Provider passed to 0x.js.
* @param orderTransactionOpts Optional arguments this method accepts.
* @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async batchFillOrdersAsync(orderFillRequests: OrderFillRequest[],
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
takerAddress: string): Promise<void> {
takerAddress: string,
orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema);
const exchangeContractAddresses = _.map(
orderFillRequests,
@@ -302,12 +334,21 @@ export class ExchangeWrapper extends ContractWrapper {
ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress);
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
for (const orderFillRequest of orderFillRequests) {
await this.validateFillOrderThrowIfInvalidAsync(
orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount, takerAddress);
const shouldValidate = _.isUndefined(orderTransactionOpts) ?
SHOULD_VALIDATE_BY_DEFAULT :
orderTransactionOpts.shouldValidate;
if (shouldValidate) {
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
for (const orderFillRequest of orderFillRequests) {
await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
exchangeTradeEmulator, orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount,
takerAddress, zrxTokenAddress,
);
}
}
if (_.isEmpty(orderFillRequests)) {
return; // no-op
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
}
const orderAddressesValuesAmountsAndSignatureArray = _.map(orderFillRequests, orderFillRequest => {
@@ -325,7 +366,7 @@ export class ExchangeWrapper extends ContractWrapper {
);
const exchangeInstance = await this._getExchangeContractAsync();
const gas = await exchangeInstance.batchFillOrders.estimateGas(
const gas = await exchangeInstance.batchFillOrders.estimateGasAsync(
orderAddressesArray,
orderValuesArray,
fillTakerTokenAmounts,
@@ -337,7 +378,7 @@ export class ExchangeWrapper extends ContractWrapper {
from: takerAddress,
},
);
const response: ContractResponse = await exchangeInstance.batchFillOrders(
const txHash = await exchangeInstance.batchFillOrders.sendTransactionAsync(
orderAddressesArray,
orderValuesArray,
fillTakerTokenAmounts,
@@ -350,7 +391,7 @@ export class ExchangeWrapper extends ContractWrapper {
gas,
},
);
this._throwErrorLogsAsErrors(response.logs);
return txHash;
}
/**
* Attempts to fill a specific amount of an order. If the entire amount specified cannot be filled,
@@ -360,21 +401,32 @@ export class ExchangeWrapper extends ContractWrapper {
* @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill.
* @param takerAddress The user Ethereum address who would like to fill this order.
* Must be available via the supplied Web3.Provider passed to 0x.js.
* @param orderTransactionOpts Optional arguments this method accepts.
* @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async fillOrKillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber.BigNumber,
takerAddress: string): Promise<void> {
takerAddress: string,
orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
const exchangeInstance = await this._getExchangeContractAsync();
await this.validateFillOrKillOrderThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress);
const shouldValidate = _.isUndefined(orderTransactionOpts) ?
SHOULD_VALIDATE_BY_DEFAULT :
orderTransactionOpts.shouldValidate;
if (shouldValidate) {
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync(
exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
}
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder);
const gas = await exchangeInstance.fillOrKillOrder.estimateGas(
const gas = await exchangeInstance.fillOrKillOrder.estimateGasAsync(
orderAddresses,
orderValues,
fillTakerTokenAmount,
@@ -385,7 +437,7 @@ export class ExchangeWrapper extends ContractWrapper {
from: takerAddress,
},
);
const response: ContractResponse = await exchangeInstance.fillOrKillOrder(
const txHash = await exchangeInstance.fillOrKillOrder.sendTransactionAsync(
orderAddresses,
orderValues,
fillTakerTokenAmount,
@@ -397,40 +449,53 @@ export class ExchangeWrapper extends ContractWrapper {
gas,
},
);
this._throwErrorLogsAsErrors(response.logs);
return txHash;
}
/**
* Batch version of fillOrKill. Allows a taker to specify a batch of orders that will either be atomically
* filled (each to the specified fillAmount) or aborted.
* @param orderFillOrKillRequests An array of objects that conform to the OrderFillOrKillRequest interface.
* @param orderFillRequests An array of objects that conform to the OrderFillRequest interface.
* @param takerAddress The user Ethereum address who would like to fill there orders.
* Must be available via the supplied Web3.Provider passed to 0x.js.
* @param orderTransactionOpts Optional arguments this method accepts.
* @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async batchFillOrKillAsync(orderFillOrKillRequests: OrderFillOrKillRequest[],
takerAddress: string): Promise<void> {
assert.doesConformToSchema('orderFillOrKillRequests', orderFillOrKillRequests,
schemas.orderFillOrKillRequestsSchema);
public async batchFillOrKillAsync(orderFillRequests: OrderFillRequest[],
takerAddress: string,
orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
assert.doesConformToSchema('orderFillRequests', orderFillRequests,
schemas.orderFillRequestsSchema);
const exchangeContractAddresses = _.map(
orderFillOrKillRequests,
orderFillOrKillRequest => orderFillOrKillRequest.signedOrder.exchangeContractAddress,
orderFillRequests,
orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress,
);
assert.hasAtMostOneUniqueValue(exchangeContractAddresses,
ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
if (_.isEmpty(orderFillOrKillRequests)) {
return; // no-op
if (_.isEmpty(orderFillRequests)) {
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
}
const exchangeInstance = await this._getExchangeContractAsync();
for (const request of orderFillOrKillRequests) {
await this.validateFillOrKillOrderThrowIfInvalidAsync(
request.signedOrder, request.fillTakerAmount, takerAddress);
const shouldValidate = _.isUndefined(orderTransactionOpts) ?
SHOULD_VALIDATE_BY_DEFAULT :
orderTransactionOpts.shouldValidate;
if (shouldValidate) {
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
for (const orderFillRequest of orderFillRequests) {
await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync(
exchangeTradeEmulator, orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount,
takerAddress, zrxTokenAddress,
);
}
}
const orderAddressesValuesAndTakerTokenFillAmounts = _.map(orderFillOrKillRequests, request => {
const orderAddressesValuesAndTakerTokenFillAmounts = _.map(orderFillRequests, request => {
return [
...ExchangeWrapper._getOrderAddressesAndValues(request.signedOrder),
request.fillTakerAmount,
request.takerTokenFillAmount,
request.signedOrder.ecSignature.v,
request.signedOrder.ecSignature.r,
request.signedOrder.ecSignature.s,
@@ -441,7 +506,7 @@ export class ExchangeWrapper extends ContractWrapper {
const [orderAddresses, orderValues, fillTakerTokenAmounts, vParams, rParams, sParams] =
_.unzip<any>(orderAddressesValuesAndTakerTokenFillAmounts);
const gas = await exchangeInstance.batchFillOrKillOrders.estimateGas(
const gas = await exchangeInstance.batchFillOrKillOrders.estimateGasAsync(
orderAddresses,
orderValues,
fillTakerTokenAmounts,
@@ -452,7 +517,7 @@ export class ExchangeWrapper extends ContractWrapper {
from: takerAddress,
},
);
const response: ContractResponse = await exchangeInstance.batchFillOrKillOrders(
const txHash = await exchangeInstance.batchFillOrKillOrders.sendTransactionAsync(
orderAddresses,
orderValues,
fillTakerTokenAmounts,
@@ -464,27 +529,38 @@ export class ExchangeWrapper extends ContractWrapper {
gas,
},
);
this._throwErrorLogsAsErrors(response.logs);
return txHash;
}
/**
* Cancel a given fill amount of an order. Cancellations are cumulative.
* @param order An object that conforms to the Order or SignedOrder interface.
* The order you would like to cancel.
* @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel.
* @return The amount of the order that was cancelled (in taker token baseUnits).
* @param transactionOpts Optional arguments this method accepts.
* @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async cancelOrderAsync(
order: Order|SignedOrder, cancelTakerTokenAmount: BigNumber.BigNumber): Promise<BigNumber.BigNumber> {
public async cancelOrderAsync(order: Order|SignedOrder,
cancelTakerTokenAmount: BigNumber.BigNumber,
orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
assert.doesConformToSchema('order', order, schemas.orderSchema);
assert.isBigNumber('takerTokenCancelAmount', cancelTakerTokenAmount);
await assert.isSenderAddressAsync('order.maker', order.maker, this._web3Wrapper);
const exchangeInstance = await this._getExchangeContractAsync();
await this.validateCancelOrderThrowIfInvalidAsync(order, cancelTakerTokenAmount);
const shouldValidate = _.isUndefined(orderTransactionOpts) ?
SHOULD_VALIDATE_BY_DEFAULT :
orderTransactionOpts.shouldValidate;
if (shouldValidate) {
const orderHash = utils.getOrderHashHex(order);
const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash);
await this._orderValidationUtils.validateCancelOrderThrowIfInvalidAsync(
order, cancelTakerTokenAmount, unavailableTakerTokenAmount);
}
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order);
const gas = await exchangeInstance.cancelOrder.estimateGas(
const gas = await exchangeInstance.cancelOrder.estimateGasAsync(
orderAddresses,
orderValues,
cancelTakerTokenAmount,
@@ -492,7 +568,7 @@ export class ExchangeWrapper extends ContractWrapper {
from: order.maker,
},
);
const response: ContractResponse = await exchangeInstance.cancelOrder(
const txHash = await exchangeInstance.cancelOrder.sendTransactionAsync(
orderAddresses,
orderValues,
cancelTakerTokenAmount,
@@ -501,19 +577,19 @@ export class ExchangeWrapper extends ContractWrapper {
gas,
},
);
this._throwErrorLogsAsErrors(response.logs);
const logFillArgs = response.logs[0].args as LogCancelContractEventArgs;
const cancelledTakerTokenAmount = new BigNumber(logFillArgs.cancelledTakerTokenAmount);
return cancelledTakerTokenAmount;
return txHash;
}
/**
* Batch version of cancelOrderAsync. Atomically cancels multiple orders in a single transaction.
* All orders must be from the same maker.
* @param orderCancellationRequests An array of objects that conform to the OrderCancellationRequest
* interface.
* @param transactionOpts Optional arguments this method accepts.
* @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async batchCancelOrdersAsync(orderCancellationRequests: OrderCancellationRequest[]): Promise<void> {
public async batchCancelOrdersAsync(orderCancellationRequests: OrderCancellationRequest[],
orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
assert.doesConformToSchema('orderCancellationRequests', orderCancellationRequests,
schemas.orderCancellationRequestsSchema);
const exchangeContractAddresses = _.map(
@@ -526,13 +602,22 @@ export class ExchangeWrapper extends ContractWrapper {
assert.hasAtMostOneUniqueValue(makers, ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed);
const maker = makers[0];
await assert.isSenderAddressAsync('maker', maker, this._web3Wrapper);
for (const cancellationRequest of orderCancellationRequests) {
await this.validateCancelOrderThrowIfInvalidAsync(
cancellationRequest.order, cancellationRequest.takerTokenCancelAmount,
);
const shouldValidate = _.isUndefined(orderTransactionOpts) ?
SHOULD_VALIDATE_BY_DEFAULT :
orderTransactionOpts.shouldValidate;
if (shouldValidate) {
for (const orderCancellationRequest of orderCancellationRequests) {
const orderHash = utils.getOrderHashHex(orderCancellationRequest.order);
const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash);
await this._orderValidationUtils.validateCancelOrderThrowIfInvalidAsync(
orderCancellationRequest.order, orderCancellationRequest.takerTokenCancelAmount,
unavailableTakerTokenAmount,
);
}
}
if (_.isEmpty(orderCancellationRequests)) {
return; // no-op
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
}
const exchangeInstance = await this._getExchangeContractAsync();
const orderAddressesValuesAndTakerTokenCancelAmounts = _.map(orderCancellationRequests, cancellationRequest => {
@@ -544,7 +629,7 @@ export class ExchangeWrapper extends ContractWrapper {
// We use _.unzip<any> because _.unzip doesn't type check if values have different types :'(
const [orderAddresses, orderValues, cancelTakerTokenAmounts] =
_.unzip<any>(orderAddressesValuesAndTakerTokenCancelAmounts);
const gas = await exchangeInstance.batchCancelOrders.estimateGas(
const gas = await exchangeInstance.batchCancelOrders.estimateGasAsync(
orderAddresses,
orderValues,
cancelTakerTokenAmounts,
@@ -552,7 +637,7 @@ export class ExchangeWrapper extends ContractWrapper {
from: maker,
},
);
const response: ContractResponse = await exchangeInstance.batchCancelOrders(
const txHash = await exchangeInstance.batchCancelOrders.sendTransactionAsync(
orderAddresses,
orderValues,
cancelTakerTokenAmounts,
@@ -561,53 +646,56 @@ export class ExchangeWrapper extends ContractWrapper {
gas,
},
);
this._throwErrorLogsAsErrors(response.logs);
return txHash;
}
/**
* Subscribe to an event type emitted by the Exchange smart contract
* @param eventName The exchange contract event you would like to subscribe to.
* @param subscriptionOpts Subscriptions options that let you configure the subscription.
* @param indexFilterValues An object where the keys are indexed args returned by the event and
* the value is the value you are interested in. E.g `{maker: aUserAddressHex}`
* @param exchangeContractAddress The hex encoded address of the Exchange contract to call.
* @return ContractEventEmitter object
* Subscribe to an event type emitted by the Exchange contract.
* @param eventName The exchange contract event you would like to subscribe to.
* @param indexFilterValues An object where the keys are indexed args returned by the event and
* the value is the value you are interested in. E.g `{maker: aUserAddressHex}`
* @param callback Callback that gets called when a log is added/removed
* @return Subscription token used later to unsubscribe
*/
public async subscribeAsync(eventName: ExchangeEvents, subscriptionOpts: SubscriptionOpts,
indexFilterValues: IndexedFilterValues, exchangeContractAddress: string):
Promise<ContractEventEmitter> {
assert.isETHAddressHex('exchangeContractAddress', exchangeContractAddress);
public async subscribeAsync<ArgsType extends ExchangeContractEventArgs>(
eventName: ExchangeEvents, indexFilterValues: IndexedFilterValues,
callback: EventCallback<ArgsType>): Promise<string> {
assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents);
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
assert.isFunction('callback', callback);
const exchangeContractAddress = await this.getContractAddressAsync();
const subscriptionToken = this._subscribe<ArgsType>(
exchangeContractAddress, eventName, indexFilterValues, artifacts.ExchangeArtifact.abi, callback,
);
this._activeSubscriptions.push(subscriptionToken);
return subscriptionToken;
}
/**
* Cancel a subscription
* @param subscriptionToken Subscription token returned by `subscribe()`
*/
public unsubscribe(subscriptionToken: string): void {
_.pull(this._activeSubscriptions, subscriptionToken);
this._unsubscribe(subscriptionToken);
}
/**
* Gets historical logs without creating a subscription
* @param eventName The exchange contract event you would like to subscribe to.
* @param subscriptionOpts Subscriptions options that let you configure the subscription.
* @param indexFilterValues An object where the keys are indexed args returned by the event and
* the value is the value you are interested in. E.g `{_from: aUserAddressHex}`
* @return Array of logs that match the parameters
*/
public async getLogsAsync<ArgsType extends ExchangeContractEventArgs>(
eventName: ExchangeEvents, subscriptionOpts: SubscriptionOpts, indexFilterValues: IndexedFilterValues,
): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents);
assert.doesConformToSchema('subscriptionOpts', subscriptionOpts, schemas.subscriptionOptsSchema);
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
const exchangeContract = await this._getExchangeContractAsync();
let createLogEvent: CreateContractEvent;
switch (eventName) {
case ExchangeEvents.LogFill:
createLogEvent = exchangeContract.LogFill;
break;
case ExchangeEvents.LogError:
createLogEvent = exchangeContract.LogError;
break;
case ExchangeEvents.LogCancel:
createLogEvent = exchangeContract.LogCancel;
break;
default:
throw utils.spawnSwitchErr('ExchangeEvents', eventName);
}
const logEventObj: ContractEventObj = createLogEvent(indexFilterValues, subscriptionOpts);
const eventEmitter = eventUtils.wrapEventEmitter(logEventObj);
this._exchangeLogEventEmitters.push(eventEmitter);
return eventEmitter;
}
/**
* Stops watching for all exchange events
*/
public async stopWatchingAllEventsAsync(): Promise<void> {
const stopWatchingPromises = _.map(this._exchangeLogEventEmitters,
logEventObj => logEventObj.stopWatchingAsync());
await Promise.all(stopWatchingPromises);
this._exchangeLogEventEmitters = [];
const exchangeContractAddress = await this.getContractAddressAsync();
const logs = await this._getLogsAsync<ArgsType>(
exchangeContractAddress, eventName, subscriptionOpts, indexFilterValues, artifacts.ExchangeArtifact.abi,
);
return logs;
}
/**
* Retrieves the Ethereum address of the Exchange contract deployed on the network
@@ -619,6 +707,26 @@ export class ExchangeWrapper extends ContractWrapper {
const exchangeAddress = exchangeInstance.address;
return exchangeAddress;
}
/**
* Checks if order is still fillable and throws an error otherwise. Useful for orderbook
* pruning where you want to remove stale orders without knowing who the taker will be.
* @param signedOrder An object that conforms to the SignedOrder interface. The
* signedOrder you wish to validate.
* @param opts An object that conforms to the ValidateOrderFillableOpts
* interface. Allows specifying a specific fillTakerTokenAmount
* to validate for.
*/
public async validateOrderFillableOrThrowAsync(
signedOrder: SignedOrder, opts?: ValidateOrderFillableOpts,
): Promise<void> {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
const expectedFillTakerTokenAmount = !_.isUndefined(opts) ? opts.expectedFillTakerTokenAmount : undefined;
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
await this._orderValidationUtils.validateOrderFillableOrThrowAsync(
exchangeTradeEmulator, signedOrder, zrxTokenAddress, expectedFillTakerTokenAmount,
);
}
/**
* Checks if order fill will succeed and throws an error otherwise.
* @param signedOrder An object that conforms to the SignedOrder interface. The
@@ -633,9 +741,10 @@ export class ExchangeWrapper extends ContractWrapper {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
const zrxTokenAddress = await this._getZRXTokenAddressAsync();
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
}
/**
* Checks if cancelling a given order will succeed and throws an informative error if it won't.
@@ -666,9 +775,10 @@ export class ExchangeWrapper extends ContractWrapper {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
const zrxTokenAddress = await this._getZRXTokenAddressAsync();
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync(
signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
}
/**
* Checks if rounding error will be > 0.1% when computing makerTokenAmount by doing:
@@ -686,13 +796,44 @@ export class ExchangeWrapper extends ContractWrapper {
assert.isBigNumber('takerTokenAmount', takerTokenAmount);
assert.isBigNumber('makerTokenAmount', makerTokenAmount);
const exchangeInstance = await this._getExchangeContractAsync();
const isRoundingError = await exchangeInstance.isRoundingError.call(
const isRoundingError = await exchangeInstance.isRoundingError.callAsync(
fillTakerTokenAmount, takerTokenAmount, makerTokenAmount,
);
return isRoundingError;
}
/**
* Checks if logs contain LogError, which is emmited by Exchange contract on transaction failure.
* @param logs Transaction logs as returned by `zeroEx.awaitTransactionMinedAsync`
*/
public throwLogErrorsAsErrors(logs: Array<LogWithDecodedArgs<DecodedLogArgs>|Web3.LogEntry>): void {
const errLog = _.find(logs, {
event: ExchangeEvents.LogError,
}) as LogWithDecodedArgs<LogErrorContractEventArgs>|undefined;
if (!_.isUndefined(errLog)) {
const logArgs = errLog.args;
const errCode = logArgs.errorId.toNumber();
const errMessage = this._exchangeContractErrCodesToMsg[errCode];
throw new Error(errMessage);
}
}
/**
* Returns the ZRX token address used by the exchange contract.
* @return Address of ZRX token
*/
public async getZRXTokenAddressAsync(): Promise<string> {
const exchangeInstance = await this._getExchangeContractAsync();
const ZRXtokenAddress = await exchangeInstance.ZRX_TOKEN_CONTRACT.callAsync();
return ZRXtokenAddress;
}
/**
* Cancels all existing subscriptions
*/
public unsubscribeAll(): void {
_.forEach(this._activeSubscriptions, this._unsubscribe.bind(this));
this._activeSubscriptions = [];
}
private async _invalidateContractInstancesAsync(): Promise<void> {
await this.stopWatchingAllEventsAsync();
this.unsubscribeAll();
delete this._exchangeContractIfExists;
}
private async _isValidSignatureUsingContractCallAsync(dataHex: string, ecSignature: ECSignature,
@@ -703,7 +844,7 @@ export class ExchangeWrapper extends ContractWrapper {
const exchangeInstance = await this._getExchangeContractAsync();
const isValidSignature = await exchangeInstance.isValidSignature.call(
const isValidSignature = await exchangeInstance.isValidSignature.callAsync(
signerAddressHex,
dataHex,
ecSignature.v,
@@ -715,28 +856,23 @@ export class ExchangeWrapper extends ContractWrapper {
private async _getOrderHashHexUsingContractCallAsync(order: Order|SignedOrder): Promise<string> {
const exchangeInstance = await this._getExchangeContractAsync();
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order);
const orderHashHex = await exchangeInstance.getOrderHash.call(orderAddresses, orderValues);
const orderHashHex = await exchangeInstance.getOrderHash.callAsync(orderAddresses, orderValues);
return orderHashHex;
}
private _throwErrorLogsAsErrors(logs: ContractEvent[]): void {
const errEvent = _.find(logs, {event: 'LogError'});
if (!_.isUndefined(errEvent)) {
const errCode = (errEvent.args as LogErrorContractEventArgs).errorId.toNumber();
const errMessage = this._exchangeContractErrCodesToMsg[errCode];
throw new Error(errMessage);
}
}
private async _getExchangeContractAsync(): Promise<ExchangeContract> {
if (!_.isUndefined(this._exchangeContractIfExists)) {
return this._exchangeContractIfExists;
}
const contractInstance = await this._instantiateContractIfExistsAsync((ExchangeArtifacts as any));
const contractInstance = await this._instantiateContractIfExistsAsync<ExchangeContract>(
artifacts.ExchangeArtifact, this._contractAddressIfExists,
);
this._exchangeContractIfExists = contractInstance as ExchangeContract;
return this._exchangeContractIfExists;
}
private async _getZRXTokenAddressAsync(): Promise<string> {
private async _getTokenTransferProxyAddressAsync(): Promise<string> {
const exchangeInstance = await this._getExchangeContractAsync();
const ZRXtokenAddress = await exchangeInstance.ZRX_TOKEN_CONTRACT.call();
return ZRXtokenAddress;
const tokenTransferProxyAddress = await exchangeInstance.TOKEN_TRANSFER_PROXY_CONTRACT.callAsync();
const tokenTransferProxyAddressLowerCase = tokenTransferProxyAddress.toLowerCase();
return tokenTransferProxyAddressLowerCase;
}
}

View File

@@ -1,18 +1,20 @@
import * as _ from 'lodash';
import {_} from '../utils/lodash';
import {Web3Wrapper} from '../web3_wrapper';
import {assert} from '../utils/assert';
import {Token, TokenRegistryContract, TokenMetadata} from '../types';
import {constants} from '../utils/constants';
import {ContractWrapper} from './contract_wrapper';
import * as TokenRegistryArtifacts from '../artifacts/TokenRegistry.json';
import {artifacts} from '../artifacts';
/**
* This class includes all the functionality related to interacting with the 0x Token Registry smart contract.
*/
export class TokenRegistryWrapper extends ContractWrapper {
private _tokenRegistryContractIfExists?: TokenRegistryContract;
constructor(web3Wrapper: Web3Wrapper, gasPrice?: BigNumber.BigNumber) {
super(web3Wrapper, gasPrice);
private _contractAddressIfExists?: string;
constructor(web3Wrapper: Web3Wrapper, contractAddressIfExists?: string) {
super(web3Wrapper);
this._contractAddressIfExists = contractAddressIfExists;
}
/**
* Retrieves all the tokens currently listed in the Token Registry smart contract
@@ -35,7 +37,7 @@ export class TokenRegistryWrapper extends ContractWrapper {
*/
public async getTokenAddressesAsync(): Promise<string[]> {
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const addresses = await tokenRegistryContract.getTokenAddresses.call();
const addresses = await tokenRegistryContract.getTokenAddresses.callAsync();
return addresses;
}
/**
@@ -46,14 +48,14 @@ export class TokenRegistryWrapper extends ContractWrapper {
assert.isETHAddressHex('address', address);
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const metadata = await tokenRegistryContract.getTokenMetaData.call(address);
const metadata = await tokenRegistryContract.getTokenMetaData.callAsync(address);
const token = this._createTokenFromMetadata(metadata);
return token;
}
public async getTokenAddressBySymbolIfExistsAsync(symbol: string): Promise<string|undefined> {
assert.isString('symbol', symbol);
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const addressIfExists = await tokenRegistryContract.getTokenAddressBySymbol.call(symbol);
const addressIfExists = await tokenRegistryContract.getTokenAddressBySymbol.callAsync(symbol);
if (addressIfExists === constants.NULL_ADDRESS) {
return undefined;
}
@@ -62,7 +64,7 @@ export class TokenRegistryWrapper extends ContractWrapper {
public async getTokenAddressByNameIfExistsAsync(name: string): Promise<string|undefined> {
assert.isString('name', name);
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const addressIfExists = await tokenRegistryContract.getTokenAddressByName.call(name);
const addressIfExists = await tokenRegistryContract.getTokenAddressByName.callAsync(name);
if (addressIfExists === constants.NULL_ADDRESS) {
return undefined;
}
@@ -71,17 +73,27 @@ export class TokenRegistryWrapper extends ContractWrapper {
public async getTokenBySymbolIfExistsAsync(symbol: string): Promise<Token|undefined> {
assert.isString('symbol', symbol);
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const metadata = await tokenRegistryContract.getTokenBySymbol.call(symbol);
const metadata = await tokenRegistryContract.getTokenBySymbol.callAsync(symbol);
const token = this._createTokenFromMetadata(metadata);
return token;
}
public async getTokenByNameIfExistsAsync(name: string): Promise<Token|undefined> {
assert.isString('name', name);
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const metadata = await tokenRegistryContract.getTokenByName.call(name);
const metadata = await tokenRegistryContract.getTokenByName.callAsync(name);
const token = this._createTokenFromMetadata(metadata);
return token;
}
/**
* Retrieves the Ethereum address of the TokenRegistry contract deployed on the network
* that the user-passed web3 provider is connected to.
* @returns The Ethereum address of the TokenRegistry contract being used.
*/
public async getContractAddressAsync(): Promise<string> {
const tokenRegistryInstance = await this._getTokenRegistryContractAsync();
const tokenRegistryAddress = tokenRegistryInstance.address;
return tokenRegistryAddress;
}
private _createTokenFromMetadata(metadata: TokenMetadata): Token|undefined {
if (metadata[0] === constants.NULL_ADDRESS) {
return undefined;
@@ -101,7 +113,9 @@ export class TokenRegistryWrapper extends ContractWrapper {
if (!_.isUndefined(this._tokenRegistryContractIfExists)) {
return this._tokenRegistryContractIfExists;
}
const contractInstance = await this._instantiateContractIfExistsAsync((TokenRegistryArtifacts as any));
const contractInstance = await this._instantiateContractIfExistsAsync<TokenRegistryContract>(
artifacts.TokenRegistryArtifact, this._contractAddressIfExists,
);
this._tokenRegistryContractIfExists = contractInstance as TokenRegistryContract;
return this._tokenRegistryContractIfExists;
}

View File

@@ -1,6 +1,7 @@
import * as _ from 'lodash';
import {_} from '../utils/lodash';
import {Web3Wrapper} from '../web3_wrapper';
import {ContractWrapper} from './contract_wrapper';
import * as TokenTransferProxyArtifacts from '../artifacts/TokenTransferProxy.json';
import {artifacts} from '../artifacts';
import {TokenTransferProxyContract} from '../types';
/**
@@ -8,6 +9,11 @@ import {TokenTransferProxyContract} from '../types';
*/
export class TokenTransferProxyWrapper extends ContractWrapper {
private _tokenTransferProxyContractIfExists?: TokenTransferProxyContract;
private _tokenTransferProxyContractAddressFetcher: () => Promise<string>;
constructor(web3Wrapper: Web3Wrapper, tokenTransferProxyContractAddressFetcher: () => Promise<string>) {
super(web3Wrapper);
this._tokenTransferProxyContractAddressFetcher = tokenTransferProxyContractAddressFetcher;
}
/**
* Check if the Exchange contract address is authorized by the TokenTransferProxy contract.
* @param exchangeContractAddress The hex encoded address of the Exchange contract to call.
@@ -15,7 +21,7 @@ export class TokenTransferProxyWrapper extends ContractWrapper {
*/
public async isAuthorizedAsync(exchangeContractAddress: string): Promise<boolean> {
const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync();
const isAuthorized = await tokenTransferProxyContractInstance.authorized.call(exchangeContractAddress);
const isAuthorized = await tokenTransferProxyContractInstance.authorized.callAsync(exchangeContractAddress);
return isAuthorized;
}
/**
@@ -24,7 +30,7 @@ export class TokenTransferProxyWrapper extends ContractWrapper {
*/
public async getAuthorizedAddressesAsync(): Promise<string[]> {
const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync();
const authorizedAddresses = await tokenTransferProxyContractInstance.getAuthorizedAddresses.call();
const authorizedAddresses = await tokenTransferProxyContractInstance.getAuthorizedAddresses.callAsync();
return authorizedAddresses;
}
/**
@@ -44,7 +50,10 @@ export class TokenTransferProxyWrapper extends ContractWrapper {
if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) {
return this._tokenTransferProxyContractIfExists;
}
const contractInstance = await this._instantiateContractIfExistsAsync((TokenTransferProxyArtifacts as any));
const contractAddress = await this._tokenTransferProxyContractAddressFetcher();
const contractInstance = await this._instantiateContractIfExistsAsync<TokenTransferProxyContract>(
artifacts.TokenTransferProxyArtifact, contractAddress,
);
this._tokenTransferProxyContractIfExists = contractInstance as TokenTransferProxyContract;
return this._tokenTransferProxyContractIfExists;
}

View File

@@ -1,23 +1,22 @@
import * as _ from 'lodash';
import {_} from '../utils/lodash';
import * as BigNumber from 'bignumber.js';
import {SchemaValidator, schemas} from '0x-json-schemas';
import {schemas} from '0x-json-schemas';
import {Web3Wrapper} from '../web3_wrapper';
import {assert} from '../utils/assert';
import {utils} from '../utils/utils';
import {eventUtils} from '../utils/event_utils';
import {constants} from '../utils/constants';
import {ContractWrapper} from './contract_wrapper';
import * as TokenArtifacts from '../artifacts/Token.json';
import * as TokenTransferProxyArtifacts from '../artifacts/TokenTransferProxy.json';
import {AbiDecoder} from '../utils/abi_decoder';
import {artifacts} from '../artifacts';
import {
TokenContract,
ZeroExError,
TokenEvents,
IndexedFilterValues,
SubscriptionOpts,
CreateContractEvent,
ContractEventEmitter,
ContractEventObj,
MethodOpts,
LogWithDecodedArgs,
EventCallback,
TokenContractEventArgs,
} from '../types';
const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 47155;
@@ -30,24 +29,30 @@ const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 47155;
export class TokenWrapper extends ContractWrapper {
public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
private _tokenContractsByAddress: {[address: string]: TokenContract};
private _tokenLogEventEmitters: ContractEventEmitter[];
constructor(web3Wrapper: Web3Wrapper, gasPrice?: BigNumber.BigNumber) {
super(web3Wrapper, gasPrice);
private _activeSubscriptions: string[];
private _tokenTransferProxyContractAddressFetcher: () => Promise<string>;
constructor(web3Wrapper: Web3Wrapper, abiDecoder: AbiDecoder,
tokenTransferProxyContractAddressFetcher: () => Promise<string>) {
super(web3Wrapper, abiDecoder);
this._tokenContractsByAddress = {};
this._tokenLogEventEmitters = [];
this._activeSubscriptions = [];
this._tokenTransferProxyContractAddressFetcher = tokenTransferProxyContractAddressFetcher;
}
/**
* Retrieves an owner's ERC20 token balance.
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @param ownerAddress The hex encoded user Ethereum address whose balance you would like to check.
* @param methodOpts Optional arguments this method accepts.
* @return The owner's ERC20 token balance in base units.
*/
public async getBalanceAsync(tokenAddress: string, ownerAddress: string): Promise<BigNumber.BigNumber> {
public async getBalanceAsync(tokenAddress: string, ownerAddress: string,
methodOpts?: MethodOpts): Promise<BigNumber.BigNumber> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
let balance = await tokenContract.balanceOf.call(ownerAddress);
const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
let balance = await tokenContract.balanceOf.callAsync(ownerAddress, defaultBlock);
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
balance = new BigNumber(balance);
return balance;
@@ -60,9 +65,10 @@ export class TokenWrapper extends ContractWrapper {
* for spenderAddress.
* @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance.
* @param amountInBaseUnits The allowance amount you would like to set.
* @return Transaction hash.
*/
public async setAllowanceAsync(tokenAddress: string, ownerAddress: string, spenderAddress: string,
amountInBaseUnits: BigNumber.BigNumber): Promise<void> {
amountInBaseUnits: BigNumber.BigNumber): Promise<string> {
await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper);
assert.isETHAddressHex('spenderAddress', spenderAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
@@ -74,10 +80,11 @@ export class TokenWrapper extends ContractWrapper {
// TODO: Debug issue in testrpc and submit a PR, then remove this hack
const networkIdIfExists = await this._web3Wrapper.getNetworkIdIfExistsAsync();
const gas = networkIdIfExists === constants.TESTRPC_NETWORK_ID ? ALLOWANCE_TO_ZERO_GAS_AMOUNT : undefined;
await tokenContract.approve(spenderAddress, amountInBaseUnits, {
const txHash = await tokenContract.approve.sendTransactionAsync(spenderAddress, amountInBaseUnits, {
from: ownerAddress,
gas,
});
return txHash;
}
/**
* Sets the spender's allowance to an unlimited number of baseUnits on behalf of the owner address.
@@ -88,12 +95,14 @@ export class TokenWrapper extends ContractWrapper {
* @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance
* for spenderAddress.
* @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance.
* @return Transaction hash.
*/
public async setUnlimitedAllowanceAsync(tokenAddress: string, ownerAddress: string,
spenderAddress: string): Promise<void> {
await this.setAllowanceAsync(
spenderAddress: string): Promise<string> {
const txHash = await this.setAllowanceAsync(
tokenAddress, ownerAddress, spenderAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
);
return txHash;
}
/**
* Retrieves the owners allowance in baseUnits set to the spender's address.
@@ -101,13 +110,16 @@ export class TokenWrapper extends ContractWrapper {
* @param ownerAddress The hex encoded user Ethereum address whose allowance to spenderAddress
* you would like to retrieve.
* @param spenderAddress The hex encoded user Ethereum address who can spend the allowance you are fetching.
* @param methodOpts Optional arguments this method accepts.
*/
public async getAllowanceAsync(tokenAddress: string, ownerAddress: string, spenderAddress: string) {
public async getAllowanceAsync(tokenAddress: string, ownerAddress: string,
spenderAddress: string, methodOpts?: MethodOpts): Promise<BigNumber.BigNumber> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
let allowanceInBaseUnits = await tokenContract.allowance.call(ownerAddress, spenderAddress);
const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
let allowanceInBaseUnits = await tokenContract.allowance.callAsync(ownerAddress, spenderAddress, defaultBlock);
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
allowanceInBaseUnits = new BigNumber(allowanceInBaseUnits);
return allowanceInBaseUnits;
@@ -116,13 +128,15 @@ export class TokenWrapper extends ContractWrapper {
* Retrieves the owner's allowance in baseUnits set to the 0x proxy contract.
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @param ownerAddress The hex encoded user Ethereum address whose proxy contract allowance we are retrieving.
* @param methodOpts Optional arguments this method accepts.
*/
public async getProxyAllowanceAsync(tokenAddress: string, ownerAddress: string) {
public async getProxyAllowanceAsync(tokenAddress: string, ownerAddress: string,
methodOpts?: MethodOpts): Promise<BigNumber.BigNumber> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
const proxyAddress = await this._getProxyAddressAsync();
const allowanceInBaseUnits = await this.getAllowanceAsync(tokenAddress, ownerAddress, proxyAddress);
const proxyAddress = await this._getTokenTransferProxyAddressAsync();
const allowanceInBaseUnits = await this.getAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, methodOpts);
return allowanceInBaseUnits;
}
/**
@@ -132,15 +146,17 @@ export class TokenWrapper extends ContractWrapper {
* @param ownerAddress The hex encoded user Ethereum address who is setting an allowance
* for the Proxy contract.
* @param amountInBaseUnits The allowance amount specified in baseUnits.
* @return Transaction hash.
*/
public async setProxyAllowanceAsync(tokenAddress: string, ownerAddress: string,
amountInBaseUnits: BigNumber.BigNumber): Promise<void> {
amountInBaseUnits: BigNumber.BigNumber): Promise<string> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.isBigNumber('amountInBaseUnits', amountInBaseUnits);
const proxyAddress = await this._getProxyAddressAsync();
await this.setAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, amountInBaseUnits);
const proxyAddress = await this._getTokenTransferProxyAddressAsync();
const txHash = await this.setAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, amountInBaseUnits);
return txHash;
}
/**
* Sets the 0x proxy contract's allowance to a unlimited number of a tokens' baseUnits on behalf
@@ -150,9 +166,13 @@ export class TokenWrapper extends ContractWrapper {
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @param ownerAddress The hex encoded user Ethereum address who is setting an allowance
* for the Proxy contract.
* @return Transaction hash.
*/
public async setUnlimitedProxyAllowanceAsync(tokenAddress: string, ownerAddress: string): Promise<void> {
await this.setProxyAllowanceAsync(tokenAddress, ownerAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
public async setUnlimitedProxyAllowanceAsync(tokenAddress: string, ownerAddress: string): Promise<string> {
const txHash = await this.setProxyAllowanceAsync(
tokenAddress, ownerAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
);
return txHash;
}
/**
* Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`.
@@ -160,9 +180,10 @@ export class TokenWrapper extends ContractWrapper {
* @param fromAddress The hex encoded user Ethereum address that will send the funds.
* @param toAddress The hex encoded user Ethereum address that will receive the funds.
* @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer.
* @return Transaction hash.
*/
public async transferAsync(tokenAddress: string, fromAddress: string, toAddress: string,
amountInBaseUnits: BigNumber.BigNumber): Promise<void> {
amountInBaseUnits: BigNumber.BigNumber): Promise<string> {
assert.isETHAddressHex('tokenAddress', tokenAddress);
await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper);
assert.isETHAddressHex('toAddress', toAddress);
@@ -175,9 +196,10 @@ export class TokenWrapper extends ContractWrapper {
throw new Error(ZeroExError.InsufficientBalanceForTransfer);
}
await tokenContract.transfer(toAddress, amountInBaseUnits, {
const txHash = await tokenContract.transfer.sendTransactionAsync(toAddress, amountInBaseUnits, {
from: fromAddress,
});
return txHash;
}
/**
* Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`.
@@ -190,10 +212,11 @@ export class TokenWrapper extends ContractWrapper {
* `fromAddress` must have set an allowance to the `senderAddress`
* before this call.
* @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer.
* @return Transaction hash.
*/
public async transferFromAsync(tokenAddress: string, fromAddress: string, toAddress: string,
senderAddress: string, amountInBaseUnits: BigNumber.BigNumber):
Promise<void> {
Promise<string> {
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.isETHAddressHex('fromAddress', fromAddress);
assert.isETHAddressHex('toAddress', toAddress);
@@ -212,54 +235,74 @@ export class TokenWrapper extends ContractWrapper {
throw new Error(ZeroExError.InsufficientBalanceForTransfer);
}
await tokenContract.transferFrom(fromAddress, toAddress, amountInBaseUnits, {
from: senderAddress,
});
const txHash = await tokenContract.transferFrom.sendTransactionAsync(
fromAddress, toAddress, amountInBaseUnits,
{
from: senderAddress,
},
);
return txHash;
}
/**
* Subscribe to an event type emitted by the Token contract.
* @param tokenAddress The hex encoded address where the ERC20 token is deployed.
* @param eventName The token contract event you would like to subscribe to.
* @param subscriptionOpts Subscriptions options that let you configure the subscription.
* @param indexFilterValues An object where the keys are indexed args returned by the event and
* the value is the value you are interested in. E.g `{maker: aUserAddressHex}`
* @return ContractEventEmitter object
* @param callback Callback that gets called when a log is added/removed
* @return Subscription token used later to unsubscribe
*/
public async subscribeAsync(tokenAddress: string, eventName: TokenEvents, subscriptionOpts: SubscriptionOpts,
indexFilterValues: IndexedFilterValues): Promise<ContractEventEmitter> {
public subscribe<ArgsType extends TokenContractEventArgs>(
tokenAddress: string, eventName: TokenEvents, indexFilterValues: IndexedFilterValues,
callback: EventCallback<ArgsType>): string {
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.doesBelongToStringEnum('eventName', eventName, TokenEvents);
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
assert.isFunction('callback', callback);
const subscriptionToken = this._subscribe<ArgsType>(
tokenAddress, eventName, indexFilterValues, artifacts.TokenArtifact.abi, callback,
);
this._activeSubscriptions.push(subscriptionToken);
return subscriptionToken;
}
/**
* Cancel a subscription
* @param subscriptionToken Subscription token returned by `subscribe()`
*/
public unsubscribe(subscriptionToken: string): void {
_.pull(this._activeSubscriptions, subscriptionToken);
this._unsubscribe(subscriptionToken);
}
/**
* Gets historical logs without creating a subscription
* @param tokenAddress An address of the token that emmited the logs.
* @param eventName The token contract event you would like to subscribe to.
* @param subscriptionOpts Subscriptions options that let you configure the subscription.
* @param indexFilterValues An object where the keys are indexed args returned by the event and
* the value is the value you are interested in. E.g `{_from: aUserAddressHex}`
* @return Array of logs that match the parameters
*/
public async getLogsAsync<ArgsType extends TokenContractEventArgs>(
tokenAddress: string, eventName: TokenEvents, subscriptionOpts: SubscriptionOpts,
indexFilterValues: IndexedFilterValues): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.doesBelongToStringEnum('eventName', eventName, TokenEvents);
assert.doesConformToSchema('subscriptionOpts', subscriptionOpts, schemas.subscriptionOptsSchema);
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
let createLogEvent: CreateContractEvent;
switch (eventName) {
case TokenEvents.Approval:
createLogEvent = tokenContract.Approval;
break;
case TokenEvents.Transfer:
createLogEvent = tokenContract.Transfer;
break;
default:
throw utils.spawnSwitchErr('TokenEvents', eventName);
}
const logEventObj: ContractEventObj = createLogEvent(indexFilterValues, subscriptionOpts);
const eventEmitter = eventUtils.wrapEventEmitter(logEventObj);
this._tokenLogEventEmitters.push(eventEmitter);
return eventEmitter;
const logs = await this._getLogsAsync<ArgsType>(
tokenAddress, eventName, subscriptionOpts, indexFilterValues, artifacts.TokenArtifact.abi,
);
return logs;
}
/**
* Stops watching for all token events
* Cancels all existing subscriptions
*/
public async stopWatchingAllEventsAsync(): Promise<void> {
const stopWatchingPromises = _.map(this._tokenLogEventEmitters,
logEventObj => logEventObj.stopWatchingAsync());
await Promise.all(stopWatchingPromises);
this._tokenLogEventEmitters = [];
public unsubscribeAll(): void {
_.forEach(this._activeSubscriptions, this._unsubscribe.bind(this));
this._activeSubscriptions = [];
}
private async _invalidateContractInstancesAsync(): Promise<void> {
await this.stopWatchingAllEventsAsync();
private _invalidateContractInstancesAsync(): void {
this.unsubscribeAll();
this._tokenContractsByAddress = {};
}
private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> {
@@ -267,20 +310,15 @@ export class TokenWrapper extends ContractWrapper {
if (!_.isUndefined(tokenContract)) {
return tokenContract;
}
const contractInstance = await this._instantiateContractIfExistsAsync((TokenArtifacts as any), tokenAddress);
const contractInstance = await this._instantiateContractIfExistsAsync<TokenContract>(
artifacts.TokenArtifact, tokenAddress,
);
tokenContract = contractInstance as TokenContract;
this._tokenContractsByAddress[tokenAddress] = tokenContract;
return tokenContract;
}
private async _getProxyAddressAsync() {
const networkIdIfExists = await this._web3Wrapper.getNetworkIdIfExistsAsync();
const proxyNetworkConfigsIfExists = _.isUndefined(networkIdIfExists) ?
undefined :
(TokenTransferProxyArtifacts as any).networks[networkIdIfExists];
if (_.isUndefined(proxyNetworkConfigsIfExists)) {
throw new Error(ZeroExError.ContractNotDeployedOnNetwork);
}
const proxyAddress = proxyNetworkConfigsIfExists.address.toLowerCase();
return proxyAddress;
private async _getTokenTransferProxyAddressAsync(): Promise<string> {
const tokenTransferProxyContractAddress = await this._tokenTransferProxyContractAddressFetcher();
return tokenTransferProxyContractAddress;
}
}

49
src/globals.d.ts vendored
View File

@@ -33,25 +33,6 @@ declare module '*.json' {
/* tslint:enable */
}
// truffle-contract declarations
declare interface ContractInstance {
address: string;
}
declare interface ContractFactory {
setProvider: (providerObj: any) => void;
deployed: () => ContractInstance;
// Both any's are Web3.CallData, but I was unable to import it in this file
defaults: (config: any) => any;
at: (address: string) => ContractInstance;
}
declare interface Artifact {
networks: {[networkId: number]: any};
}
declare function contract(artifacts: Artifact): ContractFactory;
declare module 'truffle-contract' {
export = contract;
}
// find-version declarations
declare function findVersions(version: string): string[];
declare module 'find-versions' {
@@ -75,9 +56,33 @@ declare module 'ethereumjs-abi' {
}
// truffle-hdwallet-provider declarations
declare class HDWalletProvider {
constructor(mnemonic: string, rpcUrl: string);
}
declare module 'truffle-hdwallet-provider' {
import * as Web3 from 'web3';
class HDWalletProvider implements Web3.Provider {
constructor(mnemonic: string, rpcUrl: string);
public sendAsync(
payload: Web3.JSONRPCRequestPayload,
callback: (err: Error, result: Web3.JSONRPCResponsePayload) => void,
): void;
}
export = HDWalletProvider;
}
// abi-decoder declarations
interface DecodedLogArg {
name: string;
value: string|BigNumber.BigNumber;
}
interface DecodedLog {
name: string;
events: DecodedLogArg[];
}
declare module 'abi-decoder' {
import * as Web3 from 'web3';
const addABI: (abi: Web3.AbiDefinition) => void;
const decodeLogs: (logs: Web3.LogEntry[]) => DecodedLog[];
}
declare module 'web3/lib/solidity/coder' {
const decodeParams: (types: string[], data: string) => any[];
}

View File

@@ -16,10 +16,8 @@ export {
IndexedFilterValues,
SubscriptionOpts,
BlockParam,
OrderFillOrKillRequest,
OrderCancellationRequest,
OrderFillRequest,
ContractEventEmitter,
LogErrorContractEventArgs,
LogCancelContractEventArgs,
LogFillContractEventArgs,
@@ -28,6 +26,13 @@ export {
ApprovalContractEventArgs,
TokenContractEventArgs,
ContractEventArgs,
ContractEventArg,
Web3Provider,
ZeroExConfig,
TransactionReceiptWithDecodedLogs,
LogWithDecodedArgs,
MethodOpts,
OrderTransactionOpts,
FilterObject,
LogEvent,
} from './types';

View File

@@ -0,0 +1,10 @@
export const zeroExConfigSchema = {
id: '/ZeroExConfig',
properties: {
gasPrice: {$ref: '/Number'},
exchangeContractAddress: {$ref: '/Address'},
tokenRegistryContractAddress: {$ref: '/Address'},
etherTokenContractAddress: {$ref: '/Address'},
},
type: 'object',
};

View File

@@ -1,4 +1,3 @@
import * as Web3 from 'web3';
import {JSONRPCPayload} from '../types';
/*

View File

@@ -7,13 +7,19 @@ export enum ZeroExError {
UserHasNoAssociatedAddress = 'USER_HAS_NO_ASSOCIATED_ADDRESSES',
InvalidSignature = 'INVALID_SIGNATURE',
ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK',
ZrxNotInTokenRegistry = 'ZRX_NOT_IN_TOKEN_REGISTRY',
InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER',
InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER',
InsufficientEthBalanceForDeposit = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT',
InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL',
InvalidJump = 'INVALID_JUMP',
OutOfGas = 'OUT_OF_GAS',
NoNetworkId = 'NO_NETWORK_ID',
SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND',
}
export enum InternalZeroExError {
NoAbiDecoder = 'NO_ABI_DECODER',
ZrxNotInTokenRegistry = 'ZRX_NOT_IN_TOKEN_REGISTRY',
}
/**
@@ -30,152 +36,167 @@ export type OrderAddresses = [string, string, string, string, string];
export type OrderValues = [BigNumber.BigNumber, BigNumber.BigNumber, BigNumber.BigNumber,
BigNumber.BigNumber, BigNumber.BigNumber, BigNumber.BigNumber];
export type EventCallbackAsync = (err: Error, event: ContractEvent) => Promise<void>;
export type EventCallbackSync = (err: Error, event: ContractEvent) => void;
export type EventCallback = EventCallbackSync|EventCallbackAsync;
export interface ContractEventObj {
watch: (eventWatch: EventCallback) => void;
stopWatching: () => void;
export interface LogEvent<ArgsType> extends LogWithDecodedArgs<ArgsType> {
removed: boolean;
}
export type CreateContractEvent = (indexFilterValues: IndexedFilterValues,
subscriptionOpts: SubscriptionOpts) => ContractEventObj;
export interface ExchangeContract extends ContractInstance {
export type EventCallbackAsync<ArgsType> = (log: LogEvent<ArgsType>) => Promise<void>;
export type EventCallbackSync<ArgsType> = (log: LogEvent<ArgsType>) => void;
export type EventCallback<ArgsType> = EventCallbackSync<ArgsType>|EventCallbackAsync<ArgsType>;
export interface ExchangeContract extends Web3.ContractInstance {
isValidSignature: {
call: (signerAddressHex: string, dataHex: string, v: number, r: string, s: string,
txOpts?: TxOpts) => Promise<boolean>;
callAsync: (signerAddressHex: string, dataHex: string, v: number, r: string, s: string,
txOpts?: TxOpts) => Promise<boolean>;
};
LogFill: CreateContractEvent;
LogCancel: CreateContractEvent;
LogError: CreateContractEvent;
ZRX_TOKEN_CONTRACT: {
call: () => Promise<string>;
callAsync: () => Promise<string>;
};
TOKEN_TRANSFER_PROXY_CONTRACT: {
callAsync: () => Promise<string>;
};
getUnavailableTakerTokenAmount: {
call: (orderHash: string) => Promise<BigNumber.BigNumber>;
callAsync: (orderHash: string, defaultBlock?: Web3.BlockParam) => Promise<BigNumber.BigNumber>;
};
isRoundingError: {
call: (fillTakerAmount: BigNumber.BigNumber, takerTokenAmount: BigNumber.BigNumber,
makerTokenAmount: BigNumber.BigNumber, txOpts?: TxOpts) => Promise<boolean>;
callAsync: (takerTokenFillAmount: BigNumber.BigNumber, takerTokenAmount: BigNumber.BigNumber,
makerTokenAmount: BigNumber.BigNumber, txOpts?: TxOpts) => Promise<boolean>;
};
fillOrder: {
(orderAddresses: OrderAddresses, orderValues: OrderValues, fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number, r: string, s: string, txOpts?: TxOpts): Promise<ContractResponse>;
estimateGas: (orderAddresses: OrderAddresses, orderValues: OrderValues,
fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<number>;
sendTransactionAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<string>;
estimateGasAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<number>;
};
batchFillOrders: {
(orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillTakerTokenAmounts: BigNumber.BigNumber[],
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts): Promise<ContractResponse>;
estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmounts: BigNumber.BigNumber[],
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<number>;
sendTransactionAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmounts: BigNumber.BigNumber[],
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<string>;
estimateGasAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmounts: BigNumber.BigNumber[],
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<number>;
};
fillOrdersUpTo: {
(orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts): Promise<ContractResponse>;
estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<number>;
sendTransactionAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<string>;
estimateGasAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<number>;
};
cancelOrder: {
(orderAddresses: OrderAddresses, orderValues: OrderValues, cancelTakerTokenAmount: BigNumber.BigNumber,
txOpts?: TxOpts): Promise<ContractResponse>;
estimateGas: (orderAddresses: OrderAddresses, orderValues: OrderValues,
cancelTakerTokenAmount: BigNumber.BigNumber,
txOpts?: TxOpts) => Promise<number>;
sendTransactionAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
cancelTakerTokenAmount: BigNumber.BigNumber, txOpts?: TxOpts) => Promise<string>;
estimateGasAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
cancelTakerTokenAmount: BigNumber.BigNumber,
txOpts?: TxOpts) => Promise<number>;
};
batchCancelOrders: {
(orderAddresses: OrderAddresses[], orderValues: OrderValues[], cancelTakerTokenAmounts: BigNumber.BigNumber[],
txOpts?: TxOpts): Promise<ContractResponse>;
estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
cancelTakerTokenAmounts: BigNumber.BigNumber[],
txOpts?: TxOpts) => Promise<number>;
sendTransactionAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
cancelTakerTokenAmounts: BigNumber.BigNumber[], txOpts?: TxOpts) => Promise<string>;
estimateGasAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
cancelTakerTokenAmounts: BigNumber.BigNumber[],
txOpts?: TxOpts) => Promise<number>;
};
fillOrKillOrder: {
(orderAddresses: OrderAddresses, orderValues: OrderValues, fillTakerTokenAmount: BigNumber.BigNumber,
v: number, r: string, s: string, txOpts?: TxOpts): Promise<ContractResponse>;
estimateGas: (orderAddresses: OrderAddresses, orderValues: OrderValues,
fillTakerTokenAmount: BigNumber.BigNumber,
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<number>;
sendTransactionAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
fillTakerTokenAmount: BigNumber.BigNumber,
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<string>;
estimateGasAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
fillTakerTokenAmount: BigNumber.BigNumber,
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<number>;
};
batchFillOrKillOrders: {
(orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillTakerTokenAmounts: BigNumber.BigNumber[],
v: number[], r: string[], s: string[], txOpts: TxOpts): Promise<ContractResponse>;
estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmounts: BigNumber.BigNumber[],
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<number>;
sendTransactionAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmounts: BigNumber.BigNumber[],
v: number[], r: string[], s: string[], txOpts: TxOpts) => Promise<string>;
estimateGasAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmounts: BigNumber.BigNumber[],
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<number>;
};
filled: {
call: (orderHash: string) => Promise<BigNumber.BigNumber>;
callAsync: (orderHash: string, defaultBlock?: Web3.BlockParam) => Promise<BigNumber.BigNumber>;
};
cancelled: {
call: (orderHash: string) => Promise<BigNumber.BigNumber>;
callAsync: (orderHash: string, defaultBlock?: Web3.BlockParam) => Promise<BigNumber.BigNumber>;
};
getOrderHash: {
call: (orderAddresses: OrderAddresses, orderValues: OrderValues) => Promise<string>;
callAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues) => Promise<string>;
};
}
export interface TokenContract extends ContractInstance {
Transfer: CreateContractEvent;
Approval: CreateContractEvent;
export interface TokenContract extends Web3.ContractInstance {
balanceOf: {
call: (address: string) => Promise<BigNumber.BigNumber>;
callAsync: (address: string, defaultBlock?: Web3.BlockParam) => Promise<BigNumber.BigNumber>;
};
allowance: {
call: (ownerAddress: string, allowedAddress: string) => Promise<BigNumber.BigNumber>;
callAsync: (ownerAddress: string, allowedAddress: string,
defaultBlock?: Web3.BlockParam) => Promise<BigNumber.BigNumber>;
};
transfer: {
sendTransactionAsync: (toAddress: string, amountInBaseUnits: BigNumber.BigNumber,
txOpts?: TxOpts) => Promise<string>;
};
transferFrom: {
sendTransactionAsync: (fromAddress: string, toAddress: string, amountInBaseUnits: BigNumber.BigNumber,
txOpts?: TxOpts) => Promise<string>;
};
approve: {
sendTransactionAsync: (proxyAddress: string, amountInBaseUnits: BigNumber.BigNumber,
txOpts?: TxOpts) => Promise<string>;
};
transfer: (toAddress: string, amountInBaseUnits: BigNumber.BigNumber, txOpts?: TxOpts) => Promise<boolean>;
transferFrom: (fromAddress: string, toAddress: string, amountInBaseUnits: BigNumber.BigNumber,
txOpts?: TxOpts) => Promise<boolean>;
approve: (proxyAddress: string, amountInBaseUnits: BigNumber.BigNumber, txOpts?: TxOpts) => Promise<void>;
}
export interface TokenRegistryContract extends ContractInstance {
export interface TokenRegistryContract extends Web3.ContractInstance {
getTokenMetaData: {
call: (address: string) => Promise<TokenMetadata>;
callAsync: (address: string) => Promise<TokenMetadata>;
};
getTokenAddresses: {
call: () => Promise<string[]>;
callAsync: () => Promise<string[]>;
};
getTokenAddressBySymbol: {
call: (symbol: string) => Promise<string>;
callAsync: (symbol: string) => Promise<string>;
};
getTokenAddressByName: {
call: (name: string) => Promise<string>;
callAsync: (name: string) => Promise<string>;
};
getTokenBySymbol: {
call: (symbol: string) => Promise<TokenMetadata>;
callAsync: (symbol: string) => Promise<TokenMetadata>;
};
getTokenByName: {
call: (name: string) => Promise<TokenMetadata>;
callAsync: (name: string) => Promise<TokenMetadata>;
};
}
export interface EtherTokenContract extends ContractInstance {
deposit: (txOpts: TxOpts) => Promise<void>;
withdraw: (amount: BigNumber.BigNumber, txOpts: TxOpts) => Promise<void>;
export interface EtherTokenContract extends Web3.ContractInstance {
deposit: {
sendTransactionAsync: (txOpts: TxOpts) => Promise<string>;
};
withdraw: {
sendTransactionAsync: (amount: BigNumber.BigNumber, txOpts: TxOpts) => Promise<string>;
};
}
export interface TokenTransferProxyContract extends ContractInstance {
export interface TokenTransferProxyContract extends Web3.ContractInstance {
getAuthorizedAddresses: {
call: () => Promise<string[]>;
callAsync: () => Promise<string[]>;
};
authorized: {
call: (address: string) => Promise<boolean>;
callAsync: (address: string) => Promise<boolean>;
};
}
export enum SolidityTypes {
Address = 'address',
Uint256 = 'uint256',
Uint8 = 'uint8',
Uint = 'uint',
}
export enum ExchangeContractErrCodes {
@@ -209,11 +230,10 @@ export enum ExchangeContractErrs {
InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT',
MultipleTakerTokensInFillUpToDisallowed = 'MULTIPLE_TAKER_TOKENS_IN_FILL_UP_TO_DISALLOWED',
BatchOrdersMustHaveSameExchangeAddress = 'BATCH_ORDERS_MUST_HAVE_SAME_EXCHANGE_ADDRESS',
BatchOrdersMustHaveAtLeastOneItem = 'BATCH_ORDERS_MUST_HAVE_AT_LEAST_ONE_ITEM',
}
export interface ContractResponse {
logs: ContractEvent[];
}
export type RawLog = Web3.LogEntry;
export interface ContractEvent {
logIndex: number;
@@ -319,11 +339,19 @@ export enum TokenEvents {
Approval = 'Approval',
}
export type ContractEvents = TokenEvents|ExchangeEvents;
export interface IndexedFilterValues {
[index: string]: ContractEventArg;
}
export type BlockParam = 'latest'|'earliest'|'pending'|number;
export enum BlockParamLiteral {
Latest = 'latest',
Earliest = 'earliest',
Pending = 'pending',
}
export type BlockParam = BlockParamLiteral|number;
export interface SubscriptionOpts {
fromBlock: BlockParam;
@@ -332,11 +360,6 @@ export interface SubscriptionOpts {
export type DoneCallback = (err?: Error) => void;
export interface OrderFillOrKillRequest {
signedOrder: SignedOrder;
fillTakerAmount: BigNumber.BigNumber;
}
export interface OrderCancellationRequest {
order: Order|SignedOrder;
takerTokenCancelAmount: BigNumber.BigNumber;
@@ -349,19 +372,6 @@ export interface OrderFillRequest {
export type AsyncMethod = (...args: any[]) => Promise<any>;
export interface ContractInstance {
address: string;
}
export interface Artifact {
networks: {[networkId: number]: any};
}
export interface ContractEventEmitter {
watch: (eventCallback: EventCallback) => void;
stopWatchingAsync: () => Promise<void>;
}
/**
* We re-export the `Web3.Provider` type specified in the Web3 Typescript typings
* since it is the type of the `provider` argument to the `ZeroEx` constructor.
@@ -373,19 +383,91 @@ export interface ExchangeContractByAddress {
[address: string]: ExchangeContract;
}
export interface ContractArtifact {
networks: {
[networkId: number]: {
address: string;
};
};
}
export interface JSONRPCPayload {
params: any[];
method: string;
}
/*
* gasPrice: Gas price to use with every transaction
* exchangeContractAddress: The address of an exchange contract to use
* tokenRegistryContractAddress: The address of a token registry contract to use
* etherTokenContractAddress: The address of an ether token contract to use
*/
export interface ZeroExConfig {
gasPrice?: BigNumber.BigNumber; // Gas price to use with every transaction
exchangeContractAddress?: string;
tokenRegistryContractAddress?: string;
etherTokenContractAddress?: string;
}
export type TransactionReceipt = Web3.TransactionReceipt;
export enum AbiType {
Function = 'function',
Constructor = 'constructor',
Event = 'event',
Fallback = 'fallback',
}
export interface DecodedLogArgs {
[argName: string]: ContractEventArg;
}
export interface DecodedArgs<ArgsType> {
args: ArgsType;
event: string;
}
export interface LogWithDecodedArgs<ArgsType> extends Web3.LogEntry, DecodedArgs<ArgsType> {}
export interface TransactionReceiptWithDecodedLogs extends Web3.TransactionReceipt {
logs: Array<LogWithDecodedArgs<DecodedLogArgs>|Web3.LogEntry>;
}
export interface Artifact {
abi: any;
networks: {[networkId: number]: {
address: string;
}};
}
/*
* expectedFillTakerTokenAmount: If specified, the validation method will ensure that the
* supplied order maker has a sufficient allowance/balance to fill this amount of the order's
* takerTokenAmount. If not specified, the validation method ensures that the maker has a sufficient
* allowance/balance to fill the entire remaining order amount.
*/
export interface ValidateOrderFillableOpts {
expectedFillTakerTokenAmount?: BigNumber.BigNumber;
}
/*
* defaultBlock: The block up to which to query the blockchain state. Setting this to a historical block number
* let's the user query the blockchain's state at an arbitrary point in time. In order for this to work, the
* backing Ethereum node must keep the entire historical state of the chain (e.g setting `--pruning=archive`
* flag when running Parity).
*/
export interface MethodOpts {
defaultBlock?: Web3.BlockParam;
}
/*
* shouldValidate: Flag indicating whether the library should make attempts to validate a transaction before
* broadcasting it. For example, order has a valid signature, maker has sufficient funds, etc.
*/
export interface OrderTransactionOpts {
shouldValidate: boolean;
}
export type FilterObject = Web3.FilterObject;
export enum TradeSide {
Maker = 'maker',
Taker = 'taker',
}
export enum TransferType {
Trade = 'trade',
Fee = 'fee',
}

68
src/utils/abi_decoder.ts Normal file
View File

@@ -0,0 +1,68 @@
import * as Web3 from 'web3';
import {_} from '../utils/lodash';
import * as BigNumber from 'bignumber.js';
import {AbiType, DecodedLogArgs, LogWithDecodedArgs, RawLog, SolidityTypes, ContractEventArgs} from '../types';
import * as SolidityCoder from 'web3/lib/solidity/coder';
export class AbiDecoder {
private savedABIs: Web3.AbiDefinition[] = [];
private methodIds: {[signatureHash: string]: Web3.EventAbi} = {};
constructor(abiArrays: Web3.AbiDefinition[][]) {
_.map(abiArrays, this.addABI.bind(this));
}
// This method can only decode logs from the 0x smart contracts
public tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>(
log: Web3.LogEntry): LogWithDecodedArgs<ArgsType>|RawLog {
const methodId = log.topics[0];
const event = this.methodIds[methodId];
if (_.isUndefined(event)) {
return log;
}
const logData = log.data;
const decodedParams: DecodedLogArgs = {};
let dataIndex = 0;
let topicsIndex = 1;
const nonIndexedInputs = _.filter(event.inputs, input => !input.indexed);
const dataTypes = _.map(nonIndexedInputs, input => input.type);
const decodedData = SolidityCoder.decodeParams(dataTypes, logData.slice('0x'.length));
_.map(event.inputs, (param: Web3.EventParameter) => {
// Indexed parameters are stored in topics. Non-indexed ones in decodedData
let value = param.indexed ? log.topics[topicsIndex++] : decodedData[dataIndex++];
if (param.type === SolidityTypes.Address) {
value = this.padZeros(new BigNumber(value).toString(16));
} else if (param.type === SolidityTypes.Uint256 ||
param.type === SolidityTypes.Uint8 ||
param.type === SolidityTypes.Uint ) {
value = new BigNumber(value);
}
decodedParams[param.name] = value;
});
return {
...log,
event: event.name,
args: decodedParams,
};
}
private addABI(abiArray: Web3.AbiDefinition[]): void {
_.map(abiArray, (abi: Web3.AbiDefinition) => {
if (abi.type === AbiType.Event) {
const signature = `${abi.name}(${_.map(abi.inputs, input => input.type).join(',')})`;
const signatureHash = new Web3().sha3(signature);
this.methodIds[signatureHash] = abi;
}
});
this.savedABIs = this.savedABIs.concat(abiArray);
}
private padZeros(address: string) {
let formatted = address;
if (_.startsWith(formatted, '0x')) {
formatted = formatted.slice(2);
}
formatted = _.padStart(formatted, 40, '0');
return `0x${formatted}`;
}
}

View File

@@ -1,4 +1,4 @@
import * as _ from 'lodash';
import {_} from '../utils/lodash';
import * as BigNumber from 'bignumber.js';
import * as Web3 from 'web3';
import {Web3Wrapper} from '../web3_wrapper';
@@ -17,6 +17,9 @@ export const assert = {
isString(variableName: string, value: string): void {
this.assert(_.isString(value), this.typeAssertionMessage(variableName, 'string', value));
},
isFunction(variableName: string, value: any): void {
this.assert(_.isFunction(value), this.typeAssertionMessage(variableName, 'function', value));
},
isHexString(variableName: string, value: string): void {
this.assert(_.isString(value) && HEX_REGEX.test(value),
this.typeAssertionMessage(variableName, 'HexString', value));
@@ -25,7 +28,7 @@ export const assert = {
const web3 = new Web3();
this.assert(web3.isAddress(value), this.typeAssertionMessage(variableName, 'ETHAddressHex', value));
this.assert(
web3.isAddress(value) && !web3.isChecksumAddress(value),
web3.isAddress(value) && value.toLowerCase() === value,
`Checksummed addresses are not supported. Convert ${variableName} to lower case before passing`,
);
},

View File

@@ -7,4 +7,5 @@ export const constants = {
INVALID_JUMP_PATTERN: 'invalid JUMP at',
OUT_OF_GAS_PATTERN: 'out of gas',
UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
DEFAULT_BLOCK_POLLING_INTERVAL: 1000,
};

View File

@@ -1,4 +1,4 @@
import * as _ from 'lodash';
import {_} from '../utils/lodash';
import {constants} from './constants';
import {AsyncMethod, ZeroExError} from '../types';

View File

@@ -1,41 +0,0 @@
import * as _ from 'lodash';
import {EventCallback, ContractEventArg, ContractEvent, ContractEventObj, ContractEventEmitter} from '../types';
import * as BigNumber from 'bignumber.js';
import promisify = require('es6-promisify');
export const eventUtils = {
wrapEventEmitter(event: ContractEventObj): ContractEventEmitter {
const watch = (eventCallback: EventCallback) => {
const bignumberWrappingEventCallback = eventUtils._getBigNumberWrappingEventCallback(eventCallback);
event.watch(bignumberWrappingEventCallback);
};
const zeroExEvent = {
watch,
stopWatchingAsync: async () => {
await promisify(event.stopWatching, event)();
},
};
return zeroExEvent;
},
/**
* Wraps eventCallback function so that all the BigNumber arguments are wrapped in a newer version of BigNumber.
* @param eventCallback Event callback function to be wrapped
* @return Wrapped event callback function
*/
_getBigNumberWrappingEventCallback(eventCallback: EventCallback): EventCallback {
const bignumberWrappingEventCallback = (err: Error, event: ContractEvent) => {
if (_.isNull(err)) {
const wrapIfBigNumber = (value: ContractEventArg): ContractEventArg => {
// HACK: The old version of BigNumber used by Web3@0.19.0 does not support the `isBigNumber`
// and checking for a BigNumber instance using `instanceof` does not work either. We therefore
// check if the value constructor is a bignumber constructor.
const isWeb3BigNumber = _.startsWith(value.constructor.toString(), 'function BigNumber(');
return isWeb3BigNumber ? new BigNumber(value) : value;
};
event.args = _.mapValues(event.args, wrapIfBigNumber);
}
eventCallback(err, event);
};
return bignumberWrappingEventCallback;
},
};

View File

@@ -0,0 +1,131 @@
import {_} from '../utils/lodash';
import {ExchangeContractErrs, TradeSide, TransferType} from '../types';
import {TokenWrapper} from '../contract_wrappers/token_wrapper';
enum FailureReason {
Balance = 'balance',
ProxyAllowance = 'proxyAllowance',
}
const ERR_MSG_MAPPING = {
[FailureReason.Balance]: {
[TradeSide.Maker]: {
[TransferType.Trade]: ExchangeContractErrs.InsufficientMakerBalance,
[TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeBalance,
},
[TradeSide.Taker]: {
[TransferType.Trade]: ExchangeContractErrs.InsufficientTakerBalance,
[TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeBalance,
},
},
[FailureReason.ProxyAllowance]: {
[TradeSide.Maker]: {
[TransferType.Trade]: ExchangeContractErrs.InsufficientMakerAllowance,
[TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeAllowance,
},
[TradeSide.Taker]: {
[TransferType.Trade]: ExchangeContractErrs.InsufficientTakerAllowance,
[TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeAllowance,
},
},
};
/**
* Copy on read store for balances/proxyAllowances of tokens/accounts touched in trades
*/
export class BalanceAndProxyAllowanceLazyStore {
protected _token: TokenWrapper;
private _balance: {
[tokenAddress: string]: {
[userAddress: string]: BigNumber.BigNumber,
},
};
private _proxyAllowance: {
[tokenAddress: string]: {
[userAddress: string]: BigNumber.BigNumber,
},
};
constructor(token: TokenWrapper) {
this._token = token;
this._balance = {};
this._proxyAllowance = {};
}
protected async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber.BigNumber> {
if (_.isUndefined(this._balance[tokenAddress]) || _.isUndefined(this._balance[tokenAddress][userAddress])) {
const balance = await this._token.getBalanceAsync(tokenAddress, userAddress);
this.setBalance(tokenAddress, userAddress, balance);
}
const cachedBalance = this._balance[tokenAddress][userAddress];
return cachedBalance;
}
protected setBalance(tokenAddress: string, userAddress: string, balance: BigNumber.BigNumber): void {
if (_.isUndefined(this._balance[tokenAddress])) {
this._balance[tokenAddress] = {};
}
this._balance[tokenAddress][userAddress] = balance;
}
protected async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber.BigNumber> {
if (_.isUndefined(this._proxyAllowance[tokenAddress]) ||
_.isUndefined(this._proxyAllowance[tokenAddress][userAddress])) {
const proxyAllowance = await this._token.getProxyAllowanceAsync(tokenAddress, userAddress);
this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance);
}
const cachedProxyAllowance = this._proxyAllowance[tokenAddress][userAddress];
return cachedProxyAllowance;
}
protected setProxyAllowance(tokenAddress: string, userAddress: string, proxyAllowance: BigNumber.BigNumber): void {
if (_.isUndefined(this._proxyAllowance[tokenAddress])) {
this._proxyAllowance[tokenAddress] = {};
}
this._proxyAllowance[tokenAddress][userAddress] = proxyAllowance;
}
}
export class ExchangeTransferSimulator extends BalanceAndProxyAllowanceLazyStore {
/**
* Simulates transferFrom call performed by a proxy
* @param tokenAddress Address of the token to be transferred
* @param from Owner of the transferred tokens
* @param to Recipient of the transferred tokens
* @param amountInBaseUnits The amount of tokens being transferred
* @param tradeSide Is Maker/Taker transferring
* @param transferType Is it a fee payment or a value transfer
*/
public async transferFromAsync(tokenAddress: string, from: string, to: string,
amountInBaseUnits: BigNumber.BigNumber, tradeSide: TradeSide,
transferType: TransferType): Promise<void> {
const balance = await this.getBalanceAsync(tokenAddress, from);
const proxyAllowance = await this.getProxyAllowanceAsync(tokenAddress, from);
if (proxyAllowance.lessThan(amountInBaseUnits)) {
this.throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType);
}
if (balance.lessThan(amountInBaseUnits)) {
this.throwValidationError(FailureReason.Balance, tradeSide, transferType);
}
await this.decreaseProxyAllowanceAsync(tokenAddress, from, amountInBaseUnits);
await this.decreaseBalanceAsync(tokenAddress, from, amountInBaseUnits);
await this.increaseBalanceAsync(tokenAddress, to, amountInBaseUnits);
}
private async decreaseProxyAllowanceAsync(tokenAddress: string, userAddress: string,
amountInBaseUnits: BigNumber.BigNumber): Promise<void> {
const proxyAllowance = await this.getProxyAllowanceAsync(tokenAddress, userAddress);
if (!proxyAllowance.eq(this._token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance.minus(amountInBaseUnits));
}
}
private async increaseBalanceAsync(tokenAddress: string, userAddress: string,
amountInBaseUnits: BigNumber.BigNumber): Promise<void> {
const balance = await this.getBalanceAsync(tokenAddress, userAddress);
this.setBalance(tokenAddress, userAddress, balance.plus(amountInBaseUnits));
}
private async decreaseBalanceAsync(tokenAddress: string, userAddress: string,
amountInBaseUnits: BigNumber.BigNumber): Promise<void> {
const balance = await this.getBalanceAsync(tokenAddress, userAddress);
this.setBalance(tokenAddress, userAddress, balance.minus(amountInBaseUnits));
}
private throwValidationError(failureReason: FailureReason, tradeSide: TradeSide,
transferType: TransferType): Promise<never> {
const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType];
throw new Error(errMsg);
}
}

82
src/utils/filter_utils.ts Normal file
View File

@@ -0,0 +1,82 @@
import {_} from '../utils/lodash';
import * as Web3 from 'web3';
import * as uuid from 'uuid/v4';
import * as ethUtil from 'ethereumjs-util';
import * as jsSHA3 from 'js-sha3';
import {ContractEvents, IndexedFilterValues, SubscriptionOpts} from '../types';
const TOPIC_LENGTH = 32;
export const filterUtils = {
generateUUID(): string {
return uuid();
},
getFilter(address: string, eventName: ContractEvents,
indexFilterValues: IndexedFilterValues, abi: Web3.ContractAbi,
subscriptionOpts?: SubscriptionOpts): Web3.FilterObject {
const eventAbi = _.find(abi, {name: eventName}) as Web3.EventAbi;
const eventSignature = filterUtils.getEventSignatureFromAbiByName(eventAbi, eventName);
const topicForEventSignature = ethUtil.addHexPrefix(jsSHA3.keccak256(eventSignature));
const topicsForIndexedArgs = filterUtils.getTopicsForIndexedArgs(eventAbi, indexFilterValues);
const topics = [topicForEventSignature, ...topicsForIndexedArgs];
let filter: Web3.FilterObject = {
address,
topics,
};
if (!_.isUndefined(subscriptionOpts)) {
filter = {
...subscriptionOpts,
...filter,
};
}
return filter;
},
getEventSignatureFromAbiByName(eventAbi: Web3.EventAbi, eventName: ContractEvents): string {
const types = _.map(eventAbi.inputs, 'type');
const signature = `${eventAbi.name}(${types.join(',')})`;
return signature;
},
getTopicsForIndexedArgs(abi: Web3.EventAbi, indexFilterValues: IndexedFilterValues): Array<string|null> {
const topics: Array<string|null> = [];
for (const eventInput of abi.inputs) {
if (!eventInput.indexed) {
continue;
}
if (_.isUndefined(indexFilterValues[eventInput.name])) {
// Null is a wildcard topic in a JSON-RPC call
topics.push(null);
} else {
const value = indexFilterValues[eventInput.name] as string;
const buffer = ethUtil.toBuffer(value);
const paddedBuffer = ethUtil.setLengthLeft(buffer, TOPIC_LENGTH);
const topic = ethUtil.bufferToHex(paddedBuffer);
topics.push(topic);
}
}
return topics;
},
matchesFilter(log: Web3.LogEntry, filter: Web3.FilterObject): boolean {
if (!_.isUndefined(filter.address) && log.address !== filter.address) {
return false;
}
if (!_.isUndefined(filter.topics)) {
return filterUtils.matchesTopics(log.topics, filter.topics);
}
return true;
},
matchesTopics(logTopics: string[], filterTopics: Array<string[]|string|null>): boolean {
const matchesTopic = _.zipWith(logTopics, filterTopics, filterUtils.matchesTopic.bind(filterUtils));
const matchesTopics = _.every(matchesTopic);
return matchesTopics;
},
matchesTopic(logTopic: string, filterTopic: string[]|string|null): boolean {
if (_.isArray(filterTopic)) {
return _.includes(filterTopic, logTopic);
}
if (_.isString(filterTopic)) {
return filterTopic === logTopic;
}
// null topic is a wildcard
return true;
},
};

View File

@@ -0,0 +1,20 @@
import {_} from '../utils/lodash';
export const intervalUtils = {
setAsyncExcludingInterval(fn: () => Promise<void>, intervalMs: number) {
let locked = false;
const intervalId = setInterval(async () => {
if (locked) {
return;
} else {
locked = true;
await fn();
locked = false;
}
}, intervalMs);
return intervalId;
},
clearAsyncExcludingInterval(intervalId: NodeJS.Timer): void {
clearInterval(intervalId);
},
};

55
src/utils/lodash.ts Normal file
View File

@@ -0,0 +1,55 @@
import assign from 'lodash-es/assign';
import map from 'lodash-es/map';
import values from 'lodash-es/values';
import isUndefined from 'lodash-es/isUndefined';
import isNull from 'lodash-es/isNull';
import includes from 'lodash-es/includes';
import each from 'lodash-es/each';
import forEach from 'lodash-es/forEach';
import filter from 'lodash-es/filter';
import startsWith from 'lodash-es/startsWith';
import padStart from 'lodash-es/padStart';
import find from 'lodash-es/find';
import zipWith from 'lodash-es/zipWith';
import every from 'lodash-es/every';
import isEmpty from 'lodash-es/isEmpty';
import unzip from 'lodash-es/unzip';
import pull from 'lodash-es/pull';
import keys from 'lodash-es/keys';
import uniq from 'lodash-es/uniq';
import isArray from 'lodash-es/isArray';
import isObject from 'lodash-es/isObject';
import isString from 'lodash-es/isString';
import isFinite from 'lodash-es/isFinite';
import isNumber from 'lodash-es/isNumber';
import isBoolean from 'lodash-es/isBoolean';
import isFunction from 'lodash-es/isFunction';
export const _ = {
assign,
map,
values,
isUndefined,
isNull,
includes,
each,
forEach,
filter,
startsWith,
padStart,
find,
zipWith,
every,
isEmpty,
unzip,
pull,
keys,
uniq,
isArray,
isObject,
isString,
isFinite,
isNumber,
isBoolean,
isFunction,
};

View File

@@ -1,8 +1,11 @@
import {ExchangeContractErrs, SignedOrder, Order} from '../types';
import {_} from '../utils/lodash';
import {ExchangeContractErrs, SignedOrder, Order, ZeroExError, TradeSide, TransferType} from '../types';
import {ZeroEx} from '../0x';
import {TokenWrapper} from '../contract_wrappers/token_wrapper';
import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper';
import {utils} from '../utils/utils';
import {constants} from '../utils/constants';
import {ExchangeTransferSimulator} from './exchange_transfer_simulator';
export class OrderValidationUtils {
private tokenWrapper: TokenWrapper;
@@ -11,48 +14,71 @@ export class OrderValidationUtils {
this.tokenWrapper = tokenWrapper;
this.exchangeWrapper = exchangeWrapper;
}
public async validateFillOrderThrowIfInvalidAsync(signedOrder: SignedOrder,
fillTakerTokenAmount: BigNumber.BigNumber,
takerAddress: string,
zrxTokenAddress: string): Promise<void> {
public async validateOrderFillableOrThrowAsync(
exchangeTradeEmulator: ExchangeTransferSimulator, signedOrder: SignedOrder, zrxTokenAddress: string,
expectedFillTakerTokenAmount?: BigNumber.BigNumber): Promise<void> {
const orderHash = utils.getOrderHashHex(signedOrder);
const unavailableTakerTokenAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
this.validateRemainingFillAmountNotZeroOrThrow(
signedOrder.takerTokenAmount, unavailableTakerTokenAmount,
);
this.validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
let fillTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
if (!_.isUndefined(expectedFillTakerTokenAmount)) {
fillTakerTokenAmount = expectedFillTakerTokenAmount;
}
const fillMakerTokenAmount = this.getFillMakerTokenAmount(signedOrder, fillTakerTokenAmount);
await exchangeTradeEmulator.transferFromAsync(
signedOrder.makerTokenAddress, signedOrder.maker, signedOrder.taker, fillMakerTokenAmount,
TradeSide.Maker, TransferType.Trade,
);
await exchangeTradeEmulator.transferFromAsync(
zrxTokenAddress, signedOrder.maker, signedOrder.feeRecipient, signedOrder.makerFee,
TradeSide.Maker, TransferType.Fee,
);
}
public async validateFillOrderThrowIfInvalidAsync(
exchangeTradeEmulator: ExchangeTransferSimulator, signedOrder: SignedOrder,
fillTakerTokenAmount: BigNumber.BigNumber, takerAddress: string,
zrxTokenAddress: string): Promise<BigNumber.BigNumber> {
if (fillTakerTokenAmount.eq(0)) {
throw new Error(ExchangeContractErrs.OrderFillAmountZero);
}
const orderHash = utils.getOrderHashHex(signedOrder);
const unavailableTakerTokenAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
if (signedOrder.makerTokenAmount.eq(unavailableTakerTokenAmount)) {
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
if (!ZeroEx.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker)) {
throw new Error(ZeroExError.InvalidSignature);
}
const unavailableTakerTokenAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
this.validateRemainingFillAmountNotZeroOrThrow(
signedOrder.takerTokenAmount, unavailableTakerTokenAmount,
);
if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== takerAddress) {
throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
}
const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
if (signedOrder.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
throw new Error(ExchangeContractErrs.OrderFillExpired);
}
this.validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
const remainingTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
const filledTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerTokenAmount) ?
remainingTakerTokenAmount :
fillTakerTokenAmount;
await this.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress,
exchangeTradeEmulator, signedOrder, filledTakerTokenAmount, takerAddress, zrxTokenAddress,
);
const wouldRoundingErrorOccur = await this.exchangeWrapper.isRoundingErrorAsync(
fillTakerTokenAmount, signedOrder.takerTokenAmount, signedOrder.makerTokenAmount,
filledTakerTokenAmount, signedOrder.takerTokenAmount, signedOrder.makerTokenAmount,
);
if (wouldRoundingErrorOccur) {
throw new Error(ExchangeContractErrs.OrderFillRoundingError);
}
return filledTakerTokenAmount;
}
public async validateFillOrKillOrderThrowIfInvalidAsync(signedOrder: SignedOrder,
fillTakerTokenAmount: BigNumber.BigNumber,
takerAddress: string,
zrxTokenAddress: string): Promise<void> {
await this.validateFillOrderThrowIfInvalidAsync(
signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress,
public async validateFillOrKillOrderThrowIfInvalidAsync(
exchangeTradeEmulator: ExchangeTransferSimulator, signedOrder: SignedOrder,
fillTakerTokenAmount: BigNumber.BigNumber, takerAddress: string, zrxTokenAddress: string): Promise<void> {
const filledTakerTokenAmount = await this.validateFillOrderThrowIfInvalidAsync(
exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress,
);
// Check that fillValue available >= fillTakerAmount
const orderHashHex = utils.getOrderHashHex(signedOrder);
const unavailableTakerAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHashHex);
const remainingTakerAmount = signedOrder.takerTokenAmount.minus(unavailableTakerAmount);
if (remainingTakerAmount < fillTakerTokenAmount) {
if (filledTakerTokenAmount !== fillTakerTokenAmount) {
throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount);
}
}
@@ -72,75 +98,43 @@ export class OrderValidationUtils {
}
}
public async validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
signedOrder: SignedOrder, fillTakerAmount: BigNumber.BigNumber, senderAddress: string, zrxTokenAddress: string,
): Promise<void> {
await this.validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerAmount, zrxTokenAddress,
exchangeTradeEmulator: ExchangeTransferSimulator, signedOrder: SignedOrder,
fillTakerTokenAmount: BigNumber.BigNumber, senderAddress: string, zrxTokenAddress: string): Promise<void> {
const fillMakerTokenAmount = this.getFillMakerTokenAmount(signedOrder, fillTakerTokenAmount);
await exchangeTradeEmulator.transferFromAsync(
signedOrder.makerTokenAddress, signedOrder.maker, senderAddress, fillMakerTokenAmount,
TradeSide.Maker, TransferType.Trade,
);
await this.validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerAmount, senderAddress, zrxTokenAddress,
await exchangeTradeEmulator.transferFromAsync(
signedOrder.takerTokenAddress, senderAddress, signedOrder.maker, fillTakerTokenAmount,
TradeSide.Taker, TransferType.Trade,
);
await exchangeTradeEmulator.transferFromAsync(
zrxTokenAddress, signedOrder.maker, signedOrder.feeRecipient, signedOrder.makerFee, TradeSide.Maker,
TransferType.Fee,
);
await exchangeTradeEmulator.transferFromAsync(
zrxTokenAddress, senderAddress, signedOrder.feeRecipient, signedOrder.takerFee, TradeSide.Taker,
TransferType.Fee,
);
}
private async validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder: SignedOrder, fillTakerAmount: BigNumber.BigNumber, zrxTokenAddress: string,
): Promise<void> {
const makerBalance = await this.tokenWrapper.getBalanceAsync(signedOrder.makerTokenAddress, signedOrder.maker);
const makerAllowance = await this.tokenWrapper.getProxyAllowanceAsync(
signedOrder.makerTokenAddress, signedOrder.maker);
const isMakerTokenZRX = signedOrder.makerTokenAddress === zrxTokenAddress;
// exchangeRate is the price of one maker token denominated in taker tokens
private validateRemainingFillAmountNotZeroOrThrow(
takerTokenAmount: BigNumber.BigNumber, unavailableTakerTokenAmount: BigNumber.BigNumber,
) {
if (takerTokenAmount.eq(unavailableTakerTokenAmount)) {
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
}
}
private validateOrderNotExpiredOrThrow(expirationUnixTimestampSec: BigNumber.BigNumber) {
const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
if (expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
throw new Error(ExchangeContractErrs.OrderFillExpired);
}
}
private getFillMakerTokenAmount(signedOrder: Order,
fillTakerTokenAmount: BigNumber.BigNumber): BigNumber.BigNumber {
const exchangeRate = signedOrder.takerTokenAmount.div(signedOrder.makerTokenAmount);
const fillMakerAmount = fillTakerAmount.div(exchangeRate);
const requiredMakerAmount = isMakerTokenZRX ? fillMakerAmount.plus(signedOrder.makerFee) : fillMakerAmount;
if (requiredMakerAmount.greaterThan(makerBalance)) {
throw new Error(ExchangeContractErrs.InsufficientMakerBalance);
}
if (requiredMakerAmount.greaterThan(makerAllowance)) {
throw new Error(ExchangeContractErrs.InsufficientMakerAllowance);
}
if (!isMakerTokenZRX) {
const makerZRXBalance = await this.tokenWrapper.getBalanceAsync(zrxTokenAddress, signedOrder.maker);
const makerZRXAllowance = await this.tokenWrapper.getProxyAllowanceAsync(
zrxTokenAddress, signedOrder.maker);
if (signedOrder.makerFee.greaterThan(makerZRXBalance)) {
throw new Error(ExchangeContractErrs.InsufficientMakerFeeBalance);
}
if (signedOrder.makerFee.greaterThan(makerZRXAllowance)) {
throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance);
}
}
}
private async validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder: SignedOrder, fillTakerAmount: BigNumber.BigNumber, senderAddress: string, zrxTokenAddress: string,
): Promise<void> {
const takerBalance = await this.tokenWrapper.getBalanceAsync(signedOrder.takerTokenAddress, senderAddress);
const takerAllowance = await this.tokenWrapper.getProxyAllowanceAsync(
signedOrder.takerTokenAddress, senderAddress);
const isTakerTokenZRX = signedOrder.takerTokenAddress === zrxTokenAddress;
const requiredTakerAmount = isTakerTokenZRX ? fillTakerAmount.plus(signedOrder.takerFee) : fillTakerAmount;
if (requiredTakerAmount.greaterThan(takerBalance)) {
throw new Error(ExchangeContractErrs.InsufficientTakerBalance);
}
if (requiredTakerAmount.greaterThan(takerAllowance)) {
throw new Error(ExchangeContractErrs.InsufficientTakerAllowance);
}
if (!isTakerTokenZRX) {
const takerZRXBalance = await this.tokenWrapper.getBalanceAsync(zrxTokenAddress, senderAddress);
const takerZRXAllowance = await this.tokenWrapper.getProxyAllowanceAsync(zrxTokenAddress, senderAddress);
if (signedOrder.takerFee.greaterThan(takerZRXBalance)) {
throw new Error(ExchangeContractErrs.InsufficientTakerFeeBalance);
}
if (signedOrder.takerFee.greaterThan(takerZRXAllowance)) {
throw new Error(ExchangeContractErrs.InsufficientTakerFeeAllowance);
}
}
const fillMakerTokenAmount = fillTakerTokenAmount.div(exchangeRate);
return fillMakerTokenAmount;
}
}

View File

@@ -1,4 +1,4 @@
import * as _ from 'lodash';
import {_} from '../utils/lodash';
import * as ethABI from 'ethereumjs-abi';
import * as ethUtil from 'ethereumjs-util';
import {Order, SignedOrder, SolidityTypes} from '../types';

View File

@@ -1,14 +1,20 @@
import * as _ from 'lodash';
import {_} from './utils/lodash';
import * as Web3 from 'web3';
import * as BigNumber from 'bignumber.js';
import promisify = require('es6-promisify');
import {ZeroExError, Artifact} from './types';
import {Contract} from './contract';
export class Web3Wrapper {
private web3: Web3;
private defaults: Partial<Web3.TxData>;
private networkIdIfExists?: number;
constructor(provider: Web3.Provider) {
private jsonRpcRequestId: number;
constructor(provider: Web3.Provider, defaults: Partial<Web3.TxData>) {
this.web3 = new Web3();
this.web3.setProvider(provider);
this.defaults = defaults;
this.jsonRpcRequestId = 0;
}
public setProvider(provider: Web3.Provider) {
delete this.networkIdIfExists;
@@ -25,6 +31,10 @@ export class Web3Wrapper {
const nodeVersion = await promisify(this.web3.version.getNode)();
return nodeVersion;
}
public async getTransactionReceiptAsync(txHash: string): Promise<Web3.TransactionReceipt> {
const transactionReceipt = await promisify(this.web3.eth.getTransactionReceipt)(txHash);
return transactionReceipt;
}
public getCurrentProvider(): Web3.Provider {
return this.web3.currentProvider;
}
@@ -41,6 +51,30 @@ export class Web3Wrapper {
return undefined;
}
}
public async getContractInstanceFromArtifactAsync<A extends Web3.ContractInstance>(artifact: Artifact,
address?: string): Promise<A> {
let contractAddress: string;
if (_.isUndefined(address)) {
const networkIdIfExists = await this.getNetworkIdIfExistsAsync();
if (_.isUndefined(networkIdIfExists)) {
throw new Error(ZeroExError.NoNetworkId);
}
if (_.isUndefined(artifact.networks[networkIdIfExists])) {
throw new Error(ZeroExError.ContractNotDeployedOnNetwork);
}
contractAddress = artifact.networks[networkIdIfExists].address.toLowerCase();
} else {
contractAddress = address;
}
const doesContractExist = await this.doesContractExistAtAddressAsync(contractAddress);
if (!doesContractExist) {
throw new Error(ZeroExError.ContractDoesNotExist);
}
const contractInstance = this.getContractInstance<A>(
artifact.abi, contractAddress,
);
return contractInstance;
}
public toWei(ethAmount: BigNumber.BigNumber): BigNumber.BigNumber {
const balanceWei = this.web3.toWei(ethAmount, 'ether');
return balanceWei;
@@ -60,16 +94,54 @@ export class Web3Wrapper {
const signData = await promisify(this.web3.eth.sign)(address, message);
return signData;
}
public async getBlockTimestampAsync(blockHash: string): Promise<number> {
const {timestamp} = await promisify(this.web3.eth.getBlock)(blockHash);
public async getBlockAsync(blockParam: string|Web3.BlockParam): Promise<Web3.BlockWithoutTransactionData> {
const block = await promisify(this.web3.eth.getBlock)(blockParam);
return block;
}
public async getBlockTimestampAsync(blockParam: string|Web3.BlockParam): Promise<number> {
const {timestamp} = await this.getBlockAsync(blockParam);
return timestamp;
}
public async getAvailableAddressesAsync(): Promise<string[]> {
const addresses: string[] = await promisify(this.web3.eth.getAccounts)();
return addresses;
}
public async getLogsAsync(filter: Web3.FilterObject): Promise<Web3.LogEntry[]> {
let fromBlock = filter.fromBlock;
if (_.isNumber(fromBlock)) {
fromBlock = this.web3.toHex(fromBlock);
}
let toBlock = filter.toBlock;
if (_.isNumber(toBlock)) {
toBlock = this.web3.toHex(toBlock);
}
const serializedFilter = {
...filter,
fromBlock,
toBlock,
};
const payload = {
jsonrpc: '2.0',
id: this.jsonRpcRequestId++,
method: 'eth_getLogs',
params: [serializedFilter],
};
const logs = await this.sendRawPayloadAsync(payload);
return logs;
}
private getContractInstance<A extends Web3.ContractInstance>(abi: Web3.ContractAbi, address: string): A {
const web3ContractInstance = this.web3.eth.contract(abi).at(address);
const contractInstance = new Contract(web3ContractInstance, this.defaults) as any as A;
return contractInstance;
}
private async getNetworkAsync(): Promise<number> {
const networkId = await promisify(this.web3.version.getNetwork)();
return networkId;
}
private async sendRawPayloadAsync(payload: Web3.JSONRPCRequestPayload): Promise<any> {
const sendAsync = this.web3.currentProvider.sendAsync.bind(this.web3.currentProvider);
const response = await promisify(sendAsync)(payload);
const result = response.result;
return result;
}
}

View File

@@ -1,13 +1,16 @@
import * as _ from 'lodash';
import {_} from '../src/utils/lodash';
import * as chai from 'chai';
import {chaiSetup} from './utils/chai_setup';
import 'mocha';
import * as BigNumber from 'bignumber.js';
import * as Sinon from 'sinon';
import {ZeroEx, Order} from '../src';
import {ZeroEx, Order, ZeroExError, LogWithDecodedArgs, ApprovalContractEventArgs, TokenEvents} from '../src';
import {constants} from './utils/constants';
import {TokenUtils} from './utils/token_utils';
import {web3Factory} from './utils/web3_factory';
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
const blockchainLifecycle = new BlockchainLifecycle();
chaiSetup.configure();
const expect = chai.expect;
@@ -204,4 +207,53 @@ describe('ZeroEx library', () => {
expect(ecSignature).to.deep.equal(expectedECSignature);
});
});
describe('#awaitTransactionMinedAsync', () => {
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
it('returns transaction receipt with decoded logs', async () => {
const availableAddresses = await zeroEx.getAvailableAddressesAsync();
const coinbase = availableAddresses[0];
const tokens = await zeroEx.tokenRegistry.getTokensAsync();
const tokenUtils = new TokenUtils(tokens);
const zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
const proxyAddress = await zeroEx.proxy.getContractAddressAsync();
const txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(zrxTokenAddress, coinbase);
const txReceiptWithDecodedLogs = await zeroEx.awaitTransactionMinedAsync(txHash);
const log = txReceiptWithDecodedLogs.logs[0] as LogWithDecodedArgs<ApprovalContractEventArgs>;
expect(log.event).to.be.equal(TokenEvents.Approval);
expect(log.args._owner).to.be.equal(coinbase);
expect(log.args._spender).to.be.equal(proxyAddress);
expect(log.args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
});
});
describe('#config', () => {
it('allows to specify exchange contract address', async () => {
const config = {
exchangeContractAddress: ZeroEx.NULL_ADDRESS,
};
const zeroExWithWrongExchangeAddress = new ZeroEx(web3.currentProvider, config);
return expect(zeroExWithWrongExchangeAddress.exchange.getContractAddressAsync())
.to.be.rejectedWith(ZeroExError.ContractDoesNotExist);
});
it('allows to specify ether token contract address', async () => {
const config = {
etherTokenContractAddress: ZeroEx.NULL_ADDRESS,
};
const zeroExWithWrongEtherTokenAddress = new ZeroEx(web3.currentProvider, config);
return expect(zeroExWithWrongEtherTokenAddress.etherToken.getContractAddressAsync())
.to.be.rejectedWith(ZeroExError.ContractDoesNotExist);
});
it('allows to specify token registry token contract address', async () => {
const config = {
tokenRegistryContractAddress: ZeroEx.NULL_ADDRESS,
};
const zeroExWithWrongTokenRegistryAddress = new ZeroEx(web3.currentProvider, config);
return expect(zeroExWithWrongTokenRegistryAddress.tokenRegistry.getContractAddressAsync())
.to.be.rejectedWith(ZeroExError.ContractDoesNotExist);
});
});
});

View File

@@ -23,7 +23,24 @@ describe('Artifacts', () => {
await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync();
}).timeout(TIMEOUT);
it('proxy contract is deployed', async () => {
await (zeroEx.token as any)._getProxyAddressAsync();
await (zeroEx.token as any)._getTokenTransferProxyAddressAsync();
}).timeout(TIMEOUT);
it('exchange contract is deployed', async () => {
await zeroEx.exchange.getContractAddressAsync();
}).timeout(TIMEOUT);
});
describe('contracts are deployed on ropsten', () => {
const ropstenRpcUrl = constants.ROPSTEN_RPC_URL;
const packageJSONContent = fs.readFileSync('package.json', 'utf-8');
const packageJSON = JSON.parse(packageJSONContent);
const mnemonic = packageJSON.config.mnemonic;
const web3Provider = new HDWalletProvider(mnemonic, ropstenRpcUrl);
const zeroEx = new ZeroEx(web3Provider);
it('token registry contract is deployed', async () => {
await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync();
}).timeout(TIMEOUT);
it('proxy contract is deployed', async () => {
await (zeroEx.token as any)._getTokenTransferProxyAddressAsync();
}).timeout(TIMEOUT);
it('exchange contract is deployed', async () => {
await zeroEx.exchange.getContractAddressAsync();

View File

@@ -3,7 +3,6 @@ import * as chai from 'chai';
import {chaiSetup} from './utils/chai_setup';
import * as Web3 from 'web3';
import * as BigNumber from 'bignumber.js';
import promisify = require('es6-promisify');
import {web3Factory} from './utils/web3_factory';
import {ZeroEx, ZeroExError} from '../src';
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
@@ -52,7 +51,8 @@ describe('EtherTokenWrapper', () => {
expect(preETHBalance).to.be.bignumber.gt(0);
expect(preWETHBalance).to.be.bignumber.equal(0);
await zeroEx.etherToken.depositAsync(depositWeiAmount, addressWithETH);
const txHash = await zeroEx.etherToken.depositAsync(depositWeiAmount, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash);
const postETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
@@ -86,7 +86,8 @@ describe('EtherTokenWrapper', () => {
expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI);
expect(preWETHBalance).to.be.bignumber.equal(depositWeiAmount);
await zeroEx.etherToken.withdrawAsync(depositWeiAmount, addressWithETH);
const txHash = await zeroEx.etherToken.withdrawAsync(depositWeiAmount, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash);
const postETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);

View File

@@ -0,0 +1,89 @@
import * as chai from 'chai';
import * as BigNumber from 'bignumber.js';
import {chaiSetup} from './utils/chai_setup';
import {web3Factory} from './utils/web3_factory';
import {ZeroEx, ExchangeContractErrs, Token} from '../src';
import {TradeSide, TransferType} from '../src/types';
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
import {ExchangeTransferSimulator} from '../src/utils/exchange_transfer_simulator';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle();
describe('ExchangeTransferSimulator', () => {
const web3 = web3Factory.create();
const zeroEx = new ZeroEx(web3.currentProvider);
const transferAmount = new BigNumber(5);
let userAddresses: string[];
let tokens: Token[];
let coinbase: string;
let sender: string;
let recipient: string;
let exampleTokenAddress: string;
let exchangeTransferSimulator: ExchangeTransferSimulator;
let txHash: string;
before(async () => {
userAddresses = await zeroEx.getAvailableAddressesAsync();
[coinbase, sender, recipient] = userAddresses;
tokens = await zeroEx.tokenRegistry.getTokensAsync();
exampleTokenAddress = tokens[0].address;
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('#transferFromAsync', () => {
beforeEach(() => {
exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token);
});
it('throws if the user doesn\'t have enough allowance', async () => {
return expect(exchangeTransferSimulator.transferFromAsync(
exampleTokenAddress, sender, recipient, transferAmount, TradeSide.Taker, TransferType.Trade,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerAllowance);
});
it('throws if the user doesn\'t have enough balance', async () => {
txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
return expect(exchangeTransferSimulator.transferFromAsync(
exampleTokenAddress, sender, recipient, transferAmount, TradeSide.Maker, TransferType.Trade,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance);
});
it('updates balances and proxyAllowance after transfer', async () => {
txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
await exchangeTransferSimulator.transferFromAsync(
exampleTokenAddress, sender, recipient, transferAmount, TradeSide.Taker, TransferType.Trade,
);
const senderBalance = await (exchangeTransferSimulator as any).getBalanceAsync(exampleTokenAddress, sender);
const recipientBalance = await (exchangeTransferSimulator as any).getBalanceAsync(
exampleTokenAddress, recipient);
const senderProxyAllowance = await (exchangeTransferSimulator as any).getProxyAllowanceAsync(
exampleTokenAddress, sender);
expect(senderBalance).to.be.bignumber.equal(0);
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
expect(senderProxyAllowance).to.be.bignumber.equal(0);
});
it('doesn\'t update proxyAllowance after transfer if unlimited', async () => {
txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(exampleTokenAddress, sender);
await zeroEx.awaitTransactionMinedAsync(txHash);
await exchangeTransferSimulator.transferFromAsync(
exampleTokenAddress, sender, recipient, transferAmount, TradeSide.Taker, TransferType.Trade,
);
const senderBalance = await (exchangeTransferSimulator as any).getBalanceAsync(exampleTokenAddress, sender);
const recipientBalance = await (exchangeTransferSimulator as any).getBalanceAsync(
exampleTokenAddress, recipient);
const senderProxyAllowance = await (exchangeTransferSimulator as any).getProxyAllowanceAsync(
exampleTokenAddress, sender);
expect(senderBalance).to.be.bignumber.equal(0);
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
expect(senderProxyAllowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
});
});
});

View File

@@ -3,8 +3,6 @@ import * as chai from 'chai';
import * as Web3 from 'web3';
import * as BigNumber from 'bignumber.js';
import {chaiSetup} from './utils/chai_setup';
import ChaiBigNumber = require('chai-bignumber');
import promisify = require('es6-promisify');
import {web3Factory} from './utils/web3_factory';
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
import {
@@ -18,12 +16,12 @@ import {
OrderCancellationRequest,
OrderFillRequest,
LogFillContractEventArgs,
LogCancelContractEventArgs,
LogEvent,
} from '../src';
import {DoneCallback} from '../src/types';
import {DoneCallback, BlockParamLiteral} from '../src/types';
import {FillScenarios} from './utils/fill_scenarios';
import {TokenUtils} from './utils/token_utils';
import {assert} from '../src/utils/assert';
import {TokenTransferProxyWrapper} from '../src/contract_wrappers/token_transfer_proxy_wrapper';
chaiSetup.configure();
const expect = chai.expect;
@@ -63,7 +61,7 @@ describe('ExchangeWrapper', () => {
let makerAddress: string;
let takerAddress: string;
let feeRecipient: string;
const fillTakerAmount = new BigNumber(5);
const takerTokenFillAmount = new BigNumber(5);
before(async () => {
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
tokens = await zeroEx.tokenRegistry.getTokensAsync();
@@ -81,26 +79,59 @@ describe('ExchangeWrapper', () => {
const anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
const orderFillOrKillRequests = [
const orderFillRequests = [
{
signedOrder,
fillTakerAmount: partialFillTakerAmount,
takerTokenFillAmount: partialFillTakerAmount,
},
{
signedOrder: anotherSignedOrder,
fillTakerAmount: partialFillTakerAmount,
takerTokenFillAmount: partialFillTakerAmount,
},
];
await zeroEx.exchange.batchFillOrKillAsync(orderFillOrKillRequests, takerAddress);
await zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress);
});
describe('order transaction options', () => {
let signedOrder: SignedOrder;
let orderFillRequests: OrderFillRequest[];
const fillableAmount = new BigNumber(5);
beforeEach(async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
orderFillRequests = [
{
signedOrder,
takerTokenFillAmount: new BigNumber(0),
},
];
});
it('should validate when orderTransactionOptions are not present', async () => {
return expect(zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress))
.to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
it('should validate when orderTransactionOptions specify to validate', async () => {
return expect(zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress, {
shouldValidate: true,
})).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
it('should not validate when orderTransactionOptions specify not to validate', async () => {
return expect(zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress, {
shouldValidate: false,
})).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
});
});
describe('#fillOrKillOrderAsync', () => {
let signedOrder: SignedOrder;
const fillableAmount = new BigNumber(5);
beforeEach(async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
});
describe('successful fills', () => {
it('should fill a valid order', async () => {
const fillableAmount = new BigNumber(5);
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
.to.be.bignumber.equal(fillableAmount);
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
@@ -109,21 +140,17 @@ describe('ExchangeWrapper', () => {
.to.be.bignumber.equal(0);
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
.to.be.bignumber.equal(fillableAmount);
await zeroEx.exchange.fillOrKillOrderAsync(signedOrder, fillTakerAmount, takerAddress);
await zeroEx.exchange.fillOrKillOrderAsync(signedOrder, takerTokenFillAmount, takerAddress);
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
.to.be.bignumber.equal(fillableAmount.minus(fillTakerAmount));
.to.be.bignumber.equal(fillableAmount.minus(takerTokenFillAmount));
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
.to.be.bignumber.equal(fillTakerAmount);
.to.be.bignumber.equal(takerTokenFillAmount);
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress))
.to.be.bignumber.equal(fillTakerAmount);
.to.be.bignumber.equal(takerTokenFillAmount);
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
.to.be.bignumber.equal(fillableAmount.minus(fillTakerAmount));
.to.be.bignumber.equal(fillableAmount.minus(takerTokenFillAmount));
});
it('should partially fill a valid order', async () => {
const fillableAmount = new BigNumber(5);
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
const partialFillAmount = new BigNumber(3);
await zeroEx.exchange.fillOrKillOrderAsync(signedOrder, partialFillAmount, takerAddress);
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
@@ -136,6 +163,23 @@ describe('ExchangeWrapper', () => {
.to.be.bignumber.equal(fillableAmount.minus(partialFillAmount));
});
});
describe('order transaction options', () => {
const emptyFillableAmount = new BigNumber(0);
it('should validate when orderTransactionOptions are not present', async () => {
return expect(zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress))
.to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
it('should validate when orderTransactionOptions specify to validate', async () => {
return expect(zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress, {
shouldValidate: true,
})).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
it('should not validate when orderTransactionOptions specify not to validate', async () => {
return expect(zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress, {
shouldValidate: false,
})).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
});
});
});
describe('fill order(s)', () => {
@@ -146,7 +190,7 @@ describe('ExchangeWrapper', () => {
let takerAddress: string;
let feeRecipient: string;
const fillableAmount = new BigNumber(5);
const fillTakerAmount = new BigNumber(5);
const takerTokenFillAmount = new BigNumber(5);
const shouldThrowOnInsufficientBalanceOrAllowance = true;
before(async () => {
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
@@ -169,24 +213,26 @@ describe('ExchangeWrapper', () => {
.to.be.bignumber.equal(0);
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
.to.be.bignumber.equal(fillableAmount);
await zeroEx.exchange.fillOrderAsync(
signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
const txHash = await zeroEx.exchange.fillOrderAsync(
signedOrder, takerTokenFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
await zeroEx.awaitTransactionMinedAsync(txHash);
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
.to.be.bignumber.equal(fillableAmount.minus(fillTakerAmount));
.to.be.bignumber.equal(fillableAmount.minus(takerTokenFillAmount));
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
.to.be.bignumber.equal(fillTakerAmount);
.to.be.bignumber.equal(takerTokenFillAmount);
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress))
.to.be.bignumber.equal(fillTakerAmount);
.to.be.bignumber.equal(takerTokenFillAmount);
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
.to.be.bignumber.equal(fillableAmount.minus(fillTakerAmount));
.to.be.bignumber.equal(fillableAmount.minus(takerTokenFillAmount));
});
it('should partially fill the valid order', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
const partialFillAmount = new BigNumber(3);
await zeroEx.exchange.fillOrderAsync(
const txHash = await zeroEx.exchange.fillOrderAsync(
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
await zeroEx.awaitTransactionMinedAsync(txHash);
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
.to.be.bignumber.equal(fillableAmount.minus(partialFillAmount));
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
@@ -196,34 +242,6 @@ describe('ExchangeWrapper', () => {
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
.to.be.bignumber.equal(fillableAmount.minus(partialFillAmount));
});
it('should return filled amount', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
const partialFillAmount = new BigNumber(3);
const filledAmount = await zeroEx.exchange.fillOrderAsync(
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
expect(filledAmount).to.be.bignumber.equal(partialFillAmount);
});
it('should return the partially filled amount \
if the fill amount specified is greater then the amount available', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
const partialFillAmount = new BigNumber(3);
await zeroEx.exchange.fillOrderAsync(
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
const missingBalance = new BigNumber(1);
const totalBalance = partialFillAmount.plus(missingBalance);
await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, missingBalance);
await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, totalBalance);
await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, missingBalance);
await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, totalBalance);
const remainingFillAmount = fillableAmount.minus(partialFillAmount);
const filledAmount = await zeroEx.exchange.fillOrderAsync(
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
expect(filledAmount).to.be.bignumber.equal(remainingFillAmount);
});
it('should fill the valid orders with fees', async () => {
const makerFee = new BigNumber(1);
const takerFee = new BigNumber(2);
@@ -231,12 +249,39 @@ describe('ExchangeWrapper', () => {
makerTokenAddress, takerTokenAddress, makerFee, takerFee,
makerAddress, takerAddress, fillableAmount, feeRecipient,
);
await zeroEx.exchange.fillOrderAsync(
signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
const txHash = await zeroEx.exchange.fillOrderAsync(
signedOrder, takerTokenFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
await zeroEx.awaitTransactionMinedAsync(txHash);
expect(await zeroEx.token.getBalanceAsync(zrxTokenAddress, feeRecipient))
.to.be.bignumber.equal(makerFee.plus(takerFee));
});
});
describe('order transaction options', () => {
let signedOrder: SignedOrder;
const emptyFillTakerAmount = new BigNumber(0);
beforeEach(async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
});
it('should validate when orderTransactionOptions are not present', async () => {
return expect(zeroEx.exchange.fillOrderAsync(
signedOrder, emptyFillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
)).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
it('should validate when orderTransactionOptions specify to validate', async () => {
return expect(zeroEx.exchange.fillOrderAsync(
signedOrder, emptyFillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, {
shouldValidate: true,
})).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
it('should not validate when orderTransactionOptions specify not to validate', async () => {
return expect(zeroEx.exchange.fillOrderAsync(
signedOrder, emptyFillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, {
shouldValidate: false,
})).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
});
});
describe('#batchFillOrdersAsync', () => {
let signedOrder: SignedOrder;
@@ -253,29 +298,65 @@ describe('ExchangeWrapper', () => {
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder);
orderFillBatch = [
{
signedOrder,
takerTokenFillAmount: fillTakerAmount,
},
{
signedOrder: anotherSignedOrder,
takerTokenFillAmount: fillTakerAmount,
},
];
});
describe('successful batch fills', () => {
it('should no-op for an empty batch', async () => {
await zeroEx.exchange.batchFillOrdersAsync(
[], shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
beforeEach(() => {
orderFillBatch = [
{
signedOrder,
takerTokenFillAmount,
},
{
signedOrder: anotherSignedOrder,
takerTokenFillAmount,
},
];
});
it('should throw if a batch is empty', async () => {
return expect(zeroEx.exchange.batchFillOrdersAsync(
[], shouldThrowOnInsufficientBalanceOrAllowance, takerAddress),
).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
});
it('should successfully fill multiple orders', async () => {
await zeroEx.exchange.batchFillOrdersAsync(
const txHash = await zeroEx.exchange.batchFillOrdersAsync(
orderFillBatch, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
await zeroEx.awaitTransactionMinedAsync(txHash);
const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex);
const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex);
expect(filledAmount).to.be.bignumber.equal(fillTakerAmount);
expect(anotherFilledAmount).to.be.bignumber.equal(fillTakerAmount);
expect(filledAmount).to.be.bignumber.equal(takerTokenFillAmount);
expect(anotherFilledAmount).to.be.bignumber.equal(takerTokenFillAmount);
});
});
describe('order transaction options', () => {
beforeEach(async () => {
const emptyFillTakerAmount = new BigNumber(0);
orderFillBatch = [
{
signedOrder,
takerTokenFillAmount: emptyFillTakerAmount,
},
{
signedOrder: anotherSignedOrder,
takerTokenFillAmount: emptyFillTakerAmount,
},
];
});
it('should validate when orderTransactionOptions are not present', async () => {
return expect(zeroEx.exchange.batchFillOrdersAsync(
orderFillBatch, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress),
).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
it('should validate when orderTransactionOptions specify to validate', async () => {
return expect(zeroEx.exchange.batchFillOrdersAsync(
orderFillBatch, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, {
shouldValidate: true,
})).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
it('should not validate when orderTransactionOptions specify not to validate', async () => {
return expect(zeroEx.exchange.batchFillOrdersAsync(
orderFillBatch, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, {
shouldValidate: false,
})).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
});
});
@@ -298,25 +379,41 @@ describe('ExchangeWrapper', () => {
signedOrders = [signedOrder, anotherSignedOrder];
});
describe('successful batch fills', () => {
it('should no-op for an empty batch', async () => {
await zeroEx.exchange.fillOrdersUpToAsync(
[], fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
it('should throw if a batch is empty', async () => {
return expect(zeroEx.exchange.fillOrdersUpToAsync(
[], fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress),
).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
});
it('should successfully fill up to specified amount', async () => {
await zeroEx.exchange.fillOrdersUpToAsync(
const txHash = await zeroEx.exchange.fillOrdersUpToAsync(
signedOrders, fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
);
await zeroEx.awaitTransactionMinedAsync(txHash);
const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex);
const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex);
expect(filledAmount).to.be.bignumber.equal(fillableAmount);
const remainingFillAmount = fillableAmount.minus(1);
expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount);
});
it('should return filled amount', async () => {
const filledTakerTokenAmount = await zeroEx.exchange.fillOrdersUpToAsync(
signedOrders, fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
);
expect(filledTakerTokenAmount).to.be.bignumber.equal(fillUpToAmount);
});
describe('order transaction options', () => {
const emptyFillUpToAmount = new BigNumber(0);
it('should validate when orderTransactionOptions are not present', async () => {
return expect(zeroEx.exchange.fillOrdersUpToAsync(
signedOrders, emptyFillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
)).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
it('should validate when orderTransactionOptions specify to validate', async () => {
return expect(zeroEx.exchange.fillOrdersUpToAsync(
signedOrders, emptyFillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, {
shouldValidate: true,
})).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
it('should not validate when orderTransactionOptions specify not to validate', async () => {
return expect(zeroEx.exchange.fillOrdersUpToAsync(
signedOrders, emptyFillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, {
shouldValidate: false,
})).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
});
});
@@ -344,13 +441,27 @@ describe('ExchangeWrapper', () => {
describe('#cancelOrderAsync', () => {
describe('successful cancels', () => {
it('should cancel an order', async () => {
await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount);
const txHash = await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
const cancelledAmount = await zeroEx.exchange.getCanceledTakerAmountAsync(orderHashHex);
expect(cancelledAmount).to.be.bignumber.equal(cancelAmount);
});
it('should return cancelled amount', async () => {
const cancelledAmount = await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount);
expect(cancelledAmount).to.be.bignumber.equal(cancelAmount);
});
describe('order transaction options', () => {
const emptyCancelTakerTokenAmount = new BigNumber(0);
it('should validate when orderTransactionOptions are not present', async () => {
return expect(zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount))
.to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
});
it('should validate when orderTransactionOptions specify to validate', async () => {
return expect(zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount, {
shouldValidate: true,
})).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
});
it('should not validate when orderTransactionOptions specify not to validate', async () => {
return expect(zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount, {
shouldValidate: false,
})).to.not.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
});
});
});
@@ -399,6 +510,35 @@ describe('ExchangeWrapper', () => {
expect(anotherCancelledAmount).to.be.bignumber.equal(cancelAmount);
});
});
describe('order transaction options', () => {
beforeEach(async () => {
const emptyTakerTokenCancelAmount = new BigNumber(0);
cancelBatch = [
{
order: signedOrder,
takerTokenCancelAmount: emptyTakerTokenCancelAmount,
},
{
order: anotherSignedOrder,
takerTokenCancelAmount: emptyTakerTokenCancelAmount,
},
];
});
it('should validate when orderTransactionOptions are not present', async () => {
return expect(zeroEx.exchange.batchCancelOrdersAsync(cancelBatch))
.to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
});
it('should validate when orderTransactionOptions specify to validate', async () => {
return expect(zeroEx.exchange.batchCancelOrdersAsync(cancelBatch, {
shouldValidate: true,
})).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
});
it('should not validate when orderTransactionOptions specify not to validate', async () => {
return expect(zeroEx.exchange.batchCancelOrdersAsync(cancelBatch, {
shouldValidate: false,
})).to.not.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
});
});
});
});
describe('tests that require partially filled order', () => {
@@ -483,11 +623,7 @@ describe('ExchangeWrapper', () => {
let makerAddress: string;
let fillableAmount: BigNumber.BigNumber;
let signedOrder: SignedOrder;
const subscriptionOpts: SubscriptionOpts = {
fromBlock: 0,
toBlock: 'latest',
};
const fillTakerAmountInBaseUnits = new BigNumber(1);
const takerTokenFillAmountInBaseUnits = new BigNumber(1);
const cancelTakerAmountInBaseUnits = new BigNumber(1);
before(() => {
[coinbase, makerAddress, takerAddress] = userAddresses;
@@ -502,102 +638,81 @@ describe('ExchangeWrapper', () => {
);
});
afterEach(async () => {
await zeroEx.exchange.stopWatchingAllEventsAsync();
zeroEx.exchange.unsubscribeAll();
});
// Hack: Mocha does not allow a test to be both async and have a `done` callback
// Since we need to await the receipt of the event in the `subscribeAsync` callback,
// Since we need to await the receipt of the event in the `subscribe` callback,
// we do need both. A hack is to make the top-level a sync fn w/ a done callback and then
// wrap the rest of the test in an async block
// Source: https://github.com/mochajs/mocha/issues/2407
it('Should receive the LogFill event when an order is filled', (done: DoneCallback) => {
(async () => {
const zeroExEvent = await zeroEx.exchange.subscribeAsync(
ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress,
);
zeroExEvent.watch((err: Error, event: ContractEvent) => {
expect(err).to.be.null();
expect(event).to.not.be.undefined();
expect(event.event).to.be.equal('LogFill');
const callback = (logEvent: LogEvent<LogFillContractEventArgs>) => {
expect(logEvent.event).to.be.equal(ExchangeEvents.LogFill);
done();
});
};
await zeroEx.exchange.subscribeAsync(
ExchangeEvents.LogFill, indexFilterValues, callback,
);
await zeroEx.exchange.fillOrderAsync(
signedOrder, fillTakerAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
signedOrder, takerTokenFillAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance,
takerAddress,
);
})().catch(done);
});
it('Should receive the LogCancel event when an order is cancelled', (done: DoneCallback) => {
(async () => {
const zeroExEvent = await zeroEx.exchange.subscribeAsync(
ExchangeEvents.LogCancel, subscriptionOpts, indexFilterValues, exchangeContractAddress,
const callback = (logEvent: LogEvent<LogCancelContractEventArgs>) => {
expect(logEvent.event).to.be.equal(ExchangeEvents.LogCancel);
done();
};
await zeroEx.exchange.subscribeAsync(
ExchangeEvents.LogCancel, indexFilterValues, callback,
);
zeroExEvent.watch((err: Error, event: ContractEvent) => {
expect(err).to.be.null();
expect(event).to.not.be.undefined();
expect(event.event).to.be.equal('LogCancel');
done();
});
await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerAmountInBaseUnits);
})().catch(done);
});
it('Outstanding subscriptions are cancelled when zeroEx.setProviderAsync called', (done: DoneCallback) => {
(async () => {
const eventSubscriptionToBeCancelled = await zeroEx.exchange.subscribeAsync(
ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress,
);
eventSubscriptionToBeCancelled.watch((err: Error, event: ContractEvent) => {
const callbackNeverToBeCalled = (logEvent: LogEvent<LogFillContractEventArgs>) => {
done(new Error('Expected this subscription to have been cancelled'));
});
};
await zeroEx.exchange.subscribeAsync(
ExchangeEvents.LogFill, indexFilterValues, callbackNeverToBeCalled,
);
const newProvider = web3Factory.getRpcProvider();
await zeroEx.setProviderAsync(newProvider);
const eventSubscriptionToStay = await zeroEx.exchange.subscribeAsync(
ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress,
);
eventSubscriptionToStay.watch((err: Error, event: ContractEvent) => {
expect(err).to.be.null();
expect(event).to.not.be.undefined();
expect(event.event).to.be.equal('LogFill');
const callback = (logEvent: LogEvent<LogFillContractEventArgs>) => {
expect(logEvent.event).to.be.equal(ExchangeEvents.LogFill);
done();
});
};
await zeroEx.exchange.subscribeAsync(
ExchangeEvents.LogFill, indexFilterValues, callback,
);
await zeroEx.exchange.fillOrderAsync(
signedOrder, fillTakerAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
signedOrder, takerTokenFillAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance,
takerAddress,
);
})().catch(done);
});
it('Should stop watch for events when stopWatchingAsync called on the eventEmitter', (done: DoneCallback) => {
it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => {
(async () => {
const eventSubscriptionToBeStopped = await zeroEx.exchange.subscribeAsync(
ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress,
const callbackNeverToBeCalled = (logEvent: LogEvent<LogFillContractEventArgs>) => {
done(new Error('Expected this subscription to have been cancelled'));
};
const subscriptionToken = await zeroEx.exchange.subscribeAsync(
ExchangeEvents.LogFill, indexFilterValues, callbackNeverToBeCalled,
);
eventSubscriptionToBeStopped.watch((err: Error, event: ContractEvent) => {
done(new Error('Expected this subscription to have been stopped'));
});
await eventSubscriptionToBeStopped.stopWatchingAsync();
zeroEx.exchange.unsubscribe(subscriptionToken);
await zeroEx.exchange.fillOrderAsync(
signedOrder, fillTakerAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
signedOrder, takerTokenFillAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance,
takerAddress,
);
done();
})().catch(done);
});
it('Should wrap all event args BigNumber instances in a newer version of BigNumber', (done: DoneCallback) => {
(async () => {
const zeroExEvent = await zeroEx.exchange.subscribeAsync(
ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress,
);
zeroExEvent.watch((err: Error, event: ContractEvent) => {
const args = event.args as LogFillContractEventArgs;
expect(args.filledMakerTokenAmount.isBigNumber).to.be.true();
expect(args.filledTakerTokenAmount.isBigNumber).to.be.true();
expect(args.paidMakerFee.isBigNumber).to.be.true();
expect(args.paidTakerFee.isBigNumber).to.be.true();
done();
});
await zeroEx.exchange.fillOrderAsync(
signedOrder, fillTakerAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
);
})().catch(done);
});
});
describe('#getOrderHashHexUsingContractCallAsync', () => {
let makerTokenAddress: string;
@@ -621,4 +736,86 @@ describe('ExchangeWrapper', () => {
expect(orderHash).to.equal(orderHashFromContract);
});
});
describe('#getZRXTokenAddressAsync', () => {
it('gets the same token as is in token registry', async () => {
const zrxAddress = await zeroEx.exchange.getZRXTokenAddressAsync();
const zrxToken = tokenUtils.getProtocolTokenOrThrow();
expect(zrxAddress).to.equal(zrxToken.address);
});
});
describe('#getLogsAsync', () => {
let makerTokenAddress: string;
let takerTokenAddress: string;
let makerAddress: string;
let takerAddress: string;
const fillableAmount = new BigNumber(5);
const shouldThrowOnInsufficientBalanceOrAllowance = true;
const subscriptionOpts: SubscriptionOpts = {
fromBlock: BlockParamLiteral.Earliest,
toBlock: BlockParamLiteral.Latest,
};
let txHash: string;
before(async () => {
[, makerAddress, takerAddress] = userAddresses;
const [makerToken, takerToken] = tokenUtils.getNonProtocolTokens();
makerTokenAddress = makerToken.address;
takerTokenAddress = takerToken.address;
});
it('should get logs with decoded args emitted by LogFill', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
txHash = await zeroEx.exchange.fillOrderAsync(
signedOrder, fillableAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
);
await zeroEx.awaitTransactionMinedAsync(txHash);
const eventName = ExchangeEvents.LogFill;
const indexFilterValues = {};
const logs = await zeroEx.exchange.getLogsAsync(eventName, subscriptionOpts, indexFilterValues);
expect(logs).to.have.length(1);
expect(logs[0].event).to.be.equal(eventName);
});
it('should only get the logs with the correct event name', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
txHash = await zeroEx.exchange.fillOrderAsync(
signedOrder, fillableAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
);
await zeroEx.awaitTransactionMinedAsync(txHash);
const differentEventName = ExchangeEvents.LogCancel;
const indexFilterValues = {};
const logs = await zeroEx.exchange.getLogsAsync(differentEventName, subscriptionOpts, indexFilterValues);
expect(logs).to.have.length(0);
});
it('should only get the logs with the correct indexed fields', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
txHash = await zeroEx.exchange.fillOrderAsync(
signedOrder, fillableAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
);
await zeroEx.awaitTransactionMinedAsync(txHash);
const differentMakerAddress = userAddresses[2];
const anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, differentMakerAddress, takerAddress, fillableAmount,
);
txHash = await zeroEx.exchange.fillOrderAsync(
anotherSignedOrder, fillableAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
);
await zeroEx.awaitTransactionMinedAsync(txHash);
const eventName = ExchangeEvents.LogFill;
const indexFilterValues = {
maker: differentMakerAddress,
};
const logs = await zeroEx.exchange.getLogsAsync<LogFillContractEventArgs>(
eventName, subscriptionOpts, indexFilterValues,
);
expect(logs).to.have.length(1);
const args = logs[0].args;
expect(args.maker).to.be.equal(differentMakerAddress);
});
});
});

View File

@@ -1,14 +1,16 @@
import * as chai from 'chai';
import * as Web3 from 'web3';
import * as BigNumber from 'bignumber.js';
import promisify = require('es6-promisify');
import * as Sinon from 'sinon';
import {chaiSetup} from './utils/chai_setup';
import {web3Factory} from './utils/web3_factory';
import {ZeroEx, SignedOrder, Token, ExchangeContractErrs} from '../src';
import {ZeroEx, SignedOrder, Token, ExchangeContractErrs, ZeroExError} from '../src';
import {TradeSide, TransferType} from '../src/types';
import {TokenUtils} from './utils/token_utils';
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
import {FillScenarios} from './utils/fill_scenarios';
import {OrderValidationUtils} from '../src/utils/order_validation_utils';
import {ExchangeTransferSimulator} from '../src/utils/exchange_transfer_simulator';
chaiSetup.configure();
const expect = chai.expect;
@@ -32,7 +34,6 @@ describe('OrderValidation', () => {
let orderValidationUtils: OrderValidationUtils;
const fillableAmount = new BigNumber(5);
const fillTakerAmount = new BigNumber(5);
const shouldThrowOnInsufficientBalanceOrAllowance = false;
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider);
@@ -54,6 +55,46 @@ describe('OrderValidation', () => {
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('validateOrderFillableOrThrowAsync', () => {
it('should succeed if the order is fillable', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
await zeroEx.exchange.validateOrderFillableOrThrowAsync(
signedOrder,
);
});
it('should succeed if the order is asymmetric and fillable', async () => {
const makerFillableAmount = fillableAmount;
const takerFillableAmount = fillableAmount.minus(4);
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
makerFillableAmount, takerFillableAmount,
);
await zeroEx.exchange.validateOrderFillableOrThrowAsync(
signedOrder,
);
});
it('should throw when the order is fully filled or cancelled', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(
signedOrder,
)).to.be.rejectedWith(ExchangeContractErrs.OrderRemainingFillAmountZero);
});
it('should throw when order is expired', async () => {
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
fillableAmount, expirationInPast,
);
return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(
signedOrder,
)).to.be.rejectedWith(ExchangeContractErrs.OrderFillExpired);
});
});
describe('validateFillOrderAndThrowIfInvalidAsync', () => {
it('should throw when the fill amount is zero', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
@@ -64,6 +105,16 @@ describe('OrderValidation', () => {
signedOrder, zeroFillAmount, takerAddress,
)).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
it('should throw when the signature is invalid', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
// 27 <--> 28
signedOrder.ecSignature.v = 27 + (28 - signedOrder.ecSignature.v);
return expect(zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
signedOrder, fillableAmount, takerAddress,
)).to.be.rejectedWith(ZeroExError.InvalidSignature);
});
it('should throw when the order is fully filled or cancelled', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
@@ -157,154 +208,88 @@ describe('OrderValidation', () => {
.to.be.rejectedWith(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
});
});
describe('#validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync', () => {
describe('should throw when not enough balance or allowance to fulfill the order', () => {
const balanceToSubtractFromMaker = new BigNumber(3);
const balanceToSubtractFromTaker = new BigNumber(3);
const lackingAllowance = new BigNumber(3);
let signedOrder: SignedOrder;
beforeEach('create fillable signed order', async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
});
it('should throw when taker balance is less than fill amount', async () => {
await zeroEx.token.transferAsync(
takerTokenAddress, takerAddress, coinbase, balanceToSubtractFromTaker,
);
return expect((orderValidationUtils as any).validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerBalance);
});
it('should throw when taker allowance is less than fill amount', async () => {
const newAllowanceWhichIsLessThanFillAmount = fillTakerAmount.minus(lackingAllowance);
await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress,
newAllowanceWhichIsLessThanFillAmount);
return expect((orderValidationUtils as any).validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerAllowance);
});
it('should throw when maker balance is less than maker fill amount', async () => {
await zeroEx.token.transferAsync(
makerTokenAddress, makerAddress, coinbase, balanceToSubtractFromMaker,
);
return expect((orderValidationUtils as any).validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance);
});
it('should throw when maker allowance is less than maker fill amount', async () => {
const newAllowanceWhichIsLessThanFillAmount = fillTakerAmount.minus(lackingAllowance);
await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress,
newAllowanceWhichIsLessThanFillAmount);
return expect((orderValidationUtils as any).validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerAllowance);
});
describe('#validateFillOrderBalancesAllowancesThrowIfInvalidAsync', () => {
let exchangeTransferSimulator: ExchangeTransferSimulator;
let transferFromAsync: any;
const bigNumberMatch = (expected: BigNumber.BigNumber) => {
return Sinon.match((value: BigNumber.BigNumber) => value.eq(expected));
};
beforeEach('create exchangeTransferSimulator', async () => {
exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token);
transferFromAsync = Sinon.spy();
exchangeTransferSimulator.transferFromAsync = transferFromAsync;
});
describe('should throw when not enough balance or allowance to pay fees', () => {
it('should call exchangeTransferSimulator.transferFrom in a correct order', async () => {
const makerFee = new BigNumber(2);
const takerFee = new BigNumber(2);
let signedOrder: SignedOrder;
beforeEach('setup', async () => {
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
makerTokenAddress, takerTokenAddress, makerFee, takerFee,
makerAddress, takerAddress, fillableAmount, feeRecipient,
);
});
it('should throw when maker doesn\'t have enough balance to pay fees', async () => {
const balanceToSubtractFromMaker = new BigNumber(1);
await zeroEx.token.transferAsync(
zrxTokenAddress, makerAddress, coinbase, balanceToSubtractFromMaker,
);
return expect((orderValidationUtils as any).validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerAmount, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerFeeBalance);
});
it('should throw when maker doesn\'t have enough allowance to pay fees', async () => {
const newAllowanceWhichIsLessThanFees = makerFee.minus(1);
await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress,
newAllowanceWhichIsLessThanFees);
return expect((orderValidationUtils as any).validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerAmount, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerFeeAllowance);
});
it('should throw when taker doesn\'t have enough balance to pay fees', async () => {
const balanceToSubtractFromTaker = new BigNumber(1);
await zeroEx.token.transferAsync(
zrxTokenAddress, takerAddress, coinbase, balanceToSubtractFromTaker,
);
return expect((orderValidationUtils as any).validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerFeeBalance);
});
it('should throw when taker doesn\'t have enough allowance to pay fees', async () => {
const newAllowanceWhichIsLessThanFees = makerFee.minus(1);
await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, takerAddress,
newAllowanceWhichIsLessThanFees);
return expect((orderValidationUtils as any).validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerFeeAllowance);
});
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
makerTokenAddress, takerTokenAddress, makerFee, takerFee,
makerAddress, takerAddress, fillableAmount, feeRecipient,
);
await orderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTransferSimulator, signedOrder, fillableAmount, takerAddress, zrxTokenAddress,
);
expect(transferFromAsync.callCount).to.be.equal(4);
expect(
transferFromAsync.getCall(0).calledWith(
makerTokenAddress, makerAddress, takerAddress, bigNumberMatch(fillableAmount),
TradeSide.Maker, TransferType.Trade,
),
).to.be.true();
expect(
transferFromAsync.getCall(1).calledWith(
takerTokenAddress, takerAddress, makerAddress, bigNumberMatch(fillableAmount),
TradeSide.Taker, TransferType.Trade,
),
).to.be.true();
expect(
transferFromAsync.getCall(2).calledWith(
zrxTokenAddress, makerAddress, feeRecipient, bigNumberMatch(makerFee),
TradeSide.Maker, TransferType.Fee,
),
).to.be.true();
expect(
transferFromAsync.getCall(3).calledWith(
zrxTokenAddress, takerAddress, feeRecipient, bigNumberMatch(takerFee),
TradeSide.Taker, TransferType.Fee,
),
).to.be.true();
});
describe('should throw on insufficient balance or allowance when makerToken is ZRX',
() => {
it('should call exchangeTransferSimulator.transferFrom with correct values for an open order', async () => {
const makerFee = new BigNumber(2);
const takerFee = new BigNumber(2);
let signedOrder: SignedOrder;
beforeEach(async () => {
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
zrxTokenAddress, takerTokenAddress, makerFee, takerFee,
makerAddress, takerAddress, fillableAmount, feeRecipient,
);
});
it('should throw on insufficient balance when makerToken is ZRX', async () => {
const balanceToSubtractFromMaker = new BigNumber(1);
await zeroEx.token.transferAsync(
zrxTokenAddress, makerAddress, coinbase, balanceToSubtractFromMaker,
);
return expect((orderValidationUtils as any).validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerAmount, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance);
});
it('should throw on insufficient allowance when makerToken is ZRX', async () => {
const oldAllowance = await zeroEx.token.getProxyAllowanceAsync(zrxTokenAddress, makerAddress);
const newAllowanceWhichIsInsufficient = oldAllowance.minus(1);
await zeroEx.token.setProxyAllowanceAsync(
zrxTokenAddress, makerAddress, newAllowanceWhichIsInsufficient);
return expect((orderValidationUtils as any).validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerAmount, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerAllowance);
});
});
describe('should throw on insufficient balance or allowance when takerToken is ZRX',
() => {
const makerFee = new BigNumber(2);
const takerFee = new BigNumber(2);
let signedOrder: SignedOrder;
beforeEach(async () => {
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
makerTokenAddress, zrxTokenAddress, makerFee, takerFee,
makerAddress, takerAddress, fillableAmount, feeRecipient,
);
});
it('should throw on insufficient balance when takerToken is ZRX', async () => {
const balanceToSubtractFromTaker = new BigNumber(1);
await zeroEx.token.transferAsync(
zrxTokenAddress, takerAddress, coinbase, balanceToSubtractFromTaker,
);
return expect((orderValidationUtils as any).validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerBalance);
});
it('should throw on insufficient allowance when takerToken is ZRX', async () => {
const oldAllowance = await zeroEx.token.getProxyAllowanceAsync(zrxTokenAddress, takerAddress);
const newAllowanceWhichIsInsufficient = oldAllowance.minus(1);
await zeroEx.token.setProxyAllowanceAsync(
zrxTokenAddress, takerAddress, newAllowanceWhichIsInsufficient);
return expect((orderValidationUtils as any).validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync(
signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerAllowance);
});
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
makerTokenAddress, takerTokenAddress, makerFee, takerFee,
makerAddress, ZeroEx.NULL_ADDRESS, fillableAmount, feeRecipient,
);
await orderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTransferSimulator, signedOrder, fillableAmount, takerAddress, zrxTokenAddress,
);
expect(transferFromAsync.callCount).to.be.equal(4);
expect(
transferFromAsync.getCall(0).calledWith(
makerTokenAddress, makerAddress, takerAddress, bigNumberMatch(fillableAmount),
TradeSide.Maker, TransferType.Trade,
),
).to.be.true();
expect(
transferFromAsync.getCall(1).calledWith(
takerTokenAddress, takerAddress, makerAddress, bigNumberMatch(fillableAmount),
TradeSide.Taker, TransferType.Trade,
),
).to.be.true();
expect(
transferFromAsync.getCall(2).calledWith(
zrxTokenAddress, makerAddress, feeRecipient, bigNumberMatch(makerFee),
TradeSide.Maker, TransferType.Fee,
),
).to.be.true();
expect(
transferFromAsync.getCall(3).calledWith(
zrxTokenAddress, takerAddress, feeRecipient, bigNumberMatch(takerFee),
TradeSide.Taker, TransferType.Fee,
),
).to.be.true();
});
});
});

View File

@@ -1,4 +1,4 @@
import * as _ from 'lodash';
import {_} from '../src/utils/lodash';
import 'mocha';
import * as chai from 'chai';
import {SchemaValidator, schemas} from '0x-json-schemas';

View File

@@ -14,10 +14,13 @@ import {
ContractEvent,
TransferContractEventArgs,
ApprovalContractEventArgs,
TokenContractEventArgs,
LogWithDecodedArgs,
LogEvent,
} from '../src';
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
import {TokenUtils} from './utils/token_utils';
import {DoneCallback} from '../src/types';
import {DoneCallback, BlockParamLiteral} from '../src/types';
chaiSetup.configure();
const expect = chai.expect;
@@ -58,7 +61,8 @@ describe('TokenWrapper', () => {
const toAddress = addressWithoutFunds;
const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
expect(preBalance).to.be.bignumber.equal(0);
await zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount);
const txHash = await zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
return expect(postBalance).to.be.bignumber.equal(transferAmount);
});
@@ -334,104 +338,139 @@ describe('TokenWrapper', () => {
return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
});
});
describe('#subscribeAsync', () => {
describe('#subscribe', () => {
const indexFilterValues = {};
const shouldThrowOnInsufficientBalanceOrAllowance = true;
let tokenAddress: string;
const subscriptionOpts: SubscriptionOpts = {
fromBlock: 0,
toBlock: 'latest',
};
const transferAmount = new BigNumber(42);
const allowanceAmount = new BigNumber(42);
before(() => {
const token = tokens[0];
tokenAddress = token.address;
});
afterEach(async () => {
await zeroEx.token.stopWatchingAllEventsAsync();
afterEach(() => {
zeroEx.token.unsubscribeAll();
});
// Hack: Mocha does not allow a test to be both async and have a `done` callback
// Since we need to await the receipt of the event in the `subscribeAsync` callback,
// Since we need to await the receipt of the event in the `subscribe` callback,
// we do need both. A hack is to make the top-level a sync fn w/ a done callback and then
// wrap the rest of the test in an async block
// Source: https://github.com/mochajs/mocha/issues/2407
it('Should receive the Transfer event when an order is filled', (done: DoneCallback) => {
it('Should receive the Transfer event when tokens are transfered', (done: DoneCallback) => {
(async () => {
const zeroExEvent = await zeroEx.token.subscribeAsync(
tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues);
zeroExEvent.watch((err: Error, event: ContractEvent) => {
expect(err).to.be.null();
expect(event).to.not.be.undefined();
const args = event.args as TransferContractEventArgs;
const callback = (logEvent: LogEvent<TransferContractEventArgs>) => {
expect(logEvent).to.not.be.undefined();
const args = logEvent.args;
expect(args._from).to.be.equal(coinbase);
expect(args._to).to.be.equal(addressWithoutFunds);
expect(args._value).to.be.bignumber.equal(transferAmount);
done();
});
};
zeroEx.token.subscribe(
tokenAddress, TokenEvents.Transfer, indexFilterValues, callback);
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
})().catch(done);
});
it('Should receive the Approval event when an order is cancelled', (done: DoneCallback) => {
it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => {
(async () => {
const zeroExEvent = await zeroEx.token.subscribeAsync(
tokenAddress, TokenEvents.Approval, subscriptionOpts, indexFilterValues);
zeroExEvent.watch((err: Error, event: ContractEvent) => {
expect(err).to.be.null();
expect(event).to.not.be.undefined();
const args = event.args as ApprovalContractEventArgs;
const callback = (logEvent: LogEvent<ApprovalContractEventArgs>) => {
expect(logEvent).to.not.be.undefined();
const args = logEvent.args;
expect(args._owner).to.be.equal(coinbase);
expect(args._spender).to.be.equal(addressWithoutFunds);
expect(args._value).to.be.bignumber.equal(allowanceAmount);
done();
});
};
zeroEx.token.subscribe(
tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount);
})().catch(done);
});
it('Outstanding subscriptions are cancelled when zeroEx.setProviderAsync called', (done: DoneCallback) => {
(async () => {
const eventSubscriptionToBeCancelled = await zeroEx.token.subscribeAsync(
tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues);
eventSubscriptionToBeCancelled.watch((err: Error, event: ContractEvent) => {
const callbackNeverToBeCalled = (logEvent: LogEvent<TransferContractEventArgs>) => {
done(new Error('Expected this subscription to have been cancelled'));
});
};
zeroEx.token.subscribe(
tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled,
);
const callbackToBeCalled = (logEvent: LogEvent<TransferContractEventArgs>) => {
done();
};
const newProvider = web3Factory.getRpcProvider();
await zeroEx.setProviderAsync(newProvider);
const eventSubscriptionToStay = await zeroEx.token.subscribeAsync(
tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues);
eventSubscriptionToStay.watch((err: Error, event: ContractEvent) => {
expect(err).to.be.null();
expect(event).to.not.be.undefined();
done();
});
zeroEx.token.subscribe(
tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackToBeCalled,
);
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
})().catch(done);
});
it('Should stop watch for events when stopWatchingAsync called on the eventEmitter', (done: DoneCallback) => {
it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => {
(async () => {
const eventSubscriptionToBeStopped = await zeroEx.token.subscribeAsync(
tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues);
eventSubscriptionToBeStopped.watch((err: Error, event: ContractEvent) => {
done(new Error('Expected this subscription to have been stopped'));
});
await eventSubscriptionToBeStopped.stopWatchingAsync();
const callbackNeverToBeCalled = (logEvent: LogEvent<TokenContractEventArgs>) => {
done(new Error('Expected this subscription to have been cancelled'));
};
const subscriptionToken = zeroEx.token.subscribe(
tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled);
zeroEx.token.unsubscribe(subscriptionToken);
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
done();
})().catch(done);
});
it('Should wrap all event args BigNumber instances in a newer version of BigNumber', (done: DoneCallback) => {
(async () => {
const zeroExEvent = await zeroEx.token.subscribeAsync(
tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues);
zeroExEvent.watch((err: Error, event: ContractEvent) => {
const args = event.args as TransferContractEventArgs;
expect(args._value.isBigNumber).to.be.true();
done();
});
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
})().catch(done);
});
describe('#getLogsAsync', () => {
let tokenAddress: string;
let tokenTransferProxyAddress: string;
const subscriptionOpts: SubscriptionOpts = {
fromBlock: BlockParamLiteral.Earliest,
toBlock: BlockParamLiteral.Latest,
};
let txHash: string;
before(async () => {
const token = tokens[0];
tokenAddress = token.address;
tokenTransferProxyAddress = await zeroEx.proxy.getContractAddressAsync();
});
it('should get logs with decoded args emitted by Approval', async () => {
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
await zeroEx.awaitTransactionMinedAsync(txHash);
const eventName = TokenEvents.Approval;
const indexFilterValues = {};
const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>(
tokenAddress, eventName, subscriptionOpts, indexFilterValues,
);
expect(logs).to.have.length(1);
const args = logs[0].args;
expect(logs[0].event).to.be.equal(eventName);
expect(args._owner).to.be.equal(coinbase);
expect(args._spender).to.be.equal(tokenTransferProxyAddress);
expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
});
it('should only get the logs with the correct event name', async () => {
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
await zeroEx.awaitTransactionMinedAsync(txHash);
const differentEventName = TokenEvents.Transfer;
const indexFilterValues = {};
const logs = await zeroEx.token.getLogsAsync(
tokenAddress, differentEventName, subscriptionOpts, indexFilterValues,
);
expect(logs).to.have.length(0);
});
it('should only get the logs with the correct indexed fields', async () => {
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
await zeroEx.awaitTransactionMinedAsync(txHash);
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, addressWithoutFunds);
await zeroEx.awaitTransactionMinedAsync(txHash);
const eventName = TokenEvents.Approval;
const indexFilterValues = {
_owner: coinbase,
};
const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>(
tokenAddress, eventName, subscriptionOpts, indexFilterValues,
);
expect(logs).to.have.length(1);
const args = logs[0].args;
expect(args._owner).to.be.equal(coinbase);
});
});
});

View File

@@ -2,19 +2,22 @@ import {RPC} from './rpc';
export class BlockchainLifecycle {
private rpc: RPC;
private snapshotId: number;
private snapshotIdsStack: number[];
constructor() {
this.rpc = new RPC();
this.snapshotIdsStack = [];
}
// TODO: In order to run these tests on an actual node, we should check if we are running against
// TestRPC, if so, use snapshots, otherwise re-deploy contracts before every test
public async startAsync(): Promise<void> {
this.snapshotId = await this.rpc.takeSnapshotAsync();
const snapshotId = await this.rpc.takeSnapshotAsync();
this.snapshotIdsStack.push(snapshotId);
}
public async revertAsync(): Promise<void> {
const didRevert = await this.rpc.revertSnapshotAsync(this.snapshotId);
const snapshotId = this.snapshotIdsStack.pop() as number;
const didRevert = await this.rpc.revertSnapshotAsync(snapshotId);
if (!didRevert) {
throw new Error(`Snapshot with id #${this.snapshotId} failed to revert`);
throw new Error(`Snapshot with id #${snapshotId} failed to revert`);
}
}
}

View File

@@ -3,5 +3,6 @@ export const constants = {
RPC_HOST: 'localhost',
RPC_PORT: 8545,
TESTRPC_NETWORK_ID: 50,
KOVAN_RPC_URL: 'https://kovan.0xproject.com',
KOVAN_RPC_URL: 'https://kovan.infura.io',
ROPSTEN_RPC_URL: 'https://ropsten.infura.io',
};

View File

@@ -62,7 +62,9 @@ export class FillScenarios {
fillableAmount, fillableAmount,
);
const shouldThrowOnInsufficientBalanceOrAllowance = false;
await this.zeroEx.exchange.fillOrderAsync(signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
await this.zeroEx.exchange.fillOrderAsync(
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
);
return signedOrder;
}
private async createAsymmetricFillableSignedOrderWithFeesAsync(
@@ -89,7 +91,7 @@ export class FillScenarios {
}
private async increaseBalanceAndAllowanceAsync(
tokenAddress: string, address: string, amount: BigNumber.BigNumber): Promise<void> {
if (amount.isZero()) {
if (amount.isZero() || address === ZeroEx.NULL_ADDRESS) {
return; // noop
}
await Promise.all([

View File

@@ -1,4 +1,4 @@
import * as _ from 'lodash';
import {_} from '../../src/utils/lodash';
import * as BigNumber from 'bignumber.js';
import {ZeroEx, SignedOrder} from '../../src';

View File

@@ -1,5 +1,5 @@
import * as _ from 'lodash';
import {Token, ZeroExError} from '../../src';
import {_} from '../../src/utils/lodash';
import {Token, InternalZeroExError} from '../../src/types';
const PROTOCOL_TOKEN_SYMBOL = 'ZRX';
@@ -11,7 +11,7 @@ export class TokenUtils {
public getProtocolTokenOrThrow(): Token {
const zrxToken = _.find(this.tokens, {symbol: PROTOCOL_TOKEN_SYMBOL});
if (_.isUndefined(zrxToken)) {
throw new Error(ZeroExError.ZrxNotInTokenRegistry);
throw new Error(InternalZeroExError.ZrxNotInTokenRegistry);
}
return zrxToken;
}

View File

@@ -6,7 +6,6 @@
import ProviderEngine = require('web3-provider-engine');
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
import * as Web3 from 'web3';
import * as Web3_beta from 'web3_beta';
import {constants} from './constants';
import {EmptyWalletSubProvider} from '../../src/subproviders/empty_wallet_subprovider';
@@ -29,9 +28,4 @@ export const web3Factory = {
provider.start();
return provider;
},
getProviderBeta(): Web3.Provider {
const rpcUrl = `http://${constants.RPC_HOST}:${constants.RPC_PORT}`;
const providerBeta = new Web3_beta.providers.HttpProvider(rpcUrl);
return providerBeta;
},
};

View File

@@ -1,16 +0,0 @@
import * as chai from 'chai';
import {chaiSetup} from './utils/chai_setup';
import 'mocha';
import {ZeroEx, Order, SubscriptionOpts, TokenEvents, ContractEvent} from '../src';
import {web3Factory} from './utils/web3_factory';
chaiSetup.configure();
const expect = chai.expect;
describe('ZeroEx with beta web3', () => {
const web3_beta_provider = web3Factory.getProviderBeta();
const zeroEx = new ZeroEx(web3_beta_provider);
it('is able to make a call using a beta provider', async () => {
await zeroEx.tokenRegistry.getTokenAddressesAsync();
});
});

View File

@@ -5,7 +5,7 @@
"lib": [ "es2015", "dom" ],
"outDir": "lib",
"sourceMap": true,
"declaration": true,
"allowJs": true,
"noImplicitAny": true,
"experimentalDecorators": true,
"strictNullChecks": true
@@ -13,6 +13,7 @@
"include": [
"./src/**/*",
"./test/**/*",
"./node_modules/lodash-es/**/*",
"./node_modules/types-bn/index.d.ts",
"./node_modules/types-ethereumjs-util/index.d.ts",
"./node_modules/web3-typescript-typings/index.d.ts",

View File

@@ -1,7 +1,6 @@
/**
* This is to generate the umd bundle only
*/
const _ = require('lodash');
const webpack = require('webpack');
const path = require('path');
const production = process.env.NODE_ENV === 'production';
@@ -10,7 +9,7 @@ let entry = {
'index': './src/index.ts',
};
if (production) {
entry = _.assign({}, entry, {'index.min': './src/index.ts'});
entry = Object.assign({}, entry, {'index.min': './src/index.ts'});
}
module.exports = {

1200
yarn.lock

File diff suppressed because it is too large Load Diff