Compare commits
546 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
62452db5d8 | ||
|
792646888a | ||
|
cac36e781e | ||
|
02f736ac06 | ||
|
05296b7f48 | ||
|
edeed527c2 | ||
|
4c40b60a2d | ||
|
134b9dfb23 | ||
|
18a5f7485c | ||
|
0ff6cc1997 | ||
|
4620c1c818 | ||
|
4370e19880 | ||
|
5f44b5f711 | ||
|
c83e1d57fc | ||
|
60ad5024ce | ||
|
5149fcdd74 | ||
|
130a7967aa | ||
|
6e34cf2c3b | ||
|
a98bb1f7ac | ||
|
da2661cf33 | ||
|
7d82d14a7d | ||
|
e5bb3bc75e | ||
|
3f99281309 | ||
|
28b4ff42ea | ||
|
372fc39a6b | ||
|
c6b99fcca0 | ||
|
b4a95428c1 | ||
|
07a872f802 | ||
|
9516a50f64 | ||
|
bfac021085 | ||
|
a19b40b051 | ||
|
836cea6fd7 | ||
|
66dd659a2f | ||
|
c765f115ae | ||
|
e372b0c61f | ||
|
0f646da970 | ||
|
8eb8037f9b | ||
|
5b60d9f0f4 | ||
|
dcfc0ecac6 | ||
|
a6a1601799 | ||
|
a8fd3f30cf | ||
|
05ce9733da | ||
|
96da2c26dc | ||
|
0afc95982b | ||
|
b132860f1f | ||
|
f9a8392d2c | ||
|
1e590ce4ba | ||
|
bd67cf0f9f | ||
|
5a3a8ae7d5 | ||
|
a2d579b201 | ||
|
79dcc1660e | ||
|
6f227c152d | ||
|
af242278c3 | ||
|
9b66b168ed | ||
|
9e76b2dd98 | ||
|
6d6688ddda | ||
|
2b1dc7c266 | ||
|
3e1c436fa6 | ||
|
02ae853718 | ||
|
b9f9581db9 | ||
|
31ce891beb | ||
|
6f29239211 | ||
|
ea7c6be128 | ||
|
b4970d5bb9 | ||
|
73b2f59caa | ||
|
d92340903f | ||
|
44ec05de1f | ||
|
2356c0e30e | ||
|
4e517b32f8 | ||
|
c21a0512af | ||
|
ee4182c149 | ||
|
2d714d33c6 | ||
|
92cbb48e15 | ||
|
f3fe3fb0c5 | ||
|
dd75f8bf96 | ||
|
dcc4b894e0 | ||
|
640825d18d | ||
|
933242bd9d | ||
|
5dce4c3b3b | ||
|
0ba0d2f8d1 | ||
|
846eec6269 | ||
|
a41ea299e5 | ||
|
e4f5b9cdb3 | ||
|
0d7b75801a | ||
|
82ef8b19f0 | ||
|
e216f2ff6d | ||
|
8d6045c1d5 | ||
|
dc3756bc99 | ||
|
32b2141794 | ||
|
1e7a87e2a3 | ||
|
c6e35fbc9d | ||
|
c7ce966516 | ||
|
dd9561797f | ||
|
5dc32c32a6 | ||
|
202d619435 | ||
|
37676d338e | ||
|
ae1cd90b9a | ||
|
8695f64322 | ||
|
f58c203653 | ||
|
e0a92419e4 | ||
|
18f2a93950 | ||
|
98be786764 | ||
|
c12e48d28a | ||
|
2a7da4fc4f | ||
|
bd7102efbe | ||
|
f7010dfa3a | ||
|
8ba3d608c3 | ||
|
fedded3ec1 | ||
|
e4393fdd09 | ||
|
e3f7b18deb | ||
|
9dca3b76b5 | ||
|
d837e27739 | ||
|
894ade168d | ||
|
df274591f7 | ||
|
b809839ed4 | ||
|
51f2c46ed0 | ||
|
b2b5abadb2 | ||
|
0bc9083bff | ||
|
4207400f8a | ||
|
40f4ccd888 | ||
|
c10a2d4fe4 | ||
|
45aa9ef542 | ||
|
f77cc87271 | ||
|
40ce3a9c06 | ||
|
52ac8c2251 | ||
|
e376189bc6 | ||
|
2a8c42688e | ||
|
b2fc0abdfb | ||
|
ac03cbbd48 | ||
|
87e0fe43f7 | ||
|
b2f5fc218d | ||
|
984f9e1201 | ||
|
be354d703a | ||
|
1ff24f0338 | ||
|
798620d0ef | ||
|
13d7037e8a | ||
|
01eed19abd | ||
|
1311dfe7f5 | ||
|
dd6997423f | ||
|
69668d07c1 | ||
|
f2ff14f500 | ||
|
c394073653 | ||
|
b95d93a43b | ||
|
b5bfcc772a | ||
|
20045a438a | ||
|
2e668a771a | ||
|
a32b94bac4 | ||
|
6ec3c8728e | ||
|
9099ef4ee5 | ||
|
c4dfda9485 | ||
|
9979e6796b | ||
|
6f93583d41 | ||
|
a7924cef04 | ||
|
23e3546073 | ||
|
52e88a63b9 | ||
|
a44375a6fc | ||
|
fa65bcc115 | ||
|
1c553cb6b7 | ||
|
36d5510d07 | ||
|
617ca799c5 | ||
|
42a5de1ec1 | ||
|
d175009d55 | ||
|
7f58a778d8 | ||
|
b3601fc7bc | ||
|
ac92ebc54f | ||
|
ea2317be11 | ||
|
a89e9461b6 | ||
|
fffc807823 | ||
|
da97be645b | ||
|
028a9a4880 | ||
|
89aa19f6e4 | ||
|
660aa224ca | ||
|
c6e0acdb04 | ||
|
1690aae1cf | ||
|
4d27b89fe3 | ||
|
31d068b83f | ||
|
73e8f890b5 | ||
|
c11ba988c7 | ||
|
4dda6c0949 | ||
|
defd09459d | ||
|
58d2b799d6 | ||
|
d9a8b96154 | ||
|
c7d89d98f3 | ||
|
bdbd01f965 | ||
|
97e680aba1 | ||
|
64f4a276ff | ||
|
5d31d43cf8 | ||
|
f7fac34e03 | ||
|
569cff01f3 | ||
|
d741fe5f1b | ||
|
106e096304 | ||
|
745bdb6c3f | ||
|
5fe128ccf6 | ||
|
41f0be48f1 | ||
|
b376f03102 | ||
|
468c95a4c2 | ||
|
6033c1a5d5 | ||
|
38fbf028a6 | ||
|
ef88c71b27 | ||
|
89236fff41 | ||
|
6c62c92f0c | ||
|
c05737e7a6 | ||
|
fbf89aea1c | ||
|
98e8a6dd70 | ||
|
4efba2a4bc | ||
|
d8fb58379e | ||
|
8052625e76 | ||
|
2787bdc46b | ||
|
ba46a2558d | ||
|
9bb14a1d69 | ||
|
aa344d268c | ||
|
2ab2ad5902 | ||
|
5506f7a240 | ||
|
c832cc35cc | ||
|
232bf22af6 | ||
|
2a15fe0ffe | ||
|
56bedf724f | ||
|
054843d599 | ||
|
e54e9dfa2c | ||
|
987d7a7d8a | ||
|
183ea1b33d | ||
|
994b51590f | ||
|
4fb2b5cf2b | ||
|
e7bb280d67 | ||
|
3d974854bc | ||
|
48314941bd | ||
|
23ca4047f3 | ||
|
a8c9945a0f | ||
|
be1f7db2df | ||
|
722c24d127 | ||
|
720b2a2506 | ||
|
79762a2e38 | ||
|
89167c8297 | ||
|
dffc047f56 | ||
|
7cbd408c24 | ||
|
b774a9f91c | ||
|
5a8297649f | ||
|
540ac54dee | ||
|
caaad46991 | ||
|
4ecfac6cb4 | ||
|
00c1198b34 | ||
|
15f68f9b54 | ||
|
dce13796ac | ||
|
197e38adae | ||
|
c21c322056 | ||
|
15fe8afd1e | ||
|
dc70e3522a | ||
|
2b7b9c8f8b | ||
|
8180225d1f | ||
|
b98f3fc094 | ||
|
18ab31814f | ||
|
643a7e6b3a | ||
|
baa7668cda | ||
|
48a79a8488 | ||
|
c975094df0 | ||
|
6e598792cd | ||
|
b0eb6efaae | ||
|
9bfe0c2090 | ||
|
c522cd5020 | ||
|
1441d1098f | ||
|
1f9710590a | ||
|
cf6efc6596 | ||
|
8d53c78661 | ||
|
d21ef4db82 | ||
|
1aaa0020d0 | ||
|
53b38105dd | ||
|
6f9a14929b | ||
|
f92b0e918e | ||
|
e2696c259b | ||
|
406763ff61 | ||
|
f3e66110be | ||
|
bc300a3797 | ||
|
049a3be102 | ||
|
b21a02424c | ||
|
1e54dec280 | ||
|
1d8ca6c4f3 | ||
|
db888912d0 | ||
|
305b98ee21 | ||
|
39daf6f963 | ||
|
6b80134f48 | ||
|
d5b4032b25 | ||
|
68120ad1da | ||
|
3eb21a76c1 | ||
|
73a48ddb0d | ||
|
bdfbfb829b | ||
|
0876fc6cab | ||
|
6593ddf35c | ||
|
23f32b6bba | ||
|
f7515b489d | ||
|
33f713e0cc | ||
|
712a1ba36e | ||
|
e9509b4ff3 | ||
|
014a458525 | ||
|
b46d7f9cca | ||
|
96596b1c2b | ||
|
c0a90fe84e | ||
|
198cd364db | ||
|
03a6e1dac5 | ||
|
90ddfe58a9 | ||
|
0f3f557e0b | ||
|
21cb428b38 | ||
|
de76ed478f | ||
|
2ad6a64664 | ||
|
092e0c4273 | ||
|
e19c222f30 | ||
|
8b88ad835c | ||
|
afc12ac497 | ||
|
18050b7ea3 | ||
|
9f89dbc8e4 | ||
|
b1667cdd89 | ||
|
87f2658fc9 | ||
|
9a9fd7d926 | ||
|
f2611d5b2b | ||
|
370b82ee18 | ||
|
74b2308488 | ||
|
371acc0ba1 | ||
|
3302d18f6e | ||
|
d6595f171a | ||
|
61a6040e74 | ||
|
c81855e119 | ||
|
db53c5a41f | ||
|
9c4f3b2cba | ||
|
4cb544f4f5 | ||
|
6f73589d40 | ||
|
ea6583d47e | ||
|
464c16be73 | ||
|
4d522db474 | ||
|
33494c2206 | ||
|
ea2dbaabeb | ||
|
8f8b678e7c | ||
|
5f62aed80a | ||
|
82c1d7ca7c | ||
|
e570ad28dd | ||
|
a45f6ff4af | ||
|
74e991db94 | ||
|
33216ea53f | ||
|
4ebf046cea | ||
|
43bebf6d61 | ||
|
40d1d30b58 | ||
|
196194c04a | ||
|
c5dca95f89 | ||
|
b1c7291d3c | ||
|
5611df82f9 | ||
|
f39a2134b9 | ||
|
551b6db35e | ||
|
64cf47f297 | ||
|
7d7bef2b1a | ||
|
7b24809698 | ||
|
ca92502d34 | ||
|
12b71b20f0 | ||
|
90f99e48e1 | ||
|
4fae4b4493 | ||
|
6d234c09af | ||
|
2892f45ab7 | ||
|
e6fcf9cdbf | ||
|
997964f3e2 | ||
|
4633637efe | ||
|
5a8eb77ff0 | ||
|
99051bdfdf | ||
|
20f630d1ae | ||
|
1275f243a3 | ||
|
dacf19ecae | ||
|
c9edeae6d8 | ||
|
3a4a3b9aa1 | ||
|
86c742cb11 | ||
|
92c6144b6a | ||
|
d4cef89587 | ||
|
8151523d33 | ||
|
8204409c6d | ||
|
4bcad26024 | ||
|
15bd31e629 | ||
|
287e9c7c85 | ||
|
f09a0fca75 | ||
|
649d55f0a2 | ||
|
b7705f0871 | ||
|
d506a1f985 | ||
|
8e6fbc214b | ||
|
5cb1e5853e | ||
|
86e38f45fc | ||
|
0e54418dbb | ||
|
6e0edb8d8e | ||
|
c0d4b30828 | ||
|
74ef0458f5 | ||
|
c5a93b0945 | ||
|
24e2ee831d | ||
|
cc9f0f17af | ||
|
8f90ce2cf7 | ||
|
7cedeb0be9 | ||
|
43848f45ec | ||
|
fc3b0ce553 | ||
|
a85c2b61ce | ||
|
0c07490ae5 | ||
|
6aa37ad3e4 | ||
|
1321ea5beb | ||
|
7ca113b59b | ||
|
fd242a1e06 | ||
|
83eeeb85b2 | ||
|
fa3963199e | ||
|
e1e79519b9 | ||
|
b1573fc6a7 | ||
|
cbe17aa44b | ||
|
d009b9b525 | ||
|
889410d605 | ||
|
82cceb8605 | ||
|
83bc740911 | ||
|
bc92968c5e | ||
|
d0830d55ad | ||
|
9395b53af4 | ||
|
f6ec388916 | ||
|
3f7bdfa67a | ||
|
260def1aef | ||
|
330a1cff11 | ||
|
91e2857645 | ||
|
5e5acdc56e | ||
|
b896a723c2 | ||
|
3acdeaf518 | ||
|
e9db532727 | ||
|
dd590ca79e | ||
|
6a8c54e8de | ||
|
70c7914c4c | ||
|
a4ce7ed21e | ||
|
6bf594bb4b | ||
|
75711f348a | ||
|
56cfb41d54 | ||
|
3d55f97c0a | ||
|
de0e436aad | ||
|
2c5f221073 | ||
|
df751a2af5 | ||
|
79b79a5795 | ||
|
540faac79c | ||
|
dfff718972 | ||
|
40636f1609 | ||
|
6c48e6c74f | ||
|
718b801f48 | ||
|
73eb78d793 | ||
|
f15451ebfd | ||
|
41098c6a35 | ||
|
e780664b40 | ||
|
efb5556e4e | ||
|
0f413febd3 | ||
|
60b3f3e6dd | ||
|
7d001240c1 | ||
|
a2c495a983 | ||
|
d5ed5865ca | ||
|
75ae305a6e | ||
|
0c5ba2a68e | ||
|
d21b716cb3 | ||
|
845aee35d3 | ||
|
9a6a9fc28b | ||
|
3f962585e9 | ||
|
13dfc263b6 | ||
|
4eb050967f | ||
|
cb3127c568 | ||
|
dcd08e40e6 | ||
|
438b821835 | ||
|
c52d6753ce | ||
|
e5785532ed | ||
|
49e43c9876 | ||
|
0cd9875369 | ||
|
1375126e4c | ||
|
94cab6f694 | ||
|
bd9fa3d335 | ||
|
9c2a332b69 | ||
|
b28b0fad7a | ||
|
0dbad86d23 | ||
|
25a9ba90f5 | ||
|
5001e5fc70 | ||
|
d088dcdd36 | ||
|
7b471858fa | ||
|
e7152ed58c | ||
|
c5139bca26 | ||
|
42c61ecda5 | ||
|
656d74518d | ||
|
8d2b67bce2 | ||
|
de29c23ad7 | ||
|
41e62d078f | ||
|
d49057dbfb | ||
|
8af7d2303e | ||
|
ef96c58b7f | ||
|
a1c363a8af | ||
|
40a7be0690 | ||
|
060ee5e9d3 | ||
|
096d3bdb26 | ||
|
6b044a966a | ||
|
9fe042733d | ||
|
6aaa7a0856 | ||
|
8135f98029 | ||
|
6386c43e01 | ||
|
f86d177cd4 | ||
|
20f59d556d | ||
|
1ad54ba742 | ||
|
dc7c2082df | ||
|
06dc1adb4a | ||
|
1b01251e4c | ||
|
7f32cdf29d | ||
|
999cf1be7c | ||
|
e8574a3948 | ||
|
a8f706c79f | ||
|
36fe4c01aa | ||
|
ea8cff4ce0 | ||
|
a177760593 | ||
|
ed1e2e5347 | ||
|
26f20fa3e3 | ||
|
fe63a81f6a | ||
|
f2d08ce3d9 | ||
|
5932ebb52a | ||
|
38170a24ed | ||
|
04698313a3 | ||
|
209ca30460 | ||
|
d789aa24aa | ||
|
76d6e6a748 | ||
|
424912040a | ||
|
1ff8e850a5 | ||
|
e01a19a591 | ||
|
8c585e733c | ||
|
56bfbbbecc | ||
|
501cec1fac | ||
|
022d6699c2 | ||
|
9bdca7a5bd | ||
|
fcc3616bbd | ||
|
b966e9a691 | ||
|
29be4a60af | ||
|
5deeca4b68 | ||
|
6790ca136e | ||
|
225ec50645 | ||
|
ba289c2843 | ||
|
aea8c7f7df | ||
|
b24afe2653 | ||
|
53fcdf13be | ||
|
2a56ab0846 | ||
|
ac8116bbe4 | ||
|
5ebc3a4660 | ||
|
55f4c45af1 | ||
|
96a93c4b35 | ||
|
870f9838d6 | ||
|
048302625f | ||
|
6ad1900ada | ||
|
a045eb7bbb | ||
|
52dc6b7710 | ||
|
72318314c6 | ||
|
2cac659f8b | ||
|
6cdd90e18b | ||
|
7cf31db20e | ||
|
2248514584 | ||
|
a4b431349d | ||
|
365548e126 |
101
CHANGELOG.md
Normal file
101
CHANGELOG.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# CHANGELOG
|
||||
|
||||
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)
|
||||
|
||||
v0.11.0 - _August 24, 2017_
|
||||
------------------------
|
||||
* Added `zeroEx.token.setUnlimitedProxyAllowanceAsync` (#137)
|
||||
* Added `zeroEx.token.setUnlimitedAllowanceAsync` (#137)
|
||||
* Added `zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS` (#137)
|
||||
|
||||
v0.10.4 - _Aug 24, 2017_
|
||||
------------------------
|
||||
* Fixed a bug where checksummed addresses were being pulled from artifacts and not lower-cased. (#135)
|
||||
|
||||
v0.10.1 - _Aug 24, 2017_
|
||||
------------------------
|
||||
* Added `zeroEx.exchange.validateFillOrderThrowIfInvalidAsync` (#128)
|
||||
* Added `zeroEx.exchange.validateFillOrKillOrderThrowIfInvalidAsync` (#128)
|
||||
* Added `zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync` (#128)
|
||||
* Added `zeroEx.exchange.isRoundingErrorAsync` (#128)
|
||||
* Added `zeroEx.proxy.getContractAddressAsync` (#130)
|
||||
* Added `zeroEx.tokenRegistry.getTokenAddressesAsync` (#132)
|
||||
* Added `zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync` (#132)
|
||||
* Added `zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync` (#132)
|
||||
* Added `zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync` (#132)
|
||||
* Added `zeroEx.tokenRegistry.getTokenByNameIfExistsAsync` (#132)
|
||||
* Added clear error message when checksummed address is passed to a public method (#124)
|
||||
* Fixes the description of `shouldThrowOnInsufficientBalanceOrAllowance` in docs (#127)
|
||||
|
||||
v0.9.3 - _Aug 22, 2017_
|
||||
------------------------
|
||||
* Update contract artifacts to include latest Kovan and Mainnet deploys (#118)
|
||||
|
||||
v0.9.2 - _Aug 21, 2017_
|
||||
------------------------
|
||||
* *This version was unpublished because of a publishing issue.*
|
||||
* Update contract artifacts to include latest Kovan and Mainnet deploys (#118)
|
||||
|
||||
v0.9.1 - _Aug. 16, 2017_
|
||||
------------------------
|
||||
* Fixed the bug causing `zeroEx.token.getBalanceAsync()` to fail if no addresses available (#120)
|
||||
|
||||
v0.9.0 - _Jul. 26, 2017_
|
||||
------------------------
|
||||
* Migrated to the new version of smart contracts (#101)
|
||||
* Removed the ability to call methods on multiple authorized Exchange smart contracts (#106)
|
||||
* Made `zeroEx.getOrderHashHex` a static method (#107)
|
||||
* Cached `net_version` requests and invalidate the cache on calls to `setProvider` (#95)
|
||||
* Renamed `zeroEx.exchange.batchCancelOrderAsync` to `zeroEx.exchange.batchCancelOrdersAsync`
|
||||
* Renamed `zeroEx.exchange.batchFillOrderAsync` to `zeroEx.exchange.batchFillOrdersAsync`
|
||||
* Updated to typescript v2.4 (#104)
|
||||
* Fixed an issue with incorrect balance/allowance validation when ZRX is one of the tokens traded (#109)
|
||||
|
||||
v0.8.0 - _Jul. 4, 2017_
|
||||
------------------------
|
||||
* Added the ability to call methods on different authorized versions of the Exchange smart contract (#82)
|
||||
* Updated contract artifacts to reflect latest changes to the smart contracts (0xproject/contracts#59)
|
||||
* Added `zeroEx.proxy.isAuthorizedAsync` and `zeroEx.proxy.getAuthorizedAddressesAsync` (#89)
|
||||
* Added `zeroEx.token.subscribeAsync` (#90)
|
||||
* Made contract invalidation functions private (#90)
|
||||
* `zeroEx.token.invalidateContractInstancesAsync`
|
||||
* `zeroEx.exchange.invalidateContractInstancesAsync`
|
||||
* `zeroEx.proxy.invalidateContractInstance`
|
||||
* `zeroEx.tokenRegistry.invalidateContractInstance`
|
||||
* Fixed the bug where `zeroEx.setProviderAsync` didn't invalidate etherToken contract's instance
|
||||
|
||||
v0.7.1 - _Jun. 26, 2017_
|
||||
------------------------
|
||||
* Added the ability to convert Ether to wrapped Ether tokens and back via `zeroEx.etherToken.depostAsync` and `zeroEx.etherToken.withdrawAsync` (#81)
|
||||
|
||||
v0.7.0 - _Jun. 22, 2017_
|
||||
------------------------
|
||||
* Added Kovan smart contract artifacts (#78)
|
||||
* Started returning fillAmount from `fillOrderAsync` and `fillUpToAsync` (#72)
|
||||
* Started returning cancelledAmount from `cancelOrderAsync` (#72)
|
||||
* Renamed type `LogCancelArgs` to `LogCancelContractEventArgs` and `LogFillArgs` to `LogFillContractEventArgs`
|
||||
|
||||
v0.6.2 - _Jun. 21, 2017_
|
||||
------------------------
|
||||
* Reduced bundle size
|
||||
* Improved documentation
|
||||
|
||||
v0.6.1 - _Jun. 19, 2017_
|
||||
------------------------
|
||||
* Improved documentation
|
||||
|
||||
v0.6.0 - _Jun. 19, 2017_
|
||||
------------------------
|
||||
* Made `ZeroEx` class accept `Web3Provider` instance instead of `Web3` instance
|
||||
* Added types for contract event arguments
|
||||
|
||||
v0.5.2 - _Jun. 15, 2017_
|
||||
------------------------
|
||||
* Fixed the bug in `postpublish` script that caused that only unminified UMD bundle was uploaded to release page
|
||||
|
||||
v0.5.1 - _Jun. 15, 2017_
|
||||
------------------------
|
||||
* Added `postpublish` script to publish to Github Releases with assets.
|
65
CONTRIBUTING.md
Normal file
65
CONTRIBUTING.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# 0x.js CONTRIBUTING.md
|
||||
|
||||
Thank you for your interest in contributing to 0x.js! We welcome contributions from anyone on the internet, and are grateful for even the smallest of fixes!
|
||||
|
||||
## Developer's guide
|
||||
|
||||
## How to contribute
|
||||
|
||||
If you'd like to contribute to 0x.js, please fork the repo, fix, commit and send a pull request against the `development` branch for the maintainers to review and merge into the main code base. If you wish to submit more complex changes though, please check up with a core dev first on [our gitter channel](https://gitter.im/0xProject/Lobby) or in the `#dev` channel on our [slack](https://slack.0xproject.com/) to ensure those changes are in line with the general philosophy of the project and/or to get some early feedback which can make both your efforts easier as well as our review and merge procedures quick and simple.
|
||||
|
||||
We encourage a “PR early” approach so create the PR as early as possible even without the fix/feature ready, so that devs and other volunteers know you have picked up the issue. These early PRs should indicate an 'in progress' status by adding the '[WIP]' prefix to the PR title. Please make sure your contributions adhere to our coding guidelines:
|
||||
|
||||
* Pull requests adding features or refactoring should be opened against the `development` branch
|
||||
* Pull requests fixing bugs in the latest release version should be opened again the `master` branch
|
||||
* Write [good commit messages](https://chris.beams.io/posts/git-commit/)
|
||||
|
||||
## Code quality
|
||||
|
||||
Because 0x.js is used by multiple relayers in production and their businesses depend on it, we strive for excellent code quality. Please follow the existing code standards and conventions. `tslint` (described below) will help you.
|
||||
If you're adding functionality, please also add tests and make sure they pass. We have an automatic coverage reporting tool, so we'll see it if they are missing ;)
|
||||
If you're adding a new public function/member, make sure you document it with Java doc-style comments. We use typedoc to generate [awesome documentation](https://0xproject.com/docs/0xjs) from the comments within our source code.
|
||||
|
||||
## Running and building
|
||||
|
||||
First thing to do with an unknown code base is to run the tests.
|
||||
We assume that you have `npm` and `yarn` installed.
|
||||
|
||||
To do that:
|
||||
|
||||
* Install dependencies: `yarn`
|
||||
* Initialize the testrpc state (migrate the contracts) by doing one of the following:
|
||||
* Manual contracts migration:
|
||||
* Run testrpc: `yarn testrpc`
|
||||
* Clone the `[contracts](https://github.com/0xProject/contracts)` repo and run `yarn migrate`
|
||||
* Use one of the existing testrpc snapshots
|
||||
* Check out `circle.yml` for an example
|
||||
* Run tests: `yarn test`
|
||||
|
||||
To build run: `yarn build`
|
||||
|
||||
We also recommend you read through the tests.
|
||||
|
||||
## Styleguide
|
||||
|
||||
We use `[tslint](https://palantir.github.io/tslint/)` with [custom configs](https://github.com/0xProject/tslint-config-0xproject) to keep our code style consistent.
|
||||
|
||||
To lint your code just run: `yarn lint`
|
||||
|
||||
If using the Atom text editor, we recommend you install the following packages:
|
||||
|
||||
* [atom-typescript](https://atom.io/packages/atom-typescript)
|
||||
* [linter-tslint](https://atom.io/packages/linter-tslint)
|
||||
|
||||
Our CI will also run it as a part of the test run when you submit your PR.
|
||||
|
||||
|
||||
## Branch structure & versioning
|
||||
|
||||
We use [semantic versioning](http://semver.org/), but before we reach v1.0.0 all breaking changes as well as new features will be minor version bumps.
|
||||
|
||||
We have two main branches: `master` and `development`.
|
||||
|
||||
`master` represents the most recent released (published on npm) version.
|
||||
|
||||
`development` represents the development state and is a default branch to which you will submit a PR. We use this structure so that we can push hotfixes to the currently released version without needing to publish all the changes made towards the next release. If a hotfix is implemented on `master`, it is back-ported to `development`.
|
2
PULL_REQUEST_TEMPLATE.md
Normal file
2
PULL_REQUEST_TEMPLATE.md
Normal file
@@ -0,0 +1,2 @@
|
||||
This PR:
|
||||
*
|
52
README.md
52
README.md
@@ -1,3 +1,53 @@
|
||||
# 0x.js
|
||||
<img src="https://github.com/0xProject/branding/blob/master/0x_Black_CMYK.png" width="200px" >
|
||||
|
||||
---
|
||||
|
||||
[0x][website-url] is an open protocol that facilitates trustless, low friction exchange of Ethereum-based assets. A full description of the protocol may be found in our [whitepaper][whitepaper-url].
|
||||
|
||||
This repository contains a Javascript library that makes it easy to build Relayers and other DApps that use the 0x protocol.
|
||||
|
||||
[](https://circleci.com/gh/0xProject/0x.js)
|
||||
[](https://badge.fury.io/js/0x.js)
|
||||
[](https://coveralls.io/github/0xProject/0x.js?branch=master)
|
||||
[](http://slack.0xProject.com)
|
||||
[](https://gitter.im/0xProject/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://greenkeeper.io/)
|
||||
|
||||
## Installation
|
||||
|
||||
0x.js ships as both a [UMD](https://github.com/umdjs/umd) module and a [CommonJS](https://en.wikipedia.org/wiki/CommonJS) package.
|
||||
|
||||
#### CommonJS *(recommended)*:
|
||||
|
||||
**Install**
|
||||
|
||||
```bash
|
||||
npm install 0x.js --save
|
||||
```
|
||||
|
||||
**Import**
|
||||
|
||||
```javascript
|
||||
import {ZeroEx} from '0x.js';
|
||||
```
|
||||
|
||||
#### UMD:
|
||||
|
||||
**Install**
|
||||
|
||||
Download the UMD module from our [releases page](https://github.com/0xProject/0x.js/releases) and add it to your project.
|
||||
|
||||
**Import**
|
||||
|
||||
```html
|
||||
<script type="text/javascript" src="0x.js"></script>
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Extensive documentation of 0x.js can be found on [our website][docs-url].
|
||||
|
||||
[website-url]: https://0xproject.com/
|
||||
[whitepaper-url]: https://0xproject.com/pdfs/0x_white_paper.pdf
|
||||
[docs-url]: https://0xproject.com/docs/0xjs
|
||||
|
25
circle.yml
25
circle.yml
@@ -1,14 +1,23 @@
|
||||
machine:
|
||||
node:
|
||||
version: 6.1.0
|
||||
version: 6.5.0
|
||||
environment:
|
||||
CONTRACTS_COMMIT_HASH: '35053f9'
|
||||
PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin"
|
||||
|
||||
dependencies:
|
||||
override:
|
||||
- yarn
|
||||
cache_directories:
|
||||
- ~/.cache/yarn
|
||||
|
||||
test:
|
||||
override:
|
||||
- npm run testrpc:
|
||||
- wget https://s3.amazonaws.com/testrpc-shapshots/${CONTRACTS_COMMIT_HASH}.zip
|
||||
- unzip ${CONTRACTS_COMMIT_HASH}.zip -d testrpc_snapshot
|
||||
- npm run testrpc -- --db testrpc_snapshot:
|
||||
background: true
|
||||
- git clone git@github.com:0xProject/contracts.git ../contracts
|
||||
- cd ../contracts; git checkout 38c2b4c; npm install && npm run migrate
|
||||
- npm run update_contracts
|
||||
- npm run test:coverage
|
||||
- npm run test:umd
|
||||
- npm run lint
|
||||
- yarn test:coverage
|
||||
- yarn report_test_coverage
|
||||
- if [ $CIRCLE_BRANCH = "master" ]; then yarn test:umd; fi
|
||||
- yarn lint
|
||||
|
65
package.json
65
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0xproject/0x.js",
|
||||
"version": "0.2.2",
|
||||
"name": "0x.js",
|
||||
"version": "0.12.1",
|
||||
"description": "A javascript library for interacting with the 0x protocol",
|
||||
"keywords": [
|
||||
"0x.js",
|
||||
@@ -9,34 +9,37 @@
|
||||
"tokens",
|
||||
"exchange"
|
||||
],
|
||||
"main": "lib/src/0x.js",
|
||||
"types": "lib/src/0x.d.ts",
|
||||
"main": "lib/src/index.js",
|
||||
"types": "lib/src/index.d.ts",
|
||||
"scripts": {
|
||||
"prebuild": "npm run clean",
|
||||
"build": "run-p build:*:prod",
|
||||
"prepublish": "run-p build:umd:prod build:commonjs:dev",
|
||||
"build": "run-p build:umd:prod build:commonjs",
|
||||
"prepublishOnly": "run-p build",
|
||||
"postpublish": "run-s release docs:json upload_docs_json",
|
||||
"release": "publish-release --assets _bundles/index.js,_bundles/index.min.js --tag $(git describe --tags) --owner 0xProject --repo 0x.js",
|
||||
"upload_docs_json": "aws s3 cp docs/index.json s3://0xjs-docs-jsons/$(git describe --tags).json --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type aplication/json",
|
||||
"lint": "tslint src/*.ts test/*.ts",
|
||||
"test": "run-s clean test:commonjs",
|
||||
"test:umd": "./scripts/test_umd.sh",
|
||||
"test:coverage": "nyc npm run test --all",
|
||||
"report_test_coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
"update_contracts": "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../0x.js/src/artifacts; done;",
|
||||
"testrpc": "testrpc -p 8545 --networkId 50 -m \"${npm_package_config_mnemonic}\"",
|
||||
"docs:json": "typedoc --json docs/index.json .",
|
||||
"docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json docs/index.json .",
|
||||
"docs:generate": "typedoc --out docs .",
|
||||
"docs:open": "opn docs/index.html",
|
||||
"clean": "shx rm -rf _bundles lib test_temp",
|
||||
"build:dev": "npm run clean && run-p build:*:dev",
|
||||
"build:umd:dev": "webpack",
|
||||
"build:umd:prod": "webpack -p",
|
||||
"build:commonjs:dev": "tsc; copyfiles -u 2 ./src/artifacts/*.json ../0x.js/lib/src/artifacts;",
|
||||
"test:commonjs": "run-s build:commonjs:dev run_mocha",
|
||||
"pretest:umd": "run-s clean build:*:dev",
|
||||
"build:umd:prod": "NODE_ENV=production webpack",
|
||||
"build:commonjs": "tsc; copyfiles -u 2 './src/artifacts/**/*.json' ./lib/src/artifacts;",
|
||||
"test:commonjs": "run-s build:commonjs run_mocha",
|
||||
"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/schemas -o -path \"./lib/src/types.*\" \\) -prune -o -type f -print | xargs rm",
|
||||
"run_mocha": "mocha lib/test/**/*_test.js --timeout 3000"
|
||||
"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"
|
||||
},
|
||||
"config": {
|
||||
"artifacts": "Proxy Exchange TokenRegistry Token Mintable EtherToken",
|
||||
"artifacts": "TokenTransferProxy Exchange TokenRegistry Token EtherToken",
|
||||
"mnemonic": "concert load couple harbor equip island argue ramp clarify fence smart topic"
|
||||
},
|
||||
"repository": {
|
||||
@@ -52,18 +55,19 @@
|
||||
"@types/jsonschema": "^1.1.1",
|
||||
"@types/lodash": "^4.14.64",
|
||||
"@types/mocha": "^2.2.41",
|
||||
"@types/node": "^7.0.22",
|
||||
"@types/node": "^8.0.1",
|
||||
"@types/sinon": "^2.2.2",
|
||||
"awesome-typescript-loader": "^3.1.3",
|
||||
"bignumber.js": "^4.0.2",
|
||||
"chai": "^4.0.1",
|
||||
"chai-as-promised": "^6.0.0",
|
||||
"chai-as-promised": "^7.1.0",
|
||||
"chai-as-promised-typescript-typings": "0.0.3",
|
||||
"chai-bignumber": "git+ssh://git@github.com:0xProject/chai-bignumber.git",
|
||||
"chai-bignumber": "^2.0.1",
|
||||
"chai-typescript-typings": "^0.0.0",
|
||||
"copyfiles": "^1.2.0",
|
||||
"dirty-chai": "^1.2.2",
|
||||
"ethereumjs-testrpc": "3.0.5",
|
||||
"coveralls": "^2.13.1",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"ethereumjs-testrpc": "4.0.1",
|
||||
"json-loader": "^0.5.4",
|
||||
"mocha": "^3.4.1",
|
||||
"npm-run-all": "^4.0.2",
|
||||
@@ -72,26 +76,31 @@
|
||||
"request": "^2.81.0",
|
||||
"request-promise-native": "^1.0.4",
|
||||
"shx": "^0.2.2",
|
||||
"sinon": "^2.3.2",
|
||||
"sinon": "^3.0.0",
|
||||
"source-map-support": "^0.4.15",
|
||||
"truffle-hdwallet-provider": "^0.0.3",
|
||||
"tslint": "^5.3.2",
|
||||
"tslint-config-0xproject": "^0.0.2",
|
||||
"typedoc": "^0.7.1",
|
||||
"typescript": "^2.3.3",
|
||||
"web3-provider-engine": "https://github.com/0xProject/provider-engine.git",
|
||||
"web3-typescript-typings": "0.0.8",
|
||||
"webpack": "^2.6.0"
|
||||
"typedoc": "^0.8.0",
|
||||
"types-bn": "^0.0.1",
|
||||
"types-ethereumjs-util": "^0.0.5",
|
||||
"typescript": "^2.4.1",
|
||||
"web3_beta": "ethereum/web3.js#1.0",
|
||||
"web3-provider-engine": "^13.0.1",
|
||||
"web3-typescript-typings": "^0.3.2",
|
||||
"webpack": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"0x-json-schemas": "^0.3.0",
|
||||
"bignumber.js": "^4.0.2",
|
||||
"compare-versions": "^3.0.1",
|
||||
"es6-promisify": "^5.0.0",
|
||||
"ethereumjs-abi": "^0.6.4",
|
||||
"ethereumjs-util": "^5.1.1",
|
||||
"find-versions": "^2.0.0",
|
||||
"jsonschema": "^1.1.1",
|
||||
"lodash": "^4.17.4",
|
||||
"publish-release": "^1.3.3",
|
||||
"truffle-contract": "^2.0.1",
|
||||
"web3": "^0.19.0"
|
||||
"web3": "^0.20.0"
|
||||
}
|
||||
}
|
||||
|
172
src/0x.ts
172
src/0x.ts
@@ -1,27 +1,30 @@
|
||||
import * as _ from '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 * as Web3 from 'web3';
|
||||
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 {ExchangeWrapper} from './contract_wrappers/exchange_wrapper';
|
||||
import {TokenRegistryWrapper} from './contract_wrappers/token_registry_wrapper';
|
||||
import {ecSignatureSchema} from './schemas/ec_signature_schema';
|
||||
import {EtherTokenWrapper} from './contract_wrappers/ether_token_wrapper';
|
||||
import {TokenWrapper} from './contract_wrappers/token_wrapper';
|
||||
import {ECSignature, ZeroExError, Order, SignedOrder} from './types';
|
||||
import * as ExchangeArtifacts from './artifacts/Exchange.json';
|
||||
import {SchemaValidator} from './utils/schema_validator';
|
||||
import {orderSchema} from './schemas/order_schemas';
|
||||
import {TokenTransferProxyWrapper} from './contract_wrappers/token_transfer_proxy_wrapper';
|
||||
import {ECSignature, ZeroExError, Order, SignedOrder, Web3Provider, ZeroExConfig} from './types';
|
||||
|
||||
// Customize our BigNumber instances
|
||||
bigNumberConfigs.configure();
|
||||
|
||||
/**
|
||||
* The ZeroEx class is the single entry-point into the 0x.js library. It contains all of the library's functionality
|
||||
* and all calls to the library should be made through a ZeroEx instance.
|
||||
*/
|
||||
export class ZeroEx {
|
||||
/**
|
||||
* When creating an order without a specified taker or feeRecipient you must supply the Solidity
|
||||
@@ -30,21 +33,41 @@ export class ZeroEx {
|
||||
*/
|
||||
public static NULL_ADDRESS = constants.NULL_ADDRESS;
|
||||
|
||||
/**
|
||||
* An instance of the ExchangeWrapper class containing methods for interacting with the 0x Exchange smart contract.
|
||||
*/
|
||||
public exchange: ExchangeWrapper;
|
||||
/**
|
||||
* An instance of the TokenRegistryWrapper class containing methods for interacting with the 0x
|
||||
* TokenRegistry smart contract.
|
||||
*/
|
||||
public tokenRegistry: TokenRegistryWrapper;
|
||||
/**
|
||||
* An instance of the TokenWrapper class containing methods for interacting with any ERC20 token smart contract.
|
||||
*/
|
||||
public token: TokenWrapper;
|
||||
/**
|
||||
* An instance of the EtherTokenWrapper class containing methods for interacting with the
|
||||
* wrapped ETH ERC20 token smart contract.
|
||||
*/
|
||||
public etherToken: EtherTokenWrapper;
|
||||
/**
|
||||
* An instance of the TokenTransferProxyWrapper class containing methods for interacting with the
|
||||
* tokenTransferProxy smart contract.
|
||||
*/
|
||||
public proxy: TokenTransferProxyWrapper;
|
||||
private _web3Wrapper: Web3Wrapper;
|
||||
/**
|
||||
* Verifies that the elliptic curve signature `signature` was generated
|
||||
* by signing `data` with the private key corresponding to the `signerAddress` address.
|
||||
* @param data The hex encoded data signed by the supplied signature.
|
||||
* @param signature A JS object containing the elliptic curve signature parameters.
|
||||
* @param signature An object containing the elliptic curve signature parameters.
|
||||
* @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
|
||||
* @return Whether the signature is valid for the supplied signerAddress and data.
|
||||
*/
|
||||
public static isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
|
||||
assert.isHexString('data', data);
|
||||
assert.doesConformToSchema('signature', signature, ecSignatureSchema);
|
||||
assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema);
|
||||
assert.isETHAddressHex('signerAddress', signerAddress);
|
||||
|
||||
const dataBuff = ethUtil.toBuffer(data);
|
||||
@@ -86,7 +109,8 @@ export class ZeroEx {
|
||||
// Since this method can be called to check if any arbitrary string conforms to an orderHash's
|
||||
// format, we only assert that we were indeed passed a string.
|
||||
assert.isString('orderHash', orderHash);
|
||||
const isValidOrderHash = utils.isValidOrderHash(orderHash);
|
||||
const schemaValidator = new SchemaValidator();
|
||||
const isValidOrderHash = schemaValidator.validate(orderHash, schemas.orderHashSchema).valid;
|
||||
return isValidOrderHash;
|
||||
}
|
||||
/**
|
||||
@@ -121,56 +145,67 @@ export class ZeroEx {
|
||||
const baseUnitAmount = amount.times(unit);
|
||||
return baseUnitAmount;
|
||||
}
|
||||
/**
|
||||
* Computes the orderHash for a supplied order.
|
||||
* @param order An object that conforms to the Order or SignedOrder interface definitions.
|
||||
* @return The resulting orderHash from hashing the supplied order.
|
||||
*/
|
||||
public static getOrderHashHex(order: Order|SignedOrder): string {
|
||||
assert.doesConformToSchema('order', order, schemas.orderSchema);
|
||||
const orderHashHex = utils.getOrderHashHex(order);
|
||||
return orderHashHex;
|
||||
}
|
||||
/**
|
||||
* Instantiates a new ZeroEx instance that provides the public interface to the 0x.js library.
|
||||
* @param web3 The Web3.js instance you would like the 0x.js library to use for interacting with
|
||||
* the Ethereum network.
|
||||
* @param provider The Web3.js Provider instance you would like the 0x.js library to use for interacting with
|
||||
* the Ethereum network.
|
||||
* @param config The configuration object. Look up the type for the description.
|
||||
* @return An instance of the 0x.js ZeroEx class.
|
||||
*/
|
||||
constructor(web3: Web3) {
|
||||
this._web3Wrapper = new Web3Wrapper(web3);
|
||||
this.token = new TokenWrapper(this._web3Wrapper);
|
||||
this.exchange = new ExchangeWrapper(this._web3Wrapper, this.token);
|
||||
this.tokenRegistry = new TokenRegistryWrapper(this._web3Wrapper);
|
||||
constructor(provider: Web3Provider, config?: ZeroExConfig) {
|
||||
assert.isWeb3Provider('provider', provider);
|
||||
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 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);
|
||||
}
|
||||
/**
|
||||
* Sets a new provider for the web3 instance used by 0x.js. Updating the provider will stop all
|
||||
* Sets a new web3 provider for 0x.js. Updating the provider will stop all
|
||||
* subscriptions so you will need to re-subscribe to all events relevant to your app after this call.
|
||||
* @param provider The Web3.Provider you would like the 0x.js library to use from now on.
|
||||
* @param provider The Web3Provider you would like the 0x.js library to use from now on.
|
||||
*/
|
||||
public async setProviderAsync(provider: Web3.Provider) {
|
||||
public async setProviderAsync(provider: Web3Provider) {
|
||||
this._web3Wrapper.setProvider(provider);
|
||||
await this.exchange.invalidateContractInstanceAsync();
|
||||
this.tokenRegistry.invalidateContractInstance();
|
||||
this.token.invalidateContractInstances();
|
||||
await (this.exchange as any)._invalidateContractInstancesAsync();
|
||||
(this.tokenRegistry as any)._invalidateContractInstance();
|
||||
await (this.token as any)._invalidateContractInstancesAsync();
|
||||
(this.proxy as any)._invalidateContractInstance();
|
||||
(this.etherToken as any)._invalidateContractInstance();
|
||||
}
|
||||
/**
|
||||
* Get addresses available throught the supplied web3 instance available for sending transactions.
|
||||
* @return An array of Ethereum addresses available.
|
||||
* Get user Ethereum addresses available through the supplied web3 provider available for sending transactions.
|
||||
* @return An array of available user Ethereum addresses.
|
||||
*/
|
||||
public async getAvailableAddressesAsync(): Promise<string[]> {
|
||||
const availableAddresses = await this._web3Wrapper.getAvailableAddressesAsync();
|
||||
return availableAddresses;
|
||||
}
|
||||
/**
|
||||
* Computes the orderHash for a supplied order.
|
||||
* @param order A JS object that conforms to the Order or SignedOrder interface definitions.
|
||||
* @return The resulting orderHash from hashing the supplied order.
|
||||
*/
|
||||
public async getOrderHashHexAsync(order: Order|SignedOrder): Promise<string> {
|
||||
assert.doesConformToSchema('order', order, orderSchema);
|
||||
|
||||
const exchangeContractAddr = await this._getExchangeAddressAsync();
|
||||
const orderHashHex = utils.getOrderHashHex(order, exchangeContractAddr);
|
||||
return orderHashHex;
|
||||
}
|
||||
/**
|
||||
* Signs an orderHash and returns it's elliptic curve signature.
|
||||
* This method currently supports TestRPC, Geth and Parity above and below V1.6.6
|
||||
* @param orderHash Hex encoded orderHash to sign.
|
||||
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
|
||||
* must be available via the Web3.Provider supplied to 0x.js.
|
||||
* @return A JS object containing the Elliptic curve signature parameters generated by signing the orderHash.
|
||||
* @return An object containing the Elliptic curve signature parameters generated by signing the orderHash.
|
||||
*/
|
||||
public async signOrderHashAsync(orderHash: string, signerAddress: string): Promise<ECSignature> {
|
||||
assert.isHexString('orderHash', orderHash);
|
||||
@@ -179,8 +214,9 @@ export class ZeroEx {
|
||||
let msgHashHex;
|
||||
const nodeVersion = await this._web3Wrapper.getNodeVersionAsync();
|
||||
const isParityNode = utils.isParityNode(nodeVersion);
|
||||
if (isParityNode) {
|
||||
// Parity node adds the personalMessage prefix itself
|
||||
const isTestRpc = utils.isTestRpc(nodeVersion);
|
||||
if (isParityNode || isTestRpc) {
|
||||
// Parity and TestRpc nodes add the personalMessage prefix itself
|
||||
msgHashHex = orderHash;
|
||||
} else {
|
||||
const orderHashBuff = ethUtil.toBuffer(orderHash);
|
||||
@@ -190,49 +226,27 @@ export class ZeroEx {
|
||||
|
||||
const signature = await this._web3Wrapper.signTransactionAsync(signerAddress, msgHashHex);
|
||||
|
||||
let signatureData;
|
||||
const [nodeVersionNumber] = findVersions(nodeVersion);
|
||||
// Parity v1.6.6 and earlier returns the signatureData as vrs instead of rsv as Geth does
|
||||
// Later versions return rsv but for the time being we still want to support version < 1.6.6
|
||||
// Date: May 23rd 2017
|
||||
const latestParityVersionWithVRS = '1.6.6';
|
||||
const isVersionBeforeParityFix = compareVersions(nodeVersionNumber, latestParityVersionWithVRS) <= 0;
|
||||
if (isParityNode && isVersionBeforeParityFix) {
|
||||
const signatureBuffer = ethUtil.toBuffer(signature);
|
||||
let v = signatureBuffer[0];
|
||||
if (v < 27) {
|
||||
v += 27;
|
||||
// HACK: There is no consensus on whether the signatureHex string should be formatted as
|
||||
// v + r + s OR r + s + v, and different clients (even different versions of the same client)
|
||||
// return the signature params in different orders. In order to support all client implementations,
|
||||
// we parse the signature in both ways, and evaluate if either one is a valid signature.
|
||||
const validVParamValues = [27, 28];
|
||||
const ecSignatureVRS = signatureUtils.parseSignatureHexAsVRS(signature);
|
||||
if (_.includes(validVParamValues, ecSignatureVRS.v)) {
|
||||
const isValidVRSSignature = ZeroEx.isValidSignature(orderHash, ecSignatureVRS, signerAddress);
|
||||
if (isValidVRSSignature) {
|
||||
return ecSignatureVRS;
|
||||
}
|
||||
signatureData = {
|
||||
v,
|
||||
r: signatureBuffer.slice(1, 33),
|
||||
s: signatureBuffer.slice(33, 65),
|
||||
};
|
||||
} else {
|
||||
signatureData = ethUtil.fromRpcSig(signature);
|
||||
}
|
||||
|
||||
const {v, r, s} = signatureData;
|
||||
const ecSignature: ECSignature = {
|
||||
v,
|
||||
r: ethUtil.bufferToHex(r),
|
||||
s: ethUtil.bufferToHex(s),
|
||||
};
|
||||
const isValidSignature = ZeroEx.isValidSignature(orderHash, ecSignature, signerAddress);
|
||||
if (!isValidSignature) {
|
||||
throw new Error(ZeroExError.INVALID_SIGNATURE);
|
||||
const ecSignatureRSV = signatureUtils.parseSignatureHexAsRSV(signature);
|
||||
if (_.includes(validVParamValues, ecSignatureRSV.v)) {
|
||||
const isValidRSVSignature = ZeroEx.isValidSignature(orderHash, ecSignatureRSV, signerAddress);
|
||||
if (isValidRSVSignature) {
|
||||
return ecSignatureRSV;
|
||||
}
|
||||
}
|
||||
return ecSignature;
|
||||
}
|
||||
private async _getExchangeAddressAsync() {
|
||||
const networkIdIfExists = await this._web3Wrapper.getNetworkIdIfExistsAsync();
|
||||
const exchangeNetworkConfigsIfExists = _.isUndefined(networkIdIfExists) ?
|
||||
undefined :
|
||||
(ExchangeArtifacts as any).networks[networkIdIfExists];
|
||||
if (_.isUndefined(exchangeNetworkConfigsIfExists)) {
|
||||
throw new Error(ZeroExError.CONTRACT_NOT_DEPLOYED_ON_NETWORK);
|
||||
}
|
||||
const exchangeAddress = exchangeNetworkConfigsIfExists.address;
|
||||
return exchangeAddress;
|
||||
|
||||
throw new Error(ZeroExError.InvalidSignature);
|
||||
}
|
||||
}
|
||||
|
@@ -184,6 +184,10 @@
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"type": "fallback"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
@@ -229,8 +233,110 @@
|
||||
"type": "event"
|
||||
}
|
||||
],
|
||||
"unlinked_binary": "0x606060405234610000575b61072d806100196000396000f300606060405236156100935763ffffffff60e060020a60003504166306fdde038114610098578063095ea7b31461012557806318160ddd1461015557806323b872dd146101745780632e1a7d4d146101aa578063313ce567146101bc57806370a08231146101df57806395d89b411461020a578063a9059cbb14610297578063d0e30db0146102c7578063dd62ed3e146102d1575b610000565b34610000576100a5610302565b6040805160208082528351818301528351919283929083019185019080838382156100eb575b8051825260208311156100eb57601f1990920191602091820191016100cb565b505050905090810190601f1680156101175780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3461000057610141600160a060020a0360043516602435610339565b604080519115158252519081900360200190f35b34610000576101626103a4565b60408051918252519081900360200190f35b3461000057610141600160a060020a03600435811690602435166044356103aa565b604080519115158252519081900360200190f35b34610000576101ba6004356104a7565b005b34610000576101c9610527565b6040805160ff9092168252519081900360200190f35b3461000057610162600160a060020a036004351661052c565b60408051918252519081900360200190f35b34610000576100a561054b565b6040805160208082528351818301528351919283929083019185019080838382156100eb575b8051825260208311156100eb57601f1990920191602091820191016100cb565b505050905090810190601f1680156101175780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3461000057610141600160a060020a0360043516602435610582565b604080519115158252519081900360200190f35b6101ba610634565b005b3461000057610162600160a060020a0360043581169060243516610683565b60408051918252519081900360200190f35b60408051808201909152600b81527f457468657220546f6b656e000000000000000000000000000000000000000000602082015281565b600160a060020a03338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60025481565b600160a060020a0383166000908152602081905260408120546103cd90836106b0565b600160a060020a03808616600090815260208181526040808320949094556001815283822033909316825291909152205461040890836106b0565b600160a060020a0380861660009081526001602090815260408083203385168452825280832094909455918616815290819052205461044790836106c9565b600160a060020a038085166000818152602081815260409182902094909455805186815290519193928816927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a35060015b9392505050565b600160a060020a0333166000908152602081905260409020546104ca90826106b0565b600160a060020a0333166000908152602081905260409020556002546104f090826106b0565b600255604051600160a060020a0333169082156108fc029083906000818181858888f19350505050151561052357610000565b5b50565b601281565b600160a060020a0381166000908152602081905260409020545b919050565b60408051808201909152600481527f5745544800000000000000000000000000000000000000000000000000000000602082015281565b600160a060020a0333166000908152602081905260408120546105a590836106b0565b600160a060020a0333811660009081526020819052604080822093909355908516815220546105d490836106c9565b600160a060020a03808516600081815260208181526040918290209490945580518681529051919333909316927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a35060015b92915050565b600160a060020a03331660009081526020819052604090205461065790346106c9565b600160a060020a03331660009081526020819052604090205560025461067d90346106c9565b6002555b565b600160a060020a038083166000908152600160209081526040808320938516835292905220545b92915050565b60006106be838311156106f1565b508082035b92915050565b60008282016106e68482108015906106e15750838210155b6106f1565b8091505b5092915050565b80151561052357610000565b5b505600a165627a7a72305820dd221653234069427a90e77c21585b4e7c7f669edbf89a71a57b5bccb1ab42a50029",
|
||||
"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"
|
||||
},
|
||||
"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": {
|
||||
@@ -279,10 +385,10 @@
|
||||
"type": "event"
|
||||
}
|
||||
},
|
||||
"updated_at": 1496427473670,
|
||||
"updated_at": 1503318938233,
|
||||
"address": "0x48bacb9266a570d521063ef5dd96e61686dbe788"
|
||||
}
|
||||
},
|
||||
"schema_version": "0.0.5",
|
||||
"updated_at": 1496427473670
|
||||
"updated_at": 1503318938233
|
||||
}
|
File diff suppressed because one or more lines are too long
@@ -1,189 +0,0 @@
|
||||
{
|
||||
"contract_name": "Mintable",
|
||||
"abi": [
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "mint",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "remaining",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
}
|
||||
],
|
||||
"unlinked_binary": "0x606060405234610000575b6104e7806100196000396000f300606060405236156100675763ffffffff60e060020a600035041663095ea7b3811461006c57806318160ddd1461009c57806323b872dd146100bb57806370a08231146100f1578063a0712d681461011c578063a9059cbb1461012e578063dd62ed3e1461015e575b610000565b3461000057610088600160a060020a036004351660243561018f565b604080519115158252519081900360200190f35b34610000576100a96101fa565b60408051918252519081900360200190f35b3461000057610088600160a060020a0360043581169060243516604435610200565b604080519115158252519081900360200190f35b34610000576100a9600160a060020a036004351661030d565b60408051918252519081900360200190f35b346100005761012c60043561032c565b005b3461000057610088600160a060020a0360043516602435610393565b604080519115158252519081900360200190f35b34610000576100a9600160a060020a0360043581169060243516610456565b60408051918252519081900360200190f35b600160a060020a03338116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b60025481565b600160a060020a0383166000908152602081905260408120548290108015906102505750600160a060020a0380851660009081526001602090815260408083203390941683529290522054829010155b80156102755750600160a060020a038316600090815260208190526040902054828101115b1561030157600160a060020a0380841660008181526020818152604080832080548801905588851680845281842080548990039055600183528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a3506001610305565b5060005b5b9392505050565b600160a060020a0381166000908152602081905260409020545b919050565b68056bc75e2d6310000081111561034257610000565b600160a060020a033316600090815260208190526040902054610366908290610483565b600160a060020a03331660009081526020819052604090205560025461038c9082610483565b6002555b50565b600160a060020a0333166000908152602081905260408120548290108015906103d55750600160a060020a038316600090815260208190526040902054828101115b1561044757600160a060020a0333811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060016101f4565b5060006101f4565b5b92915050565b600160a060020a038083166000908152600160209081526040808320938516835292905220545b92915050565b60008282016104a084821080159061049b5750838210155b6104ab565b8091505b5092915050565b80151561039057610000565b5b505600a165627a7a72305820ee38e175a277bbcf215259201687857177c3da8e0da65470b4f7c1eb1c95a21a0029",
|
||||
"networks": {},
|
||||
"schema_version": "0.0.5",
|
||||
"updated_at": 1496427420920
|
||||
}
|
@@ -1,226 +0,0 @@
|
||||
{
|
||||
"contract_name": "Proxy",
|
||||
"abi": [
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "addAuthorizedAddress",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "authorities",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "removeAuthorizedAddress",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "authorized",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "getAuthorizedAddresses",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address[]"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "caller",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "LogAuthorizedAddressAdded",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "caller",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "LogAuthorizedAddressRemoved",
|
||||
"type": "event"
|
||||
}
|
||||
],
|
||||
"unlinked_binary": "0x60606040525b60008054600160a060020a03191633600160a060020a03161790555b5b610701806100316000396000f300606060405236156100725763ffffffff60e060020a60003504166315dacbea811461007757806342f1181e146100b3578063494503d4146100e0578063707129391461010c5780638da5cb5b14610139578063b918161114610162578063d39de6e91461018f578063f2fde38b146101f7575b610000565b346100005761009f600160a060020a0360043581169060243581169060443516606435610212565b604080519115158252519081900360200190f35b346100005761009f600160a060020a03600435166102d8565b604080519115158252519081900360200190f35b34610000576100f06004356103f5565b60408051600160a060020a039092168252519081900360200190f35b346100005761009f600160a060020a0360043516610425565b604080519115158252519081900360200190f35b34610000576100f06105ee565b60408051600160a060020a039092168252519081900360200190f35b346100005761009f600160a060020a03600435166105fd565b604080519115158252519081900360200190f35b346100005761019c610612565b60408051602080825283518183015283519192839290830191858101910280838382156101e4575b8051825260208311156101e457601f1990920191602091820191016101c4565b5050509050019250505060405180910390f35b3461000057610210600160a060020a036004351661067d565b005b600160a060020a03331660009081526001602052604081205460ff16151561023957610000565b604080516000602091820181905282517f23b872dd000000000000000000000000000000000000000000000000000000008152600160a060020a0388811660048301528781166024830152604482018790529351938916936323b872dd9360648084019491938390030190829087803b156100005760325a03f11561000057505060405151151590506102cb57610000565b5060015b5b949350505050565b6000805433600160a060020a039081169116146102f457610000565b600160a060020a038216600090815260016020526040902054829060ff161561031c57610000565b600160a060020a0383166000908152600160208190526040909120805460ff191682179055600280549182018082559091908281838015829011610385576000838152602090206103859181019083015b80821115610381576000815560010161036d565b5090565b5b505050916000526020600020900160005b81546101009190910a600160a060020a0381810219909216878316918202179092556040513390911692507f94bb87f4c15c4587ff559a7584006fa01ddf9299359be6b512b94527aa961aca90600090a3600191505b5b505b919050565b600281815481101561000057906000526020600020900160005b915054906101000a9004600160a060020a031681565b60008054819033600160a060020a0390811691161461044357610000565b600160a060020a038316600090815260016020526040902054839060ff16151561046c57610000565b600160a060020a0384166000908152600160205260408120805460ff1916905591505b6002548210156105a75783600160a060020a0316600283815481101561000057906000526020600020900160005b9054906101000a9004600160a060020a0316600160a060020a0316141561059b576002805460001981019081101561000057906000526020600020900160005b9054906101000a9004600160a060020a0316600283815481101561000057906000526020600020900160005b6101000a815481600160a060020a030219169083600160a060020a031602179055506001600281818054905003915081815481835581811511610591576000838152602090206105919181019083015b80821115610381576000815560010161036d565b5090565b5b505050506105a7565b5b60019091019061048f565b604051600160a060020a0333811691908616907ff5b347a1e40749dd050f5f07fbdbeb7e3efa9756903044dd29401fd1d4bb4a1c90600090a3600192505b5b505b50919050565b600054600160a060020a031681565b60016020526000908152604090205460ff1681565b60408051602081810183526000825260028054845181840281018401909552808552929392909183018282801561067257602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610654575b505050505090505b90565b60005433600160a060020a0390811691161461069857610000565b600160a060020a038116156106d0576000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0383161790555b5b5b505600a165627a7a72305820f2ffcbe9ff4a73a57b4b40b848879fd6fd1de0cb036195f9541c0a579d2a8b8a0029",
|
||||
"networks": {
|
||||
"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": 1496427473664,
|
||||
"address": "0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c"
|
||||
}
|
||||
},
|
||||
"schema_version": "0.0.5",
|
||||
"updated_at": 1496427473664
|
||||
}
|
@@ -169,8 +169,8 @@
|
||||
"type": "event"
|
||||
}
|
||||
],
|
||||
"unlinked_binary": "0x606060405234610000575b6101d1806100196000396000f3006060604052361561005c5763ffffffff60e060020a600035041663095ea7b3811461006157806318160ddd1461009157806323b872dd146100b057806370a08231146100e6578063a9059cbb14610061578063dd62ed3e14610141575b610000565b346100005761007d600160a060020a0360043516602435610172565b604080519115158252519081900360200190f35b346100005761009e61017b565b60408051918252519081900360200190f35b346100005761007d600160a060020a0360043581169060243516604435610181565b604080519115158252519081900360200190f35b346100005761009e600160a060020a036004351661018b565b60408051918252519081900360200190f35b346100005761007d600160a060020a0360043516602435610172565b604080519115158252519081900360200190f35b346100005761009e600160a060020a0360043581169060243516610172565b60408051918252519081900360200190f35b60005b92915050565b60005b90565b60005b9392505050565b60005b919050565b60005b92915050565b60005b929150505600a165627a7a7230582091fa49296e1416a21d33862878c9ba9a9f7db53c284ddea5857ea6eb7f5c1d180029",
|
||||
"unlinked_binary": "0x6060604052341561000c57fe5b5b6101e08061001c6000396000f3006060604052361561005c5763ffffffff60e060020a600035041663095ea7b3811461005e57806318160ddd1461009157806323b872dd146100b357806370a08231146100ec578063a9059cbb1461005e578063dd62ed3e1461014d575bfe5b341561006657fe5b61007d600160a060020a0360043516602435610181565b604080519115158252519081900360200190f35b341561009957fe5b6100a161018a565b60408051918252519081900360200190f35b34156100bb57fe5b61007d600160a060020a0360043581169060243516604435610190565b604080519115158252519081900360200190f35b34156100f457fe5b6100a1600160a060020a036004351661019a565b60408051918252519081900360200190f35b341561006657fe5b61007d600160a060020a0360043516602435610181565b604080519115158252519081900360200190f35b341561015557fe5b6100a1600160a060020a0360043581169060243516610181565b60408051918252519081900360200190f35b60005b92915050565b60005b90565b60005b9392505050565b60005b919050565b60005b92915050565b60005b929150505600a165627a7a723058202e3f7ac17048343c0d0ea24fccb64620577374eeeed61539e543df4025d7d0db0029",
|
||||
"networks": {},
|
||||
"schema_version": "0.0.5",
|
||||
"updated_at": 1496427420921
|
||||
"updated_at": 1503317882695
|
||||
}
|
File diff suppressed because one or more lines are too long
298
src/artifacts/TokenTransferProxy.json
Normal file
298
src/artifacts/TokenTransferProxy.json
Normal file
@@ -0,0 +1,298 @@
|
||||
{
|
||||
"contract_name": "TokenTransferProxy",
|
||||
"abi": [
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "addAuthorizedAddress",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "authorities",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "removeAuthorizedAddress",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "authorized",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "getAuthorizedAddresses",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address[]"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "caller",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "LogAuthorizedAddressAdded",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "caller",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "LogAuthorizedAddressRemoved",
|
||||
"type": "event"
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"schema_version": "0.0.5",
|
||||
"updated_at": 1503318938227
|
||||
}
|
@@ -6,11 +6,16 @@ import {utils} from '../utils/utils';
|
||||
|
||||
export class ContractWrapper {
|
||||
protected _web3Wrapper: Web3Wrapper;
|
||||
constructor(web3Wrapper: Web3Wrapper) {
|
||||
private _gasPrice?: BigNumber.BigNumber;
|
||||
constructor(web3Wrapper: Web3Wrapper, gasPrice?: BigNumber.BigNumber) {
|
||||
this._web3Wrapper = web3Wrapper;
|
||||
this._gasPrice = gasPrice;
|
||||
}
|
||||
protected async _instantiateContractIfExistsAsync(artifact: Artifact, address?: string): Promise<ContractInstance> {
|
||||
const c = await contract(artifact);
|
||||
c.defaults({
|
||||
gasPrice: this._gasPrice,
|
||||
});
|
||||
const providerObj = this._web3Wrapper.getCurrentProvider();
|
||||
c.setProvider(providerObj);
|
||||
|
||||
@@ -22,13 +27,13 @@ export class ContractWrapper {
|
||||
if (!_.isUndefined(address)) {
|
||||
contractAddress = address;
|
||||
} else if (!_.isUndefined(artifactNetworkConfigs)) {
|
||||
contractAddress = artifactNetworkConfigs.address;
|
||||
contractAddress = artifactNetworkConfigs.address.toLowerCase();
|
||||
}
|
||||
|
||||
if (!_.isUndefined(contractAddress)) {
|
||||
const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(contractAddress);
|
||||
if (!doesContractExist) {
|
||||
throw new Error(ZeroExError.CONTRACT_DOES_NOT_EXIST);
|
||||
throw new Error(ZeroExError.ContractDoesNotExist);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +43,10 @@ export class ContractWrapper {
|
||||
} catch (err) {
|
||||
const errMsg = `${err}`;
|
||||
if (_.includes(errMsg, 'not been deployed to detected network')) {
|
||||
throw new Error(ZeroExError.CONTRACT_DOES_NOT_EXIST);
|
||||
throw new Error(ZeroExError.ContractDoesNotExist);
|
||||
} else {
|
||||
utils.consoleLog(`Notice: Error encountered: ${err} ${err.stack}`);
|
||||
throw new Error(ZeroExError.UNHANDLED_ERROR);
|
||||
throw new Error(ZeroExError.UnhandledError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
78
src/contract_wrappers/ether_token_wrapper.ts
Normal file
78
src/contract_wrappers/ether_token_wrapper.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import * as _ from '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';
|
||||
|
||||
/**
|
||||
* This class includes all the functionality related to interacting with a wrapped Ether ERC20 token contract.
|
||||
* The caller can convert ETH into the equivalent number of wrapped ETH ERC20 tokens and back.
|
||||
*/
|
||||
export class EtherTokenWrapper extends ContractWrapper {
|
||||
private _etherTokenContractIfExists?: EtherTokenContract;
|
||||
private _tokenWrapper: TokenWrapper;
|
||||
constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper, gasPrice?: BigNumber.BigNumber) {
|
||||
super(web3Wrapper, gasPrice);
|
||||
this._tokenWrapper = tokenWrapper;
|
||||
}
|
||||
/**
|
||||
* Deposit ETH into the Wrapped ETH smart contract and issues the equivalent number of wrapped ETH tokens
|
||||
* to the depositor address. These wrapped ETH tokens can be used in 0x trades and are redeemable for 1-to-1
|
||||
* 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.
|
||||
*/
|
||||
public async depositAsync(amountInWei: BigNumber.BigNumber, depositor: string): Promise<void> {
|
||||
assert.isBigNumber('amountInWei', amountInWei);
|
||||
await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper);
|
||||
|
||||
const ethBalanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(depositor);
|
||||
assert.assert(ethBalanceInWei.gte(amountInWei), ZeroExError.InsufficientEthBalanceForDeposit);
|
||||
|
||||
const wethContract = await this._getEtherTokenContractAsync();
|
||||
await wethContract.deposit({
|
||||
from: depositor,
|
||||
value: amountInWei,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public async withdrawAsync(amountInWei: BigNumber.BigNumber, withdrawer: string): Promise<void> {
|
||||
assert.isBigNumber('amountInWei', amountInWei);
|
||||
await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper);
|
||||
|
||||
const wethContractAddress = await this.getContractAddressAsync();
|
||||
const WETHBalanceInBaseUnits = await this._tokenWrapper.getBalanceAsync(wethContractAddress, withdrawer);
|
||||
assert.assert(WETHBalanceInBaseUnits.gte(amountInWei), ZeroExError.InsufficientWEthBalanceForWithdrawal);
|
||||
|
||||
const wethContract = await this._getEtherTokenContractAsync();
|
||||
await wethContract.withdraw(amountInWei, {
|
||||
from: withdrawer,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Retrieves the Wrapped Ether token contract address
|
||||
* @return The Wrapped Ether token contract address
|
||||
*/
|
||||
public async getContractAddressAsync(): Promise<string> {
|
||||
const wethContract = await this._getEtherTokenContractAsync();
|
||||
return wethContract.address;
|
||||
}
|
||||
private _invalidateContractInstance(): void {
|
||||
delete this._etherTokenContractIfExists;
|
||||
}
|
||||
private async _getEtherTokenContractAsync(): Promise<EtherTokenContract> {
|
||||
if (!_.isUndefined(this._etherTokenContractIfExists)) {
|
||||
return this._etherTokenContractIfExists;
|
||||
}
|
||||
const contractInstance = await this._instantiateContractIfExistsAsync((EtherTokenArtifacts as any));
|
||||
this._etherTokenContractIfExists = contractInstance as EtherTokenContract;
|
||||
return this._etherTokenContractIfExists;
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import {SchemaValidator, schemas} from '0x-json-schemas';
|
||||
import promisify = require('es6-promisify');
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {
|
||||
@@ -7,6 +8,7 @@ import {
|
||||
ExchangeContract,
|
||||
ExchangeContractErrCodes,
|
||||
ExchangeContractErrs,
|
||||
ZeroExError,
|
||||
OrderValues,
|
||||
OrderAddresses,
|
||||
Order,
|
||||
@@ -14,40 +16,44 @@ import {
|
||||
SignedOrder,
|
||||
ContractEvent,
|
||||
ExchangeEvents,
|
||||
ContractEventEmitter,
|
||||
SubscriptionOpts,
|
||||
IndexFilterValues,
|
||||
IndexedFilterValues,
|
||||
CreateContractEvent,
|
||||
ContractEventObj,
|
||||
EventCallback,
|
||||
ContractResponse,
|
||||
OrderCancellationRequest,
|
||||
OrderFillRequest,
|
||||
LogErrorContractEventArgs,
|
||||
LogFillContractEventArgs,
|
||||
LogCancelContractEventArgs,
|
||||
} 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 * as ExchangeArtifacts from '../artifacts/Exchange.json';
|
||||
import {ecSignatureSchema} from '../schemas/ec_signature_schema';
|
||||
import {signedOrdersSchema} from '../schemas/signed_orders_schema';
|
||||
import {orderFillRequestsSchema} from '../schemas/order_fill_requests_schema';
|
||||
import {orderCancellationRequestsSchema} from '../schemas/order_cancel_schema';
|
||||
import {orderFillOrKillRequestsSchema} from '../schemas/order_fill_or_kill_requests_schema';
|
||||
import {signedOrderSchema, orderSchema} from '../schemas/order_schemas';
|
||||
import {constants} from '../utils/constants';
|
||||
import {TokenWrapper} from './token_wrapper';
|
||||
import {decorators} from '../utils/decorators';
|
||||
import * as ExchangeArtifacts from '../artifacts/Exchange.json';
|
||||
|
||||
/**
|
||||
* 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 _exchangeContractErrCodesToMsg = {
|
||||
[ExchangeContractErrCodes.ERROR_FILL_EXPIRED]: ExchangeContractErrs.ORDER_FILL_EXPIRED,
|
||||
[ExchangeContractErrCodes.ERROR_CANCEL_EXPIRED]: ExchangeContractErrs.ORDER_FILL_EXPIRED,
|
||||
[ExchangeContractErrCodes.ERROR_FILL_NO_VALUE]: ExchangeContractErrs.ORDER_REMAINING_FILL_AMOUNT_ZERO,
|
||||
[ExchangeContractErrCodes.ERROR_CANCEL_NO_VALUE]: ExchangeContractErrs.ORDER_REMAINING_FILL_AMOUNT_ZERO,
|
||||
[ExchangeContractErrCodes.ERROR_FILL_TRUNCATION]: ExchangeContractErrs.ORDER_FILL_ROUNDING_ERROR,
|
||||
[ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.FILL_BALANCE_ALLOWANCE_ERROR,
|
||||
[ExchangeContractErrCodes.ERROR_FILL_EXPIRED]: ExchangeContractErrs.OrderFillExpired,
|
||||
[ExchangeContractErrCodes.ERROR_CANCEL_EXPIRED]: ExchangeContractErrs.OrderFillExpired,
|
||||
[ExchangeContractErrCodes.ERROR_FILL_NO_VALUE]: ExchangeContractErrs.OrderRemainingFillAmountZero,
|
||||
[ExchangeContractErrCodes.ERROR_CANCEL_NO_VALUE]: ExchangeContractErrs.OrderRemainingFillAmountZero,
|
||||
[ExchangeContractErrCodes.ERROR_FILL_TRUNCATION]: ExchangeContractErrs.OrderFillRoundingError,
|
||||
[ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.FillBalanceAllowanceError,
|
||||
};
|
||||
private _exchangeContractIfExists?: ExchangeContract;
|
||||
private _exchangeLogEventObjs: ContractEventObj[];
|
||||
private _exchangeLogEventEmitters: ContractEventEmitter[];
|
||||
private _orderValidationUtils: OrderValidationUtils;
|
||||
private _tokenWrapper: TokenWrapper;
|
||||
private static _getOrderAddressesAndValues(order: Order): [OrderAddresses, OrderValues] {
|
||||
const orderAddresses: OrderAddresses = [
|
||||
@@ -67,31 +73,28 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
];
|
||||
return [orderAddresses, orderValues];
|
||||
}
|
||||
constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper) {
|
||||
super(web3Wrapper);
|
||||
constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper, gasPrice?: BigNumber.BigNumber) {
|
||||
super(web3Wrapper, gasPrice);
|
||||
this._tokenWrapper = tokenWrapper;
|
||||
this._exchangeLogEventObjs = [];
|
||||
}
|
||||
public async invalidateContractInstanceAsync(): Promise<void> {
|
||||
await this._stopWatchingExchangeLogEventsAsync();
|
||||
delete this._exchangeContractIfExists;
|
||||
this._orderValidationUtils = new OrderValidationUtils(tokenWrapper, this);
|
||||
this._exchangeLogEventEmitters = [];
|
||||
}
|
||||
/**
|
||||
* Returns the unavailable takerAmount of an order. Unavailable amount is defined as the total
|
||||
* amount that has been filled or cancelled. The remaining takerAmount can be calculated by
|
||||
* subtracting the unavailable amount from the total order takerAmount.
|
||||
* @param orderHash The hex encoded orderHash for which you would like to retrieve the
|
||||
* unavailable takerAmount.
|
||||
* @param orderHash The hex encoded orderHash for which you would like to retrieve the
|
||||
* unavailable takerAmount.
|
||||
* @return The amount of the order (in taker tokens) that has either been filled or canceled.
|
||||
*/
|
||||
public async getUnavailableTakerAmountAsync(orderHash: string): Promise<BigNumber.BigNumber> {
|
||||
assert.isValidOrderHash('orderHash', orderHash);
|
||||
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
|
||||
|
||||
const exchangeContract = await this._getExchangeContractAsync();
|
||||
let unavailableAmountInBaseUnits = await exchangeContract.getUnavailableValueT.call(orderHash);
|
||||
let unavailableTakerTokenAmount = await exchangeContract.getUnavailableTakerTokenAmount.call(orderHash);
|
||||
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
|
||||
unavailableAmountInBaseUnits = new BigNumber(unavailableAmountInBaseUnits);
|
||||
return unavailableAmountInBaseUnits;
|
||||
unavailableTakerTokenAmount = new BigNumber(unavailableTakerTokenAmount);
|
||||
return unavailableTakerTokenAmount;
|
||||
}
|
||||
/**
|
||||
* Retrieve the takerAmount of an order that has already been filled.
|
||||
@@ -99,7 +102,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
* @return The amount of the order (in taker tokens) that has already been filled.
|
||||
*/
|
||||
public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber.BigNumber> {
|
||||
assert.isValidOrderHash('orderHash', orderHash);
|
||||
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
|
||||
|
||||
const exchangeContract = await this._getExchangeContractAsync();
|
||||
let fillAmountInBaseUnits = await exchangeContract.filled.call(orderHash);
|
||||
@@ -114,7 +117,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
* @return The amount of the order (in taker tokens) that has been cancelled.
|
||||
*/
|
||||
public async getCanceledTakerAmountAsync(orderHash: string): Promise<BigNumber.BigNumber> {
|
||||
assert.isValidOrderHash('orderHash', orderHash);
|
||||
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
|
||||
|
||||
const exchangeContract = await this._getExchangeContractAsync();
|
||||
let cancelledAmountInBaseUnits = await exchangeContract.cancelled.call(orderHash);
|
||||
@@ -126,34 +129,39 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
* Fills a signed order with an amount denominated in baseUnits of the taker token.
|
||||
* Since the order in which transactions are included in the next block is indeterminate, race-conditions
|
||||
* could arise where a users balance or allowance changes before the fillOrder executes. Because of this,
|
||||
* we allow you to specify `shouldCheckTransfer`. If true, the smart contract will not throw if while
|
||||
* executing, the parties do not have sufficient balances/allowances, preserving gas costs. Setting it to
|
||||
* false forgoes this check and causes the smart contract to throw (using all the gas supplied) instead.
|
||||
* @param signedOrder A JS object that conforms to the SignedOrder interface.
|
||||
* @param takerTokenFillAmount The amount of the order (in taker tokens baseUnits) that you wish to fill.
|
||||
* @param shouldCheckTransfer Whether or not you wish for the contract call to throw if upon
|
||||
* execution the tokens cannot be transferred.
|
||||
* @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.
|
||||
* we allow you to specify `shouldThrowOnInsufficientBalanceOrAllowance`.
|
||||
* If false, the smart contract will not throw if the parties
|
||||
* do not have sufficient balances/allowances, preserving gas costs. Setting it to true forgoes this check
|
||||
* and causes the smart contract to throw (using all the gas supplied) instead.
|
||||
* @param signedOrder An object that conforms to the SignedOrder interface.
|
||||
* @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that
|
||||
* you wish to fill.
|
||||
* @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw
|
||||
* if upon execution the tokens cannot be transferred.
|
||||
* @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).
|
||||
*/
|
||||
@decorators.contractCallErrorHandler
|
||||
public async fillOrderAsync(signedOrder: SignedOrder, takerTokenFillAmount: BigNumber.BigNumber,
|
||||
shouldCheckTransfer: boolean, takerAddress: string): Promise<void> {
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, signedOrderSchema);
|
||||
assert.isBigNumber('takerTokenFillAmount', takerTokenFillAmount);
|
||||
assert.isBoolean('shouldCheckTransfer', shouldCheckTransfer);
|
||||
public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber.BigNumber,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
|
||||
takerAddress: string): Promise<BigNumber.BigNumber> {
|
||||
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._validateFillOrderAndThrowIfInvalidAsync(signedOrder, takerTokenFillAmount, takerAddress);
|
||||
await this.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress);
|
||||
|
||||
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder);
|
||||
|
||||
const gas = await exchangeInstance.fill.estimateGas(
|
||||
const gas = await exchangeInstance.fillOrder.estimateGas(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
takerTokenFillAmount,
|
||||
shouldCheckTransfer,
|
||||
fillTakerTokenAmount,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
signedOrder.ecSignature.v,
|
||||
signedOrder.ecSignature.r,
|
||||
signedOrder.ecSignature.s,
|
||||
@@ -161,11 +169,11 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
const response: ContractResponse = await exchangeInstance.fill(
|
||||
const response: ContractResponse = await exchangeInstance.fillOrder(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
takerTokenFillAmount,
|
||||
shouldCheckTransfer,
|
||||
fillTakerTokenAmount,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
signedOrder.ecSignature.v,
|
||||
signedOrder.ecSignature.r,
|
||||
signedOrder.ecSignature.s,
|
||||
@@ -175,37 +183,46 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
},
|
||||
);
|
||||
this._throwErrorLogsAsErrors(response.logs);
|
||||
const logFillArgs = response.logs[0].args as LogFillContractEventArgs;
|
||||
const filledTakerTokenAmount = new BigNumber(logFillArgs.filledTakerTokenAmount);
|
||||
return filledTakerTokenAmount;
|
||||
}
|
||||
/**
|
||||
* Sequentially and atomically fills signedOrders up to the specified takerTokenFillAmount.
|
||||
* If the fill amount is reached - it succeeds and does not fill the rest of the orders.
|
||||
* If fill amount is not reached - it fills as much of the fill amount as possible and succeeds.
|
||||
* @param signedOrders The array of signedOrders that you would like to fill until
|
||||
* takerTokenFillAmount is reached.
|
||||
* @param takerTokenFillAmount The total amount of the takerTokens you would like to fill.
|
||||
* @param shouldCheckTransfer Whether or not you wish for the contract call to throw if upon
|
||||
* execution any of the tokens cannot be transferred. If set to false,
|
||||
* the call will continue to fill subsequent signedOrders even when
|
||||
* some cannot be filled.
|
||||
* @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 signedOrders The array of signedOrders that you would like to fill until
|
||||
* takerTokenFillAmount is reached.
|
||||
* @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill.
|
||||
* @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw if
|
||||
* upon execution any of the tokens cannot be transferred.
|
||||
* If set to false, the call will continue to fill subsequent
|
||||
* signedOrders even when some cannot be filled.
|
||||
* @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).
|
||||
*/
|
||||
@decorators.contractCallErrorHandler
|
||||
public async fillOrdersUpToAsync(signedOrders: SignedOrder[], takerTokenFillAmount: BigNumber.BigNumber,
|
||||
shouldCheckTransfer: boolean, takerAddress: string): Promise<void> {
|
||||
public async fillOrdersUpToAsync(signedOrders: SignedOrder[], fillTakerTokenAmount: BigNumber.BigNumber,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
|
||||
takerAddress: string): Promise<BigNumber.BigNumber> {
|
||||
assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
|
||||
const takerTokenAddresses = _.map(signedOrders, signedOrder => signedOrder.takerTokenAddress);
|
||||
assert.hasAtMostOneUniqueValue(takerTokenAddresses,
|
||||
ExchangeContractErrs.MULTIPLE_TAKER_TOKENS_IN_FILL_UP_TO_DISALLOWED);
|
||||
assert.isBigNumber('takerTokenFillAmount', takerTokenFillAmount);
|
||||
assert.isBoolean('shouldCheckTransfer', shouldCheckTransfer);
|
||||
assert.doesConformToSchema('signedOrders', signedOrders, signedOrdersSchema);
|
||||
ExchangeContractErrs.MultipleTakerTokensInFillUpToDisallowed);
|
||||
const exchangeContractAddresses = _.map(signedOrders, signedOrder => signedOrder.exchangeContractAddress);
|
||||
assert.hasAtMostOneUniqueValue(exchangeContractAddresses,
|
||||
ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress);
|
||||
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
|
||||
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
|
||||
for (const signedOrder of signedOrders) {
|
||||
await this._validateFillOrderAndThrowIfInvalidAsync(
|
||||
signedOrder, takerTokenFillAmount, takerAddress);
|
||||
await this.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerTokenAmount, takerAddress);
|
||||
}
|
||||
if (_.isEmpty(signedOrders)) {
|
||||
return; // no-op
|
||||
return new BigNumber(0); // no-op
|
||||
}
|
||||
|
||||
const orderAddressesValuesAndSignatureArray = _.map(signedOrders, signedOrder => {
|
||||
@@ -222,11 +239,11 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
);
|
||||
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
const gas = await exchangeInstance.fillUpTo.estimateGas(
|
||||
const gas = await exchangeInstance.fillOrdersUpTo.estimateGas(
|
||||
orderAddressesArray,
|
||||
orderValuesArray,
|
||||
takerTokenFillAmount,
|
||||
shouldCheckTransfer,
|
||||
fillTakerTokenAmount,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
vArray,
|
||||
rArray,
|
||||
sArray,
|
||||
@@ -234,11 +251,11 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
const response: ContractResponse = await exchangeInstance.fillUpTo(
|
||||
const response: ContractResponse = await exchangeInstance.fillOrdersUpTo(
|
||||
orderAddressesArray,
|
||||
orderValuesArray,
|
||||
takerTokenFillAmount,
|
||||
shouldCheckTransfer,
|
||||
fillTakerTokenAmount,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
vArray,
|
||||
rArray,
|
||||
sArray,
|
||||
@@ -248,28 +265,45 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
},
|
||||
);
|
||||
this._throwErrorLogsAsErrors(response.logs);
|
||||
let filledTakerTokenAmount = new BigNumber(0);
|
||||
_.each(response.logs, log => {
|
||||
filledTakerTokenAmount = filledTakerTokenAmount.plus(
|
||||
(log.args as LogFillContractEventArgs).filledTakerTokenAmount);
|
||||
});
|
||||
return filledTakerTokenAmount;
|
||||
}
|
||||
/**
|
||||
* Batch version of fillOrderAsync.
|
||||
* Executes multiple fills atomically in a single transaction.
|
||||
* If shouldCheckTransfer is set to true, it will continue filling subsequent orders even when earlier ones fail.
|
||||
* When shouldCheckTransfer is set to false, if any fill fails, the entire batch fails.
|
||||
* @param orderFillRequests An array of JS objects that conform to the OrderFillRequest interface.
|
||||
* @param shouldCheckTransfer Whether or not you wish for the contract call to throw if upon
|
||||
* execution any of the tokens cannot be transferred. If set to false,
|
||||
* the call will continue to fill subsequent signedOrders even when some
|
||||
* cannot be filled.
|
||||
* @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.
|
||||
* If shouldThrowOnInsufficientBalanceOrAllowance is set to true, it will continue filling subsequent orders even
|
||||
* when earlier ones fail.
|
||||
* When shouldThrowOnInsufficientBalanceOrAllowance is set to false, if any fill fails, the entire batch fails.
|
||||
* @param orderFillRequests An array of objects that conform to the
|
||||
* OrderFillRequest interface.
|
||||
* @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw
|
||||
* if upon execution any of the tokens cannot be
|
||||
* transferred. If set to false, the call will continue to
|
||||
* fill subsequent signedOrders even when some
|
||||
* cannot be filled.
|
||||
* @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.
|
||||
*/
|
||||
@decorators.contractCallErrorHandler
|
||||
public async batchFillOrderAsync(orderFillRequests: OrderFillRequest[],
|
||||
shouldCheckTransfer: boolean, takerAddress: string): Promise<void> {
|
||||
assert.isBoolean('shouldCheckTransfer', shouldCheckTransfer);
|
||||
public async batchFillOrdersAsync(orderFillRequests: OrderFillRequest[],
|
||||
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
|
||||
takerAddress: string): Promise<void> {
|
||||
assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema);
|
||||
const exchangeContractAddresses = _.map(
|
||||
orderFillRequests,
|
||||
orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress,
|
||||
);
|
||||
assert.hasAtMostOneUniqueValue(exchangeContractAddresses,
|
||||
ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress);
|
||||
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
|
||||
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
|
||||
assert.doesConformToSchema('orderFillRequests', orderFillRequests, orderFillRequestsSchema);
|
||||
for (const orderFillRequest of orderFillRequests) {
|
||||
await this._validateFillOrderAndThrowIfInvalidAsync(
|
||||
await this.validateFillOrderThrowIfInvalidAsync(
|
||||
orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount, takerAddress);
|
||||
}
|
||||
if (_.isEmpty(orderFillRequests)) {
|
||||
@@ -286,16 +320,16 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
];
|
||||
});
|
||||
// We use _.unzip<any> because _.unzip doesn't type check if values have different types :'(
|
||||
const [orderAddressesArray, orderValuesArray, takerTokenFillAmountArray, vArray, rArray, sArray] = _.unzip<any>(
|
||||
const [orderAddressesArray, orderValuesArray, fillTakerTokenAmounts, vArray, rArray, sArray] = _.unzip<any>(
|
||||
orderAddressesValuesAmountsAndSignatureArray,
|
||||
);
|
||||
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
const gas = await exchangeInstance.batchFill.estimateGas(
|
||||
const gas = await exchangeInstance.batchFillOrders.estimateGas(
|
||||
orderAddressesArray,
|
||||
orderValuesArray,
|
||||
takerTokenFillAmountArray,
|
||||
shouldCheckTransfer,
|
||||
fillTakerTokenAmounts,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
vArray,
|
||||
rArray,
|
||||
sArray,
|
||||
@@ -303,11 +337,11 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
const response: ContractResponse = await exchangeInstance.batchFill(
|
||||
const response: ContractResponse = await exchangeInstance.batchFillOrders(
|
||||
orderAddressesArray,
|
||||
orderValuesArray,
|
||||
takerTokenFillAmountArray,
|
||||
shouldCheckTransfer,
|
||||
fillTakerTokenAmounts,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
vArray,
|
||||
rArray,
|
||||
sArray,
|
||||
@@ -321,31 +355,29 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
/**
|
||||
* Attempts to fill a specific amount of an order. If the entire amount specified cannot be filled,
|
||||
* the fill order is abandoned.
|
||||
* @param signedOrder A JS object that conforms to the SignedOrder interface. The
|
||||
* @param signedOrder An object that conforms to the SignedOrder interface. The
|
||||
* signedOrder you wish to fill.
|
||||
* @param takerTokenFillAmount The total amount of the takerTokens you would like to fill.
|
||||
* @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.
|
||||
*/
|
||||
@decorators.contractCallErrorHandler
|
||||
public async fillOrKillOrderAsync(signedOrder: SignedOrder, takerTokenFillAmount: BigNumber.BigNumber,
|
||||
public async fillOrKillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber.BigNumber,
|
||||
takerAddress: string): Promise<void> {
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, signedOrderSchema);
|
||||
assert.isBigNumber('takerTokenFillAmount', takerTokenFillAmount);
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
|
||||
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
|
||||
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
await this._validateFillOrderAndThrowIfInvalidAsync(signedOrder, takerTokenFillAmount, takerAddress);
|
||||
|
||||
await this._validateFillOrKillOrderAndThrowIfInvalidAsync(signedOrder, exchangeInstance.address,
|
||||
takerTokenFillAmount);
|
||||
await this.validateFillOrKillOrderThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress);
|
||||
|
||||
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder);
|
||||
|
||||
const gas = await exchangeInstance.fillOrKill.estimateGas(
|
||||
const gas = await exchangeInstance.fillOrKillOrder.estimateGas(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
takerTokenFillAmount,
|
||||
fillTakerTokenAmount,
|
||||
signedOrder.ecSignature.v,
|
||||
signedOrder.ecSignature.r,
|
||||
signedOrder.ecSignature.s,
|
||||
@@ -353,10 +385,10 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
const response: ContractResponse = await exchangeInstance.fillOrKill(
|
||||
const response: ContractResponse = await exchangeInstance.fillOrKillOrder(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
takerTokenFillAmount,
|
||||
fillTakerTokenAmount,
|
||||
signedOrder.ecSignature.v,
|
||||
signedOrder.ecSignature.r,
|
||||
signedOrder.ecSignature.s,
|
||||
@@ -370,19 +402,29 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
/**
|
||||
* 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 JS objects that conform to the OrderFillOrKillRequest interface.
|
||||
* @param orderFillOrKillRequests An array of objects that conform to the OrderFillOrKillRequest 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.
|
||||
*/
|
||||
@decorators.contractCallErrorHandler
|
||||
public async batchFillOrKillAsync(orderFillOrKillRequests: OrderFillOrKillRequest[],
|
||||
takerAddress: string): Promise<void> {
|
||||
assert.doesConformToSchema('orderFillOrKillRequests', orderFillOrKillRequests,
|
||||
schemas.orderFillOrKillRequestsSchema);
|
||||
const exchangeContractAddresses = _.map(
|
||||
orderFillOrKillRequests,
|
||||
orderFillOrKillRequest => orderFillOrKillRequest.signedOrder.exchangeContractAddress,
|
||||
);
|
||||
assert.hasAtMostOneUniqueValue(exchangeContractAddresses,
|
||||
ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress);
|
||||
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
|
||||
assert.doesConformToSchema('orderFillOrKillRequests', orderFillOrKillRequests, orderFillOrKillRequestsSchema);
|
||||
if (_.isEmpty(orderFillOrKillRequests)) {
|
||||
return; // no-op
|
||||
}
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
for (const request of orderFillOrKillRequests) {
|
||||
await this._validateFillOrKillOrderAndThrowIfInvalidAsync(request.signedOrder, exchangeInstance.address,
|
||||
request.fillTakerAmount);
|
||||
await this.validateFillOrKillOrderThrowIfInvalidAsync(
|
||||
request.signedOrder, request.fillTakerAmount, takerAddress);
|
||||
}
|
||||
|
||||
const orderAddressesValuesAndTakerTokenFillAmounts = _.map(orderFillOrKillRequests, request => {
|
||||
@@ -396,13 +438,13 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
});
|
||||
|
||||
// We use _.unzip<any> because _.unzip doesn't type check if values have different types :'(
|
||||
const [orderAddresses, orderValues, fillTakerAmounts, vParams, rParams, sParams] =
|
||||
const [orderAddresses, orderValues, fillTakerTokenAmounts, vParams, rParams, sParams] =
|
||||
_.unzip<any>(orderAddressesValuesAndTakerTokenFillAmounts);
|
||||
|
||||
const gas = await exchangeInstance.batchFillOrKill.estimateGas(
|
||||
const gas = await exchangeInstance.batchFillOrKillOrders.estimateGas(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
fillTakerAmounts,
|
||||
fillTakerTokenAmounts,
|
||||
vParams,
|
||||
rParams,
|
||||
sParams,
|
||||
@@ -410,10 +452,10 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
const response: ContractResponse = await exchangeInstance.batchFillOrKill(
|
||||
const response: ContractResponse = await exchangeInstance.batchFillOrKillOrders(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
fillTakerAmounts,
|
||||
fillTakerTokenAmounts,
|
||||
vParams,
|
||||
rParams,
|
||||
sParams,
|
||||
@@ -426,56 +468,66 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
}
|
||||
/**
|
||||
* Cancel a given fill amount of an order. Cancellations are cumulative.
|
||||
* @param order A JS object that conforms to the Order or SignedOrder interface.
|
||||
* @param order An object that conforms to the Order or SignedOrder interface.
|
||||
* The order you would like to cancel.
|
||||
* @param takerTokenCancelAmount The amount (specified in taker tokens) that 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).
|
||||
*/
|
||||
@decorators.contractCallErrorHandler
|
||||
public async cancelOrderAsync(
|
||||
order: Order|SignedOrder, takerTokenCancelAmount: BigNumber.BigNumber): Promise<void> {
|
||||
assert.doesConformToSchema('order', order, orderSchema);
|
||||
assert.isBigNumber('takerTokenCancelAmount', takerTokenCancelAmount);
|
||||
order: Order|SignedOrder, cancelTakerTokenAmount: BigNumber.BigNumber): Promise<BigNumber.BigNumber> {
|
||||
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._validateCancelOrderAndThrowIfInvalidAsync(order, takerTokenCancelAmount);
|
||||
await this.validateCancelOrderThrowIfInvalidAsync(order, cancelTakerTokenAmount);
|
||||
|
||||
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order);
|
||||
const gas = await exchangeInstance.cancel.estimateGas(
|
||||
const gas = await exchangeInstance.cancelOrder.estimateGas(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
takerTokenCancelAmount,
|
||||
cancelTakerTokenAmount,
|
||||
{
|
||||
from: order.maker,
|
||||
},
|
||||
);
|
||||
const response: ContractResponse = await exchangeInstance.cancel(
|
||||
const response: ContractResponse = await exchangeInstance.cancelOrder(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
takerTokenCancelAmount,
|
||||
cancelTakerTokenAmount,
|
||||
{
|
||||
from: order.maker,
|
||||
gas,
|
||||
},
|
||||
);
|
||||
this._throwErrorLogsAsErrors(response.logs);
|
||||
const logFillArgs = response.logs[0].args as LogCancelContractEventArgs;
|
||||
const cancelledTakerTokenAmount = new BigNumber(logFillArgs.cancelledTakerTokenAmount);
|
||||
return cancelledTakerTokenAmount;
|
||||
}
|
||||
/**
|
||||
* 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 JS objects that conform to the OrderCancellationRequest
|
||||
* @param orderCancellationRequests An array of objects that conform to the OrderCancellationRequest
|
||||
* interface.
|
||||
*/
|
||||
@decorators.contractCallErrorHandler
|
||||
public async batchCancelOrderAsync(orderCancellationRequests: OrderCancellationRequest[]): Promise<void> {
|
||||
public async batchCancelOrdersAsync(orderCancellationRequests: OrderCancellationRequest[]): Promise<void> {
|
||||
assert.doesConformToSchema('orderCancellationRequests', orderCancellationRequests,
|
||||
schemas.orderCancellationRequestsSchema);
|
||||
const exchangeContractAddresses = _.map(
|
||||
orderCancellationRequests,
|
||||
orderCancellationRequest => orderCancellationRequest.order.exchangeContractAddress,
|
||||
);
|
||||
assert.hasAtMostOneUniqueValue(exchangeContractAddresses,
|
||||
ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress);
|
||||
const makers = _.map(orderCancellationRequests, cancellationRequest => cancellationRequest.order.maker);
|
||||
assert.hasAtMostOneUniqueValue(makers, ExchangeContractErrs.MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH_DISALLOWED);
|
||||
assert.hasAtMostOneUniqueValue(makers, ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed);
|
||||
const maker = makers[0];
|
||||
await assert.isSenderAddressAsync('maker', maker, this._web3Wrapper);
|
||||
assert.doesConformToSchema('orderCancellationRequests', orderCancellationRequests,
|
||||
orderCancellationRequestsSchema);
|
||||
for (const cancellationRequest of orderCancellationRequests) {
|
||||
await this._validateCancelOrderAndThrowIfInvalidAsync(
|
||||
await this.validateCancelOrderThrowIfInvalidAsync(
|
||||
cancellationRequest.order, cancellationRequest.takerTokenCancelAmount,
|
||||
);
|
||||
}
|
||||
@@ -490,20 +542,20 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
];
|
||||
});
|
||||
// We use _.unzip<any> because _.unzip doesn't type check if values have different types :'(
|
||||
const [orderAddresses, orderValues, takerTokenCancelAmounts] =
|
||||
const [orderAddresses, orderValues, cancelTakerTokenAmounts] =
|
||||
_.unzip<any>(orderAddressesValuesAndTakerTokenCancelAmounts);
|
||||
const gas = await exchangeInstance.batchCancel.estimateGas(
|
||||
const gas = await exchangeInstance.batchCancelOrders.estimateGas(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
takerTokenCancelAmounts,
|
||||
cancelTakerTokenAmounts,
|
||||
{
|
||||
from: maker,
|
||||
},
|
||||
);
|
||||
const response: ContractResponse = await exchangeInstance.batchCancel(
|
||||
const response: ContractResponse = await exchangeInstance.batchCancelOrders(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
takerTokenCancelAmounts,
|
||||
cancelTakerTokenAmounts,
|
||||
{
|
||||
from: maker,
|
||||
gas,
|
||||
@@ -513,15 +565,20 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
}
|
||||
/**
|
||||
* 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 A JS 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 The callback that will be called everytime a matching event is found.
|
||||
* @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
|
||||
*/
|
||||
public async subscribeAsync(eventName: ExchangeEvents, subscriptionOpts: SubscriptionOpts,
|
||||
indexFilterValues: IndexFilterValues, callback: EventCallback):
|
||||
Promise<void> {
|
||||
indexFilterValues: IndexedFilterValues, exchangeContractAddress: string):
|
||||
Promise<ContractEventEmitter> {
|
||||
assert.isETHAddressHex('exchangeContractAddress', exchangeContractAddress);
|
||||
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) {
|
||||
@@ -535,18 +592,113 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
createLogEvent = exchangeContract.LogCancel;
|
||||
break;
|
||||
default:
|
||||
utils.spawnSwitchErr('ExchangeEvents', eventName);
|
||||
return;
|
||||
throw utils.spawnSwitchErr('ExchangeEvents', eventName);
|
||||
}
|
||||
|
||||
const logEventObj: ContractEventObj = createLogEvent(indexFilterValues, subscriptionOpts);
|
||||
logEventObj.watch(callback);
|
||||
this._exchangeLogEventObjs.push(logEventObj);
|
||||
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 = [];
|
||||
}
|
||||
/**
|
||||
* Retrieves the Ethereum address of the Exchange contract deployed on the network
|
||||
* that the user-passed web3 provider is connected to.
|
||||
* @returns The Ethereum address of the Exchange contract being used.
|
||||
*/
|
||||
public async getContractAddressAsync(): Promise<string> {
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
const exchangeAddress = exchangeInstance.address;
|
||||
return exchangeAddress;
|
||||
}
|
||||
/**
|
||||
* Checks if order fill will succeed and throws an error otherwise.
|
||||
* @param signedOrder An object that conforms to the SignedOrder interface. The
|
||||
* signedOrder you wish to fill.
|
||||
* @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.
|
||||
*/
|
||||
public async validateFillOrderThrowIfInvalidAsync(signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber.BigNumber,
|
||||
takerAddress: string): Promise<void> {
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
|
||||
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
|
||||
const zrxTokenAddress = await this._getZRXTokenAddressAsync();
|
||||
await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
|
||||
}
|
||||
/**
|
||||
* Checks if cancelling a given order will succeed and throws an informative error if it won't.
|
||||
* @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.
|
||||
*/
|
||||
public async validateCancelOrderThrowIfInvalidAsync(
|
||||
order: Order, cancelTakerTokenAmount: BigNumber.BigNumber): Promise<void> {
|
||||
assert.doesConformToSchema('order', order, schemas.orderSchema);
|
||||
assert.isBigNumber('cancelTakerTokenAmount', cancelTakerTokenAmount);
|
||||
const orderHash = utils.getOrderHashHex(order);
|
||||
const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash);
|
||||
await this._orderValidationUtils.validateCancelOrderThrowIfInvalidAsync(
|
||||
order, cancelTakerTokenAmount, unavailableTakerTokenAmount);
|
||||
}
|
||||
/**
|
||||
* Checks if calling fillOrKill on a given order will succeed and throws an informative error if it won't.
|
||||
* @param signedOrder An object that conforms to the SignedOrder interface. The
|
||||
* signedOrder you wish to fill.
|
||||
* @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.
|
||||
*/
|
||||
public async validateFillOrKillOrderThrowIfInvalidAsync(signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber.BigNumber,
|
||||
takerAddress: string): Promise<void> {
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
|
||||
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
|
||||
const zrxTokenAddress = await this._getZRXTokenAddressAsync();
|
||||
await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
|
||||
}
|
||||
/**
|
||||
* Checks if rounding error will be > 0.1% when computing makerTokenAmount by doing:
|
||||
* `(fillTakerTokenAmount * makerTokenAmount) / takerTokenAmount`.
|
||||
* 0x Protocol does not accept any trades that result in large rounding errors. This means that tokens with few or
|
||||
* no decimals can only be filled in quantities and ratios that avoid large rounding errors.
|
||||
* @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that you wish to fill.
|
||||
* @param takerTokenAmount The order size on the taker side
|
||||
* @param makerTokenAmount The order size on the maker side
|
||||
*/
|
||||
public async isRoundingErrorAsync(fillTakerTokenAmount: BigNumber.BigNumber,
|
||||
takerTokenAmount: BigNumber.BigNumber,
|
||||
makerTokenAmount: BigNumber.BigNumber): Promise<boolean> {
|
||||
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
assert.isBigNumber('takerTokenAmount', takerTokenAmount);
|
||||
assert.isBigNumber('makerTokenAmount', makerTokenAmount);
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
const isRoundingError = await exchangeInstance.isRoundingError.call(
|
||||
fillTakerTokenAmount, takerTokenAmount, makerTokenAmount,
|
||||
);
|
||||
return isRoundingError;
|
||||
}
|
||||
private async _invalidateContractInstancesAsync(): Promise<void> {
|
||||
await this.stopWatchingAllEventsAsync();
|
||||
delete this._exchangeContractIfExists;
|
||||
}
|
||||
private async _isValidSignatureUsingContractCallAsync(dataHex: string, ecSignature: ECSignature,
|
||||
signerAddressHex: string): Promise<boolean> {
|
||||
assert.isHexString('dataHex', dataHex);
|
||||
assert.doesConformToSchema('ecSignature', ecSignature, ecSignatureSchema);
|
||||
assert.doesConformToSchema('ecSignature', ecSignature, schemas.ecSignatureSchema);
|
||||
assert.isETHAddressHex('signerAddressHex', signerAddressHex);
|
||||
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
@@ -560,153 +712,20 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
);
|
||||
return isValidSignature;
|
||||
}
|
||||
private async _getOrderHashHexAsync(order: Order|SignedOrder): Promise<string> {
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
const orderHashHex = utils.getOrderHashHex(order, exchangeInstance.address);
|
||||
return orderHashHex;
|
||||
}
|
||||
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);
|
||||
return orderHashHex;
|
||||
}
|
||||
private async _stopWatchingExchangeLogEventsAsync() {
|
||||
const stopWatchingPromises = _.map(this._exchangeLogEventObjs, logEventObj => {
|
||||
return promisify(logEventObj.stopWatching, logEventObj)();
|
||||
});
|
||||
await Promise.all(stopWatchingPromises);
|
||||
this._exchangeLogEventObjs = [];
|
||||
}
|
||||
private async _validateFillOrderAndThrowIfInvalidAsync(signedOrder: SignedOrder,
|
||||
fillTakerAmount: BigNumber.BigNumber,
|
||||
senderAddress: string): Promise<void> {
|
||||
if (fillTakerAmount.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.ORDER_REMAINING_FILL_AMOUNT_ZERO);
|
||||
}
|
||||
if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== senderAddress) {
|
||||
throw new Error(ExchangeContractErrs.TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER);
|
||||
}
|
||||
const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
|
||||
if (signedOrder.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
|
||||
throw new Error(ExchangeContractErrs.ORDER_FILL_EXPIRED);
|
||||
}
|
||||
const zrxTokenAddress = await this._getZRXTokenAddressAsync();
|
||||
await this._validateFillOrderBalancesAndAllowancesAndThrowIfInvalidAsync(signedOrder, fillTakerAmount,
|
||||
senderAddress, zrxTokenAddress);
|
||||
|
||||
const wouldRoundingErrorOccur = await this._isRoundingErrorAsync(
|
||||
signedOrder.takerTokenAmount, fillTakerAmount, signedOrder.makerTokenAmount,
|
||||
);
|
||||
if (wouldRoundingErrorOccur) {
|
||||
throw new Error(ExchangeContractErrs.ORDER_FILL_ROUNDING_ERROR);
|
||||
}
|
||||
}
|
||||
private async _validateCancelOrderAndThrowIfInvalidAsync(
|
||||
order: Order, takerTokenCancelAmount: BigNumber.BigNumber): Promise<void> {
|
||||
if (takerTokenCancelAmount.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.ORDER_CANCEL_AMOUNT_ZERO);
|
||||
}
|
||||
const orderHash = await this._getOrderHashHexAsync(order);
|
||||
const unavailableAmount = await this.getUnavailableTakerAmountAsync(orderHash);
|
||||
if (order.takerTokenAmount.minus(unavailableAmount).eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.ORDER_ALREADY_CANCELLED_OR_FILLED);
|
||||
}
|
||||
const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
|
||||
if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
|
||||
throw new Error(ExchangeContractErrs.ORDER_CANCEL_EXPIRED);
|
||||
}
|
||||
}
|
||||
private async _validateFillOrKillOrderAndThrowIfInvalidAsync(signedOrder: SignedOrder,
|
||||
exchangeAddress: string,
|
||||
fillTakerAmount: BigNumber.BigNumber) {
|
||||
// Check that fillValue available >= fillTakerAmount
|
||||
const orderHashHex = utils.getOrderHashHex(signedOrder, exchangeAddress);
|
||||
const unavailableTakerAmount = await this.getUnavailableTakerAmountAsync(orderHashHex);
|
||||
const remainingTakerAmount = signedOrder.takerTokenAmount.minus(unavailableTakerAmount);
|
||||
if (remainingTakerAmount < fillTakerAmount) {
|
||||
throw new Error(ExchangeContractErrs.INSUFFICIENT_REMAINING_FILL_AMOUNT);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This method does not currently validate the edge-case where the makerToken or takerToken is also the token used
|
||||
* to pay fees (ZRX). It is possible for them to have enough for fees and the transfer but not both.
|
||||
* Handling the edge-cases that arise when this happens would require making sure that the user has sufficient
|
||||
* funds to pay both the fees and the transfer amount. We decided to punt on this for now as the contracts
|
||||
* will throw for these edge-cases.
|
||||
* TODO: Throw errors before calling the smart contract for these edge-cases in order to minimize
|
||||
* the callers gas costs.
|
||||
*/
|
||||
private async _validateFillOrderBalancesAndAllowancesAndThrowIfInvalidAsync(signedOrder: SignedOrder,
|
||||
fillTakerAmount: BigNumber.BigNumber,
|
||||
senderAddress: string,
|
||||
zrxTokenAddress: string,
|
||||
): Promise<void> {
|
||||
|
||||
const makerBalance = await this._tokenWrapper.getBalanceAsync(signedOrder.makerTokenAddress,
|
||||
signedOrder.maker);
|
||||
const takerBalance = await this._tokenWrapper.getBalanceAsync(signedOrder.takerTokenAddress, senderAddress);
|
||||
const makerAllowance = await this._tokenWrapper.getProxyAllowanceAsync(signedOrder.makerTokenAddress,
|
||||
signedOrder.maker);
|
||||
const takerAllowance = await this._tokenWrapper.getProxyAllowanceAsync(signedOrder.takerTokenAddress,
|
||||
senderAddress);
|
||||
|
||||
// exchangeRate is the price of one maker token denominated in taker tokens
|
||||
const exchangeRate = signedOrder.takerTokenAmount.div(signedOrder.makerTokenAmount);
|
||||
const fillMakerAmountInBaseUnits = fillTakerAmount.div(exchangeRate);
|
||||
|
||||
if (fillTakerAmount.greaterThan(takerBalance)) {
|
||||
throw new Error(ExchangeContractErrs.INSUFFICIENT_TAKER_BALANCE);
|
||||
}
|
||||
if (fillTakerAmount.greaterThan(takerAllowance)) {
|
||||
throw new Error(ExchangeContractErrs.INSUFFICIENT_TAKER_ALLOWANCE);
|
||||
}
|
||||
if (fillMakerAmountInBaseUnits.greaterThan(makerBalance)) {
|
||||
throw new Error(ExchangeContractErrs.INSUFFICIENT_MAKER_BALANCE);
|
||||
}
|
||||
if (fillMakerAmountInBaseUnits.greaterThan(makerAllowance)) {
|
||||
throw new Error(ExchangeContractErrs.INSUFFICIENT_MAKER_ALLOWANCE);
|
||||
}
|
||||
|
||||
const makerFeeBalance = await this._tokenWrapper.getBalanceAsync(zrxTokenAddress,
|
||||
signedOrder.maker);
|
||||
const takerFeeBalance = await this._tokenWrapper.getBalanceAsync(zrxTokenAddress, senderAddress);
|
||||
const makerFeeAllowance = await this._tokenWrapper.getProxyAllowanceAsync(zrxTokenAddress,
|
||||
signedOrder.maker);
|
||||
const takerFeeAllowance = await this._tokenWrapper.getProxyAllowanceAsync(zrxTokenAddress,
|
||||
senderAddress);
|
||||
|
||||
if (signedOrder.takerFee.greaterThan(takerFeeBalance)) {
|
||||
throw new Error(ExchangeContractErrs.INSUFFICIENT_TAKER_FEE_BALANCE);
|
||||
}
|
||||
if (signedOrder.takerFee.greaterThan(takerFeeAllowance)) {
|
||||
throw new Error(ExchangeContractErrs.INSUFFICIENT_TAKER_FEE_ALLOWANCE);
|
||||
}
|
||||
if (signedOrder.makerFee.greaterThan(makerFeeBalance)) {
|
||||
throw new Error(ExchangeContractErrs.INSUFFICIENT_MAKER_FEE_BALANCE);
|
||||
}
|
||||
if (signedOrder.makerFee.greaterThan(makerFeeAllowance)) {
|
||||
throw new Error(ExchangeContractErrs.INSUFFICIENT_MAKER_FEE_ALLOWANCE);
|
||||
}
|
||||
}
|
||||
private _throwErrorLogsAsErrors(logs: ContractEvent[]): void {
|
||||
const errEvent = _.find(logs, {event: 'LogError'});
|
||||
if (!_.isUndefined(errEvent)) {
|
||||
const errCode = errEvent.args.errorId.toNumber();
|
||||
const errCode = (errEvent.args as LogErrorContractEventArgs).errorId.toNumber();
|
||||
const errMessage = this._exchangeContractErrCodesToMsg[errCode];
|
||||
throw new Error(errMessage);
|
||||
}
|
||||
}
|
||||
private async _isRoundingErrorAsync(takerTokenAmount: BigNumber.BigNumber,
|
||||
fillTakerAmount: BigNumber.BigNumber,
|
||||
makerTokenAmount: BigNumber.BigNumber): Promise<boolean> {
|
||||
await assert.isUserAddressAvailableAsync(this._web3Wrapper);
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
const isRoundingError = await exchangeInstance.isRoundingError.call(
|
||||
takerTokenAmount, fillTakerAmount, makerTokenAmount,
|
||||
);
|
||||
return isRoundingError;
|
||||
}
|
||||
private async _getExchangeContractAsync(): Promise<ExchangeContract> {
|
||||
if (!_.isUndefined(this._exchangeContractIfExists)) {
|
||||
return this._exchangeContractIfExists;
|
||||
@@ -717,6 +736,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
}
|
||||
private async _getZRXTokenAddressAsync(): Promise<string> {
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
return exchangeInstance.ZRX.call();
|
||||
const ZRXtokenAddress = await exchangeInstance.ZRX_TOKEN_CONTRACT.call();
|
||||
return ZRXtokenAddress;
|
||||
}
|
||||
}
|
||||
|
@@ -1,41 +1,101 @@
|
||||
import * as _ from 'lodash';
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {Token, TokenRegistryContract, TokenMetadata} from '../types';
|
||||
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';
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
super(web3Wrapper);
|
||||
}
|
||||
public invalidateContractInstance(): void {
|
||||
delete this._tokenRegistryContractIfExists;
|
||||
constructor(web3Wrapper: Web3Wrapper, gasPrice?: BigNumber.BigNumber) {
|
||||
super(web3Wrapper, gasPrice);
|
||||
}
|
||||
/**
|
||||
* Retrieves all the tokens currently listed in the Token Registry smart contract
|
||||
* @return An array of JS objects that conform to the Token interface.
|
||||
* @return An array of objects that conform to the Token interface.
|
||||
*/
|
||||
public async getTokensAsync(): Promise<Token[]> {
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
|
||||
const addresses = await tokenRegistryContract.getTokenAddresses.call();
|
||||
const tokenMetadataPromises: Array<Promise<TokenMetadata>> = _.map(
|
||||
const addresses = await this.getTokenAddressesAsync();
|
||||
const tokenPromises: Array<Promise<Token|undefined>> = _.map(
|
||||
addresses,
|
||||
(address: string) => (tokenRegistryContract.getTokenMetaData.call(address)),
|
||||
(address: string) => (this.getTokenIfExistsAsync(address)),
|
||||
);
|
||||
const tokensMetadata = await Promise.all(tokenMetadataPromises);
|
||||
const tokens = _.map(tokensMetadata, metadata => {
|
||||
return {
|
||||
address: metadata[0],
|
||||
name: metadata[1],
|
||||
symbol: metadata[2],
|
||||
url: metadata[3],
|
||||
decimals: metadata[4].toNumber(),
|
||||
};
|
||||
});
|
||||
return tokens;
|
||||
const tokens = await Promise.all(tokenPromises);
|
||||
return tokens as Token[];
|
||||
}
|
||||
/**
|
||||
* Retrieves all the addresses of the tokens currently listed in the Token Registry smart contract
|
||||
* @return An array of token addresses.
|
||||
*/
|
||||
public async getTokenAddressesAsync(): Promise<string[]> {
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
const addresses = await tokenRegistryContract.getTokenAddresses.call();
|
||||
return addresses;
|
||||
}
|
||||
/**
|
||||
* Retrieves a token by address currently listed in the Token Registry smart contract
|
||||
* @return An object that conforms to the Token interface or undefined if token not found.
|
||||
*/
|
||||
public async getTokenIfExistsAsync(address: string): Promise<Token|undefined> {
|
||||
assert.isETHAddressHex('address', address);
|
||||
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
const metadata = await tokenRegistryContract.getTokenMetaData.call(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);
|
||||
if (addressIfExists === constants.NULL_ADDRESS) {
|
||||
return undefined;
|
||||
}
|
||||
return addressIfExists;
|
||||
}
|
||||
public async getTokenAddressByNameIfExistsAsync(name: string): Promise<string|undefined> {
|
||||
assert.isString('name', name);
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
const addressIfExists = await tokenRegistryContract.getTokenAddressByName.call(name);
|
||||
if (addressIfExists === constants.NULL_ADDRESS) {
|
||||
return undefined;
|
||||
}
|
||||
return addressIfExists;
|
||||
}
|
||||
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 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 token = this._createTokenFromMetadata(metadata);
|
||||
return token;
|
||||
}
|
||||
private _createTokenFromMetadata(metadata: TokenMetadata): Token|undefined {
|
||||
if (metadata[0] === constants.NULL_ADDRESS) {
|
||||
return undefined;
|
||||
}
|
||||
const token = {
|
||||
address: metadata[0],
|
||||
name: metadata[1],
|
||||
symbol: metadata[2],
|
||||
decimals: metadata[3].toNumber(),
|
||||
};
|
||||
return token;
|
||||
}
|
||||
private _invalidateContractInstance(): void {
|
||||
delete this._tokenRegistryContractIfExists;
|
||||
}
|
||||
private async _getTokenRegistryContractAsync(): Promise<TokenRegistryContract> {
|
||||
if (!_.isUndefined(this._tokenRegistryContractIfExists)) {
|
||||
|
51
src/contract_wrappers/token_transfer_proxy_wrapper.ts
Normal file
51
src/contract_wrappers/token_transfer_proxy_wrapper.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import * as _ from 'lodash';
|
||||
import {ContractWrapper} from './contract_wrapper';
|
||||
import * as TokenTransferProxyArtifacts from '../artifacts/TokenTransferProxy.json';
|
||||
import {TokenTransferProxyContract} from '../types';
|
||||
|
||||
/**
|
||||
* This class includes the functionality related to interacting with the TokenTransferProxy contract.
|
||||
*/
|
||||
export class TokenTransferProxyWrapper extends ContractWrapper {
|
||||
private _tokenTransferProxyContractIfExists?: TokenTransferProxyContract;
|
||||
/**
|
||||
* Check if the Exchange contract address is authorized by the TokenTransferProxy contract.
|
||||
* @param exchangeContractAddress The hex encoded address of the Exchange contract to call.
|
||||
* @return Whether the exchangeContractAddress is authorized.
|
||||
*/
|
||||
public async isAuthorizedAsync(exchangeContractAddress: string): Promise<boolean> {
|
||||
const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync();
|
||||
const isAuthorized = await tokenTransferProxyContractInstance.authorized.call(exchangeContractAddress);
|
||||
return isAuthorized;
|
||||
}
|
||||
/**
|
||||
* Get the list of all Exchange contract addresses authorized by the TokenTransferProxy contract.
|
||||
* @return The list of authorized addresses.
|
||||
*/
|
||||
public async getAuthorizedAddressesAsync(): Promise<string[]> {
|
||||
const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync();
|
||||
const authorizedAddresses = await tokenTransferProxyContractInstance.getAuthorizedAddresses.call();
|
||||
return authorizedAddresses;
|
||||
}
|
||||
/**
|
||||
* Retrieves the Ethereum address of the TokenTransferProxy contract deployed on the network
|
||||
* that the user-passed web3 provider is connected to.
|
||||
* @returns The Ethereum address of the TokenTransferProxy contract being used.
|
||||
*/
|
||||
public async getContractAddressAsync(): Promise<string> {
|
||||
const proxyInstance = await this._getTokenTransferProxyContractAsync();
|
||||
const proxyAddress = proxyInstance.address;
|
||||
return proxyAddress;
|
||||
}
|
||||
private _invalidateContractInstance(): void {
|
||||
delete this._tokenTransferProxyContractIfExists;
|
||||
}
|
||||
private async _getTokenTransferProxyContractAsync(): Promise<TokenTransferProxyContract> {
|
||||
if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) {
|
||||
return this._tokenTransferProxyContractIfExists;
|
||||
}
|
||||
const contractInstance = await this._instantiateContractIfExistsAsync((TokenTransferProxyArtifacts as any));
|
||||
this._tokenTransferProxyContractIfExists = contractInstance as TokenTransferProxyContract;
|
||||
return this._tokenTransferProxyContractIfExists;
|
||||
}
|
||||
}
|
@@ -1,33 +1,50 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import {SchemaValidator, 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 ProxyArtifacts from '../artifacts/Proxy.json';
|
||||
import {TokenContract, ZeroExError} from '../types';
|
||||
import * as TokenTransferProxyArtifacts from '../artifacts/TokenTransferProxy.json';
|
||||
import {
|
||||
TokenContract,
|
||||
ZeroExError,
|
||||
TokenEvents,
|
||||
IndexedFilterValues,
|
||||
SubscriptionOpts,
|
||||
CreateContractEvent,
|
||||
ContractEventEmitter,
|
||||
ContractEventObj,
|
||||
} from '../types';
|
||||
|
||||
const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 45730;
|
||||
const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 47155;
|
||||
|
||||
/**
|
||||
* This class includes all the functionality related to interacting with ERC20 token contracts.
|
||||
* All ERC20 method calls are supported, along with some convenience methods for getting/setting allowances
|
||||
* to the 0x Proxy smart contract.
|
||||
*/
|
||||
export class TokenWrapper extends ContractWrapper {
|
||||
public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
|
||||
private _tokenContractsByAddress: {[address: string]: TokenContract};
|
||||
constructor(web3Wrapper: Web3Wrapper) {
|
||||
super(web3Wrapper);
|
||||
this._tokenContractsByAddress = {};
|
||||
}
|
||||
public invalidateContractInstances() {
|
||||
private _tokenLogEventEmitters: ContractEventEmitter[];
|
||||
constructor(web3Wrapper: Web3Wrapper, gasPrice?: BigNumber.BigNumber) {
|
||||
super(web3Wrapper, gasPrice);
|
||||
this._tokenContractsByAddress = {};
|
||||
this._tokenLogEventEmitters = [];
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
* @return The owner's ERC20 token balance in base units.
|
||||
*/
|
||||
public async getBalanceAsync(tokenAddress: string, ownerAddress: string): Promise<BigNumber.BigNumber> {
|
||||
assert.isETHAddressHex('ownerAddress', ownerAddress);
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
await assert.isUserAddressAvailableAsync(this._web3Wrapper);
|
||||
|
||||
const tokenContract = await this._getTokenContractAsync(tokenAddress);
|
||||
let balance = await tokenContract.balanceOf.call(ownerAddress);
|
||||
@@ -62,6 +79,22 @@ export class TokenWrapper extends ContractWrapper {
|
||||
gas,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Sets the spender's allowance to an unlimited number of baseUnits on behalf of the owner address.
|
||||
* Equivalent to the ERC20 spec method `approve`.
|
||||
* Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating
|
||||
* allowances set to the max amount (e.g ZRX, WETH)
|
||||
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
|
||||
* @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.
|
||||
*/
|
||||
public async setUnlimitedAllowanceAsync(tokenAddress: string, ownerAddress: string,
|
||||
spenderAddress: string): Promise<void> {
|
||||
await this.setAllowanceAsync(
|
||||
tokenAddress, ownerAddress, spenderAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Retrieves the owners allowance in baseUnits set to the spender's address.
|
||||
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
|
||||
@@ -72,7 +105,6 @@ export class TokenWrapper extends ContractWrapper {
|
||||
public async getAllowanceAsync(tokenAddress: string, ownerAddress: string, spenderAddress: string) {
|
||||
assert.isETHAddressHex('ownerAddress', ownerAddress);
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
await assert.isUserAddressAvailableAsync(this._web3Wrapper);
|
||||
|
||||
const tokenContract = await this._getTokenContractAsync(tokenAddress);
|
||||
let allowanceInBaseUnits = await tokenContract.allowance.call(ownerAddress, spenderAddress);
|
||||
@@ -110,6 +142,18 @@ export class TokenWrapper extends ContractWrapper {
|
||||
const proxyAddress = await this._getProxyAddressAsync();
|
||||
await this.setAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, amountInBaseUnits);
|
||||
}
|
||||
/**
|
||||
* Sets the 0x proxy contract's allowance to a unlimited number of a tokens' baseUnits on behalf
|
||||
* of an owner address.
|
||||
* Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating
|
||||
* allowances set to the max amount (e.g ZRX, WETH)
|
||||
* @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.
|
||||
*/
|
||||
public async setUnlimitedProxyAllowanceAsync(tokenAddress: string, ownerAddress: string): Promise<void> {
|
||||
await this.setProxyAllowanceAsync(tokenAddress, ownerAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
}
|
||||
/**
|
||||
* Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`.
|
||||
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
|
||||
@@ -128,7 +172,7 @@ export class TokenWrapper extends ContractWrapper {
|
||||
|
||||
const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress);
|
||||
if (fromAddressBalance.lessThan(amountInBaseUnits)) {
|
||||
throw new Error(ZeroExError.INSUFFICIENT_BALANCE_FOR_TRANSFER);
|
||||
throw new Error(ZeroExError.InsufficientBalanceForTransfer);
|
||||
}
|
||||
|
||||
await tokenContract.transfer(toAddress, amountInBaseUnits, {
|
||||
@@ -138,12 +182,13 @@ export class TokenWrapper extends ContractWrapper {
|
||||
/**
|
||||
* Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`.
|
||||
* Requires the fromAddress to have sufficient funds and to have approved an allowance of
|
||||
* `amountInBaseUnits` for senderAddress.
|
||||
* `amountInBaseUnits` to `senderAddress`.
|
||||
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
|
||||
* @param fromAddress The hex encoded user Ethereum address that will send the funds.
|
||||
* @param fromAddress The hex encoded user Ethereum address whose funds are being sent.
|
||||
* @param toAddress The hex encoded user Ethereum address that will receive the funds.
|
||||
* @param senderAddress The hex encoded user Ethereum address whose funds are being sent. The senderAddress
|
||||
* must have set an allowance to the fromAddress before this call.
|
||||
* @param senderAddress The hex encoded user Ethereum address whose initiates the fund transfer. The
|
||||
* `fromAddress` must have set an allowance to the `senderAddress`
|
||||
* before this call.
|
||||
* @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer.
|
||||
*/
|
||||
public async transferFromAsync(tokenAddress: string, fromAddress: string, toAddress: string,
|
||||
@@ -159,18 +204,64 @@ export class TokenWrapper extends ContractWrapper {
|
||||
|
||||
const fromAddressAllowance = await this.getAllowanceAsync(tokenAddress, fromAddress, senderAddress);
|
||||
if (fromAddressAllowance.lessThan(amountInBaseUnits)) {
|
||||
throw new Error(ZeroExError.INSUFFICIENT_ALLOWANCE_FOR_TRANSFER);
|
||||
throw new Error(ZeroExError.InsufficientAllowanceForTransfer);
|
||||
}
|
||||
|
||||
const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress);
|
||||
if (fromAddressBalance.lessThan(amountInBaseUnits)) {
|
||||
throw new Error(ZeroExError.INSUFFICIENT_BALANCE_FOR_TRANSFER);
|
||||
throw new Error(ZeroExError.InsufficientBalanceForTransfer);
|
||||
}
|
||||
|
||||
await tokenContract.transferFrom(fromAddress, toAddress, amountInBaseUnits, {
|
||||
from: senderAddress,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public async subscribeAsync(tokenAddress: string, eventName: TokenEvents, subscriptionOpts: SubscriptionOpts,
|
||||
indexFilterValues: IndexedFilterValues): Promise<ContractEventEmitter> {
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* Stops watching for all token events
|
||||
*/
|
||||
public async stopWatchingAllEventsAsync(): Promise<void> {
|
||||
const stopWatchingPromises = _.map(this._tokenLogEventEmitters,
|
||||
logEventObj => logEventObj.stopWatchingAsync());
|
||||
await Promise.all(stopWatchingPromises);
|
||||
this._tokenLogEventEmitters = [];
|
||||
}
|
||||
private async _invalidateContractInstancesAsync(): Promise<void> {
|
||||
await this.stopWatchingAllEventsAsync();
|
||||
this._tokenContractsByAddress = {};
|
||||
}
|
||||
private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> {
|
||||
let tokenContract = this._tokenContractsByAddress[tokenAddress];
|
||||
if (!_.isUndefined(tokenContract)) {
|
||||
@@ -185,11 +276,11 @@ export class TokenWrapper extends ContractWrapper {
|
||||
const networkIdIfExists = await this._web3Wrapper.getNetworkIdIfExistsAsync();
|
||||
const proxyNetworkConfigsIfExists = _.isUndefined(networkIdIfExists) ?
|
||||
undefined :
|
||||
(ProxyArtifacts as any).networks[networkIdIfExists];
|
||||
(TokenTransferProxyArtifacts as any).networks[networkIdIfExists];
|
||||
if (_.isUndefined(proxyNetworkConfigsIfExists)) {
|
||||
throw new Error(ZeroExError.CONTRACT_NOT_DEPLOYED_ON_NETWORK);
|
||||
throw new Error(ZeroExError.ContractNotDeployedOnNetwork);
|
||||
}
|
||||
const proxyAddress = proxyNetworkConfigsIfExists.address;
|
||||
const proxyAddress = proxyNetworkConfigsIfExists.address.toLowerCase();
|
||||
return proxyAddress;
|
||||
}
|
||||
}
|
||||
|
27
src/globals.d.ts
vendored
27
src/globals.d.ts
vendored
@@ -1,16 +1,12 @@
|
||||
/// <reference types='chai-typescript-typings' />
|
||||
/// <reference types='chai-as-promised-typescript-typings' />
|
||||
declare module 'web3_beta';
|
||||
declare module 'chai-bignumber';
|
||||
declare module 'dirty-chai';
|
||||
declare module 'bn.js';
|
||||
declare module 'request-promise-native';
|
||||
declare module 'web3-provider-engine';
|
||||
declare module 'web3-provider-engine/subproviders/rpc';
|
||||
|
||||
declare interface Schema {
|
||||
id: string;
|
||||
}
|
||||
|
||||
// HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion
|
||||
// interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise
|
||||
// disallow `namespace`, we disable tslint for the following.
|
||||
@@ -37,17 +33,6 @@ declare module '*.json' {
|
||||
/* tslint:enable */
|
||||
}
|
||||
|
||||
declare module 'ethereumjs-util' {
|
||||
const toBuffer: (dataHex: string) => Buffer;
|
||||
const hashPersonalMessage: (msg: Buffer) => Buffer;
|
||||
const bufferToHex: (buff: Buffer) => string;
|
||||
const ecrecover: (msgHashBuff: Buffer, v: number, r: Buffer, s: Buffer) => string;
|
||||
const pubToAddress: (pubKey: string) => Buffer;
|
||||
const isValidAddress: (address: string) => boolean;
|
||||
const bufferToInt: (buffer: Buffer) => number;
|
||||
const fromRpcSig: (signature: string) => {v: number, r: Buffer, s: Buffer};
|
||||
}
|
||||
|
||||
// truffle-contract declarations
|
||||
declare interface ContractInstance {
|
||||
address: string;
|
||||
@@ -55,6 +40,8 @@ declare interface ContractInstance {
|
||||
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 {
|
||||
@@ -86,3 +73,11 @@ declare module 'es6-promisify' {
|
||||
declare module 'ethereumjs-abi' {
|
||||
const soliditySHA3: (argTypes: string[], args: any[]) => Buffer;
|
||||
}
|
||||
|
||||
// truffle-hdwallet-provider declarations
|
||||
declare class HDWalletProvider {
|
||||
constructor(mnemonic: string, rpcUrl: string);
|
||||
}
|
||||
declare module 'truffle-hdwallet-provider' {
|
||||
export = HDWalletProvider;
|
||||
}
|
||||
|
33
src/index.ts
Normal file
33
src/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export {ZeroEx} from './0x';
|
||||
|
||||
export {
|
||||
Order,
|
||||
SignedOrder,
|
||||
ECSignature,
|
||||
ZeroExError,
|
||||
EventCallback,
|
||||
EventCallbackAsync,
|
||||
EventCallbackSync,
|
||||
ExchangeContractErrs,
|
||||
ContractEvent,
|
||||
Token,
|
||||
ExchangeEvents,
|
||||
TokenEvents,
|
||||
IndexedFilterValues,
|
||||
SubscriptionOpts,
|
||||
BlockParam,
|
||||
OrderFillOrKillRequest,
|
||||
OrderCancellationRequest,
|
||||
OrderFillRequest,
|
||||
ContractEventEmitter,
|
||||
LogErrorContractEventArgs,
|
||||
LogCancelContractEventArgs,
|
||||
LogFillContractEventArgs,
|
||||
ExchangeContractEventArgs,
|
||||
TransferContractEventArgs,
|
||||
ApprovalContractEventArgs,
|
||||
TokenContractEventArgs,
|
||||
ContractEventArgs,
|
||||
Web3Provider,
|
||||
ZeroExConfig,
|
||||
} from './types';
|
@@ -1,11 +0,0 @@
|
||||
export const addressSchema = {
|
||||
id: '/addressSchema',
|
||||
type: 'string',
|
||||
pattern: '^0[xX][0-9A-Fa-f]{40}$',
|
||||
};
|
||||
|
||||
export const numberSchema = {
|
||||
id: '/numberSchema',
|
||||
type: 'string',
|
||||
pattern: '^\\d+(\\.\\d+)?$',
|
||||
};
|
@@ -1,20 +0,0 @@
|
||||
export const ecSignatureParameterSchema = {
|
||||
id: '/ecSignatureParameter',
|
||||
type: 'string',
|
||||
pattern: '^0[xX][0-9A-Fa-f]{64}$',
|
||||
};
|
||||
|
||||
export const ecSignatureSchema = {
|
||||
id: '/ECSignature',
|
||||
properties: {
|
||||
v: {
|
||||
type: 'number',
|
||||
minimum: 27,
|
||||
maximum: 28,
|
||||
},
|
||||
r: {$ref: '/ecSignatureParameter'},
|
||||
s: {$ref: '/ecSignatureParameter'},
|
||||
},
|
||||
required: ['v', 'r', 's'],
|
||||
type: 'object',
|
||||
};
|
@@ -1,12 +0,0 @@
|
||||
export const orderCancellationRequestsSchema = {
|
||||
id: '/OrderCancellationRequests',
|
||||
type: 'array',
|
||||
items: {
|
||||
properties: {
|
||||
order: {$ref: '/orderSchema'},
|
||||
takerTokenCancelAmount: {$ref: '/numberSchema'},
|
||||
},
|
||||
required: ['order', 'takerTokenCancelAmount'],
|
||||
type: 'object',
|
||||
},
|
||||
};
|
@@ -1,12 +0,0 @@
|
||||
export const orderFillOrKillRequestsSchema = {
|
||||
id: '/OrderFillOrKillRequests',
|
||||
type: 'array',
|
||||
items: {
|
||||
properties: {
|
||||
signedOrder: {$ref: '/signedOrderSchema'},
|
||||
fillTakerAmount: {$ref: '/numberSchema'},
|
||||
},
|
||||
required: ['signedOrder', 'fillTakerAmount'],
|
||||
type: 'object',
|
||||
},
|
||||
};
|
@@ -1,12 +0,0 @@
|
||||
export const orderFillRequestsSchema = {
|
||||
id: '/OrderFillRequests',
|
||||
type: 'array',
|
||||
items: {
|
||||
properties: {
|
||||
signedOrder: {$ref: '/signedOrderSchema'},
|
||||
takerTokenFillAmount: {$ref: '/numberSchema'},
|
||||
},
|
||||
required: ['signedOrder', 'takerTokenFillAmount'],
|
||||
type: 'object',
|
||||
},
|
||||
};
|
@@ -1,38 +0,0 @@
|
||||
export const orderSchema = {
|
||||
id: '/orderSchema',
|
||||
properties: {
|
||||
maker: {$ref: '/addressSchema'},
|
||||
taker: {$ref: '/addressSchema'},
|
||||
|
||||
makerFee: {$ref: '/numberSchema'},
|
||||
takerFee: {$ref: '/numberSchema'},
|
||||
|
||||
makerTokenAmount: {$ref: '/numberSchema'},
|
||||
takerTokenAmount: {$ref: '/numberSchema'},
|
||||
|
||||
makerTokenAddress: {$ref: '/addressSchema'},
|
||||
takerTokenAddress: {$ref: '/addressSchema'},
|
||||
|
||||
salt: {$ref: '/numberSchema'},
|
||||
feeRecipient: {$ref: '/addressSchema'},
|
||||
expirationUnixTimestampSec: {$ref: '/numberSchema'},
|
||||
},
|
||||
required: [
|
||||
'maker', 'taker', 'makerFee', 'takerFee', 'makerTokenAmount', 'takerTokenAmount',
|
||||
'salt', 'feeRecipient', 'expirationUnixTimestampSec',
|
||||
],
|
||||
type: 'object',
|
||||
};
|
||||
|
||||
export const signedOrderSchema = {
|
||||
id: '/signedOrderSchema',
|
||||
allOf: [
|
||||
{ $ref: '/orderSchema' },
|
||||
{
|
||||
properties: {
|
||||
ecSignature: {$ref: '/ECSignature'},
|
||||
},
|
||||
required: ['ecSignature'],
|
||||
},
|
||||
],
|
||||
};
|
@@ -1,5 +0,0 @@
|
||||
export const signedOrdersSchema = {
|
||||
id: '/signedOrdersSchema',
|
||||
type: 'array',
|
||||
items: {$ref: '/signedOrderSchema'},
|
||||
};
|
@@ -1,22 +0,0 @@
|
||||
export const tokenSchema = {
|
||||
id: '/token',
|
||||
properties: {
|
||||
name: {type: 'string'},
|
||||
symbol: {type: 'string'},
|
||||
decimals: {type: 'number'},
|
||||
address: {$ref: '/addressSchema'},
|
||||
url: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'string',
|
||||
format: 'uri',
|
||||
},
|
||||
{
|
||||
enum: [''],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
required: ['name', 'symbol', 'decimals', 'address', 'url'],
|
||||
type: 'object',
|
||||
};
|
25
src/subproviders/empty_wallet_subprovider.ts
Normal file
25
src/subproviders/empty_wallet_subprovider.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as Web3 from 'web3';
|
||||
import {JSONRPCPayload} from '../types';
|
||||
|
||||
/*
|
||||
* This class implements the web3-provider-engine subprovider interface and returns
|
||||
* that the provider has no addresses when queried.
|
||||
* Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js
|
||||
*/
|
||||
export class EmptyWalletSubProvider {
|
||||
public handleRequest(payload: JSONRPCPayload, next: () => void, end: (err: Error|null, result: any) => void) {
|
||||
switch (payload.method) {
|
||||
case 'eth_accounts':
|
||||
end(null, []);
|
||||
return;
|
||||
|
||||
default:
|
||||
next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Required to implement this method despite not needing it for this subprovider
|
||||
public setEngine(engine: any) {
|
||||
// noop
|
||||
}
|
||||
}
|
324
src/types.ts
324
src/types.ts
@@ -1,28 +1,21 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
// Utility function to create a K:V from a list of strings
|
||||
// Adapted from: https://basarat.gitbooks.io/typescript/content/docs/types/literal-types.html
|
||||
function strEnum(values: string[]): {[key: string]: string} {
|
||||
return _.reduce(values, (result, key) => {
|
||||
result[key] = key;
|
||||
return result;
|
||||
}, Object.create(null));
|
||||
export enum ZeroExError {
|
||||
ContractDoesNotExist = 'CONTRACT_DOES_NOT_EXIST',
|
||||
ExchangeContractDoesNotExist = 'EXCHANGE_CONTRACT_DOES_NOT_EXIST',
|
||||
UnhandledError = 'UNHANDLED_ERROR',
|
||||
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',
|
||||
}
|
||||
|
||||
export const ZeroExError = strEnum([
|
||||
'CONTRACT_DOES_NOT_EXIST',
|
||||
'UNHANDLED_ERROR',
|
||||
'USER_HAS_NO_ASSOCIATED_ADDRESSES',
|
||||
'INVALID_SIGNATURE',
|
||||
'CONTRACT_NOT_DEPLOYED_ON_NETWORK',
|
||||
'ZRX_NOT_IN_TOKEN_REGISTRY',
|
||||
'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER',
|
||||
'INSUFFICIENT_BALANCE_FOR_TRANSFER',
|
||||
'INVALID_JUMP',
|
||||
'OUT_OF_GAS',
|
||||
]);
|
||||
export type ZeroExError = keyof typeof ZeroExError;
|
||||
|
||||
/**
|
||||
* Elliptic Curve signature
|
||||
*/
|
||||
@@ -44,7 +37,7 @@ export interface ContractEventObj {
|
||||
watch: (eventWatch: EventCallback) => void;
|
||||
stopWatching: () => void;
|
||||
}
|
||||
export type CreateContractEvent = (indexFilterValues: IndexFilterValues,
|
||||
export type CreateContractEvent = (indexFilterValues: IndexedFilterValues,
|
||||
subscriptionOpts: SubscriptionOpts) => ContractEventObj;
|
||||
export interface ExchangeContract extends ContractInstance {
|
||||
isValidSignature: {
|
||||
@@ -54,70 +47,85 @@ export interface ExchangeContract extends ContractInstance {
|
||||
LogFill: CreateContractEvent;
|
||||
LogCancel: CreateContractEvent;
|
||||
LogError: CreateContractEvent;
|
||||
ZRX: {
|
||||
ZRX_TOKEN_CONTRACT: {
|
||||
call: () => Promise<string>;
|
||||
};
|
||||
getUnavailableValueT: {
|
||||
call: (orderHash: string) => BigNumber.BigNumber;
|
||||
getUnavailableTakerTokenAmount: {
|
||||
call: (orderHash: string) => Promise<BigNumber.BigNumber>;
|
||||
};
|
||||
isRoundingError: {
|
||||
call: (takerTokenAmount: BigNumber.BigNumber, fillTakerAmount: BigNumber.BigNumber,
|
||||
call: (fillTakerAmount: BigNumber.BigNumber, takerTokenAmount: BigNumber.BigNumber,
|
||||
makerTokenAmount: BigNumber.BigNumber, txOpts?: TxOpts) => Promise<boolean>;
|
||||
};
|
||||
fill: {
|
||||
(orderAddresses: OrderAddresses, orderValues: OrderValues, fillAmount: BigNumber.BigNumber,
|
||||
shouldCheckTransfer: boolean, v: number, r: string, s: string, txOpts?: TxOpts): ContractResponse;
|
||||
estimateGas: (orderAddresses: OrderAddresses, orderValues: OrderValues, fillAmount: BigNumber.BigNumber,
|
||||
shouldCheckTransfer: boolean, v: number, r: string, s: string, txOpts?: TxOpts) => number;
|
||||
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>;
|
||||
};
|
||||
batchFill: {
|
||||
(orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillAmounts: BigNumber.BigNumber[],
|
||||
shouldCheckTransfer: boolean, v: number[], r: string[], s: string[], txOpts?: TxOpts): ContractResponse;
|
||||
estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillAmounts: BigNumber.BigNumber[],
|
||||
shouldCheckTransfer: boolean, v: number[], r: string[], s: string[], txOpts?: TxOpts) => 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>;
|
||||
};
|
||||
fillUpTo: {
|
||||
(orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillAmount: BigNumber.BigNumber,
|
||||
shouldCheckTransfer: boolean, v: number[], r: string[], s: string[], txOpts?: TxOpts): ContractResponse;
|
||||
estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillAmount: BigNumber.BigNumber,
|
||||
shouldCheckTransfer: boolean, v: number[], r: string[], s: string[], txOpts?: TxOpts) => 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>;
|
||||
};
|
||||
cancel: {
|
||||
(orderAddresses: OrderAddresses, orderValues: OrderValues, cancelAmount: BigNumber.BigNumber,
|
||||
txOpts?: TxOpts): ContractResponse;
|
||||
estimateGas: (orderAddresses: OrderAddresses, orderValues: OrderValues, cancelAmount: BigNumber.BigNumber,
|
||||
txOpts?: TxOpts) => 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>;
|
||||
};
|
||||
batchCancel: {
|
||||
(orderAddresses: OrderAddresses[], orderValues: OrderValues[], cancelAmount: BigNumber.BigNumber[],
|
||||
txOpts?: TxOpts): ContractResponse;
|
||||
estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[], cancelAmount: BigNumber.BigNumber[],
|
||||
txOpts?: TxOpts) => 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>;
|
||||
};
|
||||
fillOrKill: {
|
||||
(orderAddresses: OrderAddresses, orderValues: OrderValues, fillAmount: BigNumber.BigNumber,
|
||||
v: number, r: string, s: string, txOpts?: TxOpts): ContractResponse;
|
||||
estimateGas: (orderAddresses: OrderAddresses, orderValues: OrderValues, fillAmount: BigNumber.BigNumber,
|
||||
v: number, r: string, s: string, txOpts?: TxOpts) => 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>;
|
||||
};
|
||||
batchFillOrKill: {
|
||||
(orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillValuesT: BigNumber.BigNumber[],
|
||||
v: number[], r: string[], s: string[], txOpts: TxOpts): ContractResponse;
|
||||
estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillValuesT: BigNumber.BigNumber[],
|
||||
v: number[], r: string[], s: string[], txOpts?: TxOpts) => 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>;
|
||||
};
|
||||
filled: {
|
||||
call: (orderHash: string) => BigNumber.BigNumber;
|
||||
call: (orderHash: string) => Promise<BigNumber.BigNumber>;
|
||||
};
|
||||
cancelled: {
|
||||
call: (orderHash: string) => BigNumber.BigNumber;
|
||||
call: (orderHash: string) => Promise<BigNumber.BigNumber>;
|
||||
};
|
||||
getOrderHash: {
|
||||
call: (orderAddresses: OrderAddresses, orderValues: OrderValues) => string;
|
||||
call: (orderAddresses: OrderAddresses, orderValues: OrderValues) => Promise<string>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TokenContract extends ContractInstance {
|
||||
Transfer: CreateContractEvent;
|
||||
Approval: CreateContractEvent;
|
||||
balanceOf: {
|
||||
call: (address: string) => Promise<BigNumber.BigNumber>;
|
||||
};
|
||||
@@ -127,7 +135,7 @@ export interface TokenContract extends ContractInstance {
|
||||
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) => void;
|
||||
approve: (proxyAddress: string, amountInBaseUnits: BigNumber.BigNumber, txOpts?: TxOpts) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface TokenRegistryContract extends ContractInstance {
|
||||
@@ -137,13 +145,38 @@ export interface TokenRegistryContract extends ContractInstance {
|
||||
getTokenAddresses: {
|
||||
call: () => Promise<string[]>;
|
||||
};
|
||||
getTokenAddressBySymbol: {
|
||||
call: (symbol: string) => Promise<string>;
|
||||
};
|
||||
getTokenAddressByName: {
|
||||
call: (name: string) => Promise<string>;
|
||||
};
|
||||
getTokenBySymbol: {
|
||||
call: (symbol: string) => Promise<TokenMetadata>;
|
||||
};
|
||||
getTokenByName: {
|
||||
call: (name: string) => Promise<TokenMetadata>;
|
||||
};
|
||||
}
|
||||
|
||||
export const SolidityTypes = strEnum([
|
||||
'address',
|
||||
'uint256',
|
||||
]);
|
||||
export type SolidityTypes = keyof typeof SolidityTypes;
|
||||
export interface EtherTokenContract extends ContractInstance {
|
||||
deposit: (txOpts: TxOpts) => Promise<void>;
|
||||
withdraw: (amount: BigNumber.BigNumber, txOpts: TxOpts) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface TokenTransferProxyContract extends ContractInstance {
|
||||
getAuthorizedAddresses: {
|
||||
call: () => Promise<string[]>;
|
||||
};
|
||||
authorized: {
|
||||
call: (address: string) => Promise<boolean>;
|
||||
};
|
||||
}
|
||||
|
||||
export enum SolidityTypes {
|
||||
Address = 'address',
|
||||
Uint256 = 'uint256',
|
||||
}
|
||||
|
||||
export enum ExchangeContractErrCodes {
|
||||
ERROR_FILL_EXPIRED, // Order has already expired
|
||||
@@ -154,28 +187,29 @@ export enum ExchangeContractErrCodes {
|
||||
ERROR_CANCEL_NO_VALUE, // Order has already been fully filled or cancelled
|
||||
}
|
||||
|
||||
export const ExchangeContractErrs = strEnum([
|
||||
'ORDER_FILL_EXPIRED',
|
||||
'ORDER_CANCEL_EXPIRED',
|
||||
'ORDER_CANCEL_AMOUNT_ZERO',
|
||||
'ORDER_ALREADY_CANCELLED_OR_FILLED',
|
||||
'ORDER_REMAINING_FILL_AMOUNT_ZERO',
|
||||
'ORDER_FILL_ROUNDING_ERROR',
|
||||
'FILL_BALANCE_ALLOWANCE_ERROR',
|
||||
'INSUFFICIENT_TAKER_BALANCE',
|
||||
'INSUFFICIENT_TAKER_ALLOWANCE',
|
||||
'INSUFFICIENT_MAKER_BALANCE',
|
||||
'INSUFFICIENT_MAKER_ALLOWANCE',
|
||||
'INSUFFICIENT_TAKER_FEE_BALANCE',
|
||||
'INSUFFICIENT_TAKER_FEE_ALLOWANCE',
|
||||
'INSUFFICIENT_MAKER_FEE_BALANCE',
|
||||
'INSUFFICIENT_MAKER_FEE_ALLOWANCE',
|
||||
'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER',
|
||||
'MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH_DISALLOWED',
|
||||
'INSUFFICIENT_REMAINING_FILL_AMOUNT',
|
||||
'MULTIPLE_TAKER_TOKENS_IN_FILL_UP_TO_DISALLOWED',
|
||||
]);
|
||||
export type ExchangeContractErrs = keyof typeof ExchangeContractErrs;
|
||||
export enum ExchangeContractErrs {
|
||||
OrderFillExpired = 'ORDER_FILL_EXPIRED',
|
||||
OrderCancelExpired = 'ORDER_CANCEL_EXPIRED',
|
||||
OrderCancelAmountZero = 'ORDER_CANCEL_AMOUNT_ZERO',
|
||||
OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED',
|
||||
OrderFillAmountZero = 'ORDER_FILL_AMOUNT_ZERO',
|
||||
OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO',
|
||||
OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR',
|
||||
FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR',
|
||||
InsufficientTakerBalance = 'INSUFFICIENT_TAKER_BALANCE',
|
||||
InsufficientTakerAllowance = 'INSUFFICIENT_TAKER_ALLOWANCE',
|
||||
InsufficientMakerBalance = 'INSUFFICIENT_MAKER_BALANCE',
|
||||
InsufficientMakerAllowance = 'INSUFFICIENT_MAKER_ALLOWANCE',
|
||||
InsufficientTakerFeeBalance = 'INSUFFICIENT_TAKER_FEE_BALANCE',
|
||||
InsufficientTakerFeeAllowance = 'INSUFFICIENT_TAKER_FEE_ALLOWANCE',
|
||||
InsufficientMakerFeeBalance = 'INSUFFICIENT_MAKER_FEE_BALANCE',
|
||||
InsufficientMakerFeeAllowance = 'INSUFFICIENT_MAKER_FEE_ALLOWANCE',
|
||||
TransactionSenderIsNotFillOrderTaker = 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER',
|
||||
MultipleMakersInSingleCancelBatchDisallowed = 'MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH_DISALLOWED',
|
||||
InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT',
|
||||
MultipleTakerTokensInFillUpToDisallowed = 'MULTIPLE_TAKER_TOKENS_IN_FILL_UP_TO_DISALLOWED',
|
||||
BatchOrdersMustHaveSameExchangeAddress = 'BATCH_ORDERS_MUST_HAVE_SAME_EXCHANGE_ADDRESS',
|
||||
}
|
||||
|
||||
export interface ContractResponse {
|
||||
logs: ContractEvent[];
|
||||
@@ -190,9 +224,51 @@ export interface ContractEvent {
|
||||
address: string;
|
||||
type: string;
|
||||
event: string;
|
||||
args: any;
|
||||
args: ContractEventArgs;
|
||||
}
|
||||
|
||||
export interface LogFillContractEventArgs {
|
||||
maker: string;
|
||||
taker: string;
|
||||
feeRecipient: string;
|
||||
makerToken: string;
|
||||
takerToken: string;
|
||||
filledMakerTokenAmount: BigNumber.BigNumber;
|
||||
filledTakerTokenAmount: BigNumber.BigNumber;
|
||||
paidMakerFee: BigNumber.BigNumber;
|
||||
paidTakerFee: BigNumber.BigNumber;
|
||||
tokens: string;
|
||||
orderHash: string;
|
||||
}
|
||||
export interface LogCancelContractEventArgs {
|
||||
maker: string;
|
||||
feeRecipient: string;
|
||||
makerToken: string;
|
||||
takerToken: string;
|
||||
cancelledMakerTokenAmount: BigNumber.BigNumber;
|
||||
cancelledTakerTokenAmount: BigNumber.BigNumber;
|
||||
tokens: string;
|
||||
orderHash: string;
|
||||
}
|
||||
export interface LogErrorContractEventArgs {
|
||||
errorId: BigNumber.BigNumber;
|
||||
orderHash: string;
|
||||
}
|
||||
export type ExchangeContractEventArgs = LogFillContractEventArgs|LogCancelContractEventArgs|LogErrorContractEventArgs;
|
||||
export interface TransferContractEventArgs {
|
||||
_from: string;
|
||||
_to: string;
|
||||
_value: BigNumber.BigNumber;
|
||||
}
|
||||
export interface ApprovalContractEventArgs {
|
||||
_owner: string;
|
||||
_spender: string;
|
||||
_value: BigNumber.BigNumber;
|
||||
}
|
||||
export type TokenContractEventArgs = TransferContractEventArgs|ApprovalContractEventArgs;
|
||||
export type ContractEventArgs = ExchangeContractEventArgs|TokenContractEventArgs;
|
||||
export type ContractEventArg = string|BigNumber.BigNumber;
|
||||
|
||||
export interface Order {
|
||||
maker: string;
|
||||
taker: string;
|
||||
@@ -203,6 +279,7 @@ export interface Order {
|
||||
makerTokenAddress: string;
|
||||
takerTokenAddress: string;
|
||||
salt: BigNumber.BigNumber;
|
||||
exchangeContractAddress: string;
|
||||
feeRecipient: string;
|
||||
expirationUnixTimestampSec: BigNumber.BigNumber;
|
||||
}
|
||||
@@ -211,35 +288,39 @@ export interface SignedOrder extends Order {
|
||||
ecSignature: ECSignature;
|
||||
}
|
||||
|
||||
// [address, name, symbol, projectUrl, decimals, ipfsHash, swarmHash]
|
||||
export type TokenMetadata = [string, string, string, string, BigNumber.BigNumber, string, string];
|
||||
// [address, name, symbol, decimals, ipfsHash, swarmHash]
|
||||
export type TokenMetadata = [string, string, string, BigNumber.BigNumber, string, string];
|
||||
|
||||
export interface Token {
|
||||
name: string;
|
||||
address: string;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface TxOpts {
|
||||
from: string;
|
||||
gas?: number;
|
||||
value?: BigNumber.BigNumber;
|
||||
}
|
||||
|
||||
export interface TokenAddressBySymbol {
|
||||
[symbol: string]: string;
|
||||
}
|
||||
|
||||
export const ExchangeEvents = strEnum([
|
||||
'LogFill',
|
||||
'LogCancel',
|
||||
'LogError',
|
||||
]);
|
||||
export type ExchangeEvents = keyof typeof ExchangeEvents;
|
||||
export enum ExchangeEvents {
|
||||
LogFill = 'LogFill',
|
||||
LogCancel = 'LogCancel',
|
||||
LogError = 'LogError',
|
||||
}
|
||||
|
||||
export interface IndexFilterValues {
|
||||
[index: string]: any;
|
||||
export enum TokenEvents {
|
||||
Transfer = 'Transfer',
|
||||
Approval = 'Approval',
|
||||
}
|
||||
|
||||
export interface IndexedFilterValues {
|
||||
[index: string]: ContractEventArg;
|
||||
}
|
||||
|
||||
export type BlockParam = 'latest'|'earliest'|'pending'|number;
|
||||
@@ -275,3 +356,36 @@ export interface ContractInstance {
|
||||
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.
|
||||
* It is however a `Web3` library type, not a native `0x.js` type.
|
||||
*/
|
||||
export type Web3Provider = Web3.Provider;
|
||||
|
||||
export interface ExchangeContractByAddress {
|
||||
[address: string]: ExchangeContract;
|
||||
}
|
||||
|
||||
export interface ContractArtifact {
|
||||
networks: {
|
||||
[networkId: number]: {
|
||||
address: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface JSONRPCPayload {
|
||||
params: any[];
|
||||
method: string;
|
||||
}
|
||||
|
||||
export interface ZeroExConfig {
|
||||
gasPrice?: BigNumber.BigNumber; // Gas price to use with every transaction
|
||||
}
|
||||
|
@@ -2,8 +2,7 @@ import * as _ from 'lodash';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import * as Web3 from 'web3';
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {SchemaValidator} from './schema_validator';
|
||||
import {utils} from './utils';
|
||||
import {SchemaValidator, Schema} from '0x-json-schemas';
|
||||
|
||||
const HEX_REGEX = /^0x[0-9A-F]*$/i;
|
||||
|
||||
@@ -25,18 +24,33 @@ export const assert = {
|
||||
isETHAddressHex(variableName: string, value: string): void {
|
||||
const web3 = new Web3();
|
||||
this.assert(web3.isAddress(value), this.typeAssertionMessage(variableName, 'ETHAddressHex', value));
|
||||
this.assert(
|
||||
web3.isAddress(value) && !web3.isChecksumAddress(value),
|
||||
`Checksummed addresses are not supported. Convert ${variableName} to lower case before passing`,
|
||||
);
|
||||
},
|
||||
doesBelongToStringEnum(variableName: string, value: string,
|
||||
stringEnum: any /* There is no base type for every string enum */): void {
|
||||
const doesBelongToStringEnum = !_.isUndefined(stringEnum[value]);
|
||||
const enumValues = _.keys(stringEnum);
|
||||
const enumValuesAsStrings = _.map(enumValues, enumValue => `'${enumValue}'`);
|
||||
const enumValuesAsString = enumValuesAsStrings.join(', ');
|
||||
assert.assert(
|
||||
doesBelongToStringEnum,
|
||||
`Expected ${variableName} to be one of: ${enumValuesAsString}, encountered: ${value}`,
|
||||
);
|
||||
},
|
||||
async isSenderAddressAsync(variableName: string, senderAddressHex: string,
|
||||
web3Wrapper: Web3Wrapper): Promise<void> {
|
||||
assert.isETHAddressHex(variableName, senderAddressHex);
|
||||
const isSenderAddressAvailable = await web3Wrapper.isSenderAddressAvailableAsync(senderAddressHex);
|
||||
assert.assert(isSenderAddressAvailable,
|
||||
`Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 instance`,
|
||||
`Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`,
|
||||
);
|
||||
},
|
||||
async isUserAddressAvailableAsync(web3Wrapper: Web3Wrapper): Promise<void> {
|
||||
const availableAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
||||
this.assert(!_.isEmpty(availableAddresses), 'No addresses were available on the provided web3 instance');
|
||||
this.assert(!_.isEmpty(availableAddresses), 'No addresses were available on the provided web3 provider');
|
||||
},
|
||||
hasAtMostOneUniqueValue(value: any[], errMsg: string): void {
|
||||
this.assert(_.uniq(value).length <= 1, errMsg);
|
||||
@@ -44,13 +58,14 @@ export const assert = {
|
||||
isNumber(variableName: string, value: number): void {
|
||||
this.assert(_.isFinite(value), this.typeAssertionMessage(variableName, 'number', value));
|
||||
},
|
||||
isValidOrderHash(variableName: string, value: string): void {
|
||||
this.assert(utils.isValidOrderHash(value), this.typeAssertionMessage(variableName, 'orderHash', value));
|
||||
},
|
||||
isBoolean(variableName: string, value: boolean): void {
|
||||
this.assert(_.isBoolean(value), this.typeAssertionMessage(variableName, 'boolean', value));
|
||||
},
|
||||
doesConformToSchema(variableName: string, value: object, schema: Schema): void {
|
||||
isWeb3Provider(variableName: string, value: Web3.Provider): void {
|
||||
const isWeb3Provider = _.isFunction((value as any).send) || _.isFunction((value as any).sendAsync);
|
||||
this.assert(isWeb3Provider, this.typeAssertionMessage(variableName, 'Web3.Provider', value));
|
||||
},
|
||||
doesConformToSchema(variableName: string, value: any, schema: Schema): void {
|
||||
const schemaValidator = new SchemaValidator();
|
||||
const validationResult = schemaValidator.validate(value, schema);
|
||||
const hasValidationErrors = validationResult.errors.length > 0;
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
|
||||
export const constants = {
|
||||
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
|
||||
TESTRPC_NETWORK_ID: 50,
|
||||
MAX_DIGITS_IN_UNSIGNED_256_INT: 78,
|
||||
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),
|
||||
};
|
||||
|
@@ -21,10 +21,10 @@ export const decorators = {
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (_.includes(error.message, constants.INVALID_JUMP_PATTERN)) {
|
||||
throw new Error(ZeroExError.INVALID_JUMP);
|
||||
throw new Error(ZeroExError.InvalidJump);
|
||||
}
|
||||
if (_.includes(error.message, constants.OUT_OF_GAS_PATTERN)) {
|
||||
throw new Error(ZeroExError.OUT_OF_GAS);
|
||||
throw new Error(ZeroExError.OutOfGas);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
41
src/utils/event_utils.ts
Normal file
41
src/utils/event_utils.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
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;
|
||||
},
|
||||
};
|
146
src/utils/order_validation_utils.ts
Normal file
146
src/utils/order_validation_utils.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import {ExchangeContractErrs, SignedOrder, Order} from '../types';
|
||||
import {TokenWrapper} from '../contract_wrappers/token_wrapper';
|
||||
import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper';
|
||||
import {utils} from '../utils/utils';
|
||||
import {constants} from '../utils/constants';
|
||||
|
||||
export class OrderValidationUtils {
|
||||
private tokenWrapper: TokenWrapper;
|
||||
private exchangeWrapper: ExchangeWrapper;
|
||||
constructor(tokenWrapper: TokenWrapper, exchangeWrapper: ExchangeWrapper) {
|
||||
this.tokenWrapper = tokenWrapper;
|
||||
this.exchangeWrapper = exchangeWrapper;
|
||||
}
|
||||
public async validateFillOrderThrowIfInvalidAsync(signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber.BigNumber,
|
||||
takerAddress: string,
|
||||
zrxTokenAddress: string): Promise<void> {
|
||||
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 (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);
|
||||
}
|
||||
await this.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress,
|
||||
);
|
||||
|
||||
const wouldRoundingErrorOccur = await this.exchangeWrapper.isRoundingErrorAsync(
|
||||
fillTakerTokenAmount, signedOrder.takerTokenAmount, signedOrder.makerTokenAmount,
|
||||
);
|
||||
if (wouldRoundingErrorOccur) {
|
||||
throw new Error(ExchangeContractErrs.OrderFillRoundingError);
|
||||
}
|
||||
}
|
||||
public async validateFillOrKillOrderThrowIfInvalidAsync(signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber.BigNumber,
|
||||
takerAddress: string,
|
||||
zrxTokenAddress: string): Promise<void> {
|
||||
await this.validateFillOrderThrowIfInvalidAsync(
|
||||
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) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount);
|
||||
}
|
||||
}
|
||||
public async validateCancelOrderThrowIfInvalidAsync(order: Order,
|
||||
cancelTakerTokenAmount: BigNumber.BigNumber,
|
||||
unavailableTakerTokenAmount: BigNumber.BigNumber,
|
||||
): Promise<void> {
|
||||
if (cancelTakerTokenAmount.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.OrderCancelAmountZero);
|
||||
}
|
||||
if (order.takerTokenAmount.eq(unavailableTakerTokenAmount)) {
|
||||
throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
|
||||
}
|
||||
const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
|
||||
if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
|
||||
throw new Error(ExchangeContractErrs.OrderCancelExpired);
|
||||
}
|
||||
}
|
||||
public async validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
signedOrder: SignedOrder, fillTakerAmount: BigNumber.BigNumber, senderAddress: string, zrxTokenAddress: string,
|
||||
): Promise<void> {
|
||||
await this.validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerAmount, zrxTokenAddress,
|
||||
);
|
||||
await this.validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerAmount, senderAddress, zrxTokenAddress,
|
||||
);
|
||||
}
|
||||
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
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
import {Validator, ValidatorResult} from 'jsonschema';
|
||||
import {ecSignatureSchema, ecSignatureParameterSchema} from '../schemas/ec_signature_schema';
|
||||
import {orderSchema, signedOrderSchema} from '../schemas/order_schemas';
|
||||
import {addressSchema, numberSchema} from '../schemas/basic_type_schemas';
|
||||
import {tokenSchema} from '../schemas/token_schema';
|
||||
import {orderFillOrKillRequestsSchema} from '../schemas/order_fill_or_kill_requests_schema';
|
||||
|
||||
export class SchemaValidator {
|
||||
private validator: Validator;
|
||||
constructor() {
|
||||
this.validator = new Validator();
|
||||
this.validator.addSchema(tokenSchema, tokenSchema.id);
|
||||
this.validator.addSchema(orderSchema, orderSchema.id);
|
||||
this.validator.addSchema(numberSchema, numberSchema.id);
|
||||
this.validator.addSchema(addressSchema, addressSchema.id);
|
||||
this.validator.addSchema(ecSignatureSchema, ecSignatureSchema.id);
|
||||
this.validator.addSchema(signedOrderSchema, signedOrderSchema.id);
|
||||
this.validator.addSchema(ecSignatureParameterSchema, ecSignatureParameterSchema.id);
|
||||
this.validator.addSchema(orderFillOrKillRequestsSchema, orderFillOrKillRequestsSchema.id);
|
||||
}
|
||||
// In order to validate a complex JS object using jsonschema, we must replace any complex
|
||||
// sub-types (e.g BigNumber) with a simpler string representation. Since BigNumber and other
|
||||
// complex types implement the `toString` method, we can stringify the object and
|
||||
// then parse it. The resultant object can then be checked using jsonschema.
|
||||
public validate(instance: any, schema: Schema): ValidatorResult {
|
||||
const jsonSchemaCompatibleObject = JSON.parse(JSON.stringify(instance));
|
||||
return this.validator.validate(jsonSchemaCompatibleObject, schema);
|
||||
}
|
||||
}
|
29
src/utils/signature_utils.ts
Normal file
29
src/utils/signature_utils.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import {ECSignature} from '../types';
|
||||
|
||||
export const signatureUtils = {
|
||||
parseSignatureHexAsVRS(signatureHex: string): ECSignature {
|
||||
const signatureBuffer = ethUtil.toBuffer(signatureHex);
|
||||
let v = signatureBuffer[0];
|
||||
if (v < 27) {
|
||||
v += 27;
|
||||
}
|
||||
const r = signatureBuffer.slice(1, 33);
|
||||
const s = signatureBuffer.slice(33, 65);
|
||||
const ecSignature: ECSignature = {
|
||||
v,
|
||||
r: ethUtil.bufferToHex(r),
|
||||
s: ethUtil.bufferToHex(s),
|
||||
};
|
||||
return ecSignature;
|
||||
},
|
||||
parseSignatureHexAsRSV(signatureHex: string): ECSignature {
|
||||
const {v, r, s} = ethUtil.fromRpcSig(signatureHex);
|
||||
const ecSignature: ECSignature = {
|
||||
v,
|
||||
r: ethUtil.bufferToHex(r),
|
||||
s: ethUtil.bufferToHex(s),
|
||||
};
|
||||
return ecSignature;
|
||||
},
|
||||
};
|
@@ -1,9 +1,9 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as BN from 'bn.js';
|
||||
import * as ethABI from 'ethereumjs-abi';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import {Order, SignedOrder, SolidityTypes} from '../types';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import BN = require('bn.js');
|
||||
|
||||
export const utils = {
|
||||
/**
|
||||
@@ -22,27 +22,26 @@ export const utils = {
|
||||
isParityNode(nodeVersion: string): boolean {
|
||||
return _.includes(nodeVersion, 'Parity');
|
||||
},
|
||||
isValidOrderHash(orderHashHex: string) {
|
||||
const isValid = /^0x[0-9A-F]{64}$/i.test(orderHashHex);
|
||||
return isValid;
|
||||
isTestRpc(nodeVersion: string): boolean {
|
||||
return _.includes(nodeVersion, 'TestRPC');
|
||||
},
|
||||
spawnSwitchErr(name: string, value: any) {
|
||||
spawnSwitchErr(name: string, value: any): Error {
|
||||
return new Error(`Unexpected switch value: ${value} encountered for ${name}`);
|
||||
},
|
||||
getOrderHashHex(order: Order|SignedOrder, exchangeContractAddr: string): string {
|
||||
getOrderHashHex(order: Order|SignedOrder): string {
|
||||
const orderParts = [
|
||||
{value: exchangeContractAddr, type: SolidityTypes.address},
|
||||
{value: order.maker, type: SolidityTypes.address},
|
||||
{value: order.taker, type: SolidityTypes.address},
|
||||
{value: order.makerTokenAddress, type: SolidityTypes.address},
|
||||
{value: order.takerTokenAddress, type: SolidityTypes.address},
|
||||
{value: order.feeRecipient, type: SolidityTypes.address},
|
||||
{value: utils.bigNumberToBN(order.makerTokenAmount), type: SolidityTypes.uint256},
|
||||
{value: utils.bigNumberToBN(order.takerTokenAmount), type: SolidityTypes.uint256},
|
||||
{value: utils.bigNumberToBN(order.makerFee), type: SolidityTypes.uint256},
|
||||
{value: utils.bigNumberToBN(order.takerFee), type: SolidityTypes.uint256},
|
||||
{value: utils.bigNumberToBN(order.expirationUnixTimestampSec), type: SolidityTypes.uint256},
|
||||
{value: utils.bigNumberToBN(order.salt), type: SolidityTypes.uint256},
|
||||
{value: order.exchangeContractAddress, type: SolidityTypes.Address},
|
||||
{value: order.maker, type: SolidityTypes.Address},
|
||||
{value: order.taker, type: SolidityTypes.Address},
|
||||
{value: order.makerTokenAddress, type: SolidityTypes.Address},
|
||||
{value: order.takerTokenAddress, type: SolidityTypes.Address},
|
||||
{value: order.feeRecipient, type: SolidityTypes.Address},
|
||||
{value: utils.bigNumberToBN(order.makerTokenAmount), type: SolidityTypes.Uint256},
|
||||
{value: utils.bigNumberToBN(order.takerTokenAmount), type: SolidityTypes.Uint256},
|
||||
{value: utils.bigNumberToBN(order.makerFee), type: SolidityTypes.Uint256},
|
||||
{value: utils.bigNumberToBN(order.takerFee), type: SolidityTypes.Uint256},
|
||||
{value: utils.bigNumberToBN(order.expirationUnixTimestampSec), type: SolidityTypes.Uint256},
|
||||
{value: utils.bigNumberToBN(order.salt), type: SolidityTypes.Uint256},
|
||||
];
|
||||
const types = _.map(orderParts, o => o.type);
|
||||
const values = _.map(orderParts, o => o.value);
|
||||
|
@@ -2,16 +2,16 @@ import * as _ from 'lodash';
|
||||
import * as Web3 from 'web3';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import promisify = require('es6-promisify');
|
||||
import {ZeroExError} from './types';
|
||||
import {assert} from './utils/assert';
|
||||
|
||||
export class Web3Wrapper {
|
||||
private web3: Web3;
|
||||
constructor(web3: Web3) {
|
||||
private networkIdIfExists?: number;
|
||||
constructor(provider: Web3.Provider) {
|
||||
this.web3 = new Web3();
|
||||
this.web3.setProvider(web3.currentProvider);
|
||||
this.web3.setProvider(provider);
|
||||
}
|
||||
public setProvider(provider: Web3.Provider) {
|
||||
delete this.networkIdIfExists;
|
||||
this.web3.setProvider(provider);
|
||||
}
|
||||
public isAddress(address: string): boolean {
|
||||
@@ -29,17 +29,26 @@ export class Web3Wrapper {
|
||||
return this.web3.currentProvider;
|
||||
}
|
||||
public async getNetworkIdIfExistsAsync(): Promise<number|undefined> {
|
||||
if (!_.isUndefined(this.networkIdIfExists)) {
|
||||
return this.networkIdIfExists;
|
||||
}
|
||||
|
||||
try {
|
||||
const networkId = await this.getNetworkAsync();
|
||||
return Number(networkId);
|
||||
this.networkIdIfExists = Number(networkId);
|
||||
return this.networkIdIfExists;
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
public async getBalanceInEthAsync(owner: string): Promise<BigNumber.BigNumber> {
|
||||
const balanceInWei = await promisify(this.web3.eth.getBalance)(owner);
|
||||
const balanceEth = this.web3.fromWei(balanceInWei, 'ether');
|
||||
return balanceEth;
|
||||
public toWei(ethAmount: BigNumber.BigNumber): BigNumber.BigNumber {
|
||||
const balanceWei = this.web3.toWei(ethAmount, 'ether');
|
||||
return balanceWei;
|
||||
}
|
||||
public async getBalanceInWeiAsync(owner: string): Promise<BigNumber.BigNumber> {
|
||||
let balanceInWei = await promisify(this.web3.eth.getBalance)(owner);
|
||||
balanceInWei = new BigNumber(balanceInWei);
|
||||
return balanceInWei;
|
||||
}
|
||||
public async doesContractExistAtAddressAsync(address: string): Promise<boolean> {
|
||||
const code = await promisify(this.web3.eth.getCode)(address);
|
||||
|
@@ -4,20 +4,18 @@ import {chaiSetup} from './utils/chai_setup';
|
||||
import 'mocha';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import * as Sinon from 'sinon';
|
||||
import {ZeroEx} from '../src/0x';
|
||||
import {ZeroEx, Order} from '../src';
|
||||
import {constants} from './utils/constants';
|
||||
import {Order} from '../src/types';
|
||||
import {ECSignature} from '../src/types';
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('ZeroEx library', () => {
|
||||
const web3 = web3Factory.create();
|
||||
const zeroEx = new ZeroEx(web3.currentProvider);
|
||||
describe('#setProvider', () => {
|
||||
it('overrides provider in nested web3s and invalidates contractInstances', async () => {
|
||||
const web3 = web3Factory.create();
|
||||
const zeroEx = new ZeroEx(web3);
|
||||
// Instantiate the contract instances with the current provider
|
||||
await (zeroEx.exchange as any)._getExchangeContractAsync();
|
||||
await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync();
|
||||
@@ -33,7 +31,7 @@ describe('ZeroEx library', () => {
|
||||
expect((zeroEx.exchange as any)._exchangeContractIfExists).to.be.undefined();
|
||||
expect((zeroEx.tokenRegistry as any)._tokenRegistryContractIfExists).to.be.undefined();
|
||||
|
||||
// Check that all nested web3 instances return the updated provider
|
||||
// Check that all nested web3 wrapper instances return the updated provider
|
||||
const nestedWeb3WrapperProvider = (zeroEx as any)._web3Wrapper.getCurrentProvider();
|
||||
expect((nestedWeb3WrapperProvider as any).zeroExTestId).to.be.a('number');
|
||||
const exchangeWeb3WrapperProvider = (zeroEx.exchange as any)._web3Wrapper.getCurrentProvider();
|
||||
@@ -52,8 +50,6 @@ describe('ZeroEx library', () => {
|
||||
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
|
||||
};
|
||||
const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
|
||||
const web3 = web3Factory.create();
|
||||
const zeroEx = new ZeroEx(web3);
|
||||
it('should return false if the data doesn\'t pertain to the signature & address', async () => {
|
||||
expect(ZeroEx.isValidSignature('0x0', signature, address)).to.be.false();
|
||||
return expect(
|
||||
@@ -61,7 +57,7 @@ describe('ZeroEx library', () => {
|
||||
).to.become(false);
|
||||
});
|
||||
it('should return false if the address doesn\'t pertain to the signature & data', async () => {
|
||||
const validUnrelatedAddress = '0x8b0292B11a196601eD2ce54B665CaFEca0347D42';
|
||||
const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42';
|
||||
expect(ZeroEx.isValidSignature(dataHex, signature, validUnrelatedAddress)).to.be.false();
|
||||
return expect(
|
||||
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, signature,
|
||||
@@ -127,15 +123,16 @@ describe('ZeroEx library', () => {
|
||||
expect(baseUnitAmount).to.be.bignumber.equal(expectedUnitAmount);
|
||||
});
|
||||
});
|
||||
describe('#getOrderHashHexAsync', () => {
|
||||
const exchangeContractAddress = constants.NULL_ADDRESS;
|
||||
const expectedOrderHash = '0x103a5e97dab5dbeb8f385636f86a7d1e458a7ccbe1bd194727f0b2f85ab116c7';
|
||||
describe('#getOrderHashHex', () => {
|
||||
const expectedOrderHash = '0x39da987067a3c9e5f1617694f1301326ba8c8b0498ebef5df4863bed394e3c83';
|
||||
const fakeExchangeContractAddress = '0xb69e673309512a9d726f87304c6984054f87a93b';
|
||||
const order: Order = {
|
||||
maker: constants.NULL_ADDRESS,
|
||||
taker: constants.NULL_ADDRESS,
|
||||
feeRecipient: constants.NULL_ADDRESS,
|
||||
makerTokenAddress: constants.NULL_ADDRESS,
|
||||
takerTokenAddress: constants.NULL_ADDRESS,
|
||||
exchangeContractAddress: fakeExchangeContractAddress,
|
||||
salt: new BigNumber(0),
|
||||
makerFee: new BigNumber(0),
|
||||
takerFee: new BigNumber(0),
|
||||
@@ -143,30 +140,14 @@ describe('ZeroEx library', () => {
|
||||
takerTokenAmount: new BigNumber(0),
|
||||
expirationUnixTimestampSec: new BigNumber(0),
|
||||
};
|
||||
let stubs: Sinon.SinonStub[] = [];
|
||||
afterEach(() => {
|
||||
// clean up any stubs after the test has completed
|
||||
_.each(stubs, s => s.restore());
|
||||
stubs = [];
|
||||
});
|
||||
it('calculates the order hash', async () => {
|
||||
const web3 = web3Factory.create();
|
||||
const zeroEx = new ZeroEx(web3);
|
||||
|
||||
stubs = [
|
||||
Sinon.stub((zeroEx as any), '_getExchangeAddressAsync')
|
||||
.returns(Promise.resolve(exchangeContractAddress)),
|
||||
];
|
||||
|
||||
const orderHash = await zeroEx.getOrderHashHexAsync(order);
|
||||
const orderHash = ZeroEx.getOrderHashHex(order);
|
||||
expect(orderHash).to.be.equal(expectedOrderHash);
|
||||
});
|
||||
});
|
||||
describe('#signOrderHashAsync', () => {
|
||||
let stubs: Sinon.SinonStub[] = [];
|
||||
let makerAddress: string;
|
||||
const web3 = web3Factory.create();
|
||||
const zeroEx = new ZeroEx(web3);
|
||||
before(async () => {
|
||||
const availableAddreses = await zeroEx.getAvailableAddressesAsync();
|
||||
makerAddress = availableAddreses[0];
|
||||
@@ -176,7 +157,7 @@ describe('ZeroEx library', () => {
|
||||
_.each(stubs, s => s.restore());
|
||||
stubs = [];
|
||||
});
|
||||
it ('Should return the correct ECSignature on TestPRC nodeVersion', async () => {
|
||||
it('Should return the correct ECSignature', async () => {
|
||||
const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
|
||||
const expectedECSignature = {
|
||||
v: 27,
|
||||
@@ -186,8 +167,7 @@ describe('ZeroEx library', () => {
|
||||
const ecSignature = await zeroEx.signOrderHashAsync(orderHash, makerAddress);
|
||||
expect(ecSignature).to.deep.equal(expectedECSignature);
|
||||
});
|
||||
it ('should return the correct ECSignature on Parity > V1.6.6', async () => {
|
||||
const newParityNodeVersion = 'Parity//v1.6.7-beta-e128418-20170518/x86_64-macos/rustc1.17.0';
|
||||
it('should return the correct ECSignature for signatureHex concatenated as R + S + V', async () => {
|
||||
const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004';
|
||||
// tslint:disable-next-line: max-line-length
|
||||
const signature = '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb021b';
|
||||
@@ -197,8 +177,6 @@ describe('ZeroEx library', () => {
|
||||
s: '0x050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb02',
|
||||
};
|
||||
stubs = [
|
||||
Sinon.stub((zeroEx as any)._web3Wrapper, 'getNodeVersionAsync')
|
||||
.returns(Promise.resolve(newParityNodeVersion)),
|
||||
Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync')
|
||||
.returns(Promise.resolve(signature)),
|
||||
Sinon.stub(ZeroEx, 'isValidSignature').returns(true),
|
||||
@@ -207,8 +185,7 @@ describe('ZeroEx library', () => {
|
||||
const ecSignature = await zeroEx.signOrderHashAsync(orderHash, makerAddress);
|
||||
expect(ecSignature).to.deep.equal(expectedECSignature);
|
||||
});
|
||||
it ('should return the correct ECSignature on Parity < V1.6.6', async () => {
|
||||
const newParityNodeVersion = 'Parity//v1.6.6-beta-8c6e3f3-20170411/x86_64-macos/rustc1.16.0';
|
||||
it('should return the correct ECSignature for signatureHex concatenated as V + R + S', async () => {
|
||||
const orderHash = '0xc793e33ffded933b76f2f48d9aa3339fc090399d5e7f5dec8d3660f5480793f7';
|
||||
// tslint:disable-next-line: max-line-length
|
||||
const signature = '0x1bc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee02dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960';
|
||||
@@ -218,8 +195,6 @@ describe('ZeroEx library', () => {
|
||||
s: '0x2dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960',
|
||||
};
|
||||
stubs = [
|
||||
Sinon.stub((zeroEx as any)._web3Wrapper, 'getNodeVersionAsync')
|
||||
.returns(Promise.resolve(newParityNodeVersion)),
|
||||
Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync')
|
||||
.returns(Promise.resolve(signature)),
|
||||
Sinon.stub(ZeroEx, 'isValidSignature').returns(true),
|
||||
|
32
test/artifacts_test.ts
Normal file
32
test/artifacts_test.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import * as fs from 'fs';
|
||||
import * as chai from 'chai';
|
||||
import {chaiSetup} from './utils/chai_setup';
|
||||
import HDWalletProvider = require('truffle-hdwallet-provider');
|
||||
import {ZeroEx} from '../src';
|
||||
import {constants} from './utils/constants';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
// Those tests are slower cause they're talking to a remote node
|
||||
const TIMEOUT = 10000;
|
||||
|
||||
describe('Artifacts', () => {
|
||||
describe('contracts are deployed on kovan', () => {
|
||||
const kovanRpcUrl = constants.KOVAN_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, kovanRpcUrl);
|
||||
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)._getProxyAddressAsync();
|
||||
}).timeout(TIMEOUT);
|
||||
it('exchange contract is deployed', async () => {
|
||||
await zeroEx.exchange.getContractAddressAsync();
|
||||
}).timeout(TIMEOUT);
|
||||
});
|
||||
});
|
@@ -1,6 +1,6 @@
|
||||
import * as chai from 'chai';
|
||||
import 'mocha';
|
||||
import {ZeroEx} from '../src/0x';
|
||||
import {ZeroEx} from '../src';
|
||||
import {assert} from '../src/utils/assert';
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
|
||||
@@ -8,7 +8,7 @@ const expect = chai.expect;
|
||||
|
||||
describe('Assertion library', () => {
|
||||
const web3 = web3Factory.create();
|
||||
const zeroEx = new ZeroEx(web3);
|
||||
const zeroEx = new ZeroEx(web3.currentProvider);
|
||||
describe('#isSenderAddressHexAsync', () => {
|
||||
it('throws when address is invalid', async () => {
|
||||
const address = '0xdeadbeef';
|
||||
@@ -21,7 +21,7 @@ describe('Assertion library', () => {
|
||||
const varName = 'address';
|
||||
return expect(assert.isSenderAddressAsync(varName, validUnrelatedAddress, (zeroEx as any)._web3Wrapper))
|
||||
.to.be.rejectedWith(
|
||||
`Specified ${varName} ${validUnrelatedAddress} isn't available through the supplied web3 instance`,
|
||||
`Specified ${varName} ${validUnrelatedAddress} isn't available through the supplied web3 provider`,
|
||||
);
|
||||
});
|
||||
it('doesn\'t throw if address is available', async () => {
|
||||
|
110
test/ether_token_wrapper_test.ts
Normal file
110
test/ether_token_wrapper_test.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import 'mocha';
|
||||
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';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle();
|
||||
|
||||
// Since the address depositing/withdrawing ETH/WETH also needs to pay gas costs for the transaction,
|
||||
// a small amount of ETH will be used to pay this gas cost. We therefore check that the difference between
|
||||
// the expected balance and actual balance (given the amount of ETH deposited), only deviates by the amount
|
||||
// required to pay gas costs.
|
||||
const MAX_REASONABLE_GAS_COST_IN_WEI = 62237;
|
||||
|
||||
describe('EtherTokenWrapper', () => {
|
||||
let web3: Web3;
|
||||
let zeroEx: ZeroEx;
|
||||
let userAddresses: string[];
|
||||
let addressWithETH: string;
|
||||
let wethContractAddress: string;
|
||||
let depositWeiAmount: BigNumber.BigNumber;
|
||||
let decimalPlaces: number;
|
||||
const gasPrice = new BigNumber(1);
|
||||
const zeroExConfig = {
|
||||
gasPrice,
|
||||
};
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3.currentProvider, zeroExConfig);
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
addressWithETH = userAddresses[0];
|
||||
wethContractAddress = await zeroEx.etherToken.getContractAddressAsync();
|
||||
depositWeiAmount = (zeroEx as any)._web3Wrapper.toWei(new BigNumber(5));
|
||||
decimalPlaces = 7;
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('#depositAsync', () => {
|
||||
it('should successfully deposit ETH and issue Wrapped ETH tokens', async () => {
|
||||
const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
|
||||
const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
|
||||
expect(preETHBalance).to.be.bignumber.gt(0);
|
||||
expect(preWETHBalance).to.be.bignumber.equal(0);
|
||||
|
||||
await zeroEx.etherToken.depositAsync(depositWeiAmount, addressWithETH);
|
||||
|
||||
const postETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
|
||||
const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
|
||||
|
||||
expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(depositWeiAmount);
|
||||
const remainingETHInWei = preETHBalance.minus(depositWeiAmount);
|
||||
const gasCost = remainingETHInWei.minus(postETHBalanceInWei);
|
||||
expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI);
|
||||
});
|
||||
it('should throw if user has insufficient ETH balance for deposit', async () => {
|
||||
const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
|
||||
|
||||
const extraETHBalance = (zeroEx as any)._web3Wrapper.toWei(5, 'ether');
|
||||
const overETHBalanceinWei = preETHBalance.add(extraETHBalance);
|
||||
|
||||
return expect(
|
||||
zeroEx.etherToken.depositAsync(overETHBalanceinWei, addressWithETH),
|
||||
).to.be.rejectedWith(ZeroExError.InsufficientEthBalanceForDeposit);
|
||||
});
|
||||
});
|
||||
describe('#withdrawAsync', () => {
|
||||
it('should successfully withdraw ETH in return for Wrapped ETH tokens', async () => {
|
||||
const ETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
|
||||
|
||||
await zeroEx.etherToken.depositAsync(depositWeiAmount, addressWithETH);
|
||||
|
||||
const expectedPreETHBalance = ETHBalanceInWei.minus(depositWeiAmount);
|
||||
const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
|
||||
const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
|
||||
let gasCost = expectedPreETHBalance.minus(preETHBalance);
|
||||
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 postETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
|
||||
const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
|
||||
|
||||
expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(0);
|
||||
const expectedETHBalance = preETHBalance.add(depositWeiAmount).round(decimalPlaces);
|
||||
gasCost = expectedETHBalance.minus(postETHBalance);
|
||||
expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI);
|
||||
});
|
||||
it('should throw if user has insufficient WETH balance for withdrawl', async () => {
|
||||
const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
|
||||
expect(preWETHBalance).to.be.bignumber.equal(0);
|
||||
|
||||
const overWETHBalance = preWETHBalance.add(999999999);
|
||||
|
||||
return expect(
|
||||
zeroEx.etherToken.withdrawAsync(overWETHBalance, addressWithETH),
|
||||
).to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal);
|
||||
});
|
||||
});
|
||||
});
|
@@ -6,22 +6,24 @@ import {chaiSetup} from './utils/chai_setup';
|
||||
import ChaiBigNumber = require('chai-bignumber');
|
||||
import promisify = require('es6-promisify');
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {ZeroEx} from '../src/0x';
|
||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||
import {
|
||||
ZeroEx,
|
||||
Token,
|
||||
Order,
|
||||
SignedOrder,
|
||||
SubscriptionOpts,
|
||||
ExchangeEvents,
|
||||
ContractEvent,
|
||||
DoneCallback,
|
||||
ExchangeContractErrs,
|
||||
OrderCancellationRequest,
|
||||
OrderFillRequest,
|
||||
} from '../src/types';
|
||||
LogFillContractEventArgs,
|
||||
} from '../src';
|
||||
import {DoneCallback} 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;
|
||||
@@ -37,14 +39,16 @@ describe('ExchangeWrapper', () => {
|
||||
let userAddresses: string[];
|
||||
let zrxTokenAddress: string;
|
||||
let fillScenarios: FillScenarios;
|
||||
let exchangeContractAddress: string;
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3);
|
||||
userAddresses = await promisify(web3.eth.getAccounts)();
|
||||
zeroEx = new ZeroEx(web3.currentProvider);
|
||||
exchangeContractAddress = await zeroEx.exchange.getContractAddressAsync();
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
tokenUtils = new TokenUtils(tokens);
|
||||
zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
|
||||
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress);
|
||||
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress);
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
@@ -91,24 +95,6 @@ describe('ExchangeWrapper', () => {
|
||||
});
|
||||
});
|
||||
describe('#fillOrKillOrderAsync', () => {
|
||||
describe('failed fillOrKill', () => {
|
||||
it('should throw if remaining fillAmount is less then the desired fillAmount', async () => {
|
||||
const fillableAmount = new BigNumber(5);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
const tooLargeFillAmount = new BigNumber(7);
|
||||
const fillAmountDifference = tooLargeFillAmount.minus(fillableAmount);
|
||||
await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, fillAmountDifference);
|
||||
await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, tooLargeFillAmount);
|
||||
await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, fillAmountDifference);
|
||||
await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, tooLargeFillAmount);
|
||||
|
||||
return expect(zeroEx.exchange.fillOrKillOrderAsync(
|
||||
signedOrder, tooLargeFillAmount, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_REMAINING_FILL_AMOUNT);
|
||||
});
|
||||
});
|
||||
describe('successful fills', () => {
|
||||
it('should fill a valid order', async () => {
|
||||
const fillableAmount = new BigNumber(5);
|
||||
@@ -161,7 +147,7 @@ describe('ExchangeWrapper', () => {
|
||||
let feeRecipient: string;
|
||||
const fillableAmount = new BigNumber(5);
|
||||
const fillTakerAmount = new BigNumber(5);
|
||||
const shouldCheckTransfer = false;
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
before(async () => {
|
||||
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
@@ -170,135 +156,6 @@ describe('ExchangeWrapper', () => {
|
||||
takerTokenAddress = takerToken.address;
|
||||
});
|
||||
describe('#fillOrderAsync', () => {
|
||||
describe('failed fills', () => {
|
||||
it('should throw when the fill amount is zero', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
const zeroFillAmount = new BigNumber(0);
|
||||
return expect(zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, zeroFillAmount, shouldCheckTransfer, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.ORDER_REMAINING_FILL_AMOUNT_ZERO);
|
||||
});
|
||||
it('should throw when sender is not a taker', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
const nonTakerAddress = userAddresses[6];
|
||||
return expect(zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmount, shouldCheckTransfer, nonTakerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER);
|
||||
});
|
||||
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.fillOrderAsync(
|
||||
signedOrder, fillTakerAmount, shouldCheckTransfer, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.ORDER_FILL_EXPIRED);
|
||||
});
|
||||
describe('should throw when not enough balance or allowance to fulfill the order', () => {
|
||||
const balanceToSubtractFromMaker = 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, balanceToSubtractFromMaker,
|
||||
);
|
||||
return expect(zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmount, shouldCheckTransfer, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_TAKER_BALANCE);
|
||||
});
|
||||
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(zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmount, shouldCheckTransfer, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_TAKER_ALLOWANCE);
|
||||
});
|
||||
it('should throw when maker balance is less than maker fill amount', async () => {
|
||||
await zeroEx.token.transferAsync(
|
||||
makerTokenAddress, makerAddress, coinbase, balanceToSubtractFromMaker,
|
||||
);
|
||||
return expect(zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmount, shouldCheckTransfer, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_MAKER_BALANCE);
|
||||
});
|
||||
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(zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmount, shouldCheckTransfer, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_MAKER_ALLOWANCE);
|
||||
});
|
||||
});
|
||||
it('should throw when there a rounding error would have occurred', async () => {
|
||||
const makerAmount = new BigNumber(3);
|
||||
const takerAmount = new BigNumber(5);
|
||||
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||
makerAmount, takerAmount,
|
||||
);
|
||||
const fillTakerAmountThatCausesRoundingError = new BigNumber(3);
|
||||
return expect(zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmountThatCausesRoundingError, shouldCheckTransfer, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.ORDER_FILL_ROUNDING_ERROR);
|
||||
});
|
||||
describe('should throw when not enough balance or allowance to pay fees', () => {
|
||||
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(zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmount, shouldCheckTransfer, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_MAKER_FEE_BALANCE);
|
||||
});
|
||||
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(zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmount, shouldCheckTransfer, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_MAKER_FEE_ALLOWANCE);
|
||||
});
|
||||
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(zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmount, shouldCheckTransfer, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_TAKER_FEE_BALANCE);
|
||||
});
|
||||
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(zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmount, shouldCheckTransfer, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.INSUFFICIENT_TAKER_FEE_ALLOWANCE);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('successful fills', () => {
|
||||
it('should fill a valid order', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
@@ -313,7 +170,7 @@ describe('ExchangeWrapper', () => {
|
||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
|
||||
.to.be.bignumber.equal(fillableAmount);
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmount, shouldCheckTransfer, takerAddress);
|
||||
signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
|
||||
.to.be.bignumber.equal(fillableAmount.minus(fillTakerAmount));
|
||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
|
||||
@@ -329,7 +186,7 @@ describe('ExchangeWrapper', () => {
|
||||
);
|
||||
const partialFillAmount = new BigNumber(3);
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, partialFillAmount, shouldCheckTransfer, takerAddress);
|
||||
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
|
||||
.to.be.bignumber.equal(fillableAmount.minus(partialFillAmount));
|
||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
|
||||
@@ -339,6 +196,34 @@ 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);
|
||||
@@ -347,13 +232,13 @@ describe('ExchangeWrapper', () => {
|
||||
makerAddress, takerAddress, fillableAmount, feeRecipient,
|
||||
);
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmount, shouldCheckTransfer, takerAddress);
|
||||
signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
expect(await zeroEx.token.getBalanceAsync(zrxTokenAddress, feeRecipient))
|
||||
.to.be.bignumber.equal(makerFee.plus(takerFee));
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#batchFillOrderAsync', () => {
|
||||
describe('#batchFillOrdersAsync', () => {
|
||||
let signedOrder: SignedOrder;
|
||||
let signedOrderHashHex: string;
|
||||
let anotherSignedOrder: SignedOrder;
|
||||
@@ -363,11 +248,11 @@ describe('ExchangeWrapper', () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
signedOrderHashHex = await zeroEx.getOrderHashHexAsync(signedOrder);
|
||||
signedOrderHashHex = ZeroEx.getOrderHashHex(signedOrder);
|
||||
anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
anotherOrderHashHex = await zeroEx.getOrderHashHexAsync(anotherSignedOrder);
|
||||
anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder);
|
||||
orderFillBatch = [
|
||||
{
|
||||
signedOrder,
|
||||
@@ -381,10 +266,12 @@ describe('ExchangeWrapper', () => {
|
||||
});
|
||||
describe('successful batch fills', () => {
|
||||
it('should no-op for an empty batch', async () => {
|
||||
await zeroEx.exchange.batchFillOrderAsync([], shouldCheckTransfer, takerAddress);
|
||||
await zeroEx.exchange.batchFillOrdersAsync(
|
||||
[], shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
});
|
||||
it('should successfully fill multiple orders', async () => {
|
||||
await zeroEx.exchange.batchFillOrderAsync(orderFillBatch, shouldCheckTransfer, takerAddress);
|
||||
await zeroEx.exchange.batchFillOrdersAsync(
|
||||
orderFillBatch, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex);
|
||||
const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex);
|
||||
expect(filledAmount).to.be.bignumber.equal(fillTakerAmount);
|
||||
@@ -403,20 +290,21 @@ describe('ExchangeWrapper', () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
signedOrderHashHex = await zeroEx.getOrderHashHexAsync(signedOrder);
|
||||
signedOrderHashHex = ZeroEx.getOrderHashHex(signedOrder);
|
||||
anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
anotherOrderHashHex = await zeroEx.getOrderHashHexAsync(anotherSignedOrder);
|
||||
anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder);
|
||||
signedOrders = [signedOrder, anotherSignedOrder];
|
||||
});
|
||||
describe('successful batch fills', () => {
|
||||
it('should no-op for an empty batch', async () => {
|
||||
await zeroEx.exchange.fillOrdersUpToAsync([], fillUpToAmount, shouldCheckTransfer, takerAddress);
|
||||
await zeroEx.exchange.fillOrdersUpToAsync(
|
||||
[], fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
});
|
||||
it('should successfully fill up to specified amount', async () => {
|
||||
await zeroEx.exchange.fillOrdersUpToAsync(
|
||||
signedOrders, fillUpToAmount, shouldCheckTransfer, takerAddress,
|
||||
signedOrders, fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
);
|
||||
const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex);
|
||||
const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex);
|
||||
@@ -424,6 +312,12 @@ describe('ExchangeWrapper', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -445,40 +339,22 @@ describe('ExchangeWrapper', () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
orderHashHex = await zeroEx.getOrderHashHexAsync(signedOrder);
|
||||
orderHashHex = ZeroEx.getOrderHashHex(signedOrder);
|
||||
});
|
||||
describe('#cancelOrderAsync', () => {
|
||||
describe('failed cancels', () => {
|
||||
it('should throw when cancel amount is zero', async () => {
|
||||
const zeroCancelAmount = new BigNumber(0);
|
||||
return expect(zeroEx.exchange.cancelOrderAsync(signedOrder, zeroCancelAmount))
|
||||
.to.be.rejectedWith(ExchangeContractErrs.ORDER_CANCEL_AMOUNT_ZERO);
|
||||
});
|
||||
it('should throw when order is expired', async () => {
|
||||
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
|
||||
const expiredSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||
fillableAmount, expirationInPast,
|
||||
);
|
||||
orderHashHex = await zeroEx.getOrderHashHexAsync(expiredSignedOrder);
|
||||
return expect(zeroEx.exchange.cancelOrderAsync(expiredSignedOrder, cancelAmount))
|
||||
.to.be.rejectedWith(ExchangeContractErrs.ORDER_CANCEL_EXPIRED);
|
||||
});
|
||||
it('should throw when order is already cancelled or filled', async () => {
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
|
||||
return expect(zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount))
|
||||
.to.be.rejectedWith(ExchangeContractErrs.ORDER_ALREADY_CANCELLED_OR_FILLED);
|
||||
});
|
||||
});
|
||||
describe('successful cancels', () => {
|
||||
it('should cancel an order', async () => {
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount);
|
||||
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('#batchCancelOrderAsync', () => {
|
||||
describe('#batchCancelOrdersAsync', () => {
|
||||
let anotherSignedOrder: SignedOrder;
|
||||
let anotherOrderHashHex: string;
|
||||
let cancelBatch: OrderCancellationRequest[];
|
||||
@@ -486,7 +362,7 @@ describe('ExchangeWrapper', () => {
|
||||
anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
anotherOrderHashHex = await zeroEx.getOrderHashHexAsync(anotherSignedOrder);
|
||||
anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder);
|
||||
cancelBatch = [
|
||||
{
|
||||
order: signedOrder,
|
||||
@@ -503,21 +379,22 @@ describe('ExchangeWrapper', () => {
|
||||
const signedOrderWithDifferentMaker = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, takerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
return expect(zeroEx.exchange.batchCancelOrderAsync([
|
||||
return expect(zeroEx.exchange.batchCancelOrdersAsync([
|
||||
cancelBatch[0],
|
||||
{
|
||||
order: signedOrderWithDifferentMaker,
|
||||
takerTokenCancelAmount: cancelAmount,
|
||||
},
|
||||
])).to.be.rejectedWith(ExchangeContractErrs.MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH);
|
||||
])).to.be.rejectedWith(ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed);
|
||||
});
|
||||
});
|
||||
describe('successful batch cancels', () => {
|
||||
it('should cancel a batch of orders', async () => {
|
||||
await zeroEx.exchange.batchCancelOrderAsync(cancelBatch);
|
||||
await zeroEx.exchange.batchCancelOrdersAsync(cancelBatch);
|
||||
const cancelledAmount = await zeroEx.exchange.getCanceledTakerAmountAsync(orderHashHex);
|
||||
const anotherCancelledAmount = await zeroEx.exchange.getCanceledTakerAmountAsync(
|
||||
anotherOrderHashHex);
|
||||
anotherOrderHashHex,
|
||||
);
|
||||
expect(cancelledAmount).to.be.bignumber.equal(cancelAmount);
|
||||
expect(anotherCancelledAmount).to.be.bignumber.equal(cancelAmount);
|
||||
});
|
||||
@@ -544,50 +421,51 @@ describe('ExchangeWrapper', () => {
|
||||
signedOrder = await fillScenarios.createPartiallyFilledSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, takerAddress, fillableAmount, partialFillAmount,
|
||||
);
|
||||
orderHash = await zeroEx.getOrderHashHexAsync(signedOrder);
|
||||
orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
});
|
||||
describe('#getUnavailableTakerAmountAsync', () => {
|
||||
it ('should throw if passed an invalid orderHash', async () => {
|
||||
it('should throw if passed an invalid orderHash', async () => {
|
||||
const invalidOrderHashHex = '0x123';
|
||||
return expect(zeroEx.exchange.getUnavailableTakerAmountAsync(invalidOrderHashHex)).to.be.rejected();
|
||||
});
|
||||
it ('should return zero if passed a valid but non-existent orderHash', async () => {
|
||||
it('should return zero if passed a valid but non-existent orderHash', async () => {
|
||||
const unavailableValueT = await zeroEx.exchange.getUnavailableTakerAmountAsync(NON_EXISTENT_ORDER_HASH);
|
||||
expect(unavailableValueT).to.be.bignumber.equal(0);
|
||||
});
|
||||
it ('should return the unavailableValueT for a valid and partially filled orderHash', async () => {
|
||||
it('should return the unavailableValueT for a valid and partially filled orderHash', async () => {
|
||||
const unavailableValueT = await zeroEx.exchange.getUnavailableTakerAmountAsync(orderHash);
|
||||
expect(unavailableValueT).to.be.bignumber.equal(partialFillAmount);
|
||||
});
|
||||
});
|
||||
describe('#getFilledTakerAmountAsync', () => {
|
||||
it ('should throw if passed an invalid orderHash', async () => {
|
||||
it('should throw if passed an invalid orderHash', async () => {
|
||||
const invalidOrderHashHex = '0x123';
|
||||
return expect(zeroEx.exchange.getFilledTakerAmountAsync(invalidOrderHashHex)).to.be.rejected();
|
||||
});
|
||||
it ('should return zero if passed a valid but non-existent orderHash', async () => {
|
||||
const filledValueT = await zeroEx.exchange.getFilledTakerAmountAsync(NON_EXISTENT_ORDER_HASH);
|
||||
it('should return zero if passed a valid but non-existent orderHash', async () => {
|
||||
const filledValueT = await zeroEx.exchange.getFilledTakerAmountAsync(NON_EXISTENT_ORDER_HASH,
|
||||
);
|
||||
expect(filledValueT).to.be.bignumber.equal(0);
|
||||
});
|
||||
it ('should return the filledValueT for a valid and partially filled orderHash', async () => {
|
||||
it('should return the filledValueT for a valid and partially filled orderHash', async () => {
|
||||
const filledValueT = await zeroEx.exchange.getFilledTakerAmountAsync(orderHash);
|
||||
expect(filledValueT).to.be.bignumber.equal(partialFillAmount);
|
||||
});
|
||||
});
|
||||
describe('#getCanceledTakerAmountAsync', () => {
|
||||
it ('should throw if passed an invalid orderHash', async () => {
|
||||
it('should throw if passed an invalid orderHash', async () => {
|
||||
const invalidOrderHashHex = '0x123';
|
||||
return expect(zeroEx.exchange.getCanceledTakerAmountAsync(invalidOrderHashHex)).to.be.rejected();
|
||||
});
|
||||
it ('should return zero if passed a valid but non-existent orderHash', async () => {
|
||||
it('should return zero if passed a valid but non-existent orderHash', async () => {
|
||||
const cancelledValueT = await zeroEx.exchange.getCanceledTakerAmountAsync(NON_EXISTENT_ORDER_HASH);
|
||||
expect(cancelledValueT).to.be.bignumber.equal(0);
|
||||
});
|
||||
it ('should return the cancelledValueT for a valid and partially filled orderHash', async () => {
|
||||
it('should return the cancelledValueT for a valid and partially filled orderHash', async () => {
|
||||
const cancelledValueT = await zeroEx.exchange.getCanceledTakerAmountAsync(orderHash);
|
||||
expect(cancelledValueT).to.be.bignumber.equal(0);
|
||||
});
|
||||
it ('should return the cancelledValueT for a valid and cancelled orderHash', async () => {
|
||||
it('should return the cancelledValueT for a valid and cancelled orderHash', async () => {
|
||||
const cancelAmount = fillableAmount.minus(partialFillAmount);
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount);
|
||||
const cancelledValueT = await zeroEx.exchange.getCanceledTakerAmountAsync(orderHash);
|
||||
@@ -597,7 +475,7 @@ describe('ExchangeWrapper', () => {
|
||||
});
|
||||
describe('#subscribeAsync', () => {
|
||||
const indexFilterValues = {};
|
||||
const shouldCheckTransfer = false;
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
let makerTokenAddress: string;
|
||||
let takerTokenAddress: string;
|
||||
let coinbase: string;
|
||||
@@ -605,6 +483,12 @@ 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 cancelTakerAmountInBaseUnits = new BigNumber(1);
|
||||
before(() => {
|
||||
[coinbase, makerAddress, takerAddress] = userAddresses;
|
||||
const [makerToken, takerToken] = tokens;
|
||||
@@ -618,7 +502,7 @@ describe('ExchangeWrapper', () => {
|
||||
);
|
||||
});
|
||||
afterEach(async () => {
|
||||
await (zeroEx.exchange as any)._stopWatchingExchangeLogEventsAsync();
|
||||
await zeroEx.exchange.stopWatchingAllEventsAsync();
|
||||
});
|
||||
// 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,
|
||||
@@ -627,64 +511,92 @@ describe('ExchangeWrapper', () => {
|
||||
// Source: https://github.com/mochajs/mocha/issues/2407
|
||||
it('Should receive the LogFill event when an order is filled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const subscriptionOpts: SubscriptionOpts = {
|
||||
fromBlock: 0,
|
||||
toBlock: 'latest',
|
||||
};
|
||||
await zeroEx.exchange.subscribeAsync(ExchangeEvents.LogFill, subscriptionOpts,
|
||||
indexFilterValues, (err: Error, event: ContractEvent) => {
|
||||
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');
|
||||
done();
|
||||
});
|
||||
const fillTakerAmountInBaseUnits = new BigNumber(1);
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmountInBaseUnits, shouldCheckTransfer, takerAddress,
|
||||
signedOrder, fillTakerAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
);
|
||||
})();
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should receive the LogCancel event when an order is cancelled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const subscriptionOpts: SubscriptionOpts = {
|
||||
fromBlock: 0,
|
||||
toBlock: 'latest',
|
||||
};
|
||||
await zeroEx.exchange.subscribeAsync(ExchangeEvents.LogCancel, subscriptionOpts,
|
||||
indexFilterValues, (err: Error, event: ContractEvent) => {
|
||||
const zeroExEvent = await zeroEx.exchange.subscribeAsync(
|
||||
ExchangeEvents.LogCancel, 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('LogCancel');
|
||||
done();
|
||||
});
|
||||
const cancelTakerAmountInBaseUnits = new BigNumber(1);
|
||||
});
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerAmountInBaseUnits);
|
||||
})();
|
||||
})().catch(done);
|
||||
});
|
||||
it('Outstanding subscriptions are cancelled when zeroEx.setProviderAsync called', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const subscriptionOpts: SubscriptionOpts = {
|
||||
fromBlock: 0,
|
||||
toBlock: 'latest',
|
||||
};
|
||||
await zeroEx.exchange.subscribeAsync(ExchangeEvents.LogFill, subscriptionOpts,
|
||||
indexFilterValues, (err: Error, event: ContractEvent) => {
|
||||
const eventSubscriptionToBeCancelled = await zeroEx.exchange.subscribeAsync(
|
||||
ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress,
|
||||
);
|
||||
eventSubscriptionToBeCancelled.watch((err: Error, event: ContractEvent) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
});
|
||||
|
||||
const newProvider = web3Factory.getRpcProvider();
|
||||
await zeroEx.setProviderAsync(newProvider);
|
||||
|
||||
await zeroEx.exchange.subscribeAsync(ExchangeEvents.LogFill, subscriptionOpts,
|
||||
indexFilterValues, (err: Error, event: ContractEvent) => {
|
||||
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');
|
||||
done();
|
||||
});
|
||||
|
||||
const fillTakerAmountInBaseUnits = new BigNumber(1);
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmountInBaseUnits, shouldCheckTransfer, takerAddress,
|
||||
signedOrder, fillTakerAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
);
|
||||
})();
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should stop watch for events when stopWatchingAsync called on the eventEmitter', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const eventSubscriptionToBeStopped = await zeroEx.exchange.subscribeAsync(
|
||||
ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress,
|
||||
);
|
||||
eventSubscriptionToBeStopped.watch((err: Error, event: ContractEvent) => {
|
||||
done(new Error('Expected this subscription to have been stopped'));
|
||||
});
|
||||
await eventSubscriptionToBeStopped.stopWatchingAsync();
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmountInBaseUnits, 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', () => {
|
||||
@@ -703,7 +615,7 @@ describe('ExchangeWrapper', () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
const orderHash = await zeroEx.getOrderHashHexAsync(signedOrder);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
const orderHashFromContract = await (zeroEx.exchange as any)
|
||||
._getOrderHashHexUsingContractCallAsync(signedOrder);
|
||||
expect(orderHash).to.equal(orderHashFromContract);
|
||||
|
310
test/order_validation_test.ts
Normal file
310
test/order_validation_test.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
import * as chai from 'chai';
|
||||
import * as Web3 from 'web3';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import promisify = require('es6-promisify');
|
||||
import {chaiSetup} from './utils/chai_setup';
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {ZeroEx, SignedOrder, Token, ExchangeContractErrs} from '../src';
|
||||
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';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle();
|
||||
|
||||
describe('OrderValidation', () => {
|
||||
let web3: Web3;
|
||||
let zeroEx: ZeroEx;
|
||||
let userAddresses: string[];
|
||||
let tokens: Token[];
|
||||
let tokenUtils: TokenUtils;
|
||||
let exchangeContractAddress: string;
|
||||
let zrxTokenAddress: string;
|
||||
let fillScenarios: FillScenarios;
|
||||
let makerTokenAddress: string;
|
||||
let takerTokenAddress: string;
|
||||
let coinbase: string;
|
||||
let makerAddress: string;
|
||||
let takerAddress: string;
|
||||
let feeRecipient: string;
|
||||
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);
|
||||
exchangeContractAddress = await zeroEx.exchange.getContractAddressAsync();
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
tokenUtils = new TokenUtils(tokens);
|
||||
zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
|
||||
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress);
|
||||
const [makerToken, takerToken] = tokenUtils.getNonProtocolTokens();
|
||||
makerTokenAddress = makerToken.address;
|
||||
takerTokenAddress = takerToken.address;
|
||||
orderValidationUtils = new OrderValidationUtils(zeroEx.token, zeroEx.exchange);
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('validateFillOrderAndThrowIfInvalidAsync', () => {
|
||||
it('should throw when the fill amount is zero', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
const zeroFillAmount = new BigNumber(0);
|
||||
return expect(zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder, zeroFillAmount, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
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.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder, fillableAmount, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
});
|
||||
it('should throw when sender is not a taker', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
const nonTakerAddress = userAddresses[6];
|
||||
return expect(zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerAmount, nonTakerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
|
||||
});
|
||||
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.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerAmount, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.OrderFillExpired);
|
||||
});
|
||||
it('should throw when there a rounding error would have occurred', async () => {
|
||||
const makerAmount = new BigNumber(3);
|
||||
const takerAmount = new BigNumber(5);
|
||||
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||
makerAmount, takerAmount,
|
||||
);
|
||||
const fillTakerAmountThatCausesRoundingError = new BigNumber(3);
|
||||
return expect(zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerAmountThatCausesRoundingError, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.OrderFillRoundingError);
|
||||
});
|
||||
});
|
||||
describe('#validateFillOrKillOrderAndThrowIfInvalidAsync', () => {
|
||||
it('should throw if remaining fillAmount is less then the desired fillAmount', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
const tooLargeFillAmount = new BigNumber(7);
|
||||
const fillAmountDifference = tooLargeFillAmount.minus(fillableAmount);
|
||||
await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, fillAmountDifference);
|
||||
await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, tooLargeFillAmount);
|
||||
await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, fillAmountDifference);
|
||||
await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, tooLargeFillAmount);
|
||||
|
||||
return expect(zeroEx.exchange.validateFillOrKillOrderThrowIfInvalidAsync(
|
||||
signedOrder, tooLargeFillAmount, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientRemainingFillAmount);
|
||||
});
|
||||
});
|
||||
describe('validateCancelOrderAndThrowIfInvalidAsync', () => {
|
||||
let signedOrder: SignedOrder;
|
||||
let orderHashHex: string;
|
||||
const cancelAmount = new BigNumber(3);
|
||||
beforeEach(async () => {
|
||||
[coinbase, makerAddress, takerAddress] = userAddresses;
|
||||
const [makerToken, takerToken] = tokenUtils.getNonProtocolTokens();
|
||||
makerTokenAddress = makerToken.address;
|
||||
takerTokenAddress = takerToken.address;
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
orderHashHex = ZeroEx.getOrderHashHex(signedOrder);
|
||||
});
|
||||
it('should throw when cancel amount is zero', async () => {
|
||||
const zeroCancelAmount = new BigNumber(0);
|
||||
return expect(zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, zeroCancelAmount))
|
||||
.to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
|
||||
});
|
||||
it('should throw when order is expired', async () => {
|
||||
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
|
||||
const expiredSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||
fillableAmount, expirationInPast,
|
||||
);
|
||||
orderHashHex = ZeroEx.getOrderHashHex(expiredSignedOrder);
|
||||
return expect(zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(expiredSignedOrder, cancelAmount))
|
||||
.to.be.rejectedWith(ExchangeContractErrs.OrderCancelExpired);
|
||||
});
|
||||
it('should throw when order is already cancelled or filled', async () => {
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
|
||||
return expect(zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, fillableAmount))
|
||||
.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('should throw when not enough balance or allowance to pay fees', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
describe('should throw on insufficient balance or allowance when makerToken is ZRX',
|
||||
() => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,293 +0,0 @@
|
||||
import 'mocha';
|
||||
import * as _ from 'lodash';
|
||||
import * as chai from 'chai';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import promisify = require('es6-promisify');
|
||||
import {constants} from './utils/constants';
|
||||
import {SchemaValidator} from '../src/utils/schema_validator';
|
||||
import {tokenSchema} from '../src/schemas/token_schema';
|
||||
import {orderSchema, signedOrderSchema} from '../src/schemas/order_schemas';
|
||||
import {addressSchema, numberSchema} from '../src/schemas/basic_type_schemas';
|
||||
import {orderFillOrKillRequestsSchema} from '../src/schemas/order_fill_or_kill_requests_schema';
|
||||
import {ecSignatureParameterSchema, ecSignatureSchema} from '../src/schemas/ec_signature_schema';
|
||||
import {orderCancellationRequestsSchema} from '../src/schemas/order_cancel_schema';
|
||||
import {orderFillRequestsSchema} from '../src/schemas/order_fill_requests_schema';
|
||||
|
||||
chai.config.includeStack = true;
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('Schema', () => {
|
||||
const validator = new SchemaValidator();
|
||||
const validateAgainstSchema = (testCases: any[], schema: any, shouldFail = false) => {
|
||||
_.forEach(testCases, (testCase: any) => {
|
||||
if (shouldFail) {
|
||||
expect(validator.validate(testCase, schema).errors).to.be.lengthOf.at.least(1);
|
||||
} else {
|
||||
expect(validator.validate(testCase, schema).errors).to.be.lengthOf(0);
|
||||
}
|
||||
});
|
||||
};
|
||||
describe('#numberSchema', () => {
|
||||
it('should validate valid numbers', () => {
|
||||
const testCases = ['42', '0', '1.3', '0.2', '00.00'];
|
||||
validateAgainstSchema(testCases, numberSchema);
|
||||
});
|
||||
it('should fail for invalid numbers', () => {
|
||||
const testCases = ['.3', '1.', 'abacaba', 'и', '1..0'];
|
||||
const shouldFail = true;
|
||||
validateAgainstSchema(testCases, numberSchema, shouldFail);
|
||||
});
|
||||
});
|
||||
describe('#addressSchema', () => {
|
||||
it('should validate valid addresses', () => {
|
||||
const testCases = ['0x8b0292B11a196601eD2ce54B665CaFEca0347D42', constants.NULL_ADDRESS];
|
||||
validateAgainstSchema(testCases, addressSchema);
|
||||
});
|
||||
it('should fail for invalid addresses', () => {
|
||||
const testCases = ['0x', '0', '0x00', '0xzzzzzzB11a196601eD2ce54B665CaFEca0347D42'];
|
||||
const shouldFail = true;
|
||||
validateAgainstSchema(testCases, addressSchema, shouldFail);
|
||||
});
|
||||
});
|
||||
describe('#ecSignatureParameterSchema', () => {
|
||||
it('should validate valid parameters', () => {
|
||||
const testCases = [
|
||||
'0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
|
||||
'0X40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
|
||||
];
|
||||
validateAgainstSchema(testCases, ecSignatureParameterSchema);
|
||||
});
|
||||
it('should fail for invalid parameters', () => {
|
||||
const testCases = [
|
||||
'0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3', // shorter
|
||||
'0xzzzz9190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', // invalid characters
|
||||
'40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254', // no 0x
|
||||
];
|
||||
const shouldFail = true;
|
||||
validateAgainstSchema(testCases, ecSignatureParameterSchema, shouldFail);
|
||||
});
|
||||
});
|
||||
describe('#ecSignatureSchema', () => {
|
||||
it('should validate valid signature', () => {
|
||||
const signature = {
|
||||
v: 27,
|
||||
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
|
||||
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
|
||||
};
|
||||
const testCases = [
|
||||
signature,
|
||||
{
|
||||
...signature,
|
||||
v: 28,
|
||||
},
|
||||
];
|
||||
validateAgainstSchema(testCases, ecSignatureSchema);
|
||||
});
|
||||
it('should fail for invalid signature', () => {
|
||||
const v = 27;
|
||||
const r = '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33';
|
||||
const s = '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254';
|
||||
const testCases = [
|
||||
{},
|
||||
{v},
|
||||
{r, s, v: 31},
|
||||
];
|
||||
const shouldFail = true;
|
||||
validateAgainstSchema(testCases, ecSignatureSchema, shouldFail);
|
||||
});
|
||||
});
|
||||
describe('#tokenSchema', () => {
|
||||
const token = {
|
||||
name: 'Zero Ex',
|
||||
symbol: 'ZRX',
|
||||
decimals: 100500,
|
||||
address: '0x8b0292B11a196601eD2ce54B665CaFEca0347D42',
|
||||
url: 'https://0xproject.com',
|
||||
};
|
||||
it('should validate valid token', () => {
|
||||
const testCases = [
|
||||
token,
|
||||
];
|
||||
validateAgainstSchema(testCases, tokenSchema);
|
||||
});
|
||||
it('should fail for invalid token', () => {
|
||||
const testCases = [
|
||||
{
|
||||
...token,
|
||||
address: null,
|
||||
},
|
||||
{
|
||||
...token,
|
||||
decimals: undefined,
|
||||
},
|
||||
[],
|
||||
4,
|
||||
{
|
||||
...token,
|
||||
url: 'not an url',
|
||||
},
|
||||
];
|
||||
const shouldFail = true;
|
||||
validateAgainstSchema(testCases, tokenSchema, shouldFail);
|
||||
});
|
||||
});
|
||||
describe('order including schemas', () => {
|
||||
const order = {
|
||||
maker: constants.NULL_ADDRESS,
|
||||
taker: constants.NULL_ADDRESS,
|
||||
makerFee: '1',
|
||||
takerFee: '2',
|
||||
makerTokenAmount: '1',
|
||||
takerTokenAmount: '2',
|
||||
makerTokenAddress: constants.NULL_ADDRESS,
|
||||
takerTokenAddress: constants.NULL_ADDRESS,
|
||||
salt: '256',
|
||||
feeRecipient: constants.NULL_ADDRESS,
|
||||
expirationUnixTimestampSec: '42',
|
||||
};
|
||||
describe('#orderSchema', () => {
|
||||
it('should validate valid order', () => {
|
||||
const testCases = [
|
||||
order,
|
||||
];
|
||||
validateAgainstSchema(testCases, orderSchema);
|
||||
});
|
||||
it('should fail for invalid order', () => {
|
||||
const testCases = [
|
||||
{
|
||||
...order,
|
||||
salt: undefined,
|
||||
},
|
||||
{
|
||||
...order,
|
||||
salt: 'salt',
|
||||
},
|
||||
'order',
|
||||
];
|
||||
const shouldFail = true;
|
||||
validateAgainstSchema(testCases, orderSchema, shouldFail);
|
||||
});
|
||||
});
|
||||
describe('signed order including schemas', () => {
|
||||
const signedOrder = {
|
||||
...order,
|
||||
ecSignature: {
|
||||
v: 27,
|
||||
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
|
||||
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
|
||||
},
|
||||
};
|
||||
describe('#signedOrderSchema', () => {
|
||||
it('should validate valid signed order', () => {
|
||||
const testCases = [
|
||||
signedOrder,
|
||||
];
|
||||
validateAgainstSchema(testCases, signedOrderSchema);
|
||||
});
|
||||
it('should fail for invalid signed order', () => {
|
||||
const testCases = [
|
||||
{
|
||||
...signedOrder,
|
||||
ecSignature: undefined,
|
||||
},
|
||||
];
|
||||
const shouldFail = true;
|
||||
validateAgainstSchema(testCases, signedOrderSchema, shouldFail);
|
||||
});
|
||||
});
|
||||
describe('#orderFillOrKillRequestsSchema', () => {
|
||||
const orderFillOrKillRequests = [
|
||||
{
|
||||
signedOrder,
|
||||
fillTakerAmount: '5',
|
||||
},
|
||||
];
|
||||
it('should validate valid order fill or kill requests', () => {
|
||||
const testCases = [
|
||||
orderFillOrKillRequests,
|
||||
];
|
||||
validateAgainstSchema(testCases, orderFillOrKillRequestsSchema);
|
||||
});
|
||||
it('should fail for invalid order fill or kill requests', () => {
|
||||
const testCases = [
|
||||
[
|
||||
{
|
||||
...orderFillOrKillRequests[0],
|
||||
fillTakerAmount: undefined,
|
||||
},
|
||||
],
|
||||
];
|
||||
const shouldFail = true;
|
||||
validateAgainstSchema(testCases, orderFillOrKillRequestsSchema, shouldFail);
|
||||
});
|
||||
});
|
||||
describe('#orderCancellationRequestsSchema', () => {
|
||||
const orderCancellationRequests = [
|
||||
{
|
||||
order,
|
||||
takerTokenCancelAmount: '5',
|
||||
},
|
||||
];
|
||||
it('should validate valid order cancellation requests', () => {
|
||||
const testCases = [
|
||||
orderCancellationRequests,
|
||||
];
|
||||
validateAgainstSchema(testCases, orderCancellationRequestsSchema);
|
||||
});
|
||||
it('should fail for invalid order cancellation requests', () => {
|
||||
const testCases = [
|
||||
[
|
||||
{
|
||||
...orderCancellationRequests[0],
|
||||
takerTokenCancelAmount: undefined,
|
||||
},
|
||||
],
|
||||
];
|
||||
const shouldFail = true;
|
||||
validateAgainstSchema(testCases, orderCancellationRequestsSchema, shouldFail);
|
||||
});
|
||||
});
|
||||
describe('#orderFillRequestsSchema', () => {
|
||||
const orderFillRequests = [
|
||||
{
|
||||
signedOrder,
|
||||
takerTokenFillAmount: '5',
|
||||
},
|
||||
];
|
||||
it('should validate valid order fill requests', () => {
|
||||
const testCases = [
|
||||
orderFillRequests,
|
||||
];
|
||||
validateAgainstSchema(testCases, orderFillRequestsSchema);
|
||||
});
|
||||
it('should fail for invalid order fill requests', () => {
|
||||
const testCases = [
|
||||
[
|
||||
{
|
||||
...orderFillRequests[0],
|
||||
takerTokenFillAmount: undefined,
|
||||
},
|
||||
],
|
||||
];
|
||||
const shouldFail = true;
|
||||
validateAgainstSchema(testCases, orderFillRequestsSchema, shouldFail);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('BigNumber serialization', () => {
|
||||
it('should correctly serialize BigNumbers', () => {
|
||||
const testCases = {
|
||||
'42': '42',
|
||||
'0': '0',
|
||||
'1.3': '1.3',
|
||||
'0.2': '0.2',
|
||||
'00.00': '0',
|
||||
'.3': '0.3',
|
||||
};
|
||||
_.forEach(testCases, (serialized: string, input: string) => {
|
||||
expect(JSON.parse(JSON.stringify(new BigNumber(input)))).to.be.equal(serialized);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,12 +1,11 @@
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
import * as chai from 'chai';
|
||||
import {SchemaValidator, schemas} from '0x-json-schemas';
|
||||
import {chaiSetup} from './utils/chai_setup';
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {ZeroEx} from '../src/0x';
|
||||
import {ZeroEx, Token} from '../src';
|
||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||
import {SchemaValidator} from '../src/utils/schema_validator';
|
||||
import {tokenSchema} from '../src/schemas/token_schema';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
@@ -16,9 +15,25 @@ const TOKEN_REGISTRY_SIZE_AFTER_MIGRATION = 7;
|
||||
|
||||
describe('TokenRegistryWrapper', () => {
|
||||
let zeroEx: ZeroEx;
|
||||
let tokens: Token[];
|
||||
const tokenAddressBySymbol: {[symbol: string]: string} = {};
|
||||
const tokenAddressByName: {[symbol: string]: string} = {};
|
||||
const tokenBySymbol: {[symbol: string]: Token} = {};
|
||||
const tokenByName: {[symbol: string]: Token} = {};
|
||||
const registeredSymbol = 'ZRX';
|
||||
const registeredName = '0x Protocol Token';
|
||||
const unregisteredSymbol = 'MAL';
|
||||
const unregisteredName = 'Malicious Token';
|
||||
before(async () => {
|
||||
const web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3);
|
||||
zeroEx = new ZeroEx(web3.currentProvider);
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
_.map(tokens, token => {
|
||||
tokenAddressBySymbol[token.symbol] = token.address;
|
||||
tokenAddressByName[token.name] = token.address;
|
||||
tokenBySymbol[token.symbol] = token;
|
||||
tokenByName[token.name] = token;
|
||||
});
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
@@ -28,14 +43,81 @@ describe('TokenRegistryWrapper', () => {
|
||||
});
|
||||
describe('#getTokensAsync', () => {
|
||||
it('should return all the tokens added to the tokenRegistry during the migration', async () => {
|
||||
const tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
expect(tokens).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION);
|
||||
|
||||
const schemaValidator = new SchemaValidator();
|
||||
_.each(tokens, token => {
|
||||
const validationResult = schemaValidator.validate(token, tokenSchema);
|
||||
const validationResult = schemaValidator.validate(token, schemas.tokenSchema);
|
||||
expect(validationResult.errors).to.have.lengthOf(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#getTokenAddressesAsync', () => {
|
||||
it('should return all the token addresses added to the tokenRegistry during the migration', async () => {
|
||||
const tokenAddresses = await zeroEx.tokenRegistry.getTokenAddressesAsync();
|
||||
expect(tokenAddresses).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION);
|
||||
|
||||
const schemaValidator = new SchemaValidator();
|
||||
_.each(tokenAddresses, tokenAddress => {
|
||||
const validationResult = schemaValidator.validate(tokenAddress, schemas.addressSchema);
|
||||
expect(validationResult.errors).to.have.lengthOf(0);
|
||||
expect(tokenAddress).to.not.be.equal(ZeroEx.NULL_ADDRESS);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#getTokenAddressBySymbol', () => {
|
||||
it('should return correct address for a token in the registry', async () => {
|
||||
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(registeredSymbol);
|
||||
expect(tokenAddress).to.be.equal(tokenAddressBySymbol[registeredSymbol]);
|
||||
});
|
||||
it('should return undefined for a token out of registry', async () => {
|
||||
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(unregisteredSymbol);
|
||||
expect(tokenAddress).to.be.undefined();
|
||||
});
|
||||
});
|
||||
describe('#getTokenAddressByName', () => {
|
||||
it('should return correct address for a token in the registry', async () => {
|
||||
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(registeredName);
|
||||
expect(tokenAddress).to.be.equal(tokenAddressByName[registeredName]);
|
||||
});
|
||||
it('should return undefined for a token out of registry', async () => {
|
||||
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(unregisteredName);
|
||||
expect(tokenAddress).to.be.undefined();
|
||||
});
|
||||
});
|
||||
describe('#getTokenBySymbol', () => {
|
||||
it('should return correct token for a token in the registry', async () => {
|
||||
const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(registeredSymbol);
|
||||
expect(token).to.be.deep.equal(tokenBySymbol[registeredSymbol]);
|
||||
});
|
||||
it('should return undefined for a token out of registry', async () => {
|
||||
const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(unregisteredSymbol);
|
||||
expect(token).to.be.undefined();
|
||||
});
|
||||
});
|
||||
describe('#getTokenByName', () => {
|
||||
it('should return correct token for a token in the registry', async () => {
|
||||
const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(registeredName);
|
||||
expect(token).to.be.deep.equal(tokenByName[registeredName]);
|
||||
});
|
||||
it('should return undefined for a token out of registry', async () => {
|
||||
const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(unregisteredName);
|
||||
expect(token).to.be.undefined();
|
||||
});
|
||||
});
|
||||
describe('#getTokenIfExistsAsync', () => {
|
||||
it('should return the token added to the tokenRegistry during the migration', async () => {
|
||||
const aToken = tokens[0];
|
||||
|
||||
const token = await zeroEx.tokenRegistry.getTokenIfExistsAsync(aToken.address);
|
||||
const schemaValidator = new SchemaValidator();
|
||||
const validationResult = schemaValidator.validate(token, schemas.tokenSchema);
|
||||
expect(validationResult.errors).to.have.lengthOf(0);
|
||||
});
|
||||
it('should return return undefined when passed a token address not in the tokenRegistry', async () => {
|
||||
const unregisteredTokenAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
|
||||
const tokenIfExists = await zeroEx.tokenRegistry.getTokenIfExistsAsync(unregisteredTokenAddress);
|
||||
expect(tokenIfExists).to.be.undefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
31
test/token_transfer_proxy_wrapper_test.ts
Normal file
31
test/token_transfer_proxy_wrapper_test.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as chai from 'chai';
|
||||
import {chaiSetup} from './utils/chai_setup';
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {ZeroEx} from '../src';
|
||||
import {TokenTransferProxyWrapper} from '../src/contract_wrappers/token_transfer_proxy_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('TokenTransferProxyWrapper', () => {
|
||||
let zeroEx: ZeroEx;
|
||||
before(async () => {
|
||||
const web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3.currentProvider);
|
||||
});
|
||||
describe('#isAuthorizedAsync', () => {
|
||||
it('should return false if the address is not authorized', async () => {
|
||||
const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(ZeroEx.NULL_ADDRESS);
|
||||
expect(isAuthorized).to.be.false();
|
||||
});
|
||||
});
|
||||
describe('#getAuthorizedAddressesAsync', () => {
|
||||
it('should return the list of authorized addresses', async () => {
|
||||
const authorizedAddresses = await zeroEx.proxy.getAuthorizedAddressesAsync();
|
||||
for (const authorizedAddress of authorizedAddresses) {
|
||||
const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(authorizedAddress);
|
||||
expect(isAuthorized).to.be.true();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
@@ -5,9 +5,19 @@ import * as Web3 from 'web3';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import promisify = require('es6-promisify');
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {ZeroEx} from '../src/0x';
|
||||
import {ZeroExError, Token} from '../src/types';
|
||||
import {
|
||||
ZeroEx,
|
||||
ZeroExError,
|
||||
Token,
|
||||
SubscriptionOpts,
|
||||
TokenEvents,
|
||||
ContractEvent,
|
||||
TransferContractEventArgs,
|
||||
ApprovalContractEventArgs,
|
||||
} from '../src';
|
||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||
import {TokenUtils} from './utils/token_utils';
|
||||
import {DoneCallback} from '../src/types';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
@@ -18,13 +28,15 @@ describe('TokenWrapper', () => {
|
||||
let zeroEx: ZeroEx;
|
||||
let userAddresses: string[];
|
||||
let tokens: Token[];
|
||||
let tokenUtils: TokenUtils;
|
||||
let coinbase: string;
|
||||
let addressWithoutFunds: string;
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3);
|
||||
userAddresses = await promisify(web3.eth.getAccounts)();
|
||||
zeroEx = new ZeroEx(web3.currentProvider);
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
tokenUtils = new TokenUtils(tokens);
|
||||
coinbase = userAddresses[0];
|
||||
addressWithoutFunds = userAddresses[1];
|
||||
});
|
||||
@@ -55,7 +67,7 @@ describe('TokenWrapper', () => {
|
||||
const toAddress = coinbase;
|
||||
return expect(zeroEx.token.transferAsync(
|
||||
token.address, fromAddress, toAddress, transferAmount,
|
||||
)).to.be.rejectedWith(ZeroExError.INSUFFICIENT_BALANCE_FOR_TRANSFER);
|
||||
)).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer);
|
||||
});
|
||||
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
|
||||
const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065';
|
||||
@@ -63,7 +75,7 @@ describe('TokenWrapper', () => {
|
||||
const toAddress = coinbase;
|
||||
return expect(zeroEx.token.transferAsync(
|
||||
nonExistentTokenAddress, fromAddress, toAddress, transferAmount,
|
||||
)).to.be.rejectedWith(ZeroExError.CONTRACT_DOES_NOT_EXIST);
|
||||
)).to.be.rejectedWith(ZeroExError.ContractDoesNotExist);
|
||||
});
|
||||
});
|
||||
describe('#transferFromAsync', () => {
|
||||
@@ -88,7 +100,7 @@ describe('TokenWrapper', () => {
|
||||
|
||||
return expect(zeroEx.token.transferFromAsync(
|
||||
token.address, fromAddress, toAddress, senderAddress, transferAmount,
|
||||
)).to.be.rejectedWith(ZeroExError.INSUFFICIENT_ALLOWANCE_FOR_TRANSFER);
|
||||
)).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer);
|
||||
});
|
||||
it('[regression] should fail to transfer tokens if set allowance for toAddress instead of senderAddress',
|
||||
async () => {
|
||||
@@ -99,7 +111,7 @@ describe('TokenWrapper', () => {
|
||||
|
||||
return expect(zeroEx.token.transferFromAsync(
|
||||
token.address, fromAddress, toAddress, senderAddress, transferAmount,
|
||||
)).to.be.rejectedWith(ZeroExError.INSUFFICIENT_ALLOWANCE_FOR_TRANSFER);
|
||||
)).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer);
|
||||
});
|
||||
it('should fail to transfer tokens if fromAddress has insufficient balance', async () => {
|
||||
const fromAddress = addressWithoutFunds;
|
||||
@@ -115,7 +127,7 @@ describe('TokenWrapper', () => {
|
||||
|
||||
return expect(zeroEx.token.transferFromAsync(
|
||||
token.address, fromAddress, toAddress, senderAddress, transferAmount,
|
||||
)).to.be.rejectedWith(ZeroExError.INSUFFICIENT_BALANCE_FOR_TRANSFER);
|
||||
)).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer);
|
||||
});
|
||||
it('should successfully transfer tokens', async () => {
|
||||
const fromAddress = coinbase;
|
||||
@@ -136,29 +148,46 @@ describe('TokenWrapper', () => {
|
||||
const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065';
|
||||
return expect(zeroEx.token.transferFromAsync(
|
||||
nonExistentTokenAddress, fromAddress, toAddress, senderAddress, new BigNumber(42),
|
||||
)).to.be.rejectedWith(ZeroExError.CONTRACT_DOES_NOT_EXIST);
|
||||
)).to.be.rejectedWith(ZeroExError.ContractDoesNotExist);
|
||||
});
|
||||
});
|
||||
describe('#getBalanceAsync', () => {
|
||||
it('should return the balance for an existing ERC20 token', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const balance = await zeroEx.token.getBalanceAsync(token.address, ownerAddress);
|
||||
const expectedBalance = new BigNumber('100000000000000000000000000');
|
||||
return expect(balance).to.be.bignumber.equal(expectedBalance);
|
||||
describe('With web3 provider with accounts', () => {
|
||||
it('should return the balance for an existing ERC20 token', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const balance = await zeroEx.token.getBalanceAsync(token.address, ownerAddress);
|
||||
const expectedBalance = new BigNumber('100000000000000000000000000');
|
||||
return expect(balance).to.be.bignumber.equal(expectedBalance);
|
||||
});
|
||||
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
|
||||
const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065';
|
||||
const ownerAddress = coinbase;
|
||||
return expect(zeroEx.token.getBalanceAsync(nonExistentTokenAddress, ownerAddress))
|
||||
.to.be.rejectedWith(ZeroExError.ContractDoesNotExist);
|
||||
});
|
||||
it('should return a balance of 0 for a non-existent owner address', async () => {
|
||||
const token = tokens[0];
|
||||
const nonExistentOwner = '0x198c6ad858f213fb31b6fe809e25040e6b964593';
|
||||
const balance = await zeroEx.token.getBalanceAsync(token.address, nonExistentOwner);
|
||||
const expectedBalance = new BigNumber(0);
|
||||
return expect(balance).to.be.bignumber.equal(expectedBalance);
|
||||
});
|
||||
});
|
||||
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
|
||||
const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065';
|
||||
const ownerAddress = coinbase;
|
||||
return expect(zeroEx.token.getBalanceAsync(nonExistentTokenAddress, ownerAddress))
|
||||
.to.be.rejectedWith(ZeroExError.CONTRACT_DOES_NOT_EXIST);
|
||||
});
|
||||
it('should return a balance of 0 for a non-existent owner address', async () => {
|
||||
const token = tokens[0];
|
||||
const nonExistentOwner = '0x198C6Ad858F213Fb31b6FE809E25040E6B964593';
|
||||
const balance = await zeroEx.token.getBalanceAsync(token.address, nonExistentOwner);
|
||||
const expectedBalance = new BigNumber(0);
|
||||
return expect(balance).to.be.bignumber.equal(expectedBalance);
|
||||
describe('With web3 provider without accounts', () => {
|
||||
let zeroExWithoutAccounts: ZeroEx;
|
||||
before(async () => {
|
||||
const hasAddresses = false;
|
||||
const web3WithoutAccounts = web3Factory.create(hasAddresses);
|
||||
zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider);
|
||||
});
|
||||
it('should return balance even when called with Web3 provider instance without addresses', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const balance = await zeroExWithoutAccounts.token.getBalanceAsync(token.address, ownerAddress);
|
||||
const expectedBalance = new BigNumber('100000000000000000000000000');
|
||||
return expect(balance).to.be.bignumber.equal(expectedBalance);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#setAllowanceAsync', () => {
|
||||
@@ -180,26 +209,89 @@ describe('TokenWrapper', () => {
|
||||
return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet);
|
||||
});
|
||||
});
|
||||
describe('#getAllowanceAsync', () => {
|
||||
it('should get the proxy allowance', async () => {
|
||||
describe('#setUnlimitedAllowanceAsync', () => {
|
||||
it('should set the unlimited spender\'s allowance', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const spenderAddress = addressWithoutFunds;
|
||||
|
||||
const amountInBaseUnits = new BigNumber(50);
|
||||
await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits);
|
||||
|
||||
await zeroEx.token.setUnlimitedAllowanceAsync(token.address, ownerAddress, spenderAddress);
|
||||
const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
|
||||
const expectedAllowance = amountInBaseUnits;
|
||||
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
|
||||
return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
it('should return 0 if no allowance set yet', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const spenderAddress = addressWithoutFunds;
|
||||
const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
|
||||
const expectedAllowance = new BigNumber(0);
|
||||
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
|
||||
it('should reduce the gas cost for transfers including tokens with unlimited allowance support', async () => {
|
||||
const transferAmount = new BigNumber(5);
|
||||
const zrx = tokenUtils.getProtocolTokenOrThrow();
|
||||
const [, userWithNormalAllowance, userWithUnlimitedAllowance] = userAddresses;
|
||||
await zeroEx.token.setAllowanceAsync(zrx.address, coinbase, userWithNormalAllowance, transferAmount);
|
||||
await zeroEx.token.setUnlimitedAllowanceAsync(zrx.address, coinbase, userWithUnlimitedAllowance);
|
||||
|
||||
const initBalanceWithNormalAllowance = await promisify(web3.eth.getBalance)(userWithNormalAllowance);
|
||||
const initBalanceWithUnlimitedAllowance = await promisify(web3.eth.getBalance)(userWithUnlimitedAllowance);
|
||||
|
||||
await zeroEx.token.transferFromAsync(
|
||||
zrx.address, coinbase, userWithNormalAllowance, userWithNormalAllowance, transferAmount,
|
||||
);
|
||||
await zeroEx.token.transferFromAsync(
|
||||
zrx.address, coinbase, userWithUnlimitedAllowance, userWithUnlimitedAllowance, transferAmount,
|
||||
);
|
||||
|
||||
const finalBalanceWithNormalAllowance = await promisify(web3.eth.getBalance)(userWithNormalAllowance);
|
||||
const finalBalanceWithUnlimitedAllowance = await promisify(web3.eth.getBalance)(userWithUnlimitedAllowance);
|
||||
|
||||
const normalGasCost = initBalanceWithNormalAllowance.minus(finalBalanceWithNormalAllowance);
|
||||
const unlimitedGasCost = initBalanceWithUnlimitedAllowance.minus(finalBalanceWithUnlimitedAllowance);
|
||||
|
||||
// In theory the gas cost with unlimited allowance should be smaller, but with testrpc it's actually bigger.
|
||||
// This needs to be investigated in ethereumjs-vm. This test is essentially a repro.
|
||||
// TODO: Make this test pass with inverted assertion.
|
||||
expect(unlimitedGasCost.toNumber()).to.be.gt(normalGasCost.toNumber());
|
||||
});
|
||||
});
|
||||
describe('#getAllowanceAsync', () => {
|
||||
describe('With web3 provider with accounts', () => {
|
||||
it('should get the proxy allowance', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const spenderAddress = addressWithoutFunds;
|
||||
|
||||
const amountInBaseUnits = new BigNumber(50);
|
||||
await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits);
|
||||
|
||||
const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
|
||||
const expectedAllowance = amountInBaseUnits;
|
||||
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
|
||||
});
|
||||
it('should return 0 if no allowance set yet', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const spenderAddress = addressWithoutFunds;
|
||||
const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
|
||||
const expectedAllowance = new BigNumber(0);
|
||||
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
|
||||
});
|
||||
});
|
||||
describe('With web3 provider without accounts', () => {
|
||||
let zeroExWithoutAccounts: ZeroEx;
|
||||
before(async () => {
|
||||
const hasAddresses = false;
|
||||
const web3WithoutAccounts = web3Factory.create(hasAddresses);
|
||||
zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider);
|
||||
});
|
||||
it('should get the proxy allowance', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const spenderAddress = addressWithoutFunds;
|
||||
|
||||
const amountInBaseUnits = new BigNumber(50);
|
||||
await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits);
|
||||
|
||||
const allowance = await zeroExWithoutAccounts.token.getAllowanceAsync(
|
||||
token.address, ownerAddress, spenderAddress,
|
||||
);
|
||||
const expectedAllowance = amountInBaseUnits;
|
||||
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#getProxyAllowanceAsync', () => {
|
||||
@@ -232,4 +324,114 @@ describe('TokenWrapper', () => {
|
||||
return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet);
|
||||
});
|
||||
});
|
||||
describe('#setUnlimitedProxyAllowanceAsync', () => {
|
||||
it('should set the unlimited proxy allowance', async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
|
||||
await zeroEx.token.setUnlimitedProxyAllowanceAsync(token.address, ownerAddress);
|
||||
const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress);
|
||||
return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
});
|
||||
describe('#subscribeAsync', () => {
|
||||
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();
|
||||
});
|
||||
// Hack: Mocha does not allow a test to be both async and have a `done` callback
|
||||
// Since we need to await the receipt of the event in the `subscribeAsync` callback,
|
||||
// we do need both. A hack is to make the top-level a sync fn w/ a done callback and then
|
||||
// wrap the rest of the test in an async block
|
||||
// Source: https://github.com/mochajs/mocha/issues/2407
|
||||
it('Should receive the Transfer event when an order is filled', (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;
|
||||
expect(args._from).to.be.equal(coinbase);
|
||||
expect(args._to).to.be.equal(addressWithoutFunds);
|
||||
expect(args._value).to.be.bignumber.equal(transferAmount);
|
||||
done();
|
||||
});
|
||||
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should receive the Approval event when an order is cancelled', (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;
|
||||
expect(args._owner).to.be.equal(coinbase);
|
||||
expect(args._spender).to.be.equal(addressWithoutFunds);
|
||||
expect(args._value).to.be.bignumber.equal(allowanceAmount);
|
||||
done();
|
||||
});
|
||||
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) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should stop watch for events when stopWatchingAsync called on the eventEmitter', (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();
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -2,4 +2,6 @@ export const constants = {
|
||||
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
|
||||
RPC_HOST: 'localhost',
|
||||
RPC_PORT: 8545,
|
||||
TESTRPC_NETWORK_ID: 50,
|
||||
KOVAN_RPC_URL: 'https://kovan.0xproject.com',
|
||||
};
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import {ZeroEx} from '../../src/0x';
|
||||
import {Token, SignedOrder} from '../../src/types';
|
||||
import {ZeroEx, Token, SignedOrder} from '../../src';
|
||||
import {orderFactory} from '../utils/order_factory';
|
||||
import {constants} from './constants';
|
||||
|
||||
@@ -10,12 +9,15 @@ export class FillScenarios {
|
||||
private tokens: Token[];
|
||||
private coinbase: string;
|
||||
private zrxTokenAddress: string;
|
||||
constructor(zeroEx: ZeroEx, userAddresses: string[], tokens: Token[], zrxTokenAddress: string) {
|
||||
private exchangeContractAddress: string;
|
||||
constructor(zeroEx: ZeroEx, userAddresses: string[],
|
||||
tokens: Token[], zrxTokenAddress: string, exchangeContractAddress: string) {
|
||||
this.zeroEx = zeroEx;
|
||||
this.userAddresses = userAddresses;
|
||||
this.tokens = tokens;
|
||||
this.coinbase = userAddresses[0];
|
||||
this.zrxTokenAddress = zrxTokenAddress;
|
||||
this.exchangeContractAddress = exchangeContractAddress;
|
||||
}
|
||||
public async createFillableSignedOrderAsync(makerTokenAddress: string, takerTokenAddress: string,
|
||||
makerAddress: string, takerAddress: string,
|
||||
@@ -59,8 +61,8 @@ export class FillScenarios {
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||
fillableAmount, fillableAmount,
|
||||
);
|
||||
const shouldCheckTransfer = false;
|
||||
await this.zeroEx.exchange.fillOrderAsync(signedOrder, partialFillAmount, shouldCheckTransfer, takerAddress);
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = false;
|
||||
await this.zeroEx.exchange.fillOrderAsync(signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
return signedOrder;
|
||||
}
|
||||
private async createAsymmetricFillableSignedOrderWithFeesAsync(
|
||||
@@ -69,42 +71,42 @@ export class FillScenarios {
|
||||
makerAddress: string, takerAddress: string,
|
||||
makerFillableAmount: BigNumber.BigNumber, takerFillableAmount: BigNumber.BigNumber,
|
||||
feeRecepient: string, expirationUnixTimestampSec?: BigNumber.BigNumber): Promise<SignedOrder> {
|
||||
await this.zeroEx.token.transferAsync(makerTokenAddress, this.coinbase, makerAddress, makerFillableAmount);
|
||||
const oldMakerAllowance = await this.zeroEx.token.getProxyAllowanceAsync(makerTokenAddress, makerAddress);
|
||||
const newMakerAllowance = oldMakerAllowance.plus(makerFillableAmount);
|
||||
await this.zeroEx.token.setProxyAllowanceAsync(
|
||||
makerTokenAddress, makerAddress, newMakerAllowance,
|
||||
);
|
||||
await this.zeroEx.token.transferAsync(takerTokenAddress, this.coinbase, takerAddress, takerFillableAmount);
|
||||
const oldTakerAllowance = await this.zeroEx.token.getProxyAllowanceAsync(takerTokenAddress, takerAddress);
|
||||
const newTakerAllowance = oldTakerAllowance.plus(takerFillableAmount);
|
||||
await this.zeroEx.token.setProxyAllowanceAsync(
|
||||
takerTokenAddress, takerAddress, newTakerAllowance,
|
||||
);
|
||||
|
||||
if (!makerFee.isZero()) {
|
||||
await this.zeroEx.token.transferAsync(this.zrxTokenAddress, this.coinbase, makerAddress, makerFee);
|
||||
const oldMakerFeeAllowance =
|
||||
await this.zeroEx.token.getProxyAllowanceAsync(this.zrxTokenAddress, makerAddress);
|
||||
const newMakerFeeAllowance = oldMakerFeeAllowance.plus(makerFee);
|
||||
await this.zeroEx.token.setProxyAllowanceAsync(
|
||||
this.zrxTokenAddress, makerAddress, newMakerFeeAllowance,
|
||||
);
|
||||
}
|
||||
if (!takerFee.isZero()) {
|
||||
await this.zeroEx.token.transferAsync(this.zrxTokenAddress, this.coinbase, takerAddress, takerFee);
|
||||
const oldTakerFeeAllowance =
|
||||
await this.zeroEx.token.getProxyAllowanceAsync(this.zrxTokenAddress, takerAddress);
|
||||
const newTakerFeeAllowance = oldTakerFeeAllowance.plus(takerFee);
|
||||
await this.zeroEx.token.setProxyAllowanceAsync(
|
||||
this.zrxTokenAddress, takerAddress, newTakerFeeAllowance,
|
||||
);
|
||||
}
|
||||
await Promise.all([
|
||||
this.increaseBalanceAndAllowanceAsync(makerTokenAddress, makerAddress, makerFillableAmount),
|
||||
this.increaseBalanceAndAllowanceAsync(takerTokenAddress, takerAddress, takerFillableAmount),
|
||||
]);
|
||||
await Promise.all([
|
||||
this.increaseBalanceAndAllowanceAsync(this.zrxTokenAddress, makerAddress, makerFee),
|
||||
this.increaseBalanceAndAllowanceAsync(this.zrxTokenAddress, takerAddress, takerFee),
|
||||
]);
|
||||
|
||||
const signedOrder = await orderFactory.createSignedOrderAsync(this.zeroEx,
|
||||
makerAddress, takerAddress, makerFee, takerFee,
|
||||
makerFillableAmount, makerTokenAddress, takerFillableAmount, takerTokenAddress,
|
||||
feeRecepient, expirationUnixTimestampSec);
|
||||
this.exchangeContractAddress, feeRecepient, expirationUnixTimestampSec);
|
||||
return signedOrder;
|
||||
}
|
||||
private async increaseBalanceAndAllowanceAsync(
|
||||
tokenAddress: string, address: string, amount: BigNumber.BigNumber): Promise<void> {
|
||||
if (amount.isZero()) {
|
||||
return; // noop
|
||||
}
|
||||
await Promise.all([
|
||||
this.increaseBalanceAsync(tokenAddress, address, amount),
|
||||
this.increaseAllowanceAsync(tokenAddress, address, amount),
|
||||
]);
|
||||
}
|
||||
private async increaseBalanceAsync(
|
||||
tokenAddress: string, address: string, amount: BigNumber.BigNumber): Promise<void> {
|
||||
await this.zeroEx.token.transferAsync(tokenAddress, this.coinbase, address, amount);
|
||||
}
|
||||
private async increaseAllowanceAsync(
|
||||
tokenAddress: string, address: string, amount: BigNumber.BigNumber): Promise<void> {
|
||||
const oldMakerAllowance = await this.zeroEx.token.getProxyAllowanceAsync(tokenAddress, address);
|
||||
const newMakerAllowance = oldMakerAllowance.plus(amount);
|
||||
await this.zeroEx.token.setProxyAllowanceAsync(
|
||||
tokenAddress, address, newMakerAllowance,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import {SignedOrder} from '../../src/types';
|
||||
import {ZeroEx} from '../../src/0x';
|
||||
import {ZeroEx, SignedOrder} from '../../src';
|
||||
|
||||
export const orderFactory = {
|
||||
async createSignedOrderAsync(
|
||||
@@ -14,6 +13,7 @@ export const orderFactory = {
|
||||
makerTokenAddress: string,
|
||||
takerTokenAmount: BigNumber.BigNumber,
|
||||
takerTokenAddress: string,
|
||||
exchangeContractAddress: string,
|
||||
feeRecipient: string,
|
||||
expirationUnixTimestampSec?: BigNumber.BigNumber): Promise<SignedOrder> {
|
||||
const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite
|
||||
@@ -30,10 +30,11 @@ export const orderFactory = {
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
salt: ZeroEx.generatePseudoRandomSalt(),
|
||||
exchangeContractAddress,
|
||||
feeRecipient,
|
||||
expirationUnixTimestampSec,
|
||||
};
|
||||
const orderHash = await zeroEx.getOrderHashHexAsync(order);
|
||||
const orderHash = ZeroEx.getOrderHashHex(order);
|
||||
const ecSignature = await zeroEx.signOrderHashAsync(orderHash, maker);
|
||||
const signedOrder: SignedOrder = _.assign(order, {ecSignature});
|
||||
return signedOrder;
|
||||
|
@@ -40,6 +40,9 @@ export class RPC {
|
||||
method: 'POST',
|
||||
uri: `http://${this.host}:${this.port}`,
|
||||
body: payload,
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
};
|
||||
const bodyString = await request(opts);
|
||||
const body = JSON.parse(bodyString);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import * as _ from 'lodash';
|
||||
import {Token, ZeroExError} from '../../src/types';
|
||||
import {Token, ZeroExError} from '../../src';
|
||||
|
||||
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.ZRX_NOT_IN_TOKEN_REGISTRY);
|
||||
throw new Error(ZeroExError.ZrxNotInTokenRegistry);
|
||||
}
|
||||
return zrxToken;
|
||||
}
|
||||
|
@@ -6,22 +6,32 @@
|
||||
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';
|
||||
|
||||
export const web3Factory = {
|
||||
create(): Web3 {
|
||||
const provider = this.getRpcProvider();
|
||||
create(hasAddresses: boolean = true): Web3 {
|
||||
const provider = this.getRpcProvider(hasAddresses);
|
||||
const web3 = new Web3();
|
||||
web3.setProvider(provider);
|
||||
return web3;
|
||||
},
|
||||
getRpcProvider(): Web3.Provider {
|
||||
getRpcProvider(hasAddresses: boolean = true): Web3.Provider {
|
||||
const provider = new ProviderEngine();
|
||||
const rpcUrl = `http://${constants.RPC_HOST}:${constants.RPC_PORT}`;
|
||||
if (!hasAddresses) {
|
||||
provider.addProvider(new EmptyWalletSubProvider());
|
||||
}
|
||||
provider.addProvider(new RpcSubprovider({
|
||||
rpcUrl,
|
||||
}));
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
16
test/web3_beta_test.ts
Normal file
16
test/web3_beta_test.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
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();
|
||||
});
|
||||
});
|
29
test/web3_wrapper_test.ts
Normal file
29
test/web3_wrapper_test.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as chai from 'chai';
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {ZeroEx} from '../src/';
|
||||
import {Web3Wrapper} from '../src/web3_wrapper';
|
||||
import {constants} from './utils/constants';
|
||||
|
||||
chai.config.includeStack = true;
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('Web3Wrapper', () => {
|
||||
const web3Provider = web3Factory.create().currentProvider;
|
||||
describe('#getNetworkIdIfExistsAsync', () => {
|
||||
it('caches network id requests', async () => {
|
||||
const web3Wrapper = (new ZeroEx(web3Provider) as any)._web3Wrapper as Web3Wrapper;
|
||||
expect((web3Wrapper as any).networkIdIfExists).to.be.undefined();
|
||||
const networkIdIfExists = await web3Wrapper.getNetworkIdIfExistsAsync();
|
||||
expect((web3Wrapper as any).networkIdIfExists).to.be.equal(constants.TESTRPC_NETWORK_ID);
|
||||
});
|
||||
it('invalidates network id cache on setProvider call', async () => {
|
||||
const web3Wrapper = (new ZeroEx(web3Provider) as any)._web3Wrapper as Web3Wrapper;
|
||||
expect((web3Wrapper as any).networkIdIfExists).to.be.undefined();
|
||||
const networkIdIfExists = await web3Wrapper.getNetworkIdIfExistsAsync();
|
||||
expect((web3Wrapper as any).networkIdIfExists).to.be.equal(constants.TESTRPC_NETWORK_ID);
|
||||
const newProvider = web3Factory.create().currentProvider;
|
||||
web3Wrapper.setProvider(newProvider);
|
||||
expect((web3Wrapper as any).networkIdIfExists).to.be.undefined();
|
||||
});
|
||||
});
|
||||
});
|
@@ -13,6 +13,8 @@
|
||||
"include": [
|
||||
"./src/**/*",
|
||||
"./test/**/*",
|
||||
"./node_modules/types-bn/index.d.ts",
|
||||
"./node_modules/types-ethereumjs-util/index.d.ts",
|
||||
"./node_modules/web3-typescript-typings/index.d.ts",
|
||||
"./node_modules/chai-typescript-typings/index.d.ts",
|
||||
"./node_modules/chai-as-promised-typescript-typings/index.d.ts"
|
||||
|
@@ -1,16 +1,16 @@
|
||||
/**
|
||||
* This is to generate the umd bundle only
|
||||
*/
|
||||
const lodash = require('lodash');
|
||||
const _ = require('lodash');
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const production = process.env.NODE_ENV === 'production';
|
||||
|
||||
let entry = {
|
||||
'0x': './src/0x.ts',
|
||||
'index': './src/index.ts',
|
||||
};
|
||||
if (production) {
|
||||
entry = _.assign({}, entry, {'0x.min': './src/0x.ts'});
|
||||
entry = _.assign({}, entry, {'index.min': './src/index.ts'});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
Reference in New Issue
Block a user