Compare commits
1393 Commits
v0.21.1
...
@0xproject
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8df2cc103e | ||
|
|
8e0e9c7d3a | ||
|
|
913930b561 | ||
|
|
994c8db1f3 | ||
|
|
6811bdec40 | ||
|
|
6682abf89d | ||
|
|
78fbf0f7ba | ||
|
|
89547332ee | ||
|
|
75539bf675 | ||
|
|
eaeb715e56 | ||
|
|
ca55cc99ed | ||
|
|
7cc4a8f5ce | ||
|
|
fa35768fc9 | ||
|
|
4d0ff0dce4 | ||
|
|
8aac6e46d4 | ||
|
|
1feac1a308 | ||
|
|
d3e42e4b3e | ||
|
|
ecf86d1d13 | ||
|
|
adc6170f02 | ||
|
|
02600f40d2 | ||
|
|
16ea0348a9 | ||
|
|
57acb8db5c | ||
|
|
2bcb7d5639 | ||
|
|
a99e54330a | ||
|
|
e219772b2a | ||
|
|
144a507a2e | ||
|
|
5019c51940 | ||
|
|
da1071526f | ||
|
|
7ad314472d | ||
|
|
4f6168a982 | ||
|
|
218a872968 | ||
|
|
0043c5e1ac | ||
|
|
d0e7046a89 | ||
|
|
52ad16b920 | ||
|
|
5a4c0bff6a | ||
|
|
6205209fbb | ||
|
|
6ebf8a57d1 | ||
|
|
c02dfc4fb1 | ||
|
|
9d62e5fb6f | ||
|
|
6f13d107c4 | ||
|
|
e56b2ceebb | ||
|
|
7de244ed62 | ||
|
|
1e9147b8bb | ||
|
|
2f65fadeaa | ||
|
|
a17091b394 | ||
|
|
ed77c6cb54 | ||
|
|
bc37cc8a91 | ||
|
|
387363283c | ||
|
|
709026bf1a | ||
|
|
f2b2b86786 | ||
|
|
d0fbea76d8 | ||
|
|
8269610a5c | ||
|
|
20c88a46d9 | ||
|
|
661029f7cc | ||
|
|
850d32d60c | ||
|
|
eb881b9729 | ||
|
|
091ba473ff | ||
|
|
fca051c565 | ||
|
|
6463cda204 | ||
|
|
d004df56e3 | ||
|
|
359dd482c4 | ||
|
|
89e98240b4 | ||
|
|
8d30058a6d | ||
|
|
69151c06e4 | ||
|
|
2e3c02887e | ||
|
|
7603cef308 | ||
|
|
be370c4e19 | ||
|
|
c6dece6bd1 | ||
|
|
93a5b3f457 | ||
|
|
4242176d29 | ||
|
|
1cadbeed88 | ||
|
|
86cc011212 | ||
|
|
c0facfc28f | ||
|
|
ad52a82190 | ||
|
|
60b6ed514f | ||
|
|
ef32822b31 | ||
|
|
bb0cedd2de | ||
|
|
8175c7c085 | ||
|
|
5c2d725721 | ||
|
|
72571628da | ||
|
|
fa6130c907 | ||
|
|
8ccdd54974 | ||
|
|
6c1409b00d | ||
|
|
542a1a11b9 | ||
|
|
63ffa80c5c | ||
|
|
609342be7a | ||
|
|
52394884da | ||
|
|
af08177f79 | ||
|
|
45fdfc2d3d | ||
|
|
d18554e0e8 | ||
|
|
3c3f9ca85b | ||
|
|
005a02efeb | ||
|
|
6206ebc994 | ||
|
|
dd9f5adc2e | ||
|
|
748d805a32 | ||
|
|
4b59bf01b3 | ||
|
|
b4faa4851a | ||
|
|
f37fcc147c | ||
|
|
dc5694e544 | ||
|
|
e588f6f8c3 | ||
|
|
d227f2ad7c | ||
|
|
97c228031a | ||
|
|
ad85011d62 | ||
|
|
a70379625d | ||
|
|
7350333129 | ||
|
|
216420fdc5 | ||
|
|
c559fbbe8c | ||
|
|
71d68f975c | ||
|
|
390534497e | ||
|
|
3f0ec89f11 | ||
|
|
d3aa4f2bc7 | ||
|
|
f58f0ddb67 | ||
|
|
633e3129d4 | ||
|
|
877bdce4c8 | ||
|
|
6e1fbd2d97 | ||
|
|
36bfe62a8f | ||
|
|
b08bd0f9ab | ||
|
|
3998b47d84 | ||
|
|
d965fdb11d | ||
|
|
ddb64b3ec1 | ||
|
|
5055ec8617 | ||
|
|
8a858501f2 | ||
|
|
e1af25c8a6 | ||
|
|
6091f818da | ||
|
|
a45f9b4802 | ||
|
|
9ccf63b44a | ||
|
|
d34135ae43 | ||
|
|
a81f6f9ad1 | ||
|
|
8a80b10cc2 | ||
|
|
22b1ee132a | ||
|
|
ab7f681f15 | ||
|
|
2ac806ef08 | ||
|
|
13ec8ed03c | ||
|
|
fb77817c2d | ||
|
|
ceb8a492b1 | ||
|
|
cfc868bf4d | ||
|
|
7b4e2257d8 | ||
|
|
4ac6e5477d | ||
|
|
614ea14a7f | ||
|
|
ce45f99006 | ||
|
|
9203813a61 | ||
|
|
ba987b0574 | ||
|
|
b20f34adb7 | ||
|
|
63d0d810b1 | ||
|
|
5491400684 | ||
|
|
6a5165e9b3 | ||
|
|
9a7c4442d2 | ||
|
|
0e5363b5c3 | ||
|
|
d41bce36be | ||
|
|
1380cd811e | ||
|
|
d99bb3a87c | ||
|
|
4b9501318d | ||
|
|
e5eec04f92 | ||
|
|
f9c21efc30 | ||
|
|
ae72b71895 | ||
|
|
87e3fe725d | ||
|
|
807d9e3eef | ||
|
|
9e569b3791 | ||
|
|
024f093585 | ||
|
|
996e9e9102 | ||
|
|
d58bfb46cf | ||
|
|
eb0d7df50b | ||
|
|
547bee38c0 | ||
|
|
60614ba250 | ||
|
|
278f68ae77 | ||
|
|
b4375d6f64 | ||
|
|
9f47c72d31 | ||
|
|
de3bf03f42 | ||
|
|
59a39ac57d | ||
|
|
df9c2b193e | ||
|
|
3894311d68 | ||
|
|
35501dd4fc | ||
|
|
e2ca713658 | ||
|
|
a45de6d427 | ||
|
|
a350638526 | ||
|
|
59f9605ed9 | ||
|
|
9521bf8d4f | ||
|
|
7c1c94d39b | ||
|
|
8a74963815 | ||
|
|
3ebd8b7f45 | ||
|
|
ae8cb2e6a8 | ||
|
|
1ccb978612 | ||
|
|
7040a01cf2 | ||
|
|
6bc0e815e9 | ||
|
|
593f7e826c | ||
|
|
835fa0af13 | ||
|
|
0e3bd0c6c1 | ||
|
|
86668eb7b9 | ||
|
|
771f65c858 | ||
|
|
89d6326a83 | ||
|
|
8b81ea162f | ||
|
|
a53e6db537 | ||
|
|
62e3feeb94 | ||
|
|
a950494503 | ||
|
|
3cb310122e | ||
|
|
feace988b4 | ||
|
|
6a56f20928 | ||
|
|
80a46d14be | ||
|
|
fbcbf066cd | ||
|
|
4d30e1115f | ||
|
|
897cfb491c | ||
|
|
c2c7512431 | ||
|
|
568cf4d182 | ||
|
|
f54330f1c5 | ||
|
|
97e01d7a42 | ||
|
|
4be3e000e1 | ||
|
|
b47baa9ee1 | ||
|
|
f6c98112df | ||
|
|
94d29ab22e | ||
|
|
b0b179550a | ||
|
|
41c7ab4f9c | ||
|
|
432c7c63fe | ||
|
|
2bba1ae292 | ||
|
|
7830d518e0 | ||
|
|
571b3c4da8 | ||
|
|
5b6f8d4c3f | ||
|
|
ae57b21b98 | ||
|
|
292c3bbff8 | ||
|
|
065570ebf5 | ||
|
|
1f5dfd71d5 | ||
|
|
31b3ac4b98 | ||
|
|
7f4a383d75 | ||
|
|
de943c5f30 | ||
|
|
e34b0af251 | ||
|
|
fa7237fde7 | ||
|
|
ed530cef72 | ||
|
|
0f8b2703d1 | ||
|
|
a2176f566b | ||
|
|
bef056b084 | ||
|
|
c8c86c44f8 | ||
|
|
f917a4a34a | ||
|
|
78fd942faa | ||
|
|
a3274ac9b1 | ||
|
|
2e67d69434 | ||
|
|
c2d6e78b2a | ||
|
|
7233a11ba0 | ||
|
|
3a1360ce11 | ||
|
|
ba761044a6 | ||
|
|
d8636a2c5d | ||
|
|
9889c8f9e0 | ||
|
|
7e2e392ecc | ||
|
|
6912a67eba | ||
|
|
6abc83de83 | ||
|
|
0ca66a1469 | ||
|
|
a88be1af44 | ||
|
|
ae47fda257 | ||
|
|
6eb5411029 | ||
|
|
73d13d61a7 | ||
|
|
fdf213ef72 | ||
|
|
22fe605b1e | ||
|
|
bcc902a889 | ||
|
|
24ff2ec30c | ||
|
|
4e0c67e2a1 | ||
|
|
0cc21db654 | ||
|
|
dd5d1c00e5 | ||
|
|
cad2555c03 | ||
|
|
005d8184aa | ||
|
|
c228eddc57 | ||
|
|
ae37f8d9b6 | ||
|
|
6a8113af1d | ||
|
|
0c8cf12650 | ||
|
|
2e29a51ccf | ||
|
|
162f1d94dc | ||
|
|
a76bce1f32 | ||
|
|
be20e04c7b | ||
|
|
d9df82f31d | ||
|
|
6210333628 | ||
|
|
a552a790d3 | ||
|
|
9acb745956 | ||
|
|
875edaf96f | ||
|
|
a9afc0f3f0 | ||
|
|
3bbeaabe26 | ||
|
|
c04cbb480a | ||
|
|
2c241c294e | ||
|
|
ce851f3c28 | ||
|
|
817a6d1828 | ||
|
|
4d0735e7f6 | ||
|
|
80291930f0 | ||
|
|
cf8678b979 | ||
|
|
67722c0fb8 | ||
|
|
0b6e246c04 | ||
|
|
3992295678 | ||
|
|
da404b9acc | ||
|
|
da8db153be | ||
|
|
2c90f7324b | ||
|
|
2cdb0de1c3 | ||
|
|
66bd798e80 | ||
|
|
cf37dfb257 | ||
|
|
217f84204e | ||
|
|
fbbe0ec3db | ||
|
|
1ec34767f9 | ||
|
|
14e94a5376 | ||
|
|
08508afdb2 | ||
|
|
4220f25e01 | ||
|
|
4ff5afecc8 | ||
|
|
2eabb439d9 | ||
|
|
2bc9d5d490 | ||
|
|
9e1c142f02 | ||
|
|
1a38893b52 | ||
|
|
f28d75cf7c | ||
|
|
a3c9d0ace5 | ||
|
|
aea1799a64 | ||
|
|
ef76c4c62b | ||
|
|
210e7c5579 | ||
|
|
3fa0046542 | ||
|
|
d6754685cc | ||
|
|
bb5721700b | ||
|
|
5c4a1dcbd5 | ||
|
|
4f488dee61 | ||
|
|
0d33758104 | ||
|
|
15b5fcbaf9 | ||
|
|
0718b0d5b0 | ||
|
|
0af2ba1b7d | ||
|
|
ea2fdecb83 | ||
|
|
07eb250083 | ||
|
|
ee04717e1b | ||
|
|
d7d22c36ed | ||
|
|
36bf91f920 | ||
|
|
98e9c3b800 | ||
|
|
a7f1137db8 | ||
|
|
f86159e717 | ||
|
|
6e93223ea3 | ||
|
|
a6d677043f | ||
|
|
eb1bf198ac | ||
|
|
0a2f82a19d | ||
|
|
27d9fba785 | ||
|
|
e6a783aff8 | ||
|
|
a767c353d2 | ||
|
|
a39d3d7233 | ||
|
|
40c7ee6355 | ||
|
|
eb760aa33f | ||
|
|
262c34abac | ||
|
|
8019b1b823 | ||
|
|
7a56e83fa3 | ||
|
|
a1c38d9cb2 | ||
|
|
734cf5819a | ||
|
|
35e0b6143a | ||
|
|
e019ae4aed | ||
|
|
c700046b76 | ||
|
|
326a6b729f | ||
|
|
e2ef0c5b94 | ||
|
|
c49f68ef3e | ||
|
|
d0f66f8624 | ||
|
|
4dfa720f2d | ||
|
|
f3b8bac477 | ||
|
|
ce6abad97f | ||
|
|
807e908f78 | ||
|
|
94c75b5262 | ||
|
|
eb0dd6b6e9 | ||
|
|
1c542c3e0d | ||
|
|
d46fcb33c5 | ||
|
|
d5e0da0eb6 | ||
|
|
e744e4cd98 | ||
|
|
9a96e8c704 | ||
|
|
31a94f468e | ||
|
|
7422b9b53b | ||
|
|
161a9d6ad2 | ||
|
|
f684cc3711 | ||
|
|
ab0c8c3496 | ||
|
|
47534b200d | ||
|
|
f2976af734 | ||
|
|
ee463058f1 | ||
|
|
c36a7471a8 | ||
|
|
6ea8cee551 | ||
|
|
69806c8839 | ||
|
|
690036aa30 | ||
|
|
8fe81c9d09 | ||
|
|
eda6b8d01b | ||
|
|
9f3acf8e28 | ||
|
|
31c9c82f6c | ||
|
|
cbf06b2165 | ||
|
|
e884eb9882 | ||
|
|
f8cbfea4a1 | ||
|
|
e01c0f054d | ||
|
|
12dc8c0d15 | ||
|
|
cb3582289f | ||
|
|
734d220d60 | ||
|
|
d725de7286 | ||
|
|
3e91773cd9 | ||
|
|
dc1d2a33a5 | ||
|
|
ac4074911d | ||
|
|
66cf60f9cb | ||
|
|
fffe8c355e | ||
|
|
b94d13b413 | ||
|
|
85e16c1233 | ||
|
|
2d53b7d9a4 | ||
|
|
23052a5c2e | ||
|
|
42b3a7c9d7 | ||
|
|
33315046cd | ||
|
|
2cf647d5ad | ||
|
|
b1d88ca643 | ||
|
|
b79b48cfbe | ||
|
|
a2bf19efc1 | ||
|
|
6636a0861d | ||
|
|
5d4078bcf4 | ||
|
|
396e3d3ec2 | ||
|
|
e88b01ed5b | ||
|
|
cb11aec84d | ||
|
|
3e3587c281 | ||
|
|
972e1675f6 | ||
|
|
05ee368763 | ||
|
|
291e2c8fa0 | ||
|
|
ad61fe4b54 | ||
|
|
439fe10e1e | ||
|
|
d05a36deed | ||
|
|
03fd73fab8 | ||
|
|
1d430cf62f | ||
|
|
9ed05f56d3 | ||
|
|
23aa3ec5ea | ||
|
|
a0f5ceddce | ||
|
|
3171943228 | ||
|
|
455f74d449 | ||
|
|
01bc254cd0 | ||
|
|
2a7cafbf20 | ||
|
|
bf1603839e | ||
|
|
8ed776e113 | ||
|
|
c3508dcdc6 | ||
|
|
3579f063d6 | ||
|
|
ecc6cb2b7c | ||
|
|
de6fe25751 | ||
|
|
d639a22cff | ||
|
|
b1f36a9024 | ||
|
|
d8d15149fc | ||
|
|
c07de97fd8 | ||
|
|
78eb79396e | ||
|
|
abce410897 | ||
|
|
9594185f11 | ||
|
|
35c20d505e | ||
|
|
b829d55752 | ||
|
|
e92d6ff84f | ||
|
|
0056c66d32 | ||
|
|
613fada49f | ||
|
|
b603197ae8 | ||
|
|
69b81836fe | ||
|
|
0be4e1e1b3 | ||
|
|
14c994ce14 | ||
|
|
dd39fadce7 | ||
|
|
5d24022209 | ||
|
|
c75eaca5b6 | ||
|
|
53798302da | ||
|
|
b799a8a108 | ||
|
|
3d6c0b75a1 | ||
|
|
c531d734d4 | ||
|
|
f239522a19 | ||
|
|
d1c36f50d5 | ||
|
|
484312e677 | ||
|
|
0dc5178083 | ||
|
|
08f2406ca2 | ||
|
|
41104b2d45 | ||
|
|
766824120a | ||
|
|
c39ac903a9 | ||
|
|
ca2bb60877 | ||
|
|
3c66f18a46 | ||
|
|
8a940a800a | ||
|
|
06bd5a9b03 | ||
|
|
93518802d6 | ||
|
|
75f637bd75 | ||
|
|
e38c7ff076 | ||
|
|
66fe7b88b0 | ||
|
|
16179d2f8e | ||
|
|
1316a2dd2a | ||
|
|
e5b5fc400a | ||
|
|
786ea52738 | ||
|
|
ed6756898f | ||
|
|
a5d2cbfd6f | ||
|
|
04268d7f4b | ||
|
|
156dae1d33 | ||
|
|
436d78eb93 | ||
|
|
f269ecd95e | ||
|
|
1e4fdcf615 | ||
|
|
b7004b3002 | ||
|
|
c6af59a3e7 | ||
|
|
32396b5eca | ||
|
|
2930537a51 | ||
|
|
16c91bca0a | ||
|
|
1f85d31663 | ||
|
|
684542d073 | ||
|
|
bab01abe27 | ||
|
|
c63f76dde7 | ||
|
|
79bd416c61 | ||
|
|
7710989dee | ||
|
|
fb0b7efc45 | ||
|
|
1af427e4a7 | ||
|
|
bb6d594ec7 | ||
|
|
a57dda2c2a | ||
|
|
a3a2df098b | ||
|
|
90d1c3e2c9 | ||
|
|
950406f1f9 | ||
|
|
064405126d | ||
|
|
6d447fd8a5 | ||
|
|
400228e139 | ||
|
|
edfae32484 | ||
|
|
1aed970ce3 | ||
|
|
445ff1e28e | ||
|
|
df9d3e3e16 | ||
|
|
4dc6694651 | ||
|
|
98c01c2f80 | ||
|
|
f698dd4077 | ||
|
|
0578116f1d | ||
|
|
abbad68eb8 | ||
|
|
90d274ffc4 | ||
|
|
449fd9f024 | ||
|
|
22c4ee6ef7 | ||
|
|
951fbc9b76 | ||
|
|
89f368a8b8 | ||
|
|
672c8acaca | ||
|
|
681617480a | ||
|
|
95dfac6f9b | ||
|
|
b2256679be | ||
|
|
9e914be975 | ||
|
|
edbe6915e0 | ||
|
|
fd3e8cd813 | ||
|
|
b640e2cc49 | ||
|
|
51af6de624 | ||
|
|
054c00e323 | ||
|
|
5664333490 | ||
|
|
ac78c64f9e | ||
|
|
1f748afed9 | ||
|
|
4a73c05435 | ||
|
|
fe8f2d87c7 | ||
|
|
a71a1edf2c | ||
|
|
87209b8f57 | ||
|
|
13279f1f20 | ||
|
|
b46dd2e0a2 | ||
|
|
3eb08735d4 | ||
|
|
c8e52882ca | ||
|
|
484dd4c33a | ||
|
|
94f40a54b4 | ||
|
|
818a50a3fb | ||
|
|
2041e9945e | ||
|
|
126048bac9 | ||
|
|
274aa50d74 | ||
|
|
c36d85a46c | ||
|
|
ac1fbeb962 | ||
|
|
de8c445f96 | ||
|
|
c23d42fea5 | ||
|
|
a0aa21103b | ||
|
|
ce242b10e2 | ||
|
|
6d386220d9 | ||
|
|
7ce1021798 | ||
|
|
9ad9e1204c | ||
|
|
a63061f5b2 | ||
|
|
4997331410 | ||
|
|
f7f4b6da8d | ||
|
|
c975676f6c | ||
|
|
d702c65a5b | ||
|
|
4391f9913e | ||
|
|
28c409fc6d | ||
|
|
449a04d39c | ||
|
|
4bcd7ba31e | ||
|
|
5bda301fec | ||
|
|
84c7c6eb12 | ||
|
|
a5b7980b06 | ||
|
|
f746f5bfa3 | ||
|
|
3ed2eedfda | ||
|
|
4ce8eaa158 | ||
|
|
7abb026819 | ||
|
|
abd55411d4 | ||
|
|
cb596c1413 | ||
|
|
6120a43fff | ||
|
|
02e7354b53 | ||
|
|
0a0d3503c0 | ||
|
|
105bcc6664 | ||
|
|
ceae51fe32 | ||
|
|
b054080fa1 | ||
|
|
565010408e | ||
|
|
5619780cc1 | ||
|
|
d69143487e | ||
|
|
2ef763a195 | ||
|
|
6ba4ed99c4 | ||
|
|
aec3c83191 | ||
|
|
19d8449981 | ||
|
|
91f276d925 | ||
|
|
6bda6a22e3 | ||
|
|
1fbe638950 | ||
|
|
1e9f23ebba | ||
|
|
bbb768c5cf | ||
|
|
5678196706 | ||
|
|
987637abed | ||
|
|
ff422ecfb2 | ||
|
|
1fb643cb69 | ||
|
|
88db8c3724 | ||
|
|
cf50a82ecc | ||
|
|
145fea5253 | ||
|
|
b97f140b78 | ||
|
|
1dda4d5f78 | ||
|
|
6f2cb66163 | ||
|
|
8b29f6f18d | ||
|
|
2cc410e61f | ||
|
|
548fda8dba | ||
|
|
b58bf8259d | ||
|
|
e6862e9739 | ||
|
|
7008e882c0 | ||
|
|
7d59faa650 | ||
|
|
eaa6484f24 | ||
|
|
6630b1d8a5 | ||
|
|
d0916e196c | ||
|
|
8e74a22c6f | ||
|
|
2ffcc853fd | ||
|
|
caeb5cb6ab | ||
|
|
4f72c527de | ||
|
|
71cb2e05d1 | ||
|
|
d1fb61e0de | ||
|
|
cb7188d473 | ||
|
|
02f22d9155 | ||
|
|
707b98849c | ||
|
|
d778378b37 | ||
|
|
f3f596b231 | ||
|
|
276ee9903a | ||
|
|
16a970b65d | ||
|
|
1647e45247 | ||
|
|
3477a84bbd | ||
|
|
c61fe3ce64 | ||
|
|
f94dc1fe44 | ||
|
|
1339aadf4e | ||
|
|
a14424ae5f | ||
|
|
f1e7ea118b | ||
|
|
9c6453e129 | ||
|
|
a6f9718131 | ||
|
|
47075edc97 | ||
|
|
25cee41358 | ||
|
|
d08d65a3af | ||
|
|
d14ed71191 | ||
|
|
f341f62b2b | ||
|
|
ec3d8a034f | ||
|
|
c452294bcc | ||
|
|
d5757499bc | ||
|
|
828dffffed | ||
|
|
af8d24d0eb | ||
|
|
ca85a97106 | ||
|
|
5eea829be9 | ||
|
|
e822e3562d | ||
|
|
f109d132e4 | ||
|
|
1fc1eae39a | ||
|
|
deb6aeae43 | ||
|
|
f48f126b3a | ||
|
|
e0d79bd332 | ||
|
|
5e75aab8ea | ||
|
|
36125c3539 | ||
|
|
72ced622d7 | ||
|
|
b362e2c28e | ||
|
|
2260a0a4cd | ||
|
|
3c64b33f5c | ||
|
|
5301086c68 | ||
|
|
5687279c8d | ||
|
|
139c8c2e78 | ||
|
|
6c2bf8ed26 | ||
|
|
f1ecdcf602 | ||
|
|
1eaefac12b | ||
|
|
215e33fa6c | ||
|
|
be17b75ad3 | ||
|
|
0c23f5e07e | ||
|
|
86e1fa8153 | ||
|
|
fae0651b0c | ||
|
|
720641f14c | ||
|
|
e8495b0c7b | ||
|
|
e80579a605 | ||
|
|
5967d39ca9 | ||
|
|
b82b50e2f0 | ||
|
|
e893e8c442 | ||
|
|
3db5efa264 | ||
|
|
5401c69163 | ||
|
|
b7030cffd9 | ||
|
|
73b4b4488e | ||
|
|
825402b0f9 | ||
|
|
f23071a214 | ||
|
|
f1b267cc9f | ||
|
|
3d12b84f1d | ||
|
|
adf1afc6ba | ||
|
|
0abbdc6b96 | ||
|
|
598f1dd2d8 | ||
|
|
594bd2de1c | ||
|
|
55083316fc | ||
|
|
8c87394b2b | ||
|
|
1dba4b85d0 | ||
|
|
22de68205b | ||
|
|
f76543ebfa | ||
|
|
f0c27f98b8 | ||
|
|
da678ba018 | ||
|
|
86ed1b4554 | ||
|
|
d8adc88c52 | ||
|
|
5119e49e47 | ||
|
|
cfb9f87418 | ||
|
|
06e348f80b | ||
|
|
f05e0174a0 | ||
|
|
6bea3ac157 | ||
|
|
e4d42a079c | ||
|
|
5bbdb7a8f7 | ||
|
|
3cf7cb1054 | ||
|
|
78274440cb | ||
|
|
e59934ca21 | ||
|
|
c93bb4ef98 | ||
|
|
e3763bade2 | ||
|
|
1ea1c02387 | ||
|
|
f6321a8e70 | ||
|
|
08168c6e7d | ||
|
|
b5030df4e3 | ||
|
|
8414c18866 | ||
|
|
038668efdf | ||
|
|
4441d76725 | ||
|
|
1f494feec4 | ||
|
|
1153fa093b | ||
|
|
c64ec92fb2 | ||
|
|
20e28d6c70 | ||
|
|
c0015c2c11 | ||
|
|
3880f2b3cc | ||
|
|
293847053a | ||
|
|
e042f49e27 | ||
|
|
c036b96848 | ||
|
|
e95dba2c25 | ||
|
|
9891d7aaa6 | ||
|
|
1ce66b4a81 | ||
|
|
63b1199bd5 | ||
|
|
47789d770d | ||
|
|
df58593ff4 | ||
|
|
e2ef7a74db | ||
|
|
cde52b10b1 | ||
|
|
43983f1bb3 | ||
|
|
eb4adcf797 | ||
|
|
32568ebf09 | ||
|
|
c53d195ed0 | ||
|
|
9b1c9ecb8b | ||
|
|
8cd204423a | ||
|
|
2abc5a88c5 | ||
|
|
4c0bc02c48 | ||
|
|
16b27c4ae8 | ||
|
|
dc5f4ab28b | ||
|
|
f88ecaa035 | ||
|
|
c0cf47138e | ||
|
|
e1127dc2e8 | ||
|
|
5d1b845cad | ||
|
|
355514ca38 | ||
|
|
518d0eba84 | ||
|
|
78fb8d2bdc | ||
|
|
5673b42ec4 | ||
|
|
438c8ff807 | ||
|
|
ee15143dd7 | ||
|
|
042caa3363 | ||
|
|
eb667f653c | ||
|
|
6cbd0d4537 | ||
|
|
11bf2a0e06 | ||
|
|
9485e4f4f3 | ||
|
|
865ee090c8 | ||
|
|
0fc356740a | ||
|
|
f285a46d05 | ||
|
|
1e1c99d8d6 | ||
|
|
c291419141 | ||
|
|
290a96d41f | ||
|
|
ca9518c48c | ||
|
|
d44d6ccfd8 | ||
|
|
cec0b2afe3 | ||
|
|
e85a69655c | ||
|
|
4b3e038323 | ||
|
|
c57190dead | ||
|
|
3d73167f53 | ||
|
|
2eefec54b4 | ||
|
|
ab72656fdf | ||
|
|
b6f2f98db6 | ||
|
|
c051c48600 | ||
|
|
04fc16587b | ||
|
|
f1d5a7d31f | ||
|
|
7197356928 | ||
|
|
a75d547af6 | ||
|
|
bdecb18e3e | ||
|
|
e2adcaa185 | ||
|
|
4193893349 | ||
|
|
53522a98b9 | ||
|
|
0e856ccfab | ||
|
|
6093ee7824 | ||
|
|
46f185bbeb | ||
|
|
8bfa880371 | ||
|
|
a7f0d03611 | ||
|
|
9ce899d3f3 | ||
|
|
e71c7fdb16 | ||
|
|
4258e6dab1 | ||
|
|
e59b683047 | ||
|
|
3c3033586d | ||
|
|
166c741beb | ||
|
|
629a0fa3a5 | ||
|
|
15ce862334 | ||
|
|
840557be0d | ||
|
|
72a00ac2df | ||
|
|
da0af12834 | ||
|
|
7617b6681c | ||
|
|
8e13c477a3 | ||
|
|
ff334b0e53 | ||
|
|
a98d6bf496 | ||
|
|
bbcf669bd9 | ||
|
|
328ecd0533 | ||
|
|
ab91717199 | ||
|
|
e4dfdc6b5c | ||
|
|
9e673f0073 | ||
|
|
353abffba9 | ||
|
|
bead76a5b7 | ||
|
|
9f5e724aec | ||
|
|
50bf6a991d | ||
|
|
1d584b1329 | ||
|
|
0c5a9fbc2c | ||
|
|
d5e58ecdd8 | ||
|
|
d6a9e7520d | ||
|
|
36b21e6e7b | ||
|
|
977a6b2794 | ||
|
|
fcddd503b7 | ||
|
|
3472bdcfd4 | ||
|
|
78f0ab3682 | ||
|
|
5a59ac4c6b | ||
|
|
0a19ba3014 | ||
|
|
88bd0f5328 | ||
|
|
6e2a69976b | ||
|
|
0500602ac3 | ||
|
|
e6887dc9d4 | ||
|
|
082c5c375e | ||
|
|
5977fa881e | ||
|
|
128fccb763 | ||
|
|
65697f5896 | ||
|
|
ee93f091ea | ||
|
|
33a8c7a9fb | ||
|
|
d1065cd266 | ||
|
|
0e44a630f0 | ||
|
|
7a21c6854b | ||
|
|
54ef916b93 | ||
|
|
4a770dee84 | ||
|
|
426a412ba1 | ||
|
|
f862a2af6d | ||
|
|
32867c9a07 | ||
|
|
cf0a8b2d05 | ||
|
|
4a17f5e820 | ||
|
|
694c37150c | ||
|
|
b04d07815f | ||
|
|
e7b523074a | ||
|
|
4e03155588 | ||
|
|
3e5abc60d3 | ||
|
|
edf80aba1a | ||
|
|
25ec4cf7b0 | ||
|
|
48b3d85265 | ||
|
|
ecfee00fec | ||
|
|
b5ce876327 | ||
|
|
d1342c6326 | ||
|
|
d3dcb2fd40 | ||
|
|
c448d048fd | ||
|
|
f0b3ee84b4 | ||
|
|
ab78c54d6a | ||
|
|
cee0d2706f | ||
|
|
8dea47f038 | ||
|
|
b7b1721145 | ||
|
|
5068f1666a | ||
|
|
37f0051d83 | ||
|
|
c780d04cee | ||
|
|
8c54e9a873 | ||
|
|
062f85e506 | ||
|
|
b3c0d54acd | ||
|
|
3d11afd872 | ||
|
|
96d15a8931 | ||
|
|
50915e6007 | ||
|
|
52007e5864 | ||
|
|
0c74d5ba01 | ||
|
|
0cd1959706 | ||
|
|
55ddf62df2 | ||
|
|
7f69d26247 | ||
|
|
b3c01f6750 | ||
|
|
34beee1edc | ||
|
|
7bc6a7b23f | ||
|
|
4a19655fb0 | ||
|
|
e32c453bc3 | ||
|
|
7b8d9193e2 | ||
|
|
74633126ce | ||
|
|
2fa5bb2028 | ||
|
|
010e6f8d7f | ||
|
|
3d5b6ec110 | ||
|
|
db2917b01c | ||
|
|
87d34f9c7f | ||
|
|
d20926e150 | ||
|
|
c453099f6b | ||
|
|
08569dbe6c | ||
|
|
a38ef3655b | ||
|
|
c586d3e81d | ||
|
|
c72745b0b2 | ||
|
|
8935146240 | ||
|
|
66aaceea91 | ||
|
|
4fe28ec53c | ||
|
|
efe8e07854 | ||
|
|
63dc606a9c | ||
|
|
131236305b | ||
|
|
45c9171a2c | ||
|
|
311d42626a | ||
|
|
cbf35de4c1 | ||
|
|
92efc65847 | ||
|
|
17e41f2391 | ||
|
|
fcd37808d4 | ||
|
|
6323badc19 | ||
|
|
e01468b492 | ||
|
|
f7f2390ce1 | ||
|
|
6118561bb5 | ||
|
|
6a9f10cd36 | ||
|
|
e7a4e03194 | ||
|
|
a42e8ae873 | ||
|
|
bcc5d63516 | ||
|
|
f97074dc84 | ||
|
|
bd81124b5a | ||
|
|
5a18f43b51 | ||
|
|
924b96ce2a | ||
|
|
47236dbaec | ||
|
|
437ac301db | ||
|
|
215740fab2 | ||
|
|
c66fc63452 | ||
|
|
02aefc40f3 | ||
|
|
cd42ca1bbd | ||
|
|
05bfd764b6 | ||
|
|
99f2026ce2 | ||
|
|
6a8717b294 | ||
|
|
c3f24a9cea | ||
|
|
805a055946 | ||
|
|
c0e17f6136 | ||
|
|
b38142ddb1 | ||
|
|
0b29514836 | ||
|
|
27ce87f652 | ||
|
|
a61a7b688a | ||
|
|
f74408f390 | ||
|
|
f5e0fd8de5 | ||
|
|
353b6f3d6d | ||
|
|
cb377f29c2 | ||
|
|
0e03ef385b | ||
|
|
ac2c723ec9 | ||
|
|
3fc0eae4c0 | ||
|
|
beed223281 | ||
|
|
9c9ce97525 | ||
|
|
4bfb1fcc71 | ||
|
|
15628a1206 | ||
|
|
a1411e3d52 | ||
|
|
66750f7349 | ||
|
|
f7f1397e52 | ||
|
|
3660ba28d7 | ||
|
|
e3cc283478 | ||
|
|
9a2735d035 | ||
|
|
06cd2f1eb3 | ||
|
|
41315827c1 | ||
|
|
415fd101d3 | ||
|
|
0747e162fa | ||
|
|
8d6ba6ee7a | ||
|
|
173a707a2e | ||
|
|
c8c95b4bd2 | ||
|
|
351b7557b6 | ||
|
|
4dc1962f9e | ||
|
|
335b9629b8 | ||
|
|
d16f0508bd | ||
|
|
da03331015 | ||
|
|
43128234bb | ||
|
|
bbcee8dfa7 | ||
|
|
5b8f84f59a | ||
|
|
cfcbf34305 | ||
|
|
4bd5789203 | ||
|
|
c4669013ab | ||
|
|
3ad6020e19 | ||
|
|
3fc8645d92 | ||
|
|
fb812d59b0 | ||
|
|
3e0371685f | ||
|
|
3bc3666215 | ||
|
|
b01a4af99e | ||
|
|
9745d5348c | ||
|
|
c858ff61f7 | ||
|
|
d39c0bee39 | ||
|
|
5788b90c52 | ||
|
|
037f466e1f | ||
|
|
71475d3cea | ||
|
|
a613c3b7e7 | ||
|
|
35668fe225 | ||
|
|
83a2abeee4 | ||
|
|
49d926013c | ||
|
|
c068ec5231 | ||
|
|
86d19657b1 | ||
|
|
67ad07020d | ||
|
|
fa2c4160b5 | ||
|
|
a37f019806 | ||
|
|
20449a0cb2 | ||
|
|
856a5c3369 | ||
|
|
2c193a1244 | ||
|
|
c4d10a6f2d | ||
|
|
d6589004c7 | ||
|
|
519f1318c6 | ||
|
|
7460a36ce2 | ||
|
|
c8f6e3f923 | ||
|
|
7b373e29ea | ||
|
|
94ce7a54c3 | ||
|
|
3bb6d8871b | ||
|
|
98394c6e37 | ||
|
|
e755ed927c | ||
|
|
37a9b64503 | ||
|
|
0de00da753 | ||
|
|
a4ae3c1e14 | ||
|
|
7f595169c1 | ||
|
|
abee7d25a4 | ||
|
|
49ba456189 | ||
|
|
6a83687f45 | ||
|
|
70f4453e3e | ||
|
|
cd0f6716e8 | ||
|
|
5042b85d21 | ||
|
|
5015f2d7d7 | ||
|
|
5277d4a266 | ||
|
|
f25b2d9ab9 | ||
|
|
0402d3de80 | ||
|
|
d3e03744cd | ||
|
|
34926eb66a | ||
|
|
b7585318c7 | ||
|
|
e70c3976db | ||
|
|
2c055db0d7 | ||
|
|
50dc1d3db3 | ||
|
|
2e368b50eb | ||
|
|
7b61ad639b | ||
|
|
741774c4a7 | ||
|
|
33c1259fa2 | ||
|
|
9ee04fb14c | ||
|
|
e9a5ae21e0 | ||
|
|
0bcf7e56c2 | ||
|
|
141f185c72 | ||
|
|
e1b2c64654 | ||
|
|
d7e5c6f9b5 | ||
|
|
1e5cc3b0e5 | ||
|
|
85c3b2996d | ||
|
|
9a57f71ee6 | ||
|
|
cf7727debc | ||
|
|
9133e764a5 | ||
|
|
c32938fa43 | ||
|
|
827c245777 | ||
|
|
88d020f9f2 | ||
|
|
54c891a447 | ||
|
|
6096fe75d3 | ||
|
|
a2e1d28efc | ||
|
|
a9fbe921a0 | ||
|
|
ee8042b458 | ||
|
|
1e1b14edc5 | ||
|
|
4c505b6470 | ||
|
|
471abfa760 | ||
|
|
590552d3e0 | ||
|
|
557faba31b | ||
|
|
2411749594 | ||
|
|
4e39a957c7 | ||
|
|
0cf719a744 | ||
|
|
fe7ad22cc1 | ||
|
|
59f82c5bfb | ||
|
|
a2b8933129 | ||
|
|
031b07264a | ||
|
|
87f8e93e12 | ||
|
|
5b5c31861a | ||
|
|
c315ca6c0c | ||
|
|
655b0636fa | ||
|
|
5bd8e172c9 | ||
|
|
ebb35fd65e | ||
|
|
df7195bda4 | ||
|
|
4bd950bb6f | ||
|
|
3f39b22a68 | ||
|
|
c019280e85 | ||
|
|
ff0b0ae1ab | ||
|
|
823015435d | ||
|
|
99854d7ecf | ||
|
|
bb7d9656a5 | ||
|
|
e41994a064 | ||
|
|
79df9ef8e6 | ||
|
|
3b52686125 | ||
|
|
af7c03212d | ||
|
|
bdeedb6c91 | ||
|
|
4d61d56639 | ||
|
|
430154d543 | ||
|
|
bb6631c7c6 | ||
|
|
9bb5e6f5ce | ||
|
|
05de07496f | ||
|
|
ab9dc66b8f | ||
|
|
f98042a7f8 | ||
|
|
247b4686fa | ||
|
|
c78cb27175 | ||
|
|
752603284d | ||
|
|
dcfe8bae1c | ||
|
|
8f4be963b2 | ||
|
|
61496d77a5 | ||
|
|
a12069f03f | ||
|
|
56b5619d24 | ||
|
|
bc61b92070 | ||
|
|
682fbd3b76 | ||
|
|
574ea453b0 | ||
|
|
2b806455a5 | ||
|
|
49898525af | ||
|
|
24493a4556 | ||
|
|
8de64c9495 | ||
|
|
02bbcf6b0e | ||
|
|
08963f269b | ||
|
|
2d0fd14d3c | ||
|
|
a15f78652f | ||
|
|
3d62312657 | ||
|
|
9b083eebd7 | ||
|
|
c7e57a4124 | ||
|
|
ee1a44ebeb | ||
|
|
5b70383ce6 | ||
|
|
abb2ad45ce | ||
|
|
087ea1f068 | ||
|
|
33f479c271 | ||
|
|
f936363440 | ||
|
|
315e4015de | ||
|
|
0c91b66f45 | ||
|
|
4354d3f121 | ||
|
|
3e8e3478a3 | ||
|
|
cf29530dd0 | ||
|
|
b0f13c17e2 | ||
|
|
1147cb56ba | ||
|
|
e29c3e4c70 | ||
|
|
b1b473d3cb | ||
|
|
c088d9ddd9 | ||
|
|
2b26981e3d | ||
|
|
f3e4576625 | ||
|
|
5dae2b8d2b | ||
|
|
62da364e5d | ||
|
|
58a318b754 | ||
|
|
ff07f49002 | ||
|
|
8d69d8553c | ||
|
|
a072954176 | ||
|
|
23de8185c6 | ||
|
|
d9c80e9b6a | ||
|
|
dcc4272c4e | ||
|
|
2060940ecc | ||
|
|
14274ef67b | ||
|
|
70661c179f | ||
|
|
8a0ae68f27 | ||
|
|
06ada87370 | ||
|
|
a5d9b71eb6 | ||
|
|
aa3385d516 | ||
|
|
af60b41c89 | ||
|
|
506bf45272 | ||
|
|
2655daa2f4 | ||
|
|
0b095ce5ce | ||
|
|
ae461b77f8 | ||
|
|
4f3b9dc61a | ||
|
|
a246702511 | ||
|
|
2ae47d64b7 | ||
|
|
6a76349730 | ||
|
|
e6482554f5 | ||
|
|
c4ee2d7386 | ||
|
|
a74ec0effa | ||
|
|
e33027c624 | ||
|
|
b0be323e89 | ||
|
|
a22661670f | ||
|
|
442f35a1fd | ||
|
|
5aef16c2aa | ||
|
|
e512e38efb | ||
|
|
7ea0b138bc | ||
|
|
d73fb5a23c | ||
|
|
610298a25d | ||
|
|
7b50a6490d | ||
|
|
fdb82d5dd4 | ||
|
|
a587697883 | ||
|
|
3204c077d1 | ||
|
|
d52825a5b1 | ||
|
|
84c965d459 | ||
|
|
22cd6989a0 | ||
|
|
a9ae555b88 | ||
|
|
d4dc428124 | ||
|
|
f5608d2c94 | ||
|
|
bcad937003 | ||
|
|
53c918cc78 | ||
|
|
009f81fe4f | ||
|
|
81ce4a0229 | ||
|
|
6bcd9adb9e | ||
|
|
61e7b735dc | ||
|
|
44c15fc1ef | ||
|
|
9d3fe1258a | ||
|
|
e72ba39c41 | ||
|
|
ffcc487763 | ||
|
|
473ce8b617 | ||
|
|
70436fa535 | ||
|
|
4921f61e76 | ||
|
|
75b390cf93 | ||
|
|
dcda8fe538 | ||
|
|
6edae86516 | ||
|
|
f163e6d8cc | ||
|
|
742660591f | ||
|
|
ddbcf5f470 | ||
|
|
6becf22a2f | ||
|
|
32246fd26b | ||
|
|
42e3ab91a7 | ||
|
|
6daf70b745 | ||
|
|
12298ea392 | ||
|
|
5e77e8809a | ||
|
|
1b3f84c9ad | ||
|
|
e06539e76d | ||
|
|
fdb3fa6801 | ||
|
|
1392a855bb | ||
|
|
d4cab6e62f | ||
|
|
5d2b6585c6 | ||
|
|
a2f89347a9 | ||
|
|
50ee23ebfa | ||
|
|
719c51f61a | ||
|
|
3e2a614eb9 | ||
|
|
abb23631df | ||
|
|
dae5a063cf | ||
|
|
e57a507ba0 | ||
|
|
4e194d7766 | ||
|
|
d34ea79d93 | ||
|
|
ee73659f16 | ||
|
|
394417ff07 | ||
|
|
a85b1f016d | ||
|
|
c6f97f20fb | ||
|
|
b66600338e | ||
|
|
4ae9482d50 | ||
|
|
72fcf7b2ab | ||
|
|
a8b6bbd6bc | ||
|
|
e5d04f4467 | ||
|
|
62ac8e1952 | ||
|
|
d61f34ec12 | ||
|
|
252fdd03d7 | ||
|
|
0fe5c5dac3 | ||
|
|
037e992de4 | ||
|
|
12023073f4 | ||
|
|
0db0694aad | ||
|
|
d4f763aa68 | ||
|
|
4e708c81ca | ||
|
|
041d00301c | ||
|
|
0ec51b124b | ||
|
|
6012926e82 | ||
|
|
ca9c1bca4a | ||
|
|
0d957ea71d | ||
|
|
697926641f | ||
|
|
2bf65fda1f | ||
|
|
4262ac3c89 | ||
|
|
76b66872d8 | ||
|
|
2f92aaea0c | ||
|
|
c5b347bb15 | ||
|
|
d11087c28f | ||
|
|
e6139e02b8 | ||
|
|
583b92e672 | ||
|
|
d90756e8ef | ||
|
|
9b9ab983d6 | ||
|
|
d5746652a2 | ||
|
|
47f9e171fc | ||
|
|
a1bc18e5cf | ||
|
|
0cd5bf7967 | ||
|
|
0205f9ede3 | ||
|
|
960c83315b | ||
|
|
f9c84fb6f4 | ||
|
|
1b14561748 | ||
|
|
27519e1dfa | ||
|
|
46e2da24a4 | ||
|
|
dbbcbed344 | ||
|
|
c0db88168b | ||
|
|
d98435b4dc | ||
|
|
595dc6de03 | ||
|
|
322e054f1a | ||
|
|
7f606e1e64 | ||
|
|
b0491b0ee2 | ||
|
|
0e69356ca9 | ||
|
|
90348e08c1 | ||
|
|
c60d7e2db8 | ||
|
|
50d3a14825 | ||
|
|
0ff3ba5250 | ||
|
|
02cc3f9116 | ||
|
|
62861d1e13 | ||
|
|
cd3c7f1b97 | ||
|
|
0c8886ad0c | ||
|
|
9d24325207 | ||
|
|
126a165f55 | ||
|
|
1c6e6842c6 | ||
|
|
6f5a55b5fe | ||
|
|
530f5a700e | ||
|
|
441c1f9ab7 | ||
|
|
d98d885924 | ||
|
|
6aa91d89e0 | ||
|
|
ecc54b07c7 | ||
|
|
ce11a38d70 | ||
|
|
c9e0b29878 | ||
|
|
3a96fec03b | ||
|
|
7a231b3166 | ||
|
|
31f6934787 | ||
|
|
c5dc89886d | ||
|
|
545cc0b026 | ||
|
|
9ff42053c3 | ||
|
|
41a0ce146d | ||
|
|
709fa06af6 | ||
|
|
5623400557 | ||
|
|
1351e02065 | ||
|
|
4f030ac45c | ||
|
|
c7c81a1f7e | ||
|
|
ae74965774 | ||
|
|
e952c98ca8 | ||
|
|
6f00c422c7 | ||
|
|
e592cedbb4 | ||
|
|
a10bb4b2fa | ||
|
|
c89eec4261 | ||
|
|
edc0fec808 | ||
|
|
bb5474660c | ||
|
|
63f16b5f99 | ||
|
|
0b84c469d3 | ||
|
|
ff5d18d327 | ||
|
|
1980b3fae4 | ||
|
|
6714b8958b | ||
|
|
f601a5d356 | ||
|
|
e7f60032bc | ||
|
|
589bd8694f | ||
|
|
eace1a9840 | ||
|
|
3ddb203317 | ||
|
|
84b8e77aaa | ||
|
|
247eefc33a | ||
|
|
fd54a6a3ad | ||
|
|
a2ffd7de2e | ||
|
|
cea2fb0fe6 | ||
|
|
23d7d7d140 | ||
|
|
f8179bc5a9 | ||
|
|
a4e93558aa | ||
|
|
cb3cae0f30 | ||
|
|
04e0199790 | ||
|
|
453f3405a7 | ||
|
|
a8585df81b | ||
|
|
c96c681758 | ||
|
|
6007609f71 | ||
|
|
5a6ed252c4 | ||
|
|
641dff8991 | ||
|
|
ee3115550e | ||
|
|
d39852c0cf | ||
|
|
c57894633f | ||
|
|
a7bedad9f0 | ||
|
|
92df3d953f | ||
|
|
a896904ae7 | ||
|
|
6bfcd253f8 | ||
|
|
456f7e7304 | ||
|
|
ed7917f9df | ||
|
|
2a25ece363 | ||
|
|
1c90c3af42 | ||
|
|
6ca6290f6a | ||
|
|
fd2c5d46ad | ||
|
|
a6c110f558 | ||
|
|
02b33f988f | ||
|
|
7bf6e6188a | ||
|
|
26394813f4 | ||
|
|
f21f42f11e | ||
|
|
68a8556cd2 | ||
|
|
e4d8b1c4d2 | ||
|
|
eac467fe9a | ||
|
|
1bb9c912cd | ||
|
|
02d470892f | ||
|
|
161f62dc27 | ||
|
|
f53472e717 | ||
|
|
7fa5d34c45 | ||
|
|
b49d1dae7d | ||
|
|
9b0496b049 | ||
|
|
fec8f8a881 | ||
|
|
e08c54878c | ||
|
|
e78398862b | ||
|
|
122a5e9b63 | ||
|
|
5d759d82ed | ||
|
|
744a9b8931 | ||
|
|
45c4042e2b | ||
|
|
bba7704732 | ||
|
|
fa3e88c454 | ||
|
|
e0ff550e19 | ||
|
|
48dbee2e10 | ||
|
|
1ba2df8024 | ||
|
|
e17f6979c3 | ||
|
|
8330dabda8 | ||
|
|
1fd8c2a6e2 | ||
|
|
0c6be94222 | ||
|
|
89103c40fb | ||
|
|
fbe34663da | ||
|
|
9d0ccdfdcc | ||
|
|
cd1f0c74c1 | ||
|
|
3deac0f100 | ||
|
|
641d3b75ea | ||
|
|
fab2fa9adf | ||
|
|
b3d8cefbe6 | ||
|
|
32e8e52ad7 | ||
|
|
77b3a43e17 | ||
|
|
ddec91ba52 | ||
|
|
0329b36430 | ||
|
|
f62dc0f46c | ||
|
|
f64638173a | ||
|
|
14a0dcecf5 | ||
|
|
e17cca9834 | ||
|
|
9b0f68f9a9 | ||
|
|
f4eb73ca7c | ||
|
|
4ea6ebb0ae | ||
|
|
8931f2e736 | ||
|
|
7ae78ca3c8 | ||
|
|
d24eeec1a1 | ||
|
|
a798f32cc8 | ||
|
|
df5fe4a84f | ||
|
|
9aef222f79 | ||
|
|
5591378245 | ||
|
|
dde2268f9f | ||
|
|
0eaca6c691 | ||
|
|
ba654c04a0 | ||
|
|
f4fbac2694 | ||
|
|
b86f6322e1 | ||
|
|
74c6be3698 | ||
|
|
d114613384 | ||
|
|
c23ea1e688 | ||
|
|
f9d4799ebe | ||
|
|
9ec4f6dcab | ||
|
|
bcdd063d70 | ||
|
|
c8b54f3bac |
122
.circleci/config.yml
Normal file
122
.circleci/config.yml
Normal file
@@ -0,0 +1,122 @@
|
||||
version: 2
|
||||
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/node:6.12
|
||||
environment:
|
||||
CONTRACTS_COMMIT_HASH: '9ed05f5'
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
- run: echo 'export PATH=$HOME/CIRCLE_PROJECT_REPONAME/node_modules/.bin:$PATH' >> $BASH_ENV
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "package.json" }}
|
||||
- run:
|
||||
name: yarn
|
||||
command: yarn --frozen-lockfile
|
||||
- save_cache:
|
||||
key: dependency-cache-{{ checksum "package.json" }}
|
||||
paths:
|
||||
- ./node_modules
|
||||
- run: wget https://s3.amazonaws.com/testrpc-shapshots/${CONTRACTS_COMMIT_HASH}.zip
|
||||
- run: unzip ${CONTRACTS_COMMIT_HASH}.zip -d testrpc_snapshot
|
||||
- run: node ./node_modules/lerna/bin/lerna.js bootstrap
|
||||
- run: yarn lerna:run build
|
||||
- save_cache:
|
||||
key: repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
paths:
|
||||
- ~/repo
|
||||
test-0xjs:
|
||||
docker:
|
||||
- image: circleci/node:6.12
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
name: testrpc
|
||||
command: npm run testrpc -- --db testrpc_snapshot
|
||||
background: true
|
||||
- run: yarn lerna:run --scope 0x.js test:circleci
|
||||
test-contracts:
|
||||
docker:
|
||||
- image: circleci/node:6.12
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
name: testrpc
|
||||
command: npm run testrpc -- --db testrpc_snapshot
|
||||
background: true
|
||||
- run: yarn lerna:run --scope contracts test:circleci
|
||||
test-deployer:
|
||||
docker:
|
||||
- image: circleci/node:6.12
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
name: testrpc
|
||||
command: npm run testrpc -- --db testrpc_snapshot
|
||||
background: true
|
||||
- run: yarn lerna:run --scope @0xproject/deployer test:circleci
|
||||
test-rest:
|
||||
docker:
|
||||
- image: circleci/node:6.12
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
name: testrpc
|
||||
command: npm run testrpc -- --db testrpc_snapshot
|
||||
background: true
|
||||
- run: yarn lerna:run --ignore contracts --ignore 0x.js --ignore @0xproject/deployer test:circleci
|
||||
lint:
|
||||
working_directory: ~/repo
|
||||
docker:
|
||||
- image: circleci/node:6.12
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run: yarn lerna:run lint
|
||||
prettier:
|
||||
working_directory: ~/repo
|
||||
docker:
|
||||
- image: circleci/node:6.12
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run: yarn prettier:ci
|
||||
workflows:
|
||||
version: 2
|
||||
main:
|
||||
jobs:
|
||||
- build
|
||||
- test-0xjs:
|
||||
requires:
|
||||
- build
|
||||
- test-contracts:
|
||||
requires:
|
||||
- build
|
||||
- test-deployer:
|
||||
requires:
|
||||
- build
|
||||
- test-rest:
|
||||
requires:
|
||||
- build
|
||||
- prettier:
|
||||
requires:
|
||||
- build
|
||||
- lint:
|
||||
requires:
|
||||
- build
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.sol linguist-language=Solidity
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -58,9 +58,16 @@ typings/
|
||||
.env
|
||||
|
||||
# built library using in commonjs module syntax
|
||||
lib
|
||||
lib/
|
||||
# UMD bundles that export the global variable
|
||||
_bundles
|
||||
|
||||
# generated documentation
|
||||
docs
|
||||
generated_docs/
|
||||
|
||||
TODO.md
|
||||
|
||||
packages/website/public/bundle*
|
||||
|
||||
# generated binaries
|
||||
bin/
|
||||
|
||||
5
.prettierignore
Normal file
5
.prettierignore
Normal file
@@ -0,0 +1,5 @@
|
||||
lib
|
||||
generated
|
||||
.nyc_output
|
||||
/packages/contracts/build/contracts
|
||||
package.json
|
||||
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"printWidth": 120,
|
||||
"trailingComma": all,
|
||||
"singleQuote": true
|
||||
}
|
||||
@@ -1,62 +1,46 @@
|
||||
# 0x.js CONTRIBUTING.md
|
||||
## 0x Contribution Guide
|
||||
|
||||
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!
|
||||
Thank you for your interest in contributing to 0x protocol! We welcome contributions from anyone on the internet, and are grateful for even the smallest of fixes!
|
||||
|
||||
## Developer's guide
|
||||
### How to contribute
|
||||
|
||||
## How to contribute
|
||||
If you'd like to contribute to 0x protocol, 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 with a core dev first on [our RocketChat #dev channel](http://chat.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.
|
||||
|
||||
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:
|
||||
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 contributors 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
|
||||
### Code quality
|
||||
|
||||
Because 0x.js is used by multiple relayers in production and their businesses depend on it, we strive for exceptional code quality. Please follow the existing code standards and conventions. `tslint` and `prettier` (described below) will help you.
|
||||
|
||||
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
|
||||
If the sub-package you are modifying has a `CHANGELOG.md` file, make sure to add an entry in it for the change made to the package. For published packages, only changes that modify the public interface or behavior of the package need a CHANGELOG entry.
|
||||
|
||||
First thing to do with an unknown code base is to run the tests.
|
||||
We assume that you have `npm` and `yarn` installed.
|
||||
### Styleguide
|
||||
|
||||
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.
|
||||
We use [TSLint](https://palantir.github.io/tslint/) with [custom configs](https://github.com/0xProject/0x.js/tree/development/packages/tslint-config) to keep our code style consistent.
|
||||
|
||||
To lint your code just run: `yarn lint`
|
||||
|
||||
We also use [Prettier](https://prettier.io/) to auto-format our code. Be sure to either add a [text editor integration](https://prettier.io/docs/en/editors.html) or a [pre-commit hook](https://prettier.io/docs/en/precommit.html) to properly format your code changes.
|
||||
|
||||
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)
|
||||
* [prettier-atom](https://atom.io/packages/prettier-atom)
|
||||
* [language-ethereum](https://atom.io/packages/language-ethereum)
|
||||
|
||||
Our CI will also run it as a part of the test run when you submit your PR.
|
||||
Our CI will also run TSLint and Prettier as a part of the test run when you submit your PR. Make sure that the CI tests pass for your contribution.
|
||||
|
||||
### Branch structure & versioning
|
||||
|
||||
## 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 use [semantic versioning](http://semver.org/), but before a package reaches v1.0.0 all breaking changes as well as new features will be minor version bumps.
|
||||
|
||||
We have two main branches: `master` and `development`.
|
||||
|
||||
|
||||
62
ISSUE_TEMPLATE.md
Normal file
62
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,62 @@
|
||||
<!--- Thank you for taking the time to file an Issue -->
|
||||
|
||||
<!--- Before submitting please check to see if this issue was already reported -->
|
||||
|
||||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
<!--- If you're describing a bug, tell us what should happen -->
|
||||
|
||||
<!--- If you're suggesting a package change/improvement, tell us how it should work -->
|
||||
|
||||
<!--- If you're suggesting a contract or protocol change/improvement, visit our ZEIPs repo -->
|
||||
|
||||
## Current Behavior
|
||||
|
||||
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
|
||||
|
||||
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
|
||||
|
||||
## Possible Solution
|
||||
|
||||
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||
|
||||
<!--- or ideas how to implement the addition or change -->
|
||||
|
||||
## Steps to Reproduce (for bugs)
|
||||
|
||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||
|
||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||
|
||||
```
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
```
|
||||
|
||||
## Context
|
||||
|
||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||
|
||||
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||
|
||||
## Your Environment
|
||||
|
||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
||||
|
||||
| Package | Version |
|
||||
| ------------------: | :------ |
|
||||
| `0x.js` | 0.25.0 |
|
||||
| `Exchange Contract` | v1 |
|
||||
|
||||
| Network |
|
||||
| ------- |
|
||||
| NAME |
|
||||
|
||||
<!-- For example:
|
||||
| mainnet |
|
||||
| kovan |
|
||||
| testrpc |
|
||||
-->
|
||||
@@ -1,2 +1,36 @@
|
||||
This PR:
|
||||
*
|
||||
<!--- Thank you for taking the time to submit a Pull Request -->
|
||||
|
||||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
|
||||
## Description
|
||||
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Motivation and Context
|
||||
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
|
||||
<!--- If it fixes an open issue, please link to the issue here. -->
|
||||
|
||||
## How Has This Been Tested?
|
||||
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
|
||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||
|
||||
<!--- see how your change affects other areas of the code, etc. -->
|
||||
|
||||
## Types of changes
|
||||
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply:
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
|
||||
## Checklist:
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
|
||||
* [ ] Change requires a change to the documentation.
|
||||
* [ ] Added tests to cover my changes.
|
||||
|
||||
109
README.md
109
README.md
@@ -4,50 +4,111 @@
|
||||
|
||||
[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.
|
||||
This repository contains all the 0x developer tools written in TypeScript. Our hope is that these tools make it easy to build Relayers and other DApps that use the 0x protocol.
|
||||
|
||||
[website-url]: https://0xproject.com/
|
||||
[whitepaper-url]: https://0xproject.com/pdfs/0x_white_paper.pdf
|
||||
|
||||
[](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://chat.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
|
||||
### Published Packages
|
||||
|
||||
0x.js ships as both a [UMD](https://github.com/umdjs/umd) module and a [CommonJS](https://en.wikipedia.org/wiki/CommonJS) package.
|
||||
| Package | Version | Description |
|
||||
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
|
||||
| [`0x.js`](/packages/0x.js) | [](https://www.npmjs.com/package/0x.js) | A Javascript library for interacting with the 0x protocol |
|
||||
| [`chai-as-promised-typescript-typings`](/packages/chai-as-promised-typescript-typings) | [](https://www.npmjs.com/package/chai-as-promised-typescript-typings) | Chai as promised typescript typings |
|
||||
| [`chai-typescript-typings`](/packages/chai-typescript-typings) | [](https://www.npmjs.com/package/chai-typescript-typings) | Chai typescript typings |
|
||||
| [`web3-typescript-typings`](/packages/web3-typescript-typings) | [](https://www.npmjs.com/package/web3-typescript-typings) | Web3 typescript typings |
|
||||
| [`@0xproject/abi-gen`](/packages/abi-gen) | [](https://www.npmjs.com/package/@0xproject/abi-gen) | Tool to generate TS wrappers from smart contract ABIs |
|
||||
| [`@0xproject/assert`](/packages/assert) | [](https://www.npmjs.com/package/@0xproject/assert) | Type and schema assertions used by our packages |
|
||||
| [`@0xproject/connect`](/packages/connect) | [](https://www.npmjs.com/package/@0xproject/connect) | A Javascript library for interacting with the standard relayer api |
|
||||
| [`@0xproject/dev-utils`](/packages/dev-utils) | [](https://www.npmjs.com/package/@0xproject/dev-utils) | Dev utils to be shared across 0x projects and packages |
|
||||
| [`@0xproject/json-schemas`](/packages/json-schemas) | [](https://www.npmjs.com/package/@0xproject/json-schemas) | 0x-related json schemas |
|
||||
| [`@0xproject/subproviders`](/packages/subproviders) | [](https://www.npmjs.com/package/@0xproject/subproviders) | Useful web3 subproviders (e.g LedgerSubprovider) |
|
||||
| [`@0xproject/tslint-config`](/packages/tslint-config) | [](https://www.npmjs.com/package/@0xproject/tslint-config) | Custom 0x development TSLint rules |
|
||||
| [`@0xproject/types`](/packages/types) | [](https://www.npmjs.com/package/@0xproject/types) | Shared type declarations |
|
||||
| [`@0xproject/utils`](/packages/utils) | [](https://www.npmjs.com/package/@0xproject/utils) | Shared utilities |
|
||||
| [`@0xproject/web3-wrapper`](/packages/web3-wrapper) | [](https://www.npmjs.com/package/@0xproject/web3-wrapper) | Web3 wrapper |
|
||||
|
||||
#### CommonJS *(recommended)*:
|
||||
### Private Packages
|
||||
|
||||
**Install**
|
||||
| Package | Description |
|
||||
| ----------------------------------------------------------- | ---------------------------------------------------------------- |
|
||||
| [`@0xproject/contracts`](/packages/contracts) | 0x solidity smart contracts & tests |
|
||||
| [`@0xproject/monorepo-scripts`](/packages/monorepo-scripts) | Shared monorepo scripts |
|
||||
| [`@0xproject/testnet-faucets`](/packages/testnet-faucets) | A faucet micro-service that dispenses test ERC20 tokens or Ether |
|
||||
| [`@0xproject/website`](/packages/website) | 0x website & Portal DApp |
|
||||
|
||||
## Usage
|
||||
|
||||
Dedicated documentation pages:
|
||||
|
||||
* [0x.js Library](https://0xproject.com/docs/0xjs)
|
||||
* [0x Connect](https://0xproject.com/docs/connect)
|
||||
* [Smart contracts](https://0xproject.com/docs/contracts)
|
||||
* [Standard Relayer API](https://github.com/0xProject/standard-relayer-api/blob/master/README.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository.
|
||||
|
||||
Please read our [contribution guidelines](./CONTRIBUTING.md) before getting started.
|
||||
|
||||
### Install dependencies
|
||||
|
||||
If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them:
|
||||
|
||||
```bash
|
||||
npm install 0x.js --save
|
||||
yarn config set workspaces-experimental true
|
||||
```
|
||||
|
||||
**Import**
|
||||
Then install dependencies
|
||||
|
||||
```javascript
|
||||
import {ZeroEx} from '0x.js';
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
#### UMD:
|
||||
### Build
|
||||
|
||||
**Install**
|
||||
Build all packages
|
||||
|
||||
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>
|
||||
```bash
|
||||
yarn lerna:run build
|
||||
```
|
||||
|
||||
## Documentation
|
||||
### Lint
|
||||
|
||||
Extensive documentation of 0x.js can be found on [our website][docs-url].
|
||||
Lint all packages
|
||||
|
||||
[website-url]: https://0xproject.com/
|
||||
[whitepaper-url]: https://0xproject.com/pdfs/0x_white_paper.pdf
|
||||
[docs-url]: https://0xproject.com/docs/0xjs
|
||||
```bash
|
||||
yarn lerna:run lint
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
|
||||
Before running the tests, you will need to spin up a [TestRPC](https://www.npmjs.com/package/ethereumjs-testrpc) instance and deploy all the 0x smart contracts.
|
||||
|
||||
In a separate terminal, start TestRPC (a convenience command is provided as part of this repo)
|
||||
|
||||
```bash
|
||||
yarn testrpc
|
||||
```
|
||||
|
||||
Then in your main terminal run
|
||||
|
||||
```
|
||||
cd packages/contracts
|
||||
yarn migrate
|
||||
cd ..
|
||||
```
|
||||
|
||||
And finally from the root project directory run
|
||||
|
||||
```bash
|
||||
yarn lerna:run test
|
||||
```
|
||||
|
||||
23
circle.yml
23
circle.yml
@@ -1,23 +0,0 @@
|
||||
machine:
|
||||
node:
|
||||
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:
|
||||
- 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
|
||||
- yarn test:coverage
|
||||
- yarn report_test_coverage
|
||||
- if [ $CIRCLE_BRANCH = "master" ]; then yarn test:umd; fi
|
||||
- yarn lint
|
||||
7
lerna.json
Normal file
7
lerna.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"lerna": "2.5.1",
|
||||
"packages": ["packages/*"],
|
||||
"version": "independent",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true
|
||||
}
|
||||
10156
package-lock.json
generated
10156
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
130
package.json
130
package.json
@@ -1,108 +1,26 @@
|
||||
{
|
||||
"name": "0x.js",
|
||||
"version": "0.21.1",
|
||||
"description": "A javascript library for interacting with the 0x protocol",
|
||||
"keywords": [
|
||||
"0x.js",
|
||||
"0xproject",
|
||||
"ethereum",
|
||||
"tokens",
|
||||
"exchange"
|
||||
],
|
||||
"main": "lib/src/index.js",
|
||||
"types": "lib/src/index.d.ts",
|
||||
"scripts": {
|
||||
"prebuild": "npm run clean",
|
||||
"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 --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:umd:dev": "webpack",
|
||||
"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/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 5000 --bail --exit"
|
||||
},
|
||||
"config": {
|
||||
"artifacts": "TokenTransferProxy Exchange TokenRegistry Token EtherToken",
|
||||
"mnemonic": "concert load couple harbor equip island argue ramp clarify fence smart topic"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/0xProject/0x.js"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsonschema": "^1.1.1",
|
||||
"@types/lodash": "^4.14.64",
|
||||
"@types/mocha": "^2.2.41",
|
||||
"@types/node": "^8.0.1",
|
||||
"@types/sinon": "^2.2.2",
|
||||
"@types/uuid": "^3.4.2",
|
||||
"awesome-typescript-loader": "^3.1.3",
|
||||
"bignumber.js": "^4.0.2",
|
||||
"chai": "^4.0.1",
|
||||
"chai-as-promised": "^7.1.0",
|
||||
"chai-as-promised-typescript-typings": "0.0.3",
|
||||
"chai-bignumber": "^2.0.1",
|
||||
"chai-typescript-typings": "^0.0.0",
|
||||
"copyfiles": "^1.2.0",
|
||||
"coveralls": "^3.0.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"ethereumjs-testrpc": "4.0.1",
|
||||
"json-loader": "^0.5.4",
|
||||
"mocha": "^4.0.0",
|
||||
"npm-run-all": "^4.0.2",
|
||||
"nyc": "^11.0.1",
|
||||
"opn-cli": "^3.1.0",
|
||||
"request": "^2.81.0",
|
||||
"request-promise-native": "^1.0.4",
|
||||
"shx": "^0.2.2",
|
||||
"sinon": "^4.0.0",
|
||||
"source-map-support": "^0.5.0",
|
||||
"truffle-hdwallet-provider": "^0.0.3",
|
||||
"tslint": "^5.3.2",
|
||||
"tslint-config-0xproject": "^0.0.2",
|
||||
"typedoc": "^0.9.0",
|
||||
"types-bn": "^0.0.1",
|
||||
"types-ethereumjs-util": "0xProject/types-ethereumjs-util",
|
||||
"typescript": "^2.4.1",
|
||||
"web3-provider-engine": "^13.0.1",
|
||||
"web3-typescript-typings": "^0.6.2",
|
||||
"webpack": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"0x-json-schemas": "^0.6.1",
|
||||
"@types/bignumber.js": "^4.0.2",
|
||||
"bignumber.js": "^4.0.2",
|
||||
"compare-versions": "^3.0.1",
|
||||
"es6-promisify": "^5.0.0",
|
||||
"ethereumjs-abi": "^0.6.4",
|
||||
"ethereumjs-blockstream": "^2.0.6",
|
||||
"ethereumjs-util": "^5.1.1",
|
||||
"find-versions": "^2.0.0",
|
||||
"js-sha3": "^0.6.1",
|
||||
"lodash": "^4.17.4",
|
||||
"publish-release": "^1.3.3",
|
||||
"uuid": "^3.1.0",
|
||||
"web3": "^0.20.0"
|
||||
}
|
||||
"private": true,
|
||||
"name": "0x.js",
|
||||
"workspaces": ["packages/*"],
|
||||
"scripts": {
|
||||
"testrpc": "testrpc -p 8545 --networkId 50 -m \"${npm_package_config_mnemonic}\"",
|
||||
"prettier": "prettier --write '**/*.{ts,tsx,json,md}' --config .prettierrc",
|
||||
"prettier:ci": "prettier --list-different '**/*.{ts,tsx,json,md}' --config .prettierrc",
|
||||
"lerna:run": "lerna run",
|
||||
"lerna:rebuild": "lerna run clean; lerna run build;",
|
||||
"lerna:publish":
|
||||
"yarn install; lerna run clean; lerna run build; lerna publish --registry=https://registry.npmjs.org/"
|
||||
},
|
||||
"config": {
|
||||
"mnemonic": "concert load couple harbor equip island argue ramp clarify fence smart topic"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@0xproject/utils": "^0.2.0",
|
||||
"async-child-process": "^1.1.1",
|
||||
"ethereumjs-testrpc": "^6.0.3",
|
||||
"lerna": "^2.5.1",
|
||||
"prettier": "1.9.2",
|
||||
"publish-release": "0xproject/publish-release",
|
||||
"semver-sort": "^0.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,129 @@
|
||||
# CHANGELOG
|
||||
|
||||
v0.21.1 - _October 11, 2017_
|
||||
------------------------
|
||||
## v0.31.1 - _February 1, 2018_
|
||||
|
||||
* Fix the bug causing order watcher to throw is makerToken === zrx (#357)
|
||||
|
||||
## v0.31.0 - _January 30, 2018_
|
||||
|
||||
* Add the `shouldAddPersonalMessagePrefix` parameter to `signOrderHashAsync` so that the
|
||||
caller can decide on whether to add the personalMessage prefix before relaying the request
|
||||
to the signer. Parity Signer, Ledger and TestRPC add the prefix themselves, Metamask expects
|
||||
it to have already been added. (#349)
|
||||
|
||||
## v0.30.2 - _January 29, 2018_
|
||||
|
||||
* Add Rinkeby testnet addresses to artifacts (#337)
|
||||
* Move @0xproject/types to dependencies from devDependencies fixing missing type errors
|
||||
|
||||
## v0.30.1 - _January 24, 2018_
|
||||
|
||||
* Fix a bug allowing negative fill values (#212)
|
||||
* Fix a bug that made it impossible to pass a custom ZRX address (#341)
|
||||
|
||||
## v0.30.0 - _January 17, 2018_
|
||||
|
||||
* Add an error parameter to the order watcher callback (#312)
|
||||
* Fix a bug making it impossible to catch some errors from awaitTransactionMinedAsync (#312)
|
||||
* Fix a bug in fillOrdersUpTo validation making it impossible to fill up to if user doesn't have enough balance to fully fill all the orders (#321)
|
||||
|
||||
## v0.29.1 - _January 11, 2018_
|
||||
|
||||
* Fixed bignumber config issue #301 (#305)
|
||||
|
||||
## v0.29.0 - _December 28, 2017_
|
||||
|
||||
* Assert baseUnit amount supplied to `toUnitAmount` is integer amount. (#287)
|
||||
* `toBaseUnitAmount` throws if amount supplied has too many decimals (#287)
|
||||
|
||||
## v0.28.0 - _December 20, 2017_
|
||||
|
||||
* Add `etherTokenAddress` arg to `depositAsync` and `withdrawAsync` methods on `zeroEx.etherToken` (#267)
|
||||
* Removed accidentally included `unsubscribeAll` method from `zeroEx.proxy`, `zeroEx.etherToken` and `zeroEx.tokenRegistry` (#267)
|
||||
* Removed `etherTokenContractAddress` from `ZeroEx` constructor arg `ZeroExConfig` (#267)
|
||||
* Rename `SubscriptionOpts` to `BlockRange` (#272)
|
||||
* Add `zeroEx.etherToken.subscribe`, `zeroEx.etherToken.unsubscribe`, `zeroEx.etherToken.unsubscribeAll` (#277)
|
||||
* Add `zeroEx.etherToken.getLogsAsync` (#277)
|
||||
* Add new public types `BlockParamLiteral`, `EtherTokenEvents`, `EtherTokenContractEventArgs`, `DepositContractEventArgs`, `WithdrawalContractEventArgs` (#277)
|
||||
* Support `Deposit` and `Withdraw` events on etherToken (#277)
|
||||
* Improve the error message when taker is not a string (#278)
|
||||
|
||||
## v0.27.1 - _November 28, 2017_
|
||||
|
||||
* Export `TransactionOpts` type
|
||||
|
||||
## v0.27.0 - _November 28, 2017_
|
||||
|
||||
* Make `ZeroExConfig` required parameter of `ZeroEx` constructor (#233)
|
||||
* Add a required property `networkId` to `ZeroExConfig` (#233)
|
||||
* Make all `getContractAddress` functions, `zeroEx.exchange.subscribe`, `zeroEx.exchange.getZRXTokenAddress` sync (#233)
|
||||
* Remove `ZeroExError.ContractNotFound` and replace it with contract-specific errors (#233)
|
||||
* Make `DecodedLogEvent<A>` contain `LogWithDecodedArgs<A>` under log key instead of merging it in like web3 does (#234)
|
||||
* Rename `removed` to `isRemoved` in `DecodedLogEvent<A>` (#234)
|
||||
* Add config allowing to specify gasPrice and gasLimit for every transaction sending method (#235)
|
||||
* All transaction sending methods now call `estimateGas` if no gas amount was supplied (#235)
|
||||
* Modify order validation methods to validate against the `latest` block, not against the `pending` block (#236)
|
||||
|
||||
## v0.26.0 - _November 21, 2017_
|
||||
|
||||
* Add post-formatter for logs converting `blockNumber`, `logIndex`, `transactionIndex` from hexes to numbers (#231)
|
||||
* Remove support for Async callback types when used in Subscribe functions (#222)
|
||||
* In OrderWatcher subscribe to ZRX Token Transfer and Approval events when maker token is different (#225)
|
||||
|
||||
## v0.25.1 - _November 13, 2017_
|
||||
|
||||
* Standardise on Cancelled over Canceled (#217)
|
||||
* Add missing `DecodedLogEvent` type to exported types (#205)
|
||||
* Normalized the transactionReceipt status to be `null|0|1`, 1 meaning transaction execution successful, 0 unsuccessful and `null` if it is a pre-byzantinium transaction. (#200)
|
||||
|
||||
## v0.23.0 - _November 12, 2017_
|
||||
|
||||
* Fixed unhandled promise rejection error in subscribe methods (#209)
|
||||
* Subscribe callbacks now receive an error object as their first argument
|
||||
|
||||
## v0.22.6 - _November 10, 2017_
|
||||
|
||||
* Add a timeout parameter to transaction awaiting (#206)
|
||||
|
||||
## v0.22.5 - _November 7, 2017_
|
||||
|
||||
* Re-publish v0.22.4 to fix publishing issue
|
||||
|
||||
## v0.22.4 - _October 25, 2017_
|
||||
|
||||
* Upgraded bignumber.js to a new version that ships with native typings
|
||||
|
||||
## v0.22.3 - _October 25, 2017_
|
||||
|
||||
* Fixed an issue with new version of testrpc and unlimited proxy allowance (#199)
|
||||
|
||||
## v0.22.2 - _October 24, 2017_
|
||||
|
||||
* Fixed rounding of maker fill amount and incorrect validation of partial fees (#197)
|
||||
|
||||
## v0.22.0 - _October 16, 2017_
|
||||
|
||||
* Started using `OrderFillRequest` interface instead of `OrderFillOrKillRequest` interface for `zeroEx.exchange.batchFillOrKill` (#187)
|
||||
* Removed `OrderFillOrKillRequest` (#187)
|
||||
|
||||
## v0.21.4 - _October 13, 2017_
|
||||
|
||||
* Made 0x.js more type-safe by making `getLogsAsync` and `subscribe/subscribeAsync` generics parametrized with arg type (#194)
|
||||
|
||||
## v0.21.3 - _October 12, 2017_
|
||||
|
||||
* Fixed a bug causing order fills to throw `INSUFFICIENT_TAKER_ALLOWANCE` (#193)
|
||||
|
||||
## v0.21.2 - _October 11, 2017_
|
||||
|
||||
* Exported `ContractEventArg` as a public type (#190)
|
||||
|
||||
## v0.21.1 - _October 11, 2017_
|
||||
|
||||
* Fixed a bug in subscriptions (#189)
|
||||
|
||||
v0.21.0 - _October 10, 2017_
|
||||
------------------------
|
||||
## v0.21.0 - _October 10, 2017_
|
||||
|
||||
* Complete rewrite of subscription logic (#182)
|
||||
* Subscriptions no longer return historical logs. If you want them - use `getLogsAsync`
|
||||
* Subscriptions now use [ethereumjs-blockstream](https://github.com/ethereumjs/ethereumjs-blockstream) under the hood
|
||||
@@ -19,77 +137,77 @@ v0.21.0 - _October 10, 2017_
|
||||
* Renamed `zeroEx.token.stopWatchingAllEventsAsync` to `zeroEx.token.unsubscribeAll`
|
||||
* Fixed the batch fills validation by emulating all balance & proxy allowance changes (#185)
|
||||
|
||||
v0.20.0 - _October 5, 2017_
|
||||
------------------------
|
||||
## v0.20.0 - _October 5, 2017_
|
||||
|
||||
* Add `zeroEx.token.getLogsAsync` (#178)
|
||||
* Add `zeroEx.exchange.getLogsAsync` (#178)
|
||||
* Fixed fees validation when one of the tokens transferred is ZRX (#181)
|
||||
|
||||
v0.19.0 - _September 29, 2017_
|
||||
------------------------
|
||||
## v0.19.0 - _September 29, 2017_
|
||||
|
||||
* Made order validation optional (#172)
|
||||
* Added Ropsten testnet support (#173)
|
||||
* Fixed a bug causing awaitTransactionMinedAsync to DDos backend nodes (#175)
|
||||
|
||||
v0.18.0 - _September 26, 2017_
|
||||
------------------------
|
||||
## v0.18.0 - _September 26, 2017_
|
||||
|
||||
* Added `zeroEx.exchange.validateOrderFillableOrThrowAsync` to simplify orderbook pruning (#170)
|
||||
|
||||
v0.17.0 - _September 26, 2017_
|
||||
------------------------
|
||||
## v0.17.0 - _September 26, 2017_
|
||||
|
||||
* Made `zeroEx.exchange.getZRXTokenAddressAsync` public (#171)
|
||||
|
||||
v0.16.0 - _September 20, 2017_
|
||||
------------------------
|
||||
## v0.16.0 - _September 20, 2017_
|
||||
|
||||
* Added the ability to specify custom contract addresses to be used with 0x.js (#165)
|
||||
* ZeroExConfig.exchangeContractAddress
|
||||
* ZeroExConfig.tokenRegistryContractAddress
|
||||
* ZeroExConfig.etherTokenContractAddress
|
||||
* Added `zeroEx.tokenRegistry.getContractAddressAsync` (#165)
|
||||
|
||||
v0.15.0 - _September 8, 2017_
|
||||
------------------------
|
||||
## v0.15.0 - _September 8, 2017_
|
||||
|
||||
* Added the ability to specify a historical `blockNumber` at which to query the blockchain's state when calling a token or exchange method (#161)
|
||||
|
||||
v0.14.2 - _September 7, 2017_
|
||||
------------------------
|
||||
## v0.14.2 - _September 7, 2017_
|
||||
|
||||
* Fixed an issue with bignumber.js types not found (#160)
|
||||
|
||||
v0.14.1 - _September 7, 2017_
|
||||
------------------------
|
||||
## v0.14.1 - _September 7, 2017_
|
||||
|
||||
* Fixed an issue with Artifact type not found (#159)
|
||||
|
||||
v0.14.0 - _September 6, 2017_
|
||||
------------------------
|
||||
## v0.14.0 - _September 6, 2017_
|
||||
|
||||
* Added `zeroEx.exchange.throwLogErrorsAsErrors` method to public interface (#157)
|
||||
* Fixed an issue with overlapping async intervals in `zeroEx.awaitTransactionMinedAsync` (#157)
|
||||
* Fixed an issue with log decoder returning `BigNumber`s as `strings` (#157)
|
||||
|
||||
v0.13.0 - _September 6, 2017_
|
||||
------------------------
|
||||
## v0.13.0 - _September 6, 2017_
|
||||
|
||||
* Made all the functions submitting transactions to the network to immediately return transaction hash (#151)
|
||||
* Added `zeroEx.awaitTransactionMinedAsync` (#151)
|
||||
* Added `TransactionReceiptWithDecodedLogs`, `LogWithDecodedArgs`, `DecodedLogArgs` to public types (#151)
|
||||
* Added signature validation to `validateFillOrderThrowIfInvalidAsync` (#152)
|
||||
|
||||
v0.12.1 - _September 2, 2017_
|
||||
------------------------
|
||||
## 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_
|
||||
------------------------
|
||||
## 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_
|
||||
------------------------
|
||||
## 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_
|
||||
------------------------
|
||||
## v0.10.1 - _Aug 24, 2017_
|
||||
|
||||
* Added `zeroEx.exchange.validateFillOrderThrowIfInvalidAsync` (#128)
|
||||
* Added `zeroEx.exchange.validateFillOrKillOrderThrowIfInvalidAsync` (#128)
|
||||
* Added `zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync` (#128)
|
||||
@@ -103,21 +221,21 @@ v0.10.1 - _Aug 24, 2017_
|
||||
* 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_
|
||||
------------------------
|
||||
## v0.9.3 - _Aug 22, 2017_
|
||||
|
||||
* Update contract artifacts to include latest Kovan and Mainnet deploys (#118)
|
||||
|
||||
v0.9.2 - _Aug 21, 2017_
|
||||
------------------------
|
||||
## 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_
|
||||
------------------------
|
||||
## 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_
|
||||
------------------------
|
||||
## 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)
|
||||
@@ -127,8 +245,8 @@ v0.9.0 - _Jul. 26, 2017_
|
||||
* 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_
|
||||
------------------------
|
||||
## 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)
|
||||
@@ -140,35 +258,35 @@ v0.8.0 - _Jul. 4, 2017_
|
||||
* `zeroEx.tokenRegistry.invalidateContractInstance`
|
||||
* Fixed the bug where `zeroEx.setProviderAsync` didn't invalidate etherToken contract's instance
|
||||
|
||||
v0.7.1 - _Jun. 26, 2017_
|
||||
------------------------
|
||||
## 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_
|
||||
------------------------
|
||||
## 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_
|
||||
------------------------
|
||||
## v0.6.2 - _Jun. 21, 2017_
|
||||
|
||||
* Reduced bundle size
|
||||
* Improved documentation
|
||||
|
||||
v0.6.1 - _Jun. 19, 2017_
|
||||
------------------------
|
||||
## v0.6.1 - _Jun. 19, 2017_
|
||||
|
||||
* Improved documentation
|
||||
|
||||
v0.6.0 - _Jun. 19, 2017_
|
||||
------------------------
|
||||
## 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_
|
||||
------------------------
|
||||
## 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_
|
||||
------------------------
|
||||
## v0.5.1 - _Jun. 15, 2017_
|
||||
|
||||
* Added `postpublish` script to publish to Github Releases with assets.
|
||||
39
packages/0x.js/README.md
Normal file
39
packages/0x.js/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
## 0x.js
|
||||
|
||||
## 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
packages/0x.js/contract_templates/contract.handlebars
Normal file
25
packages/0x.js/contract_templates/contract.handlebars
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* This file is auto-generated using abi-gen. Don't edit directly.
|
||||
* Templates can be found at https://github.com/0xProject/0x.js/tree/development/packages/abi-gen-templates.
|
||||
*/
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
import { TxData, TxDataPayable } from '@0xproject/types';
|
||||
import { BigNumber, classUtils, promisify } from '@0xproject/utils';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
import {BaseContract} from './base_contract';
|
||||
|
||||
export class {{contractName}}Contract extends BaseContract {
|
||||
{{#each methods}}
|
||||
{{#this.constant}}
|
||||
{{> call contractName=../contractName}}
|
||||
{{/this.constant}}
|
||||
{{^this.constant}}
|
||||
{{> tx contractName=../contractName}}
|
||||
{{/this.constant}}
|
||||
{{/each}}
|
||||
constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<TxData>) {
|
||||
super(web3ContractInstance, defaults);
|
||||
classUtils.bindAll(this, ['_web3ContractInstance', '_defaults']);
|
||||
}
|
||||
} // tslint:disable:max-file-line-count
|
||||
15
packages/0x.js/contract_templates/partials/call.handlebars
Normal file
15
packages/0x.js/contract_templates/partials/call.handlebars
Normal file
@@ -0,0 +1,15 @@
|
||||
public {{this.name}} = {
|
||||
async callAsync(
|
||||
{{> typed_params inputs=inputs}}
|
||||
defaultBlock?: Web3.BlockParam,
|
||||
): Promise<{{> return_type outputs=outputs}}> {
|
||||
const self = this as {{contractName}}Contract;
|
||||
const result = await promisify<{{> return_type outputs=outputs}}>(
|
||||
self._web3ContractInstance.{{this.name}}.call,
|
||||
self._web3ContractInstance,
|
||||
)(
|
||||
{{> params inputs=inputs}}
|
||||
);
|
||||
return result;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
{{#each inputs}}
|
||||
{{name}},
|
||||
{{/each}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{{#singleReturnValue}}
|
||||
{{#returnType outputs.0.type}}{{/returnType}}
|
||||
{{/singleReturnValue}}
|
||||
{{^singleReturnValue}}
|
||||
[{{#each outputs}}{{#returnType type}}{{/returnType}}{{#unless @last}}, {{/unless}}{{/each}}]
|
||||
{{/singleReturnValue}}
|
||||
51
packages/0x.js/contract_templates/partials/tx.handlebars
Normal file
51
packages/0x.js/contract_templates/partials/tx.handlebars
Normal file
@@ -0,0 +1,51 @@
|
||||
public {{this.name}} = {
|
||||
async sendTransactionAsync(
|
||||
{{> typed_params inputs=inputs}}
|
||||
{{#this.payable}}
|
||||
txData: TxDataPayable = {},
|
||||
{{/this.payable}}
|
||||
{{^this.payable}}
|
||||
txData: TxData = {},
|
||||
{{/this.payable}}
|
||||
): Promise<string> {
|
||||
const self = this as {{contractName}}Contract;
|
||||
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
|
||||
txData,
|
||||
self.{{this.name}}.estimateGasAsync.bind(
|
||||
self,
|
||||
{{> params inputs=inputs}}
|
||||
),
|
||||
);
|
||||
const txHash = await promisify<string>(
|
||||
self._web3ContractInstance.{{this.name}}, self._web3ContractInstance,
|
||||
)(
|
||||
{{> params inputs=inputs}}
|
||||
txDataWithDefaults,
|
||||
);
|
||||
return txHash;
|
||||
},
|
||||
async estimateGasAsync(
|
||||
{{> typed_params inputs=inputs}}
|
||||
txData: TxData = {},
|
||||
): Promise<number> {
|
||||
const self = this as {{contractName}}Contract;
|
||||
const txDataWithDefaults = await self._applyDefaultsToTxDataAsync(
|
||||
txData,
|
||||
);
|
||||
const gas = await promisify<number>(
|
||||
self._web3ContractInstance.{{this.name}}.estimateGas, self._web3ContractInstance,
|
||||
)(
|
||||
{{> params inputs=inputs}}
|
||||
txDataWithDefaults,
|
||||
);
|
||||
return gas;
|
||||
},
|
||||
getABIEncodedTransactionData(
|
||||
{{> typed_params inputs=inputs}}
|
||||
txData: TxData = {},
|
||||
): string {
|
||||
const self = this as {{contractName}}Contract;
|
||||
const abiEncodedTransactionData = self._web3ContractInstance.{{this.name}}.getData();
|
||||
return abiEncodedTransactionData;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
{{#each inputs}}
|
||||
{{name}}: {{#parameterType type}}{{/parameterType}},
|
||||
{{/each}}
|
||||
94
packages/0x.js/package.json
Normal file
94
packages/0x.js/package.json
Normal file
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"name": "0x.js",
|
||||
"version": "0.31.0",
|
||||
"description": "A javascript library for interacting with the 0x protocol",
|
||||
"keywords": ["0x.js", "0xproject", "ethereum", "tokens", "exchange"],
|
||||
"main": "lib/src/index.js",
|
||||
"types": "lib/src/index.d.ts",
|
||||
"scripts": {
|
||||
"prebuild": "run-s clean generate_contract_wrappers",
|
||||
"build": "run-p build:umd:prod build:commonjs; exit 0;",
|
||||
"docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR",
|
||||
"upload_docs_json":
|
||||
"aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json",
|
||||
"generate_contract_wrappers":
|
||||
"node ../abi-gen/lib/index.js --abis 'src/artifacts/@(Exchange|Token|TokenTransferProxy|EtherToken|TokenRegistry|DummyToken).json' --template contract_templates/contract.handlebars --partials 'contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated",
|
||||
"lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'",
|
||||
"test:circleci": "run-s test:coverage report_test_coverage",
|
||||
"test": "run-s clean test:commonjs",
|
||||
"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;",
|
||||
"clean": "shx rm -rf _bundles lib test_temp",
|
||||
"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",
|
||||
"run_mocha": "mocha lib/test/**/*_test.js --timeout 10000 --bail --exit"
|
||||
},
|
||||
"config": {
|
||||
"artifacts": "TokenTransferProxy Exchange TokenRegistry Token EtherToken"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/0xProject/0x.js"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@0xproject/abi-gen": "^0.1.5",
|
||||
"@0xproject/dev-utils": "^0.0.8",
|
||||
"@0xproject/tslint-config": "^0.4.5",
|
||||
"@types/bintrees": "^1.0.2",
|
||||
"@types/jsonschema": "^1.1.1",
|
||||
"@types/lodash": "^4.14.86",
|
||||
"@types/mocha": "^2.2.42",
|
||||
"@types/node": "^8.0.53",
|
||||
"@types/sinon": "^2.2.2",
|
||||
"@types/uuid": "^3.4.2",
|
||||
"awesome-typescript-loader": "^3.1.3",
|
||||
"chai": "^4.0.1",
|
||||
"chai-as-promised": "^7.1.0",
|
||||
"chai-as-promised-typescript-typings": "^0.0.7",
|
||||
"chai-bignumber": "^2.0.1",
|
||||
"chai-typescript-typings": "^0.0.2",
|
||||
"copyfiles": "^1.2.0",
|
||||
"coveralls": "^3.0.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"json-loader": "^0.5.4",
|
||||
"mocha": "^4.0.1",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"nyc": "^11.0.1",
|
||||
"opn-cli": "^3.1.0",
|
||||
"request": "^2.81.0",
|
||||
"request-promise-native": "^1.0.5",
|
||||
"shx": "^0.2.2",
|
||||
"sinon": "^4.0.0",
|
||||
"source-map-support": "^0.5.0",
|
||||
"truffle-hdwallet-provider": "^0.0.3",
|
||||
"tslint": "5.8.0",
|
||||
"typedoc": "~0.8.0",
|
||||
"typescript": "~2.6.1",
|
||||
"web3-provider-engine": "^13.0.1",
|
||||
"web3-typescript-typings": "^0.9.7",
|
||||
"webpack": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0xproject/assert": "^0.0.14",
|
||||
"@0xproject/json-schemas": "^0.7.6",
|
||||
"@0xproject/types": "^0.1.7",
|
||||
"@0xproject/utils": "^0.2.3",
|
||||
"@0xproject/web3-wrapper": "^0.1.8",
|
||||
"bintrees": "^1.0.2",
|
||||
"bn.js": "^4.11.8",
|
||||
"ethereumjs-abi": "^0.6.4",
|
||||
"ethereumjs-blockstream": "^2.0.6",
|
||||
"ethereumjs-util": "^5.1.1",
|
||||
"js-sha3": "^0.7.0",
|
||||
"lodash": "^4.17.4",
|
||||
"uuid": "^3.1.0",
|
||||
"web3": "^0.20.0"
|
||||
}
|
||||
}
|
||||
40
packages/0x.js/scripts/postpublish.js
Normal file
40
packages/0x.js/scripts/postpublish.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const execAsync = require('async-child-process').execAsync;
|
||||
const postpublish_utils = require('../../../scripts/postpublish_utils');
|
||||
const packageJSON = require('../package.json');
|
||||
|
||||
const cwd = __dirname + '/..';
|
||||
const subPackageName = packageJSON.name;
|
||||
const S3BucketPath = 's3://0xjs-docs-jsons/';
|
||||
|
||||
let tag;
|
||||
let version;
|
||||
postpublish_utils
|
||||
.getLatestTagAndVersionAsync(subPackageName)
|
||||
.then(function(result) {
|
||||
tag = result.tag;
|
||||
version = result.version;
|
||||
const releaseName = postpublish_utils.getReleaseName(subPackageName, version);
|
||||
const assets = [__dirname + '/../_bundles/index.js', __dirname + '/../_bundles/index.min.js'];
|
||||
return postpublish_utils.publishReleaseNotes(tag, releaseName, assets);
|
||||
})
|
||||
.then(function(release) {
|
||||
console.log('POSTPUBLISH: Release successful, generating docs...');
|
||||
const jsonFilePath = __dirname + '/../' + postpublish_utils.generatedDocsDirectoryName + '/index.json';
|
||||
return execAsync('JSON_FILE_PATH=' + jsonFilePath + ' PROJECT_DIR=' + __dirname + '/.. yarn docs:json', {
|
||||
cwd,
|
||||
});
|
||||
})
|
||||
.then(function(result) {
|
||||
if (result.stderr !== '') {
|
||||
throw new Error(result.stderr);
|
||||
}
|
||||
const fileName = 'v' + version + '.json';
|
||||
console.log('POSTPUBLISH: Doc generation successful, uploading docs... as ', fileName);
|
||||
const s3Url = S3BucketPath + fileName;
|
||||
return execAsync('S3_URL=' + s3Url + ' yarn upload_docs_json', {
|
||||
cwd,
|
||||
});
|
||||
})
|
||||
.catch(function(err) {
|
||||
throw err;
|
||||
});
|
||||
@@ -1,34 +1,24 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import {SchemaValidator, schemas} from '0x-json-schemas';
|
||||
import {bigNumberConfigs} from './bignumber_config';
|
||||
import { schemas, SchemaValidator } from '@0xproject/json-schemas';
|
||||
import { TransactionReceiptWithDecodedLogs } from '@0xproject/types';
|
||||
import { AbiDecoder, BigNumber, intervalUtils } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import {Web3Wrapper} from './web3_wrapper';
|
||||
import {constants} from './utils/constants';
|
||||
import {utils} from './utils/utils';
|
||||
import {signatureUtils} from './utils/signature_utils';
|
||||
import {assert} from './utils/assert';
|
||||
import {AbiDecoder} from './utils/abi_decoder';
|
||||
import {intervalUtils} from './utils/interval_utils';
|
||||
import {artifacts} from './artifacts';
|
||||
import {ExchangeWrapper} from './contract_wrappers/exchange_wrapper';
|
||||
import {TokenRegistryWrapper} from './contract_wrappers/token_registry_wrapper';
|
||||
import {EtherTokenWrapper} from './contract_wrappers/ether_token_wrapper';
|
||||
import {TokenWrapper} from './contract_wrappers/token_wrapper';
|
||||
import {TokenTransferProxyWrapper} from './contract_wrappers/token_transfer_proxy_wrapper';
|
||||
import {
|
||||
ECSignature,
|
||||
ZeroExError,
|
||||
Order,
|
||||
SignedOrder,
|
||||
Web3Provider,
|
||||
ZeroExConfig,
|
||||
TransactionReceiptWithDecodedLogs,
|
||||
} from './types';
|
||||
import {zeroExConfigSchema} from './schemas/zero_ex_config_schema';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
// Customize our BigNumber instances
|
||||
bigNumberConfigs.configure();
|
||||
import { artifacts } from './artifacts';
|
||||
import { EtherTokenWrapper } from './contract_wrappers/ether_token_wrapper';
|
||||
import { ExchangeWrapper } from './contract_wrappers/exchange_wrapper';
|
||||
import { TokenRegistryWrapper } from './contract_wrappers/token_registry_wrapper';
|
||||
import { TokenTransferProxyWrapper } from './contract_wrappers/token_transfer_proxy_wrapper';
|
||||
import { TokenWrapper } from './contract_wrappers/token_wrapper';
|
||||
import { OrderStateWatcher } from './order_watcher/order_state_watcher';
|
||||
import { zeroExConfigSchema } from './schemas/zero_ex_config_schema';
|
||||
import { ECSignature, Order, SignedOrder, Web3Provider, ZeroExConfig, ZeroExError } from './types';
|
||||
import { assert } from './utils/assert';
|
||||
import { constants } from './utils/constants';
|
||||
import { decorators } from './utils/decorators';
|
||||
import { signatureUtils } from './utils/signature_utils';
|
||||
import { utils } from './utils/utils';
|
||||
|
||||
/**
|
||||
* The ZeroEx class is the single entry-point into the 0x.js library. It contains all of the library's functionality
|
||||
@@ -65,6 +55,11 @@ export class ZeroEx {
|
||||
* tokenTransferProxy smart contract.
|
||||
*/
|
||||
public proxy: TokenTransferProxyWrapper;
|
||||
/**
|
||||
* An instance of the OrderStateWatcher class containing methods for watching a set of orders for relevant
|
||||
* blockchain state changes.
|
||||
*/
|
||||
public orderStateWatcher: OrderStateWatcher;
|
||||
private _web3Wrapper: Web3Wrapper;
|
||||
private _abiDecoder: AbiDecoder;
|
||||
/**
|
||||
@@ -80,19 +75,8 @@ export class ZeroEx {
|
||||
assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema);
|
||||
assert.isETHAddressHex('signerAddress', signerAddress);
|
||||
|
||||
const dataBuff = ethUtil.toBuffer(data);
|
||||
const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
|
||||
try {
|
||||
const pubKey = ethUtil.ecrecover(
|
||||
msgHashBuff,
|
||||
signature.v,
|
||||
ethUtil.toBuffer(signature.r),
|
||||
ethUtil.toBuffer(signature.s));
|
||||
const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey));
|
||||
return retrievedAddress === signerAddress;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
const isValidSignature = signatureUtils.isValidSignature(data, signature, signerAddress);
|
||||
return isValidSignature;
|
||||
}
|
||||
/**
|
||||
* Generates a pseudo-random 256-bit salt.
|
||||
@@ -100,7 +84,7 @@ export class ZeroEx {
|
||||
* and will not collide with other outstanding orders that are identical in all other parameters.
|
||||
* @return A pseudo-random 256-bit number that can be used as a salt.
|
||||
*/
|
||||
public static generatePseudoRandomSalt(): BigNumber.BigNumber {
|
||||
public static generatePseudoRandomSalt(): BigNumber {
|
||||
// BigNumber.random returns a pseudo-random number between 0 & 1 with a passed in number of decimal places.
|
||||
// Source: https://mikemcl.github.io/bignumber.js/#random
|
||||
const randomNumber = BigNumber.random(constants.MAX_DIGITS_IN_UNSIGNED_256_INT);
|
||||
@@ -131,8 +115,8 @@ export class ZeroEx {
|
||||
* @param decimals The number of decimal places the unit amount has.
|
||||
* @return The amount in units.
|
||||
*/
|
||||
public static toUnitAmount(amount: BigNumber.BigNumber, decimals: number): BigNumber.BigNumber {
|
||||
assert.isBigNumber('amount', amount);
|
||||
public static toUnitAmount(amount: BigNumber, decimals: number): BigNumber {
|
||||
assert.isValidBaseUnitAmount('amount', amount);
|
||||
assert.isNumber('decimals', decimals);
|
||||
|
||||
const aUnit = new BigNumber(10).pow(decimals);
|
||||
@@ -147,12 +131,16 @@ export class ZeroEx {
|
||||
* @param decimals The number of decimal places the unit amount has.
|
||||
* @return The amount in baseUnits.
|
||||
*/
|
||||
public static toBaseUnitAmount(amount: BigNumber.BigNumber, decimals: number): BigNumber.BigNumber {
|
||||
public static toBaseUnitAmount(amount: BigNumber, decimals: number): BigNumber {
|
||||
assert.isBigNumber('amount', amount);
|
||||
assert.isNumber('decimals', decimals);
|
||||
|
||||
const unit = new BigNumber(10).pow(decimals);
|
||||
const baseUnitAmount = amount.times(unit);
|
||||
const hasDecimals = baseUnitAmount.decimalPlaces() !== 0;
|
||||
if (hasDecimals) {
|
||||
throw new Error(`Invalid unit amount: ${amount.toString()} - Too many decimal places`);
|
||||
}
|
||||
return baseUnitAmount;
|
||||
}
|
||||
/**
|
||||
@@ -160,7 +148,8 @@ export class ZeroEx {
|
||||
* @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 {
|
||||
@decorators.syncZeroExErrorHandler
|
||||
public static getOrderHashHex(order: Order | SignedOrder): string {
|
||||
assert.doesConformToSchema('order', order, schemas.orderSchema);
|
||||
const orderHashHex = utils.getOrderHashHex(order);
|
||||
return orderHashHex;
|
||||
@@ -172,60 +161,62 @@ export class ZeroEx {
|
||||
* @param config The configuration object. Look up the type for the description.
|
||||
* @return An instance of the 0x.js ZeroEx class.
|
||||
*/
|
||||
constructor(provider: Web3Provider, config?: ZeroExConfig) {
|
||||
constructor(provider: Web3Provider, config: ZeroExConfig) {
|
||||
assert.isWeb3Provider('provider', provider);
|
||||
if (!_.isUndefined(config)) {
|
||||
assert.doesConformToSchema('config', config, zeroExConfigSchema);
|
||||
}
|
||||
if (_.isUndefined((provider as any).sendAsync)) {
|
||||
// Web3@1.0 provider doesn't support synchronous http requests,
|
||||
// so it only has an async `send` method, instead of a `send` and `sendAsync` in web3@0.x.x`
|
||||
// We re-assign the send method so that Web3@1.0 providers work with 0x.js
|
||||
(provider as any).sendAsync = (provider as any).send;
|
||||
}
|
||||
assert.doesConformToSchema('config', config, zeroExConfigSchema);
|
||||
const artifactJSONs = _.values(artifacts);
|
||||
const abiArrays = _.map(artifactJSONs, artifact => artifact.abi);
|
||||
this._abiDecoder = new AbiDecoder(abiArrays);
|
||||
const gasPrice = _.isUndefined(config) ? undefined : config.gasPrice;
|
||||
const defaults = {
|
||||
gasPrice,
|
||||
gasPrice: config.gasPrice,
|
||||
};
|
||||
this._web3Wrapper = new Web3Wrapper(provider, defaults);
|
||||
this.token = new TokenWrapper(
|
||||
this.proxy = new TokenTransferProxyWrapper(
|
||||
this._web3Wrapper,
|
||||
this._abiDecoder,
|
||||
this._getTokenTransferProxyAddressAsync.bind(this),
|
||||
config.networkId,
|
||||
config.tokenTransferProxyContractAddress,
|
||||
);
|
||||
const exchageContractAddressIfExists = _.isUndefined(config) ? undefined : config.exchangeContractAddress;
|
||||
this.token = new TokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.proxy);
|
||||
this.exchange = new ExchangeWrapper(
|
||||
this._web3Wrapper,
|
||||
config.networkId,
|
||||
this._abiDecoder,
|
||||
this.token,
|
||||
config.exchangeContractAddress,
|
||||
config.zrxContractAddress,
|
||||
);
|
||||
this.tokenRegistry = new TokenRegistryWrapper(
|
||||
this._web3Wrapper,
|
||||
config.networkId,
|
||||
config.tokenRegistryContractAddress,
|
||||
);
|
||||
this.etherToken = new EtherTokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.token);
|
||||
this.orderStateWatcher = new OrderStateWatcher(
|
||||
this._web3Wrapper,
|
||||
this._abiDecoder,
|
||||
this.token,
|
||||
exchageContractAddressIfExists,
|
||||
this.exchange,
|
||||
config.orderWatcherConfig,
|
||||
);
|
||||
this.proxy = new TokenTransferProxyWrapper(
|
||||
this._web3Wrapper,
|
||||
this._getTokenTransferProxyAddressAsync.bind(this),
|
||||
);
|
||||
const tokenRegistryContractAddressIfExists = _.isUndefined(config) ?
|
||||
undefined :
|
||||
config.tokenRegistryContractAddress;
|
||||
this.tokenRegistry = new TokenRegistryWrapper(this._web3Wrapper, tokenRegistryContractAddressIfExists);
|
||||
const etherTokenContractAddressIfExists = _.isUndefined(config) ? undefined : config.etherTokenContractAddress;
|
||||
this.etherToken = new EtherTokenWrapper(this._web3Wrapper, this.token, etherTokenContractAddressIfExists);
|
||||
}
|
||||
/**
|
||||
* Sets a new web3 provider for 0x.js. Updating the provider will stop all
|
||||
* subscriptions so you will need to re-subscribe to all events relevant to your app after this call.
|
||||
* @param provider The Web3Provider you would like the 0x.js library to use from now on.
|
||||
* @param networkId The id of the network your provider is connected to
|
||||
*/
|
||||
public async setProviderAsync(provider: Web3Provider) {
|
||||
public setProvider(provider: Web3Provider, networkId: number): void {
|
||||
this._web3Wrapper.setProvider(provider);
|
||||
await (this.exchange as any)._invalidateContractInstancesAsync();
|
||||
(this.exchange as any)._invalidateContractInstances();
|
||||
(this.exchange as any)._setNetworkId(networkId);
|
||||
(this.tokenRegistry as any)._invalidateContractInstance();
|
||||
await (this.token as any)._invalidateContractInstancesAsync();
|
||||
(this.tokenRegistry as any)._setNetworkId(networkId);
|
||||
(this.token as any)._invalidateContractInstances();
|
||||
(this.token as any)._setNetworkId(networkId);
|
||||
(this.proxy as any)._invalidateContractInstance();
|
||||
(this.proxy as any)._setNetworkId(networkId);
|
||||
(this.etherToken as any)._invalidateContractInstance();
|
||||
(this.etherToken as any)._setNetworkId(networkId);
|
||||
}
|
||||
/**
|
||||
* Get user Ethereum addresses available through the supplied web3 provider available for sending transactions.
|
||||
@@ -241,20 +232,22 @@ export class ZeroEx {
|
||||
* @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.
|
||||
* @param shouldAddPersonalMessagePrefix Some signers add the personal message prefix `\x19Ethereum Signed Message`
|
||||
* themselves (e.g Parity Signer, Ledger, TestRPC) and others expect it to already be done by the client
|
||||
* (e.g Metamask). Depending on which signer this request is going to, decide on whether to add the prefix
|
||||
* before sending the request.
|
||||
* @return An object containing the Elliptic curve signature parameters generated by signing the orderHash.
|
||||
*/
|
||||
public async signOrderHashAsync(orderHash: string, signerAddress: string): Promise<ECSignature> {
|
||||
public async signOrderHashAsync(
|
||||
orderHash: string,
|
||||
signerAddress: string,
|
||||
shouldAddPersonalMessagePrefix: boolean,
|
||||
): Promise<ECSignature> {
|
||||
assert.isHexString('orderHash', orderHash);
|
||||
await assert.isSenderAddressAsync('signerAddress', signerAddress, this._web3Wrapper);
|
||||
|
||||
let msgHashHex;
|
||||
const nodeVersion = await this._web3Wrapper.getNodeVersionAsync();
|
||||
const isParityNode = utils.isParityNode(nodeVersion);
|
||||
const isTestRpc = utils.isTestRpc(nodeVersion);
|
||||
if (isParityNode || isTestRpc) {
|
||||
// Parity and TestRpc nodes add the personalMessage prefix itself
|
||||
msgHashHex = orderHash;
|
||||
} else {
|
||||
let msgHashHex = orderHash;
|
||||
if (shouldAddPersonalMessagePrefix) {
|
||||
const orderHashBuff = ethUtil.toBuffer(orderHash);
|
||||
const msgHashBuff = ethUtil.hashPersonalMessage(orderHashBuff);
|
||||
msgHashHex = ethUtil.bufferToHex(msgHashBuff);
|
||||
@@ -289,29 +282,52 @@ export class ZeroEx {
|
||||
* Waits for a transaction to be mined and returns the transaction receipt.
|
||||
* @param txHash Transaction hash
|
||||
* @param pollingIntervalMs How often (in ms) should we check if the transaction is mined.
|
||||
* @param timeoutMs How long (in ms) to poll for transaction mined until aborting.
|
||||
* @return Transaction receipt with decoded log args.
|
||||
*/
|
||||
public async awaitTransactionMinedAsync(
|
||||
txHash: string, pollingIntervalMs: number = 1000): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
txHash: string,
|
||||
pollingIntervalMs = 1000,
|
||||
timeoutMs?: number,
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
let timeoutExceeded = false;
|
||||
if (timeoutMs) {
|
||||
setTimeout(() => (timeoutExceeded = true), timeoutMs);
|
||||
}
|
||||
|
||||
const txReceiptPromise = new Promise(
|
||||
(resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => {
|
||||
const intervalId = intervalUtils.setAsyncExcludingInterval(async () => {
|
||||
const transactionReceipt = await this._web3Wrapper.getTransactionReceiptAsync(txHash);
|
||||
if (!_.isNull(transactionReceipt)) {
|
||||
intervalUtils.clearAsyncExcludingInterval(intervalId);
|
||||
const logsWithDecodedArgs = _.map(
|
||||
transactionReceipt.logs,
|
||||
this._abiDecoder.tryToDecodeLogOrNoop.bind(this._abiDecoder),
|
||||
);
|
||||
const transactionReceiptWithDecodedLogArgs: TransactionReceiptWithDecodedLogs = {
|
||||
...transactionReceipt,
|
||||
logs: logsWithDecodedArgs,
|
||||
};
|
||||
resolve(transactionReceiptWithDecodedLogArgs);
|
||||
}
|
||||
}, pollingIntervalMs);
|
||||
});
|
||||
return txReceiptPromise;
|
||||
const intervalId = intervalUtils.setAsyncExcludingInterval(
|
||||
async () => {
|
||||
if (timeoutExceeded) {
|
||||
intervalUtils.clearAsyncExcludingInterval(intervalId);
|
||||
return reject(ZeroExError.TransactionMiningTimeout);
|
||||
}
|
||||
|
||||
const transactionReceipt = await this._web3Wrapper.getTransactionReceiptAsync(txHash);
|
||||
if (!_.isNull(transactionReceipt)) {
|
||||
intervalUtils.clearAsyncExcludingInterval(intervalId);
|
||||
const logsWithDecodedArgs = _.map(
|
||||
transactionReceipt.logs,
|
||||
this._abiDecoder.tryToDecodeLogOrNoop.bind(this._abiDecoder),
|
||||
);
|
||||
const transactionReceiptWithDecodedLogArgs: TransactionReceiptWithDecodedLogs = {
|
||||
...transactionReceipt,
|
||||
logs: logsWithDecodedArgs,
|
||||
};
|
||||
resolve(transactionReceiptWithDecodedLogArgs);
|
||||
}
|
||||
},
|
||||
pollingIntervalMs,
|
||||
(err: Error) => {
|
||||
intervalUtils.clearAsyncExcludingInterval(intervalId);
|
||||
reject(err);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
const txReceipt = await txReceiptPromise;
|
||||
return txReceipt;
|
||||
}
|
||||
/*
|
||||
* HACK: `TokenWrapper` needs a token transfer proxy address. `TokenTransferProxy` address is fetched from
|
||||
18
packages/0x.js/src/artifacts.ts
Normal file
18
packages/0x.js/src/artifacts.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as DummyTokenArtifact from './artifacts/DummyToken.json';
|
||||
import * as EtherTokenArtifact from './artifacts/EtherToken.json';
|
||||
import * as ExchangeArtifact from './artifacts/Exchange.json';
|
||||
import * as TokenArtifact from './artifacts/Token.json';
|
||||
import * as TokenRegistryArtifact from './artifacts/TokenRegistry.json';
|
||||
import * as TokenTransferProxyArtifact from './artifacts/TokenTransferProxy.json';
|
||||
import * as ZRXArtifact from './artifacts/ZRX.json';
|
||||
import { Artifact } from './types';
|
||||
|
||||
export const artifacts = {
|
||||
ZRXArtifact: (ZRXArtifact as any) as Artifact,
|
||||
DummyTokenArtifact: (DummyTokenArtifact as any) as Artifact,
|
||||
TokenArtifact: (TokenArtifact as any) as Artifact,
|
||||
ExchangeArtifact: (ExchangeArtifact as any) as Artifact,
|
||||
EtherTokenArtifact: (EtherTokenArtifact as any) as Artifact,
|
||||
TokenRegistryArtifact: (TokenRegistryArtifact as any) as Artifact,
|
||||
TokenTransferProxyArtifact: (TokenTransferProxyArtifact as any) as Artifact,
|
||||
};
|
||||
22
packages/0x.js/src/artifacts/DummyToken.json
Normal file
22
packages/0x.js/src/artifacts/DummyToken.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"contract_name": "DummyToken",
|
||||
"abi": [
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "setBalance",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
}
|
||||
287
packages/0x.js/src/artifacts/EtherToken.json
Normal file
287
packages/0x.js/src/artifacts/EtherToken.json
Normal file
@@ -0,0 +1,287 @@
|
||||
{
|
||||
"contract_name": "EtherToken",
|
||||
"abi": [
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "withdraw",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [],
|
||||
"name": "deposit",
|
||||
"outputs": [],
|
||||
"payable": true,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"type": "fallback"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Deposit",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Withdrawal",
|
||||
"type": "event"
|
||||
}
|
||||
],
|
||||
"networks": {
|
||||
"1": {
|
||||
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
|
||||
},
|
||||
"3": {
|
||||
"address": "0xc00fd9820cd2898cc4c054b7bf142de637ad129a"
|
||||
},
|
||||
"4": {
|
||||
"address": "0xc778417e063141139fce010982780140aa0cd5ab"
|
||||
},
|
||||
"42": {
|
||||
"address": "0x653e49e301e508a13237c0ddc98ae7d4cd2667a1"
|
||||
},
|
||||
"50": {
|
||||
"address": "0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c"
|
||||
}
|
||||
}
|
||||
}
|
||||
610
packages/0x.js/src/artifacts/Exchange.json
Normal file
610
packages/0x.js/src/artifacts/Exchange.json
Normal file
@@ -0,0 +1,610 @@
|
||||
{
|
||||
"contract_name": "Exchange",
|
||||
"abi": [
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "numerator",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "denominator",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "target",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "isRoundingError",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "filled",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "cancelled",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "orderAddresses",
|
||||
"type": "address[5][]"
|
||||
},
|
||||
{
|
||||
"name": "orderValues",
|
||||
"type": "uint256[6][]"
|
||||
},
|
||||
{
|
||||
"name": "fillTakerTokenAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "shouldThrowOnInsufficientBalanceOrAllowance",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "v",
|
||||
"type": "uint8[]"
|
||||
},
|
||||
{
|
||||
"name": "r",
|
||||
"type": "bytes32[]"
|
||||
},
|
||||
{
|
||||
"name": "s",
|
||||
"type": "bytes32[]"
|
||||
}
|
||||
],
|
||||
"name": "fillOrdersUpTo",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "orderAddresses",
|
||||
"type": "address[5]"
|
||||
},
|
||||
{
|
||||
"name": "orderValues",
|
||||
"type": "uint256[6]"
|
||||
},
|
||||
{
|
||||
"name": "cancelTakerTokenAmount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "cancelOrder",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "ZRX_TOKEN_CONTRACT",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "orderAddresses",
|
||||
"type": "address[5][]"
|
||||
},
|
||||
{
|
||||
"name": "orderValues",
|
||||
"type": "uint256[6][]"
|
||||
},
|
||||
{
|
||||
"name": "fillTakerTokenAmounts",
|
||||
"type": "uint256[]"
|
||||
},
|
||||
{
|
||||
"name": "v",
|
||||
"type": "uint8[]"
|
||||
},
|
||||
{
|
||||
"name": "r",
|
||||
"type": "bytes32[]"
|
||||
},
|
||||
{
|
||||
"name": "s",
|
||||
"type": "bytes32[]"
|
||||
}
|
||||
],
|
||||
"name": "batchFillOrKillOrders",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "orderAddresses",
|
||||
"type": "address[5]"
|
||||
},
|
||||
{
|
||||
"name": "orderValues",
|
||||
"type": "uint256[6]"
|
||||
},
|
||||
{
|
||||
"name": "fillTakerTokenAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "v",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"name": "r",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "fillOrKillOrder",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "orderHash",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "getUnavailableTakerTokenAmount",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "signer",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "hash",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"name": "v",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"name": "r",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "isValidSignature",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "numerator",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "denominator",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "target",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getPartialAmount",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "TOKEN_TRANSFER_PROXY_CONTRACT",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "orderAddresses",
|
||||
"type": "address[5][]"
|
||||
},
|
||||
{
|
||||
"name": "orderValues",
|
||||
"type": "uint256[6][]"
|
||||
},
|
||||
{
|
||||
"name": "fillTakerTokenAmounts",
|
||||
"type": "uint256[]"
|
||||
},
|
||||
{
|
||||
"name": "shouldThrowOnInsufficientBalanceOrAllowance",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "v",
|
||||
"type": "uint8[]"
|
||||
},
|
||||
{
|
||||
"name": "r",
|
||||
"type": "bytes32[]"
|
||||
},
|
||||
{
|
||||
"name": "s",
|
||||
"type": "bytes32[]"
|
||||
}
|
||||
],
|
||||
"name": "batchFillOrders",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "orderAddresses",
|
||||
"type": "address[5][]"
|
||||
},
|
||||
{
|
||||
"name": "orderValues",
|
||||
"type": "uint256[6][]"
|
||||
},
|
||||
{
|
||||
"name": "cancelTakerTokenAmounts",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"name": "batchCancelOrders",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "orderAddresses",
|
||||
"type": "address[5]"
|
||||
},
|
||||
{
|
||||
"name": "orderValues",
|
||||
"type": "uint256[6]"
|
||||
},
|
||||
{
|
||||
"name": "fillTakerTokenAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "shouldThrowOnInsufficientBalanceOrAllowance",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "v",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"name": "r",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "fillOrder",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "filledTakerTokenAmount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "orderAddresses",
|
||||
"type": "address[5]"
|
||||
},
|
||||
{
|
||||
"name": "orderValues",
|
||||
"type": "uint256[6]"
|
||||
}
|
||||
],
|
||||
"name": "getOrderHash",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "EXTERNAL_QUERY_GAS_LIMIT",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint16"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "VERSION",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_zrxToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_tokenTransferProxy",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "maker",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "taker",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "feeRecipient",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "makerToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "takerToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "filledMakerTokenAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "filledTakerTokenAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "paidMakerFee",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "paidTakerFee",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "tokens",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "orderHash",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "LogFill",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "maker",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "feeRecipient",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "makerToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "takerToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "cancelledMakerTokenAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "cancelledTakerTokenAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "tokens",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "orderHash",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "LogCancel",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "errorId",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "orderHash",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "LogError",
|
||||
"type": "event"
|
||||
}
|
||||
],
|
||||
"networks": {
|
||||
"1": {
|
||||
"address": "0x12459c951127e0c374ff9105dda097662a027093"
|
||||
},
|
||||
"3": {
|
||||
"address": "0x479cc461fecd078f766ecc58533d6f69580cf3ac"
|
||||
},
|
||||
"4": {
|
||||
"address": "0x1d16ef40fac01cec8adac2ac49427b9384192c05"
|
||||
},
|
||||
"42": {
|
||||
"address": "0x90fe2af704b34e0224bf2299c838e04d4dcf1364"
|
||||
},
|
||||
"50": {
|
||||
"address": "0x48bacb9266a570d521063ef5dd96e61686dbe788"
|
||||
}
|
||||
}
|
||||
}
|
||||
172
packages/0x.js/src/artifacts/Token.json
Normal file
172
packages/0x.js/src/artifacts/Token.json
Normal file
@@ -0,0 +1,172 @@
|
||||
{
|
||||
"contract_name": "Token",
|
||||
"abi": [
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "supply",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "remaining",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
||||
}
|
||||
547
packages/0x.js/src/artifacts/TokenRegistry.json
Normal file
547
packages/0x.js/src/artifacts/TokenRegistry.json
Normal file
@@ -0,0 +1,547 @@
|
||||
{
|
||||
"contract_name": "TokenRegistry",
|
||||
"abi": [
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_index",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "removeToken",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_name",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"name": "getTokenAddressByName",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_symbol",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"name": "getTokenAddressBySymbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_swarmHash",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "setTokenSwarmHash",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_token",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "getTokenMetaData",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "_symbol",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "_decimals",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"name": "_ipfsHash",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"name": "_swarmHash",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "addToken",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_name",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"name": "setTokenName",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "tokens",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "symbol",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "decimals",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"name": "ipfsHash",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"name": "swarmHash",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "tokenAddresses",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_name",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"name": "getTokenByName",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "getTokenAddresses",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address[]"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_ipfsHash",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "setTokenIpfsHash",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_symbol",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"name": "getTokenBySymbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_symbol",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"name": "setTokenSymbol",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "symbol",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "decimals",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "ipfsHash",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "swarmHash",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "LogAddToken",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "symbol",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "decimals",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "ipfsHash",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "swarmHash",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "LogRemoveToken",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldName",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newName",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"name": "LogTokenNameChange",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldSymbol",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newSymbol",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"name": "LogTokenSymbolChange",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldIpfsHash",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newIpfsHash",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "LogTokenIpfsHashChange",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldSwarmHash",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newSwarmHash",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "LogTokenSwarmHashChange",
|
||||
"type": "event"
|
||||
}
|
||||
],
|
||||
"networks": {
|
||||
"1": {
|
||||
"address": "0x926a74c5c36adf004c87399e65f75628b0f98d2c"
|
||||
},
|
||||
"3": {
|
||||
"address": "0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed"
|
||||
},
|
||||
"4": {
|
||||
"address": "0x4e9aad8184de8833365fea970cd9149372fdf1e6"
|
||||
},
|
||||
"42": {
|
||||
"address": "0xf18e504561f4347bea557f3d4558f559dddbae7f"
|
||||
},
|
||||
"50": {
|
||||
"address": "0x0b1ba0af832d7c05fd64161e0db78e85978e8082"
|
||||
}
|
||||
}
|
||||
}
|
||||
187
packages/0x.js/src/artifacts/TokenTransferProxy.json
Normal file
187
packages/0x.js/src/artifacts/TokenTransferProxy.json
Normal file
@@ -0,0 +1,187 @@
|
||||
{
|
||||
"contract_name": "TokenTransferProxy",
|
||||
"abi": [
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "addAuthorizedAddress",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "authorities",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "removeAuthorizedAddress",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "authorized",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "getAuthorizedAddresses",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address[]"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "caller",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "LogAuthorizedAddressAdded",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "caller",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "LogAuthorizedAddressRemoved",
|
||||
"type": "event"
|
||||
}
|
||||
],
|
||||
"networks": {
|
||||
"1": {
|
||||
"address": "0x8da0d80f5007ef1e431dd2127178d224e32c2ef4"
|
||||
},
|
||||
"3": {
|
||||
"address": "0x4e9aad8184de8833365fea970cd9149372fdf1e6"
|
||||
},
|
||||
"4": {
|
||||
"address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d"
|
||||
},
|
||||
"42": {
|
||||
"address": "0x087eed4bc1ee3de49befbd66c662b434b15d49d4"
|
||||
},
|
||||
"50": {
|
||||
"address": "0x1dc4c1cefef38a777b15aa20260a54e584b16c48"
|
||||
}
|
||||
}
|
||||
}
|
||||
20
packages/0x.js/src/artifacts/ZRX.json
Normal file
20
packages/0x.js/src/artifacts/ZRX.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"contract_name": "ZRX",
|
||||
"networks": {
|
||||
"1": {
|
||||
"address": "0xe41d2489571d322189246dafa5ebde1f4699f498"
|
||||
},
|
||||
"3": {
|
||||
"address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d"
|
||||
},
|
||||
"4": {
|
||||
"address": "0x00f58d6d585f84b2d7267940cede30ce2fe6eae8"
|
||||
},
|
||||
"42": {
|
||||
"address": "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570"
|
||||
},
|
||||
"50": {
|
||||
"address": "0x1d7022f5b17d2f8b695918fb48fa1089c9f85401"
|
||||
}
|
||||
}
|
||||
}
|
||||
205
packages/0x.js/src/contract_wrappers/contract_wrapper.ts
Normal file
205
packages/0x.js/src/contract_wrappers/contract_wrapper.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { LogWithDecodedArgs, RawLog } from '@0xproject/types';
|
||||
import { AbiDecoder, intervalUtils } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import { Block, BlockAndLogStreamer } from 'ethereumjs-blockstream';
|
||||
import * as _ from 'lodash';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
import {
|
||||
Artifact,
|
||||
BlockParamLiteral,
|
||||
BlockRange,
|
||||
ContractEventArgs,
|
||||
ContractEvents,
|
||||
EventCallback,
|
||||
IndexedFilterValues,
|
||||
InternalZeroExError,
|
||||
ZeroExError,
|
||||
} from '../types';
|
||||
import { constants } from '../utils/constants';
|
||||
import { filterUtils } from '../utils/filter_utils';
|
||||
|
||||
const CONTRACT_NAME_TO_NOT_FOUND_ERROR: {
|
||||
[contractName: string]: ZeroExError;
|
||||
} = {
|
||||
ZRX: ZeroExError.ZRXContractDoesNotExist,
|
||||
EtherToken: ZeroExError.EtherTokenContractDoesNotExist,
|
||||
Token: ZeroExError.TokenContractDoesNotExist,
|
||||
TokenRegistry: ZeroExError.TokenRegistryContractDoesNotExist,
|
||||
TokenTransferProxy: ZeroExError.TokenTransferProxyContractDoesNotExist,
|
||||
Exchange: ZeroExError.ExchangeContractDoesNotExist,
|
||||
};
|
||||
|
||||
export class ContractWrapper {
|
||||
protected _web3Wrapper: Web3Wrapper;
|
||||
private _networkId: number;
|
||||
private _abiDecoder?: AbiDecoder;
|
||||
private _blockAndLogStreamerIfExists: BlockAndLogStreamer | undefined;
|
||||
private _blockAndLogStreamInterval: NodeJS.Timer;
|
||||
private _filters: { [filterToken: string]: Web3.FilterObject };
|
||||
private _filterCallbacks: {
|
||||
[filterToken: string]: EventCallback<ContractEventArgs>;
|
||||
};
|
||||
private _onLogAddedSubscriptionToken: string | undefined;
|
||||
private _onLogRemovedSubscriptionToken: string | undefined;
|
||||
constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder?: AbiDecoder) {
|
||||
this._web3Wrapper = web3Wrapper;
|
||||
this._networkId = networkId;
|
||||
this._abiDecoder = abiDecoder;
|
||||
this._filters = {};
|
||||
this._filterCallbacks = {};
|
||||
this._blockAndLogStreamerIfExists = undefined;
|
||||
this._onLogAddedSubscriptionToken = undefined;
|
||||
this._onLogRemovedSubscriptionToken = undefined;
|
||||
}
|
||||
protected _unsubscribeAll(): void {
|
||||
const filterTokens = _.keys(this._filterCallbacks);
|
||||
_.each(filterTokens, filterToken => {
|
||||
this._unsubscribe(filterToken);
|
||||
});
|
||||
}
|
||||
protected _unsubscribe(filterToken: string, err?: Error): void {
|
||||
if (_.isUndefined(this._filters[filterToken])) {
|
||||
throw new Error(ZeroExError.SubscriptionNotFound);
|
||||
}
|
||||
if (!_.isUndefined(err)) {
|
||||
const callback = this._filterCallbacks[filterToken];
|
||||
callback(err, undefined);
|
||||
}
|
||||
delete this._filters[filterToken];
|
||||
delete this._filterCallbacks[filterToken];
|
||||
if (_.isEmpty(this._filters)) {
|
||||
this._stopBlockAndLogStream();
|
||||
}
|
||||
}
|
||||
protected _subscribe<ArgsType extends ContractEventArgs>(
|
||||
address: string,
|
||||
eventName: ContractEvents,
|
||||
indexFilterValues: IndexedFilterValues,
|
||||
abi: Web3.ContractAbi,
|
||||
callback: EventCallback<ArgsType>,
|
||||
): string {
|
||||
const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi);
|
||||
if (_.isUndefined(this._blockAndLogStreamerIfExists)) {
|
||||
this._startBlockAndLogStream();
|
||||
}
|
||||
const filterToken = filterUtils.generateUUID();
|
||||
this._filters[filterToken] = filter;
|
||||
this._filterCallbacks[filterToken] = callback as EventCallback<ContractEventArgs>;
|
||||
return filterToken;
|
||||
}
|
||||
protected async _getLogsAsync<ArgsType extends ContractEventArgs>(
|
||||
address: string,
|
||||
eventName: ContractEvents,
|
||||
blockRange: BlockRange,
|
||||
indexFilterValues: IndexedFilterValues,
|
||||
abi: Web3.ContractAbi,
|
||||
): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
|
||||
const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi, blockRange);
|
||||
const logs = await this._web3Wrapper.getLogsAsync(filter);
|
||||
const logsWithDecodedArguments = _.map(logs, this._tryToDecodeLogOrNoop.bind(this));
|
||||
return logsWithDecodedArguments;
|
||||
}
|
||||
protected _tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>(
|
||||
log: Web3.LogEntry,
|
||||
): LogWithDecodedArgs<ArgsType> | RawLog {
|
||||
if (_.isUndefined(this._abiDecoder)) {
|
||||
throw new Error(InternalZeroExError.NoAbiDecoder);
|
||||
}
|
||||
const logWithDecodedArgs = this._abiDecoder.tryToDecodeLogOrNoop(log);
|
||||
return logWithDecodedArgs;
|
||||
}
|
||||
protected async _instantiateContractIfExistsAsync(
|
||||
artifact: Artifact,
|
||||
addressIfExists?: string,
|
||||
): Promise<Web3.ContractInstance> {
|
||||
let contractAddress: string;
|
||||
if (_.isUndefined(addressIfExists)) {
|
||||
if (_.isUndefined(artifact.networks[this._networkId])) {
|
||||
throw new Error(ZeroExError.ContractNotDeployedOnNetwork);
|
||||
}
|
||||
contractAddress = artifact.networks[this._networkId].address.toLowerCase();
|
||||
} else {
|
||||
contractAddress = addressIfExists;
|
||||
}
|
||||
const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(contractAddress);
|
||||
if (!doesContractExist) {
|
||||
throw new Error(CONTRACT_NAME_TO_NOT_FOUND_ERROR[artifact.contract_name]);
|
||||
}
|
||||
const contractInstance = this._web3Wrapper.getContractInstance(artifact.abi, contractAddress);
|
||||
return contractInstance;
|
||||
}
|
||||
protected _getContractAddress(artifact: Artifact, addressIfExists?: string): string {
|
||||
if (_.isUndefined(addressIfExists)) {
|
||||
const contractAddress = artifact.networks[this._networkId].address;
|
||||
if (_.isUndefined(contractAddress)) {
|
||||
throw new Error(ZeroExError.ExchangeContractDoesNotExist);
|
||||
}
|
||||
return contractAddress;
|
||||
} else {
|
||||
return addressIfExists;
|
||||
}
|
||||
}
|
||||
private _onLogStateChanged<ArgsType extends ContractEventArgs>(isRemoved: boolean, log: Web3.LogEntry): void {
|
||||
_.forEach(this._filters, (filter: Web3.FilterObject, filterToken: string) => {
|
||||
if (filterUtils.matchesFilter(log, filter)) {
|
||||
const decodedLog = this._tryToDecodeLogOrNoop(log) as LogWithDecodedArgs<ArgsType>;
|
||||
const logEvent = {
|
||||
log: decodedLog,
|
||||
isRemoved,
|
||||
};
|
||||
this._filterCallbacks[filterToken](null, logEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
private _startBlockAndLogStream(): void {
|
||||
if (!_.isUndefined(this._blockAndLogStreamerIfExists)) {
|
||||
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
|
||||
}
|
||||
this._blockAndLogStreamerIfExists = new BlockAndLogStreamer(
|
||||
this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper),
|
||||
this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper),
|
||||
);
|
||||
const catchAllLogFilter = {};
|
||||
this._blockAndLogStreamerIfExists.addLogFilter(catchAllLogFilter);
|
||||
this._blockAndLogStreamInterval = intervalUtils.setAsyncExcludingInterval(
|
||||
this._reconcileBlockAsync.bind(this),
|
||||
constants.DEFAULT_BLOCK_POLLING_INTERVAL,
|
||||
this._onReconcileBlockError.bind(this),
|
||||
);
|
||||
let isRemoved = false;
|
||||
this._onLogAddedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogAdded(
|
||||
this._onLogStateChanged.bind(this, isRemoved),
|
||||
);
|
||||
isRemoved = true;
|
||||
this._onLogRemovedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogRemoved(
|
||||
this._onLogStateChanged.bind(this, isRemoved),
|
||||
);
|
||||
}
|
||||
private _onReconcileBlockError(err: Error): void {
|
||||
const filterTokens = _.keys(this._filterCallbacks);
|
||||
_.each(filterTokens, filterToken => {
|
||||
this._unsubscribe(filterToken, err);
|
||||
});
|
||||
}
|
||||
private _setNetworkId(networkId: number): void {
|
||||
this._networkId = networkId;
|
||||
}
|
||||
private _stopBlockAndLogStream(): void {
|
||||
if (_.isUndefined(this._blockAndLogStreamerIfExists)) {
|
||||
throw new Error(ZeroExError.SubscriptionNotFound);
|
||||
}
|
||||
this._blockAndLogStreamerIfExists.unsubscribeFromOnLogAdded(this._onLogAddedSubscriptionToken as string);
|
||||
this._blockAndLogStreamerIfExists.unsubscribeFromOnLogRemoved(this._onLogRemovedSubscriptionToken as string);
|
||||
intervalUtils.clearAsyncExcludingInterval(this._blockAndLogStreamInterval);
|
||||
delete this._blockAndLogStreamerIfExists;
|
||||
}
|
||||
private async _reconcileBlockAsync(): Promise<void> {
|
||||
const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest);
|
||||
// We need to coerce to Block type cause Web3.Block includes types for mempool blocks
|
||||
if (!_.isUndefined(this._blockAndLogStreamerIfExists)) {
|
||||
// If we clear the interval while fetching the block - this._blockAndLogStreamer will be undefined
|
||||
await this._blockAndLogStreamerIfExists.reconcileNewBlock((latestBlock as any) as Block);
|
||||
}
|
||||
}
|
||||
}
|
||||
183
packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts
Normal file
183
packages/0x.js/src/contract_wrappers/ether_token_wrapper.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { schemas } from '@0xproject/json-schemas';
|
||||
import { LogWithDecodedArgs } from '@0xproject/types';
|
||||
import { AbiDecoder, BigNumber } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts } from '../artifacts';
|
||||
import {
|
||||
BlockRange,
|
||||
EtherTokenContractEventArgs,
|
||||
EtherTokenEvents,
|
||||
EventCallback,
|
||||
IndexedFilterValues,
|
||||
TransactionOpts,
|
||||
ZeroExError,
|
||||
} from '../types';
|
||||
import { assert } from '../utils/assert';
|
||||
|
||||
import { ContractWrapper } from './contract_wrapper';
|
||||
import { EtherTokenContract } from './generated/ether_token';
|
||||
import { TokenWrapper } from './token_wrapper';
|
||||
|
||||
/**
|
||||
* 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 _etherTokenContractsByAddress: {
|
||||
[address: string]: EtherTokenContract;
|
||||
} = {};
|
||||
private _tokenWrapper: TokenWrapper;
|
||||
constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder: AbiDecoder, tokenWrapper: TokenWrapper) {
|
||||
super(web3Wrapper, networkId, abiDecoder);
|
||||
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 etherTokenAddress EtherToken address you wish to deposit into.
|
||||
* @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.
|
||||
* @param txOpts Transaction parameters.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async depositAsync(
|
||||
etherTokenAddress: string,
|
||||
amountInWei: BigNumber,
|
||||
depositor: string,
|
||||
txOpts: TransactionOpts = {},
|
||||
): Promise<string> {
|
||||
assert.isValidBaseUnitAmount('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(etherTokenAddress);
|
||||
const txHash = await wethContract.deposit.sendTransactionAsync({
|
||||
from: depositor,
|
||||
value: amountInWei,
|
||||
gas: txOpts.gasLimit,
|
||||
gasPrice: txOpts.gasPrice,
|
||||
});
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Withdraw ETH to the withdrawer's address from the wrapped ETH smart contract in exchange for the
|
||||
* equivalent number of wrapped ETH tokens.
|
||||
* @param etherTokenAddress EtherToken address you wish to withdraw from.
|
||||
* @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.
|
||||
* @param txOpts Transaction parameters.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async withdrawAsync(
|
||||
etherTokenAddress: string,
|
||||
amountInWei: BigNumber,
|
||||
withdrawer: string,
|
||||
txOpts: TransactionOpts = {},
|
||||
): Promise<string> {
|
||||
assert.isValidBaseUnitAmount('amountInWei', amountInWei);
|
||||
await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper);
|
||||
|
||||
const WETHBalanceInBaseUnits = await this._tokenWrapper.getBalanceAsync(etherTokenAddress, withdrawer);
|
||||
assert.assert(WETHBalanceInBaseUnits.gte(amountInWei), ZeroExError.InsufficientWEthBalanceForWithdrawal);
|
||||
|
||||
const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress);
|
||||
const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, {
|
||||
from: withdrawer,
|
||||
gas: txOpts.gasLimit,
|
||||
gasPrice: txOpts.gasPrice,
|
||||
});
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Gets historical logs without creating a subscription
|
||||
* @param etherTokenAddress An address of the ether token that emmited the logs.
|
||||
* @param eventName The ether token contract event you would like to subscribe to.
|
||||
* @param blockRange Block range to get logs from.
|
||||
* @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 `{_owner: aUserAddressHex}`
|
||||
* @return Array of logs that match the parameters
|
||||
*/
|
||||
public async getLogsAsync<ArgsType extends EtherTokenContractEventArgs>(
|
||||
etherTokenAddress: string,
|
||||
eventName: EtherTokenEvents,
|
||||
blockRange: BlockRange,
|
||||
indexFilterValues: IndexedFilterValues,
|
||||
): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
|
||||
assert.isETHAddressHex('etherTokenAddress', etherTokenAddress);
|
||||
assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents);
|
||||
assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema);
|
||||
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
|
||||
const logs = await this._getLogsAsync<ArgsType>(
|
||||
etherTokenAddress,
|
||||
eventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
artifacts.EtherTokenArtifact.abi,
|
||||
);
|
||||
return logs;
|
||||
}
|
||||
/**
|
||||
* Subscribe to an event type emitted by the Token contract.
|
||||
* @param etherTokenAddress The hex encoded address where the ether token is deployed.
|
||||
* @param eventName The ether token contract event you would like to subscribe to.
|
||||
* @param indexFilterValues An object where the keys are indexed args returned by the event and
|
||||
* the value is the value you are interested in. E.g `{_owner: aUserAddressHex}`
|
||||
* @param callback Callback that gets called when a log is added/removed
|
||||
* @return Subscription token used later to unsubscribe
|
||||
*/
|
||||
public subscribe<ArgsType extends EtherTokenContractEventArgs>(
|
||||
etherTokenAddress: string,
|
||||
eventName: EtherTokenEvents,
|
||||
indexFilterValues: IndexedFilterValues,
|
||||
callback: EventCallback<ArgsType>,
|
||||
): string {
|
||||
assert.isETHAddressHex('etherTokenAddress', etherTokenAddress);
|
||||
assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents);
|
||||
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
|
||||
assert.isFunction('callback', callback);
|
||||
const subscriptionToken = this._subscribe<ArgsType>(
|
||||
etherTokenAddress,
|
||||
eventName,
|
||||
indexFilterValues,
|
||||
artifacts.EtherTokenArtifact.abi,
|
||||
callback,
|
||||
);
|
||||
return subscriptionToken;
|
||||
}
|
||||
/**
|
||||
* Cancel a subscription
|
||||
* @param subscriptionToken Subscription token returned by `subscribe()`
|
||||
*/
|
||||
public unsubscribe(subscriptionToken: string): void {
|
||||
this._unsubscribe(subscriptionToken);
|
||||
}
|
||||
/**
|
||||
* Cancels all existing subscriptions
|
||||
*/
|
||||
public _unsubscribeAll(): void {
|
||||
super._unsubscribeAll();
|
||||
}
|
||||
private _invalidateContractInstance(): void {
|
||||
this._unsubscribeAll();
|
||||
this._etherTokenContractsByAddress = {};
|
||||
}
|
||||
private async _getEtherTokenContractAsync(etherTokenAddress: string): Promise<EtherTokenContract> {
|
||||
let etherTokenContract = this._etherTokenContractsByAddress[etherTokenAddress];
|
||||
if (!_.isUndefined(etherTokenContract)) {
|
||||
return etherTokenContract;
|
||||
}
|
||||
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
|
||||
artifacts.EtherTokenArtifact,
|
||||
etherTokenAddress,
|
||||
);
|
||||
const contractInstance = new EtherTokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults());
|
||||
etherTokenContract = contractInstance;
|
||||
this._etherTokenContractsByAddress[etherTokenAddress] = etherTokenContract;
|
||||
return etherTokenContract;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
6
packages/0x.js/src/contract_wrappers/generated/.gitignore
vendored
Normal file
6
packages/0x.js/src/contract_wrappers/generated/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
dummy_token.ts
|
||||
ether_token.ts
|
||||
exchange.ts
|
||||
token_registry.ts
|
||||
token_transfer_proxy.ts
|
||||
token.ts
|
||||
@@ -0,0 +1,33 @@
|
||||
import {TxData, TxDataPayable} from '@0xproject/types';
|
||||
import * as _ from 'lodash';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
export class BaseContract {
|
||||
protected _web3ContractInstance: Web3.ContractInstance;
|
||||
protected _defaults: Partial<TxData>;
|
||||
protected async _applyDefaultsToTxDataAsync<T extends TxData|TxDataPayable>(
|
||||
txData: T,
|
||||
estimateGasAsync?: (txData: T) => Promise<number>,
|
||||
): Promise<TxData> {
|
||||
// Gas amount sourced with the following priorities:
|
||||
// 1. Optional param passed in to public method call
|
||||
// 2. Global config passed in at library instantiation
|
||||
// 3. Gas estimate calculation + safety margin
|
||||
const removeUndefinedProperties = _.pickBy;
|
||||
const txDataWithDefaults = {
|
||||
...removeUndefinedProperties(this._defaults),
|
||||
...removeUndefinedProperties(txData as any),
|
||||
// HACK: TS can't prove that T is spreadable.
|
||||
// Awaiting https://github.com/Microsoft/TypeScript/pull/13288 to be merged
|
||||
};
|
||||
if (_.isUndefined(txDataWithDefaults.gas) && !_.isUndefined(estimateGasAsync)) {
|
||||
const estimatedGas = await estimateGasAsync(txData);
|
||||
txDataWithDefaults.gas = estimatedGas;
|
||||
}
|
||||
return txDataWithDefaults;
|
||||
}
|
||||
constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<TxData>) {
|
||||
this._web3ContractInstance = web3ContractInstance;
|
||||
this._defaults = defaults;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {assert} from '../utils/assert';
|
||||
import {Token, TokenRegistryContract, TokenMetadata} from '../types';
|
||||
import {constants} from '../utils/constants';
|
||||
import {ContractWrapper} from './contract_wrapper';
|
||||
import {artifacts} from '../artifacts';
|
||||
|
||||
import { artifacts } from '../artifacts';
|
||||
import { Token, TokenMetadata } from '../types';
|
||||
import { assert } from '../utils/assert';
|
||||
import { constants } from '../utils/constants';
|
||||
|
||||
import { ContractWrapper } from './contract_wrapper';
|
||||
import { TokenRegistryContract } from './generated/token_registry';
|
||||
|
||||
/**
|
||||
* This class includes all the functionality related to interacting with the 0x Token Registry smart contract.
|
||||
@@ -12,8 +15,20 @@ import {artifacts} from '../artifacts';
|
||||
export class TokenRegistryWrapper extends ContractWrapper {
|
||||
private _tokenRegistryContractIfExists?: TokenRegistryContract;
|
||||
private _contractAddressIfExists?: string;
|
||||
constructor(web3Wrapper: Web3Wrapper, contractAddressIfExists?: string) {
|
||||
super(web3Wrapper);
|
||||
private static _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;
|
||||
}
|
||||
constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) {
|
||||
super(web3Wrapper, networkId);
|
||||
this._contractAddressIfExists = contractAddressIfExists;
|
||||
}
|
||||
/**
|
||||
@@ -21,12 +36,9 @@ export class TokenRegistryWrapper extends ContractWrapper {
|
||||
* @return An array of objects that conform to the Token interface.
|
||||
*/
|
||||
public async getTokensAsync(): Promise<Token[]> {
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
|
||||
const addresses = await this.getTokenAddressesAsync();
|
||||
const tokenPromises: Array<Promise<Token|undefined>> = _.map(
|
||||
addresses,
|
||||
(address: string) => (this.getTokenIfExistsAsync(address)),
|
||||
const tokenPromises: Array<Promise<Token | undefined>> = _.map(addresses, async (address: string) =>
|
||||
this.getTokenIfExistsAsync(address),
|
||||
);
|
||||
const tokens = await Promise.all(tokenPromises);
|
||||
return tokens as Token[];
|
||||
@@ -44,15 +56,15 @@ export class TokenRegistryWrapper extends ContractWrapper {
|
||||
* 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> {
|
||||
public async getTokenIfExistsAsync(address: string): Promise<Token | undefined> {
|
||||
assert.isETHAddressHex('address', address);
|
||||
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
const metadata = await tokenRegistryContract.getTokenMetaData.callAsync(address);
|
||||
const token = this._createTokenFromMetadata(metadata);
|
||||
const token = TokenRegistryWrapper._createTokenFromMetadata(metadata);
|
||||
return token;
|
||||
}
|
||||
public async getTokenAddressBySymbolIfExistsAsync(symbol: string): Promise<string|undefined> {
|
||||
public async getTokenAddressBySymbolIfExistsAsync(symbol: string): Promise<string | undefined> {
|
||||
assert.isString('symbol', symbol);
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
const addressIfExists = await tokenRegistryContract.getTokenAddressBySymbol.callAsync(symbol);
|
||||
@@ -61,7 +73,7 @@ export class TokenRegistryWrapper extends ContractWrapper {
|
||||
}
|
||||
return addressIfExists;
|
||||
}
|
||||
public async getTokenAddressByNameIfExistsAsync(name: string): Promise<string|undefined> {
|
||||
public async getTokenAddressByNameIfExistsAsync(name: string): Promise<string | undefined> {
|
||||
assert.isString('name', name);
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
const addressIfExists = await tokenRegistryContract.getTokenAddressByName.callAsync(name);
|
||||
@@ -70,18 +82,18 @@ export class TokenRegistryWrapper extends ContractWrapper {
|
||||
}
|
||||
return addressIfExists;
|
||||
}
|
||||
public async getTokenBySymbolIfExistsAsync(symbol: string): Promise<Token|undefined> {
|
||||
public async getTokenBySymbolIfExistsAsync(symbol: string): Promise<Token | undefined> {
|
||||
assert.isString('symbol', symbol);
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
const metadata = await tokenRegistryContract.getTokenBySymbol.callAsync(symbol);
|
||||
const token = this._createTokenFromMetadata(metadata);
|
||||
const token = TokenRegistryWrapper._createTokenFromMetadata(metadata);
|
||||
return token;
|
||||
}
|
||||
public async getTokenByNameIfExistsAsync(name: string): Promise<Token|undefined> {
|
||||
public async getTokenByNameIfExistsAsync(name: string): Promise<Token | undefined> {
|
||||
assert.isString('name', name);
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
const metadata = await tokenRegistryContract.getTokenByName.callAsync(name);
|
||||
const token = this._createTokenFromMetadata(metadata);
|
||||
const token = TokenRegistryWrapper._createTokenFromMetadata(metadata);
|
||||
return token;
|
||||
}
|
||||
/**
|
||||
@@ -89,22 +101,12 @@ export class TokenRegistryWrapper extends ContractWrapper {
|
||||
* that the user-passed web3 provider is connected to.
|
||||
* @returns The Ethereum address of the TokenRegistry contract being used.
|
||||
*/
|
||||
public async getContractAddressAsync(): Promise<string> {
|
||||
const tokenRegistryInstance = await this._getTokenRegistryContractAsync();
|
||||
const tokenRegistryAddress = tokenRegistryInstance.address;
|
||||
return tokenRegistryAddress;
|
||||
}
|
||||
private _createTokenFromMetadata(metadata: TokenMetadata): Token|undefined {
|
||||
if (metadata[0] === constants.NULL_ADDRESS) {
|
||||
return undefined;
|
||||
}
|
||||
const token = {
|
||||
address: metadata[0],
|
||||
name: metadata[1],
|
||||
symbol: metadata[2],
|
||||
decimals: metadata[3].toNumber(),
|
||||
};
|
||||
return token;
|
||||
public getContractAddress(): string {
|
||||
const contractAddress = this._getContractAddress(
|
||||
artifacts.TokenRegistryArtifact,
|
||||
this._contractAddressIfExists,
|
||||
);
|
||||
return contractAddress;
|
||||
}
|
||||
private _invalidateContractInstance(): void {
|
||||
delete this._tokenRegistryContractIfExists;
|
||||
@@ -113,10 +115,15 @@ export class TokenRegistryWrapper extends ContractWrapper {
|
||||
if (!_.isUndefined(this._tokenRegistryContractIfExists)) {
|
||||
return this._tokenRegistryContractIfExists;
|
||||
}
|
||||
const contractInstance = await this._instantiateContractIfExistsAsync<TokenRegistryContract>(
|
||||
artifacts.TokenRegistryArtifact, this._contractAddressIfExists,
|
||||
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
|
||||
artifacts.TokenRegistryArtifact,
|
||||
this._contractAddressIfExists,
|
||||
);
|
||||
this._tokenRegistryContractIfExists = contractInstance as TokenRegistryContract;
|
||||
const contractInstance = new TokenRegistryContract(
|
||||
web3ContractInstance,
|
||||
this._web3Wrapper.getContractDefaults(),
|
||||
);
|
||||
this._tokenRegistryContractIfExists = contractInstance;
|
||||
return this._tokenRegistryContractIfExists;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {ContractWrapper} from './contract_wrapper';
|
||||
import {artifacts} from '../artifacts';
|
||||
import {TokenTransferProxyContract} from '../types';
|
||||
|
||||
import { artifacts } from '../artifacts';
|
||||
|
||||
import { ContractWrapper } from './contract_wrapper';
|
||||
import { TokenTransferProxyContract } from './generated/token_transfer_proxy';
|
||||
|
||||
/**
|
||||
* This class includes the functionality related to interacting with the TokenTransferProxy contract.
|
||||
*/
|
||||
export class TokenTransferProxyWrapper extends ContractWrapper {
|
||||
private _tokenTransferProxyContractIfExists?: TokenTransferProxyContract;
|
||||
private _tokenTransferProxyContractAddressFetcher: () => Promise<string>;
|
||||
constructor(web3Wrapper: Web3Wrapper, tokenTransferProxyContractAddressFetcher: () => Promise<string>) {
|
||||
super(web3Wrapper);
|
||||
this._tokenTransferProxyContractAddressFetcher = tokenTransferProxyContractAddressFetcher;
|
||||
private _contractAddressIfExists?: string;
|
||||
constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) {
|
||||
super(web3Wrapper, networkId);
|
||||
this._contractAddressIfExists = contractAddressIfExists;
|
||||
}
|
||||
/**
|
||||
* Check if the Exchange contract address is authorized by the TokenTransferProxy contract.
|
||||
@@ -38,10 +40,12 @@ export class TokenTransferProxyWrapper extends ContractWrapper {
|
||||
* 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;
|
||||
public getContractAddress(): string {
|
||||
const contractAddress = this._getContractAddress(
|
||||
artifacts.TokenTransferProxyArtifact,
|
||||
this._contractAddressIfExists,
|
||||
);
|
||||
return contractAddress;
|
||||
}
|
||||
private _invalidateContractInstance(): void {
|
||||
delete this._tokenTransferProxyContractIfExists;
|
||||
@@ -50,11 +54,15 @@ export class TokenTransferProxyWrapper extends ContractWrapper {
|
||||
if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) {
|
||||
return this._tokenTransferProxyContractIfExists;
|
||||
}
|
||||
const contractAddress = await this._tokenTransferProxyContractAddressFetcher();
|
||||
const contractInstance = await this._instantiateContractIfExistsAsync<TokenTransferProxyContract>(
|
||||
artifacts.TokenTransferProxyArtifact, contractAddress,
|
||||
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
|
||||
artifacts.TokenTransferProxyArtifact,
|
||||
this._contractAddressIfExists,
|
||||
);
|
||||
this._tokenTransferProxyContractIfExists = contractInstance as TokenTransferProxyContract;
|
||||
const contractInstance = new TokenTransferProxyContract(
|
||||
web3ContractInstance,
|
||||
this._web3Wrapper.getContractDefaults(),
|
||||
);
|
||||
this._tokenTransferProxyContractIfExists = contractInstance;
|
||||
return this._tokenTransferProxyContractIfExists;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,26 @@
|
||||
import { schemas } from '@0xproject/json-schemas';
|
||||
import { LogWithDecodedArgs } from '@0xproject/types';
|
||||
import { AbiDecoder, BigNumber } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import {schemas} from '0x-json-schemas';
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {assert} from '../utils/assert';
|
||||
import {constants} from '../utils/constants';
|
||||
import {ContractWrapper} from './contract_wrapper';
|
||||
import {AbiDecoder} from '../utils/abi_decoder';
|
||||
import {artifacts} from '../artifacts';
|
||||
import {
|
||||
TokenContract,
|
||||
ZeroExError,
|
||||
TokenEvents,
|
||||
IndexedFilterValues,
|
||||
SubscriptionOpts,
|
||||
MethodOpts,
|
||||
LogWithDecodedArgs,
|
||||
EventCallback,
|
||||
} from '../types';
|
||||
|
||||
const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 47155;
|
||||
import { artifacts } from '../artifacts';
|
||||
import {
|
||||
BlockRange,
|
||||
EventCallback,
|
||||
IndexedFilterValues,
|
||||
MethodOpts,
|
||||
TokenContractEventArgs,
|
||||
TokenEvents,
|
||||
TransactionOpts,
|
||||
ZeroExError,
|
||||
} from '../types';
|
||||
import { assert } from '../utils/assert';
|
||||
import { constants } from '../utils/constants';
|
||||
|
||||
import { ContractWrapper } from './contract_wrapper';
|
||||
import { TokenContract } from './generated/token';
|
||||
import { TokenTransferProxyWrapper } from './token_transfer_proxy_wrapper';
|
||||
|
||||
/**
|
||||
* This class includes all the functionality related to interacting with ERC20 token contracts.
|
||||
@@ -27,15 +29,17 @@ const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 47155;
|
||||
*/
|
||||
export class TokenWrapper extends ContractWrapper {
|
||||
public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
|
||||
private _tokenContractsByAddress: {[address: string]: TokenContract};
|
||||
private _activeSubscriptions: string[];
|
||||
private _tokenTransferProxyContractAddressFetcher: () => Promise<string>;
|
||||
constructor(web3Wrapper: Web3Wrapper, abiDecoder: AbiDecoder,
|
||||
tokenTransferProxyContractAddressFetcher: () => Promise<string>) {
|
||||
super(web3Wrapper, abiDecoder);
|
||||
private _tokenContractsByAddress: { [address: string]: TokenContract };
|
||||
private _tokenTransferProxyWrapper: TokenTransferProxyWrapper;
|
||||
constructor(
|
||||
web3Wrapper: Web3Wrapper,
|
||||
networkId: number,
|
||||
abiDecoder: AbiDecoder,
|
||||
tokenTransferProxyWrapper: TokenTransferProxyWrapper,
|
||||
) {
|
||||
super(web3Wrapper, networkId, abiDecoder);
|
||||
this._tokenContractsByAddress = {};
|
||||
this._activeSubscriptions = [];
|
||||
this._tokenTransferProxyContractAddressFetcher = tokenTransferProxyContractAddressFetcher;
|
||||
this._tokenTransferProxyWrapper = tokenTransferProxyWrapper;
|
||||
}
|
||||
/**
|
||||
* Retrieves an owner's ERC20 token balance.
|
||||
@@ -44,8 +48,11 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* @param methodOpts Optional arguments this method accepts.
|
||||
* @return The owner's ERC20 token balance in base units.
|
||||
*/
|
||||
public async getBalanceAsync(tokenAddress: string, ownerAddress: string,
|
||||
methodOpts?: MethodOpts): Promise<BigNumber.BigNumber> {
|
||||
public async getBalanceAsync(
|
||||
tokenAddress: string,
|
||||
ownerAddress: string,
|
||||
methodOpts?: MethodOpts,
|
||||
): Promise<BigNumber> {
|
||||
assert.isETHAddressHex('ownerAddress', ownerAddress);
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
|
||||
@@ -64,24 +71,26 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* for spenderAddress.
|
||||
* @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance.
|
||||
* @param amountInBaseUnits The allowance amount you would like to set.
|
||||
* @param txOpts Transaction parameters.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async setAllowanceAsync(tokenAddress: string, ownerAddress: string, spenderAddress: string,
|
||||
amountInBaseUnits: BigNumber.BigNumber): Promise<string> {
|
||||
public async setAllowanceAsync(
|
||||
tokenAddress: string,
|
||||
ownerAddress: string,
|
||||
spenderAddress: string,
|
||||
amountInBaseUnits: BigNumber,
|
||||
txOpts: TransactionOpts = {},
|
||||
): Promise<string> {
|
||||
await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper);
|
||||
assert.isETHAddressHex('spenderAddress', spenderAddress);
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
assert.isBigNumber('amountInBaseUnits', amountInBaseUnits);
|
||||
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
|
||||
|
||||
const tokenContract = await this._getTokenContractAsync(tokenAddress);
|
||||
// Hack: for some reason default estimated gas amount causes `base fee exceeds gas limit` exception
|
||||
// on testrpc. Probably related to https://github.com/ethereumjs/testrpc/issues/294
|
||||
// TODO: Debug issue in testrpc and submit a PR, then remove this hack
|
||||
const networkIdIfExists = await this._web3Wrapper.getNetworkIdIfExistsAsync();
|
||||
const gas = networkIdIfExists === constants.TESTRPC_NETWORK_ID ? ALLOWANCE_TO_ZERO_GAS_AMOUNT : undefined;
|
||||
const txHash = await tokenContract.approve.sendTransactionAsync(spenderAddress, amountInBaseUnits, {
|
||||
from: ownerAddress,
|
||||
gas,
|
||||
gas: txOpts.gasLimit,
|
||||
gasPrice: txOpts.gasPrice,
|
||||
});
|
||||
return txHash;
|
||||
}
|
||||
@@ -94,12 +103,21 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance
|
||||
* for spenderAddress.
|
||||
* @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance.
|
||||
* @param txOpts Transaction parameters.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async setUnlimitedAllowanceAsync(tokenAddress: string, ownerAddress: string,
|
||||
spenderAddress: string): Promise<string> {
|
||||
public async setUnlimitedAllowanceAsync(
|
||||
tokenAddress: string,
|
||||
ownerAddress: string,
|
||||
spenderAddress: string,
|
||||
txOpts: TransactionOpts = {},
|
||||
): Promise<string> {
|
||||
const txHash = await this.setAllowanceAsync(
|
||||
tokenAddress, ownerAddress, spenderAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
|
||||
tokenAddress,
|
||||
ownerAddress,
|
||||
spenderAddress,
|
||||
this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
|
||||
txOpts,
|
||||
);
|
||||
return txHash;
|
||||
}
|
||||
@@ -111,8 +129,12 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* @param spenderAddress The hex encoded user Ethereum address who can spend the allowance you are fetching.
|
||||
* @param methodOpts Optional arguments this method accepts.
|
||||
*/
|
||||
public async getAllowanceAsync(tokenAddress: string, ownerAddress: string,
|
||||
spenderAddress: string, methodOpts?: MethodOpts): Promise<BigNumber.BigNumber> {
|
||||
public async getAllowanceAsync(
|
||||
tokenAddress: string,
|
||||
ownerAddress: string,
|
||||
spenderAddress: string,
|
||||
methodOpts?: MethodOpts,
|
||||
): Promise<BigNumber> {
|
||||
assert.isETHAddressHex('ownerAddress', ownerAddress);
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
|
||||
@@ -129,12 +151,15 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* @param ownerAddress The hex encoded user Ethereum address whose proxy contract allowance we are retrieving.
|
||||
* @param methodOpts Optional arguments this method accepts.
|
||||
*/
|
||||
public async getProxyAllowanceAsync(tokenAddress: string, ownerAddress: string,
|
||||
methodOpts?: MethodOpts): Promise<BigNumber.BigNumber> {
|
||||
public async getProxyAllowanceAsync(
|
||||
tokenAddress: string,
|
||||
ownerAddress: string,
|
||||
methodOpts?: MethodOpts,
|
||||
): Promise<BigNumber> {
|
||||
assert.isETHAddressHex('ownerAddress', ownerAddress);
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
|
||||
const proxyAddress = await this._getTokenTransferProxyAddressAsync();
|
||||
const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress();
|
||||
const allowanceInBaseUnits = await this.getAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, methodOpts);
|
||||
return allowanceInBaseUnits;
|
||||
}
|
||||
@@ -145,16 +170,27 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* @param ownerAddress The hex encoded user Ethereum address who is setting an allowance
|
||||
* for the Proxy contract.
|
||||
* @param amountInBaseUnits The allowance amount specified in baseUnits.
|
||||
* @param txOpts Transaction parameters.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async setProxyAllowanceAsync(tokenAddress: string, ownerAddress: string,
|
||||
amountInBaseUnits: BigNumber.BigNumber): Promise<string> {
|
||||
public async setProxyAllowanceAsync(
|
||||
tokenAddress: string,
|
||||
ownerAddress: string,
|
||||
amountInBaseUnits: BigNumber,
|
||||
txOpts: TransactionOpts = {},
|
||||
): Promise<string> {
|
||||
assert.isETHAddressHex('ownerAddress', ownerAddress);
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
assert.isBigNumber('amountInBaseUnits', amountInBaseUnits);
|
||||
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
|
||||
|
||||
const proxyAddress = await this._getTokenTransferProxyAddressAsync();
|
||||
const txHash = await this.setAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, amountInBaseUnits);
|
||||
const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress();
|
||||
const txHash = await this.setAllowanceAsync(
|
||||
tokenAddress,
|
||||
ownerAddress,
|
||||
proxyAddress,
|
||||
amountInBaseUnits,
|
||||
txOpts,
|
||||
);
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
@@ -165,11 +201,19 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
|
||||
* @param ownerAddress The hex encoded user Ethereum address who is setting an allowance
|
||||
* for the Proxy contract.
|
||||
* @param txOpts Transaction parameters.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async setUnlimitedProxyAllowanceAsync(tokenAddress: string, ownerAddress: string): Promise<string> {
|
||||
public async setUnlimitedProxyAllowanceAsync(
|
||||
tokenAddress: string,
|
||||
ownerAddress: string,
|
||||
txOpts: TransactionOpts = {},
|
||||
): Promise<string> {
|
||||
const txHash = await this.setProxyAllowanceAsync(
|
||||
tokenAddress, ownerAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
|
||||
tokenAddress,
|
||||
ownerAddress,
|
||||
this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
|
||||
txOpts,
|
||||
);
|
||||
return txHash;
|
||||
}
|
||||
@@ -179,14 +223,20 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* @param fromAddress The hex encoded user Ethereum address that will send the funds.
|
||||
* @param toAddress The hex encoded user Ethereum address that will receive the funds.
|
||||
* @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer.
|
||||
* @param txOpts Transaction parameters.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async transferAsync(tokenAddress: string, fromAddress: string, toAddress: string,
|
||||
amountInBaseUnits: BigNumber.BigNumber): Promise<string> {
|
||||
public async transferAsync(
|
||||
tokenAddress: string,
|
||||
fromAddress: string,
|
||||
toAddress: string,
|
||||
amountInBaseUnits: BigNumber,
|
||||
txOpts: TransactionOpts = {},
|
||||
): Promise<string> {
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper);
|
||||
assert.isETHAddressHex('toAddress', toAddress);
|
||||
assert.isBigNumber('amountInBaseUnits', amountInBaseUnits);
|
||||
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
|
||||
|
||||
const tokenContract = await this._getTokenContractAsync(tokenAddress);
|
||||
|
||||
@@ -197,6 +247,8 @@ export class TokenWrapper extends ContractWrapper {
|
||||
|
||||
const txHash = await tokenContract.transfer.sendTransactionAsync(toAddress, amountInBaseUnits, {
|
||||
from: fromAddress,
|
||||
gas: txOpts.gasLimit,
|
||||
gasPrice: txOpts.gasPrice,
|
||||
});
|
||||
return txHash;
|
||||
}
|
||||
@@ -211,16 +263,22 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* `fromAddress` must have set an allowance to the `senderAddress`
|
||||
* before this call.
|
||||
* @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer.
|
||||
* @param txOpts Transaction parameters.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async transferFromAsync(tokenAddress: string, fromAddress: string, toAddress: string,
|
||||
senderAddress: string, amountInBaseUnits: BigNumber.BigNumber):
|
||||
Promise<string> {
|
||||
public async transferFromAsync(
|
||||
tokenAddress: string,
|
||||
fromAddress: string,
|
||||
toAddress: string,
|
||||
senderAddress: string,
|
||||
amountInBaseUnits: BigNumber,
|
||||
txOpts: TransactionOpts = {},
|
||||
): Promise<string> {
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
assert.isETHAddressHex('fromAddress', fromAddress);
|
||||
assert.isETHAddressHex('toAddress', toAddress);
|
||||
await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper);
|
||||
assert.isBigNumber('amountInBaseUnits', amountInBaseUnits);
|
||||
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
|
||||
|
||||
const tokenContract = await this._getTokenContractAsync(tokenAddress);
|
||||
|
||||
@@ -235,9 +293,13 @@ export class TokenWrapper extends ContractWrapper {
|
||||
}
|
||||
|
||||
const txHash = await tokenContract.transferFrom.sendTransactionAsync(
|
||||
fromAddress, toAddress, amountInBaseUnits,
|
||||
fromAddress,
|
||||
toAddress,
|
||||
amountInBaseUnits,
|
||||
{
|
||||
from: senderAddress,
|
||||
gas: txOpts.gasLimit,
|
||||
gasPrice: txOpts.gasPrice,
|
||||
},
|
||||
);
|
||||
return txHash;
|
||||
@@ -251,16 +313,23 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* @param callback Callback that gets called when a log is added/removed
|
||||
* @return Subscription token used later to unsubscribe
|
||||
*/
|
||||
public subscribe(tokenAddress: string, eventName: TokenEvents, indexFilterValues: IndexedFilterValues,
|
||||
callback: EventCallback): string {
|
||||
public subscribe<ArgsType extends TokenContractEventArgs>(
|
||||
tokenAddress: string,
|
||||
eventName: TokenEvents,
|
||||
indexFilterValues: IndexedFilterValues,
|
||||
callback: EventCallback<ArgsType>,
|
||||
): string {
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
assert.doesBelongToStringEnum('eventName', eventName, TokenEvents);
|
||||
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
|
||||
assert.isFunction('callback', callback);
|
||||
const subscriptionToken = this._subscribe(
|
||||
tokenAddress, eventName, indexFilterValues, artifacts.TokenArtifact.abi, callback,
|
||||
const subscriptionToken = this._subscribe<ArgsType>(
|
||||
tokenAddress,
|
||||
eventName,
|
||||
indexFilterValues,
|
||||
artifacts.TokenArtifact.abi,
|
||||
callback,
|
||||
);
|
||||
this._activeSubscriptions.push(subscriptionToken);
|
||||
return subscriptionToken;
|
||||
}
|
||||
/**
|
||||
@@ -268,38 +337,44 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* @param subscriptionToken Subscription token returned by `subscribe()`
|
||||
*/
|
||||
public unsubscribe(subscriptionToken: string): void {
|
||||
_.pull(this._activeSubscriptions, subscriptionToken);
|
||||
this._unsubscribe(subscriptionToken);
|
||||
}
|
||||
/**
|
||||
* Cancels all existing subscriptions
|
||||
*/
|
||||
public _unsubscribeAll(): void {
|
||||
super._unsubscribeAll();
|
||||
}
|
||||
/**
|
||||
* Gets historical logs without creating a subscription
|
||||
* @param tokenAddress An address of the token that emmited the logs.
|
||||
* @param eventName The token contract event you would like to subscribe to.
|
||||
* @param subscriptionOpts Subscriptions options that let you configure the subscription.
|
||||
* @param blockRange Block range to get logs from.
|
||||
* @param indexFilterValues An object where the keys are indexed args returned by the event and
|
||||
* the value is the value you are interested in. E.g `{_from: aUserAddressHex}`
|
||||
* @return Array of logs that match the parameters
|
||||
*/
|
||||
public async getLogsAsync(tokenAddress: string, eventName: TokenEvents, subscriptionOpts: SubscriptionOpts,
|
||||
indexFilterValues: IndexedFilterValues): Promise<LogWithDecodedArgs[]> {
|
||||
public async getLogsAsync<ArgsType extends TokenContractEventArgs>(
|
||||
tokenAddress: string,
|
||||
eventName: TokenEvents,
|
||||
blockRange: BlockRange,
|
||||
indexFilterValues: IndexedFilterValues,
|
||||
): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
assert.doesBelongToStringEnum('eventName', eventName, TokenEvents);
|
||||
assert.doesConformToSchema('subscriptionOpts', subscriptionOpts, schemas.subscriptionOptsSchema);
|
||||
assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema);
|
||||
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
|
||||
const logs = await this._getLogsAsync(
|
||||
tokenAddress, eventName, subscriptionOpts, indexFilterValues, artifacts.TokenArtifact.abi,
|
||||
const logs = await this._getLogsAsync<ArgsType>(
|
||||
tokenAddress,
|
||||
eventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
artifacts.TokenArtifact.abi,
|
||||
);
|
||||
return logs;
|
||||
}
|
||||
/**
|
||||
* Cancels all existing subscriptions
|
||||
*/
|
||||
public unsubscribeAll(): void {
|
||||
_.forEach(this._activeSubscriptions, this._unsubscribe.bind(this));
|
||||
this._activeSubscriptions = [];
|
||||
}
|
||||
private _invalidateContractInstancesAsync(): void {
|
||||
this.unsubscribeAll();
|
||||
private _invalidateContractInstances(): void {
|
||||
this._unsubscribeAll();
|
||||
this._tokenContractsByAddress = {};
|
||||
}
|
||||
private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> {
|
||||
@@ -307,15 +382,13 @@ export class TokenWrapper extends ContractWrapper {
|
||||
if (!_.isUndefined(tokenContract)) {
|
||||
return tokenContract;
|
||||
}
|
||||
const contractInstance = await this._instantiateContractIfExistsAsync<TokenContract>(
|
||||
artifacts.TokenArtifact, tokenAddress,
|
||||
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
|
||||
artifacts.TokenArtifact,
|
||||
tokenAddress,
|
||||
);
|
||||
tokenContract = contractInstance as TokenContract;
|
||||
const contractInstance = new TokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults());
|
||||
tokenContract = contractInstance;
|
||||
this._tokenContractsByAddress[tokenAddress] = tokenContract;
|
||||
return tokenContract;
|
||||
}
|
||||
private async _getTokenTransferProxyAddressAsync(): Promise<string> {
|
||||
const tokenTransferProxyContractAddress = await this._tokenTransferProxyContractAddressFetcher();
|
||||
return tokenTransferProxyContractAddress;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
/// <reference types='chai-typescript-typings' />
|
||||
/// <reference types='chai-as-promised-typescript-typings' />
|
||||
declare module 'web3_beta';
|
||||
declare module 'chai-bignumber';
|
||||
declare module 'dirty-chai';
|
||||
@@ -12,12 +10,6 @@ declare module 'web3-provider-engine/subproviders/rpc';
|
||||
// disallow `namespace`, we disable tslint for the following.
|
||||
/* tslint:disable */
|
||||
declare namespace Chai {
|
||||
interface NumberComparer {
|
||||
(value: number|BigNumber.BigNumber, message?: string): Assertion;
|
||||
}
|
||||
interface NumericComparison {
|
||||
greaterThan: NumberComparer;
|
||||
}
|
||||
interface Assertion {
|
||||
bignumber: Assertion;
|
||||
// HACK: In order to comply with chai-as-promised we make eventually a `PromisedAssertion` not an `Assertion`
|
||||
@@ -33,24 +25,6 @@ declare module '*.json' {
|
||||
/* tslint:enable */
|
||||
}
|
||||
|
||||
// find-version declarations
|
||||
declare function findVersions(version: string): string[];
|
||||
declare module 'find-versions' {
|
||||
export = findVersions;
|
||||
}
|
||||
|
||||
// compare-version declarations
|
||||
declare function compareVersions(firstVersion: string, secondVersion: string): number;
|
||||
declare module 'compare-versions' {
|
||||
export = compareVersions;
|
||||
}
|
||||
|
||||
// es6-promisify declarations
|
||||
declare function promisify(original: any, settings?: any): ((...arg: any[]) => Promise<any>);
|
||||
declare module 'es6-promisify' {
|
||||
export = promisify;
|
||||
}
|
||||
|
||||
declare module 'ethereumjs-abi' {
|
||||
const soliditySHA3: (argTypes: string[], args: any[]) => Buffer;
|
||||
}
|
||||
@@ -67,22 +41,3 @@ declare module 'truffle-hdwallet-provider' {
|
||||
}
|
||||
export = HDWalletProvider;
|
||||
}
|
||||
|
||||
// abi-decoder declarations
|
||||
interface DecodedLogArg {
|
||||
name: string;
|
||||
value: string|BigNumber.BigNumber;
|
||||
}
|
||||
interface DecodedLog {
|
||||
name: string;
|
||||
events: DecodedLogArg[];
|
||||
}
|
||||
declare module 'abi-decoder' {
|
||||
import * as Web3 from 'web3';
|
||||
const addABI: (abi: Web3.AbiDefinition) => void;
|
||||
const decodeLogs: (logs: Web3.LogEntry[]) => DecodedLog[];
|
||||
}
|
||||
|
||||
declare module 'web3/lib/solidity/coder' {
|
||||
const decodeParams: (types: string[], data: string) => any[];
|
||||
}
|
||||
23
packages/0x.js/src/globalsAugment.d.ts
vendored
Normal file
23
packages/0x.js/src/globalsAugment.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
|
||||
// HACK: This module overrides the Chai namespace so that we can use BigNumber types inside.
|
||||
// Source: https://github.com/Microsoft/TypeScript/issues/7352#issuecomment-191547232
|
||||
declare global {
|
||||
// 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.
|
||||
/* tslint:disable */
|
||||
namespace Chai {
|
||||
interface NumberComparer {
|
||||
(value: number | BigNumber, message?: string): Assertion;
|
||||
}
|
||||
interface NumericComparison {
|
||||
greaterThan: NumberComparer;
|
||||
}
|
||||
}
|
||||
/* tslint:enable */
|
||||
interface DecodedLogArg {
|
||||
name: string;
|
||||
value: string | BigNumber;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,20 @@
|
||||
export {ZeroEx} from './0x';
|
||||
export { ZeroEx } from './0x';
|
||||
|
||||
export {
|
||||
Order,
|
||||
BlockParamLiteral,
|
||||
SignedOrder,
|
||||
ECSignature,
|
||||
ZeroExError,
|
||||
EventCallback,
|
||||
EventCallbackAsync,
|
||||
EventCallbackSync,
|
||||
ExchangeContractErrs,
|
||||
ContractEvent,
|
||||
Token,
|
||||
ExchangeEvents,
|
||||
TokenEvents,
|
||||
IndexedFilterValues,
|
||||
SubscriptionOpts,
|
||||
BlockRange,
|
||||
BlockParam,
|
||||
OrderFillOrKillRequest,
|
||||
OrderCancellationRequest,
|
||||
OrderFillRequest,
|
||||
LogErrorContractEventArgs,
|
||||
@@ -26,14 +24,26 @@ export {
|
||||
TransferContractEventArgs,
|
||||
ApprovalContractEventArgs,
|
||||
TokenContractEventArgs,
|
||||
EtherTokenContractEventArgs,
|
||||
WithdrawalContractEventArgs,
|
||||
DepositContractEventArgs,
|
||||
ContractEventArgs,
|
||||
Web3Provider,
|
||||
ZeroExConfig,
|
||||
TransactionReceiptWithDecodedLogs,
|
||||
LogWithDecodedArgs,
|
||||
DecodedLogArgs,
|
||||
EtherTokenEvents,
|
||||
MethodOpts,
|
||||
OrderTransactionOpts,
|
||||
TransactionOpts,
|
||||
FilterObject,
|
||||
LogEvent,
|
||||
DecodedLogEvent,
|
||||
EventWatcherCallback,
|
||||
OnOrderStateChangeCallback,
|
||||
OrderStateValid,
|
||||
OrderStateInvalid,
|
||||
OrderState,
|
||||
} from './types';
|
||||
|
||||
export { ContractEventArg, LogWithDecodedArgs, TransactionReceiptWithDecodedLogs } from '@0xproject/types';
|
||||
|
||||
export { TransactionReceipt } from '@0xproject/types';
|
||||
93
packages/0x.js/src/order_watcher/event_watcher.ts
Normal file
93
packages/0x.js/src/order_watcher/event_watcher.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { intervalUtils } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
import { BlockParamLiteral, EventWatcherCallback, ZeroExError } from '../types';
|
||||
import { assert } from '../utils/assert';
|
||||
|
||||
const DEFAULT_EVENT_POLLING_INTERVAL_MS = 200;
|
||||
|
||||
enum LogEventState {
|
||||
Removed,
|
||||
Added,
|
||||
}
|
||||
|
||||
/*
|
||||
* The EventWatcher watches for blockchain events at the specified block confirmation
|
||||
* depth.
|
||||
*/
|
||||
export class EventWatcher {
|
||||
private _web3Wrapper: Web3Wrapper;
|
||||
private _pollingIntervalMs: number;
|
||||
private _intervalIdIfExists?: NodeJS.Timer;
|
||||
private _lastEvents: Web3.LogEntry[] = [];
|
||||
constructor(web3Wrapper: Web3Wrapper, pollingIntervalIfExistsMs: undefined | number) {
|
||||
this._web3Wrapper = web3Wrapper;
|
||||
this._pollingIntervalMs = _.isUndefined(pollingIntervalIfExistsMs)
|
||||
? DEFAULT_EVENT_POLLING_INTERVAL_MS
|
||||
: pollingIntervalIfExistsMs;
|
||||
}
|
||||
public subscribe(callback: EventWatcherCallback): void {
|
||||
assert.isFunction('callback', callback);
|
||||
if (!_.isUndefined(this._intervalIdIfExists)) {
|
||||
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
|
||||
}
|
||||
this._intervalIdIfExists = intervalUtils.setAsyncExcludingInterval(
|
||||
this._pollForBlockchainEventsAsync.bind(this, callback),
|
||||
this._pollingIntervalMs,
|
||||
(err: Error) => {
|
||||
this.unsubscribe();
|
||||
callback(err);
|
||||
},
|
||||
);
|
||||
}
|
||||
public unsubscribe(): void {
|
||||
this._lastEvents = [];
|
||||
if (!_.isUndefined(this._intervalIdIfExists)) {
|
||||
intervalUtils.clearAsyncExcludingInterval(this._intervalIdIfExists);
|
||||
delete this._intervalIdIfExists;
|
||||
}
|
||||
}
|
||||
private async _pollForBlockchainEventsAsync(callback: EventWatcherCallback): Promise<void> {
|
||||
const pendingEvents = await this._getEventsAsync();
|
||||
if (_.isUndefined(pendingEvents)) {
|
||||
// HACK: This should never happen, but happens frequently on CI due to a ganache bug
|
||||
return;
|
||||
}
|
||||
if (pendingEvents.length === 0) {
|
||||
// HACK: Sometimes when node rebuilds the pending block we get back the empty result.
|
||||
// We don't want to emit a lot of removal events and bring them back after a couple of miliseconds,
|
||||
// that's why we just ignore those cases.
|
||||
return;
|
||||
}
|
||||
const removedEvents = _.differenceBy(this._lastEvents, pendingEvents, JSON.stringify);
|
||||
const newEvents = _.differenceBy(pendingEvents, this._lastEvents, JSON.stringify);
|
||||
await this._emitDifferencesAsync(removedEvents, LogEventState.Removed, callback);
|
||||
await this._emitDifferencesAsync(newEvents, LogEventState.Added, callback);
|
||||
this._lastEvents = pendingEvents;
|
||||
}
|
||||
private async _getEventsAsync(): Promise<Web3.LogEntry[]> {
|
||||
const eventFilter = {
|
||||
fromBlock: BlockParamLiteral.Pending,
|
||||
toBlock: BlockParamLiteral.Pending,
|
||||
};
|
||||
const events = await this._web3Wrapper.getLogsAsync(eventFilter);
|
||||
return events;
|
||||
}
|
||||
private async _emitDifferencesAsync(
|
||||
logs: Web3.LogEntry[],
|
||||
logEventState: LogEventState,
|
||||
callback: EventWatcherCallback,
|
||||
): Promise<void> {
|
||||
for (const log of logs) {
|
||||
const logEvent = {
|
||||
removed: logEventState === LogEventState.Removed,
|
||||
...log,
|
||||
};
|
||||
if (!_.isUndefined(this._intervalIdIfExists)) {
|
||||
callback(null, logEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
75
packages/0x.js/src/order_watcher/expiration_watcher.ts
Normal file
75
packages/0x.js/src/order_watcher/expiration_watcher.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { BigNumber, intervalUtils } from '@0xproject/utils';
|
||||
import { RBTree } from 'bintrees';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ZeroExError } from '../types';
|
||||
import { utils } from '../utils/utils';
|
||||
|
||||
const DEFAULT_EXPIRATION_MARGIN_MS = 0;
|
||||
const DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS = 50;
|
||||
|
||||
/**
|
||||
* This class includes the functionality to detect expired orders.
|
||||
* It stores them in a min heap by expiration time and checks for expired ones every `orderExpirationCheckingIntervalMs`
|
||||
*/
|
||||
export class ExpirationWatcher {
|
||||
private _orderHashByExpirationRBTree: RBTree<string>;
|
||||
private _expiration: { [orderHash: string]: BigNumber } = {};
|
||||
private _orderExpirationCheckingIntervalMs: number;
|
||||
private _expirationMarginMs: number;
|
||||
private _orderExpirationCheckingIntervalIdIfExists?: NodeJS.Timer;
|
||||
constructor(expirationMarginIfExistsMs?: number, orderExpirationCheckingIntervalIfExistsMs?: number) {
|
||||
this._expirationMarginMs = expirationMarginIfExistsMs || DEFAULT_EXPIRATION_MARGIN_MS;
|
||||
this._orderExpirationCheckingIntervalMs =
|
||||
expirationMarginIfExistsMs || DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS;
|
||||
const scoreFunction = (orderHash: string) => this._expiration[orderHash].toNumber();
|
||||
const comparator = (lhs: string, rhs: string) => scoreFunction(lhs) - scoreFunction(rhs);
|
||||
this._orderHashByExpirationRBTree = new RBTree(comparator);
|
||||
}
|
||||
public subscribe(callback: (orderHash: string) => void): void {
|
||||
if (!_.isUndefined(this._orderExpirationCheckingIntervalIdIfExists)) {
|
||||
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
|
||||
}
|
||||
this._orderExpirationCheckingIntervalIdIfExists = intervalUtils.setInterval(
|
||||
this._pruneExpiredOrders.bind(this, callback),
|
||||
this._orderExpirationCheckingIntervalMs,
|
||||
_.noop, // _pruneExpiredOrders never throws
|
||||
);
|
||||
}
|
||||
public unsubscribe(): void {
|
||||
if (_.isUndefined(this._orderExpirationCheckingIntervalIdIfExists)) {
|
||||
throw new Error(ZeroExError.SubscriptionNotFound);
|
||||
}
|
||||
intervalUtils.clearInterval(this._orderExpirationCheckingIntervalIdIfExists);
|
||||
delete this._orderExpirationCheckingIntervalIdIfExists;
|
||||
}
|
||||
public addOrder(orderHash: string, expirationUnixTimestampMs: BigNumber): void {
|
||||
this._expiration[orderHash] = expirationUnixTimestampMs;
|
||||
this._orderHashByExpirationRBTree.insert(orderHash);
|
||||
}
|
||||
public removeOrder(orderHash: string): void {
|
||||
this._orderHashByExpirationRBTree.remove(orderHash);
|
||||
delete this._expiration[orderHash];
|
||||
}
|
||||
private _pruneExpiredOrders(callback: (orderHash: string) => void): void {
|
||||
const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs();
|
||||
while (true) {
|
||||
const hasTrakedOrders = this._orderHashByExpirationRBTree.size === 0;
|
||||
if (hasTrakedOrders) {
|
||||
break;
|
||||
}
|
||||
const nextOrderHashToExpire = this._orderHashByExpirationRBTree.min();
|
||||
const hasNoExpiredOrders = this._expiration[nextOrderHashToExpire].greaterThan(
|
||||
currentUnixTimestampMs.plus(this._expirationMarginMs),
|
||||
);
|
||||
const isSubscriptionActive = _.isUndefined(this._orderExpirationCheckingIntervalIdIfExists);
|
||||
if (hasNoExpiredOrders || isSubscriptionActive) {
|
||||
break;
|
||||
}
|
||||
const orderHash = this._orderHashByExpirationRBTree.min();
|
||||
this._orderHashByExpirationRBTree.remove(orderHash);
|
||||
delete this._expiration[orderHash];
|
||||
callback(orderHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
381
packages/0x.js/src/order_watcher/order_state_watcher.ts
Normal file
381
packages/0x.js/src/order_watcher/order_state_watcher.ts
Normal file
@@ -0,0 +1,381 @@
|
||||
import { schemas } from '@0xproject/json-schemas';
|
||||
import { LogWithDecodedArgs } from '@0xproject/types';
|
||||
import { AbiDecoder, intervalUtils } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ZeroEx } from '../0x';
|
||||
import { ExchangeWrapper } from '../contract_wrappers/exchange_wrapper';
|
||||
import { TokenWrapper } from '../contract_wrappers/token_wrapper';
|
||||
import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allowance_lazy_store';
|
||||
import { OrderFilledCancelledLazyStore } from '../stores/order_filled_cancelled_lazy_store';
|
||||
import {
|
||||
ApprovalContractEventArgs,
|
||||
BlockParamLiteral,
|
||||
ContractEventArgs,
|
||||
DepositContractEventArgs,
|
||||
EtherTokenEvents,
|
||||
ExchangeContractErrs,
|
||||
ExchangeEvents,
|
||||
LogCancelContractEventArgs,
|
||||
LogEvent,
|
||||
LogFillContractEventArgs,
|
||||
OnOrderStateChangeCallback,
|
||||
OrderState,
|
||||
OrderStateWatcherConfig,
|
||||
SignedOrder,
|
||||
TokenEvents,
|
||||
TransferContractEventArgs,
|
||||
WithdrawalContractEventArgs,
|
||||
ZeroExError,
|
||||
} from '../types';
|
||||
import { assert } from '../utils/assert';
|
||||
import { OrderStateUtils } from '../utils/order_state_utils';
|
||||
import { utils } from '../utils/utils';
|
||||
|
||||
import { EventWatcher } from './event_watcher';
|
||||
import { ExpirationWatcher } from './expiration_watcher';
|
||||
|
||||
interface DependentOrderHashes {
|
||||
[makerAddress: string]: {
|
||||
[makerToken: string]: Set<string>;
|
||||
};
|
||||
}
|
||||
|
||||
interface OrderByOrderHash {
|
||||
[orderHash: string]: SignedOrder;
|
||||
}
|
||||
|
||||
interface OrderStateByOrderHash {
|
||||
[orderHash: string]: OrderState;
|
||||
}
|
||||
|
||||
const DEFAULT_CLEANUP_JOB_INTERVAL_MS = 1000 * 60 * 60; // 1h
|
||||
|
||||
/**
|
||||
* This class includes all the functionality related to watching a set of orders
|
||||
* for potential changes in order validity/fillability. The orderWatcher notifies
|
||||
* the subscriber of these changes so that a final decison can be made on whether
|
||||
* the order should be deemed invalid.
|
||||
*/
|
||||
export class OrderStateWatcher {
|
||||
private _orderStateByOrderHashCache: OrderStateByOrderHash = {};
|
||||
private _orderByOrderHash: OrderByOrderHash = {};
|
||||
private _dependentOrderHashes: DependentOrderHashes = {};
|
||||
private _callbackIfExists?: OnOrderStateChangeCallback;
|
||||
private _eventWatcher: EventWatcher;
|
||||
private _web3Wrapper: Web3Wrapper;
|
||||
private _abiDecoder: AbiDecoder;
|
||||
private _expirationWatcher: ExpirationWatcher;
|
||||
private _orderStateUtils: OrderStateUtils;
|
||||
private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore;
|
||||
private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore;
|
||||
private _cleanupJobInterval: number;
|
||||
private _cleanupJobIntervalIdIfExists?: NodeJS.Timer;
|
||||
constructor(
|
||||
web3Wrapper: Web3Wrapper,
|
||||
abiDecoder: AbiDecoder,
|
||||
token: TokenWrapper,
|
||||
exchange: ExchangeWrapper,
|
||||
config?: OrderStateWatcherConfig,
|
||||
) {
|
||||
this._abiDecoder = abiDecoder;
|
||||
this._web3Wrapper = web3Wrapper;
|
||||
const pollingIntervalIfExistsMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs;
|
||||
this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs);
|
||||
this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore(
|
||||
token,
|
||||
BlockParamLiteral.Pending,
|
||||
);
|
||||
this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange);
|
||||
this._orderStateUtils = new OrderStateUtils(
|
||||
this._balanceAndProxyAllowanceLazyStore,
|
||||
this._orderFilledCancelledLazyStore,
|
||||
);
|
||||
const orderExpirationCheckingIntervalMsIfExists = _.isUndefined(config)
|
||||
? undefined
|
||||
: config.orderExpirationCheckingIntervalMs;
|
||||
const expirationMarginIfExistsMs = _.isUndefined(config) ? undefined : config.expirationMarginMs;
|
||||
this._expirationWatcher = new ExpirationWatcher(
|
||||
expirationMarginIfExistsMs,
|
||||
orderExpirationCheckingIntervalMsIfExists,
|
||||
);
|
||||
this._cleanupJobInterval =
|
||||
_.isUndefined(config) || _.isUndefined(config.cleanupJobIntervalMs)
|
||||
? DEFAULT_CLEANUP_JOB_INTERVAL_MS
|
||||
: config.cleanupJobIntervalMs;
|
||||
}
|
||||
/**
|
||||
* Add an order to the orderStateWatcher. Before the order is added, it's
|
||||
* signature is verified.
|
||||
* @param signedOrder The order you wish to start watching.
|
||||
*/
|
||||
public addOrder(signedOrder: SignedOrder): void {
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker);
|
||||
this._orderByOrderHash[orderHash] = signedOrder;
|
||||
this._addToDependentOrderHashes(signedOrder, orderHash);
|
||||
const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(1000);
|
||||
this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs);
|
||||
}
|
||||
/**
|
||||
* Removes an order from the orderStateWatcher
|
||||
* @param orderHash The orderHash of the order you wish to stop watching.
|
||||
*/
|
||||
public removeOrder(orderHash: string): void {
|
||||
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
|
||||
const signedOrder = this._orderByOrderHash[orderHash];
|
||||
if (_.isUndefined(signedOrder)) {
|
||||
return; // noop
|
||||
}
|
||||
delete this._orderByOrderHash[orderHash];
|
||||
delete this._orderStateByOrderHashCache[orderHash];
|
||||
const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper;
|
||||
const zrxTokenAddress = exchange.getZRXTokenAddress();
|
||||
|
||||
this._removeFromDependentOrderHashes(signedOrder.maker, zrxTokenAddress, orderHash);
|
||||
if (zrxTokenAddress !== signedOrder.makerTokenAddress) {
|
||||
this._removeFromDependentOrderHashes(signedOrder.maker, signedOrder.makerTokenAddress, orderHash);
|
||||
}
|
||||
|
||||
this._expirationWatcher.removeOrder(orderHash);
|
||||
}
|
||||
/**
|
||||
* Starts an orderStateWatcher subscription. The callback will be called every time a watched order's
|
||||
* backing blockchain state has changed. This is a call-to-action for the caller to re-validate the order.
|
||||
* @param callback Receives the orderHash of the order that should be re-validated, together
|
||||
* with all the order-relevant blockchain state needed to re-validate the order.
|
||||
*/
|
||||
public subscribe(callback: OnOrderStateChangeCallback): void {
|
||||
assert.isFunction('callback', callback);
|
||||
if (!_.isUndefined(this._callbackIfExists)) {
|
||||
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
|
||||
}
|
||||
this._callbackIfExists = callback;
|
||||
this._eventWatcher.subscribe(this._onEventWatcherCallbackAsync.bind(this));
|
||||
this._expirationWatcher.subscribe(this._onOrderExpired.bind(this));
|
||||
this._cleanupJobIntervalIdIfExists = intervalUtils.setAsyncExcludingInterval(
|
||||
this._cleanupAsync.bind(this),
|
||||
this._cleanupJobInterval,
|
||||
(err: Error) => {
|
||||
this.unsubscribe();
|
||||
callback(err);
|
||||
},
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Ends an orderStateWatcher subscription.
|
||||
*/
|
||||
public unsubscribe(): void {
|
||||
if (_.isUndefined(this._callbackIfExists) || _.isUndefined(this._cleanupJobIntervalIdIfExists)) {
|
||||
throw new Error(ZeroExError.SubscriptionNotFound);
|
||||
}
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteAll();
|
||||
this._orderFilledCancelledLazyStore.deleteAll();
|
||||
delete this._callbackIfExists;
|
||||
this._eventWatcher.unsubscribe();
|
||||
this._expirationWatcher.unsubscribe();
|
||||
intervalUtils.clearAsyncExcludingInterval(this._cleanupJobIntervalIdIfExists);
|
||||
}
|
||||
private async _cleanupAsync(): Promise<void> {
|
||||
for (const orderHash of _.keys(this._orderByOrderHash)) {
|
||||
this._cleanupOrderRelatedState(orderHash);
|
||||
await this._emitRevalidateOrdersAsync([orderHash]);
|
||||
}
|
||||
}
|
||||
private _cleanupOrderRelatedState(orderHash: string): void {
|
||||
const signedOrder = this._orderByOrderHash[orderHash];
|
||||
|
||||
this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(orderHash);
|
||||
this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(orderHash);
|
||||
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.makerTokenAddress, signedOrder.maker);
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.makerTokenAddress, signedOrder.maker);
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.takerTokenAddress, signedOrder.taker);
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.takerTokenAddress, signedOrder.taker);
|
||||
|
||||
const zrxTokenAddress = this._getZRXTokenAddress();
|
||||
if (!signedOrder.makerFee.isZero()) {
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.maker);
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.maker);
|
||||
}
|
||||
if (!signedOrder.takerFee.isZero()) {
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.taker);
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.taker);
|
||||
}
|
||||
}
|
||||
private _onOrderExpired(orderHash: string): void {
|
||||
const orderState: OrderState = {
|
||||
isValid: false,
|
||||
orderHash,
|
||||
error: ExchangeContractErrs.OrderFillExpired,
|
||||
};
|
||||
if (!_.isUndefined(this._orderByOrderHash[orderHash])) {
|
||||
this.removeOrder(orderHash);
|
||||
if (!_.isUndefined(this._callbackIfExists)) {
|
||||
this._callbackIfExists(null, orderState);
|
||||
}
|
||||
}
|
||||
}
|
||||
private async _onEventWatcherCallbackAsync(err: Error | null, logIfExists?: LogEvent): Promise<void> {
|
||||
if (!_.isNull(err)) {
|
||||
if (!_.isUndefined(this._callbackIfExists)) {
|
||||
this._callbackIfExists(err);
|
||||
this.unsubscribe();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const log = logIfExists as LogEvent; // At this moment we are sure that no error occured and log is defined.
|
||||
const maybeDecodedLog = this._abiDecoder.tryToDecodeLogOrNoop<ContractEventArgs>(log);
|
||||
const isLogDecoded = !_.isUndefined(((maybeDecodedLog as any) as LogWithDecodedArgs<ContractEventArgs>).event);
|
||||
if (!isLogDecoded) {
|
||||
return; // noop
|
||||
}
|
||||
const decodedLog = (maybeDecodedLog as any) as LogWithDecodedArgs<ContractEventArgs>;
|
||||
let makerToken: string;
|
||||
let makerAddress: string;
|
||||
switch (decodedLog.event) {
|
||||
case TokenEvents.Approval: {
|
||||
// Invalidate cache
|
||||
const args = decodedLog.args as ApprovalContractEventArgs;
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(decodedLog.address, args._owner);
|
||||
// Revalidate orders
|
||||
makerToken = decodedLog.address;
|
||||
makerAddress = args._owner;
|
||||
if (
|
||||
!_.isUndefined(this._dependentOrderHashes[makerAddress]) &&
|
||||
!_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken])
|
||||
) {
|
||||
const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]);
|
||||
await this._emitRevalidateOrdersAsync(orderHashes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TokenEvents.Transfer: {
|
||||
// Invalidate cache
|
||||
const args = decodedLog.args as TransferContractEventArgs;
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._from);
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._to);
|
||||
// Revalidate orders
|
||||
makerToken = decodedLog.address;
|
||||
makerAddress = args._from;
|
||||
if (
|
||||
!_.isUndefined(this._dependentOrderHashes[makerAddress]) &&
|
||||
!_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken])
|
||||
) {
|
||||
const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]);
|
||||
await this._emitRevalidateOrdersAsync(orderHashes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EtherTokenEvents.Deposit: {
|
||||
// Invalidate cache
|
||||
const args = decodedLog.args as DepositContractEventArgs;
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner);
|
||||
// Revalidate orders
|
||||
makerToken = decodedLog.address;
|
||||
makerAddress = args._owner;
|
||||
if (
|
||||
!_.isUndefined(this._dependentOrderHashes[makerAddress]) &&
|
||||
!_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken])
|
||||
) {
|
||||
const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]);
|
||||
await this._emitRevalidateOrdersAsync(orderHashes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EtherTokenEvents.Withdrawal: {
|
||||
// Invalidate cache
|
||||
const args = decodedLog.args as WithdrawalContractEventArgs;
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner);
|
||||
// Revalidate orders
|
||||
makerToken = decodedLog.address;
|
||||
makerAddress = args._owner;
|
||||
if (
|
||||
!_.isUndefined(this._dependentOrderHashes[makerAddress]) &&
|
||||
!_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken])
|
||||
) {
|
||||
const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]);
|
||||
await this._emitRevalidateOrdersAsync(orderHashes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ExchangeEvents.LogFill: {
|
||||
// Invalidate cache
|
||||
const args = decodedLog.args as LogFillContractEventArgs;
|
||||
this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(args.orderHash);
|
||||
// Revalidate orders
|
||||
const orderHash = args.orderHash;
|
||||
const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]);
|
||||
if (isOrderWatched) {
|
||||
await this._emitRevalidateOrdersAsync([orderHash]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ExchangeEvents.LogCancel: {
|
||||
// Invalidate cache
|
||||
const args = decodedLog.args as LogCancelContractEventArgs;
|
||||
this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(args.orderHash);
|
||||
// Revalidate orders
|
||||
const orderHash = args.orderHash;
|
||||
const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]);
|
||||
if (isOrderWatched) {
|
||||
await this._emitRevalidateOrdersAsync([orderHash]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ExchangeEvents.LogError:
|
||||
return; // noop
|
||||
|
||||
default:
|
||||
throw utils.spawnSwitchErr('decodedLog.event', decodedLog.event);
|
||||
}
|
||||
}
|
||||
private async _emitRevalidateOrdersAsync(orderHashes: string[]): Promise<void> {
|
||||
for (const orderHash of orderHashes) {
|
||||
const signedOrder = this._orderByOrderHash[orderHash];
|
||||
// Most of these calls will never reach the network because the data is fetched from stores
|
||||
// and only updated when cache is invalidated
|
||||
const orderState = await this._orderStateUtils.getOrderStateAsync(signedOrder);
|
||||
if (_.isUndefined(this._callbackIfExists)) {
|
||||
break; // Unsubscribe was called
|
||||
}
|
||||
if (_.isEqual(orderState, this._orderStateByOrderHashCache[orderHash])) {
|
||||
// Actual order state didn't change
|
||||
continue;
|
||||
} else {
|
||||
this._orderStateByOrderHashCache[orderHash] = orderState;
|
||||
}
|
||||
this._callbackIfExists(null, orderState);
|
||||
}
|
||||
}
|
||||
private _addToDependentOrderHashes(signedOrder: SignedOrder, orderHash: string): void {
|
||||
if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker])) {
|
||||
this._dependentOrderHashes[signedOrder.maker] = {};
|
||||
}
|
||||
if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress])) {
|
||||
this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress] = new Set();
|
||||
}
|
||||
this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress].add(orderHash);
|
||||
const zrxTokenAddress = this._getZRXTokenAddress();
|
||||
if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress])) {
|
||||
this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress] = new Set();
|
||||
}
|
||||
this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress].add(orderHash);
|
||||
}
|
||||
private _removeFromDependentOrderHashes(makerAddress: string, tokenAddress: string, orderHash: string) {
|
||||
this._dependentOrderHashes[makerAddress][tokenAddress].delete(orderHash);
|
||||
if (this._dependentOrderHashes[makerAddress][tokenAddress].size === 0) {
|
||||
delete this._dependentOrderHashes[makerAddress][tokenAddress];
|
||||
}
|
||||
if (_.isEmpty(this._dependentOrderHashes[makerAddress])) {
|
||||
delete this._dependentOrderHashes[makerAddress];
|
||||
}
|
||||
}
|
||||
private _getZRXTokenAddress(): string {
|
||||
const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper;
|
||||
const zrxTokenAddress = exchange.getZRXTokenAddress();
|
||||
return zrxTokenAddress;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
|
||||
import { SignedOrder } from '../types';
|
||||
|
||||
export class RemainingFillableCalculator {
|
||||
private _signedOrder: SignedOrder;
|
||||
private _isMakerTokenZRX: boolean;
|
||||
// Transferrable Amount is the minimum of Approval and Balance
|
||||
private _transferrableMakerTokenAmount: BigNumber;
|
||||
private _transferrableMakerFeeTokenAmount: BigNumber;
|
||||
private _remainingMakerTokenAmount: BigNumber;
|
||||
private _remainingMakerFeeAmount: BigNumber;
|
||||
constructor(
|
||||
signedOrder: SignedOrder,
|
||||
isMakerTokenZRX: boolean,
|
||||
transferrableMakerTokenAmount: BigNumber,
|
||||
transferrableMakerFeeTokenAmount: BigNumber,
|
||||
remainingMakerTokenAmount: BigNumber,
|
||||
) {
|
||||
this._signedOrder = signedOrder;
|
||||
this._isMakerTokenZRX = isMakerTokenZRX;
|
||||
this._transferrableMakerTokenAmount = transferrableMakerTokenAmount;
|
||||
this._transferrableMakerFeeTokenAmount = transferrableMakerFeeTokenAmount;
|
||||
this._remainingMakerTokenAmount = remainingMakerTokenAmount;
|
||||
this._remainingMakerFeeAmount = remainingMakerTokenAmount
|
||||
.times(signedOrder.makerFee)
|
||||
.dividedToIntegerBy(signedOrder.makerTokenAmount);
|
||||
}
|
||||
public computeRemainingMakerFillable(): BigNumber {
|
||||
if (this._hasSufficientFundsForFeeAndTransferAmount()) {
|
||||
return this._remainingMakerTokenAmount;
|
||||
}
|
||||
if (this._signedOrder.makerFee.isZero()) {
|
||||
return BigNumber.min(this._remainingMakerTokenAmount, this._transferrableMakerTokenAmount);
|
||||
}
|
||||
return this._calculatePartiallyFillableMakerTokenAmount();
|
||||
}
|
||||
public computeRemainingTakerFillable(): BigNumber {
|
||||
return this.computeRemainingMakerFillable()
|
||||
.times(this._signedOrder.takerTokenAmount)
|
||||
.dividedToIntegerBy(this._signedOrder.makerTokenAmount);
|
||||
}
|
||||
private _hasSufficientFundsForFeeAndTransferAmount(): boolean {
|
||||
if (this._isMakerTokenZRX) {
|
||||
const totalZRXTransferAmountRequired = this._remainingMakerTokenAmount.plus(this._remainingMakerFeeAmount);
|
||||
const hasSufficientFunds = this._transferrableMakerTokenAmount.greaterThanOrEqualTo(
|
||||
totalZRXTransferAmountRequired,
|
||||
);
|
||||
return hasSufficientFunds;
|
||||
} else {
|
||||
const hasSufficientFundsForTransferAmount = this._transferrableMakerTokenAmount.greaterThanOrEqualTo(
|
||||
this._remainingMakerTokenAmount,
|
||||
);
|
||||
const hasSufficientFundsForFeeAmount = this._transferrableMakerFeeTokenAmount.greaterThanOrEqualTo(
|
||||
this._remainingMakerFeeAmount,
|
||||
);
|
||||
const hasSufficientFunds = hasSufficientFundsForTransferAmount && hasSufficientFundsForFeeAmount;
|
||||
return hasSufficientFunds;
|
||||
}
|
||||
}
|
||||
private _calculatePartiallyFillableMakerTokenAmount(): BigNumber {
|
||||
// Given an order for 200 wei for 2 ZRXwei fee, find 100 wei for 1 ZRXwei. Order ratio is then 100:1
|
||||
const orderToFeeRatio = this._signedOrder.makerTokenAmount.dividedBy(this._signedOrder.makerFee);
|
||||
// The number of times the maker can fill the order, if each fill only required the transfer of a single
|
||||
// baseUnit of fee tokens.
|
||||
// Given 2 ZRXwei, the maximum amount of times Maker can fill this order, in terms of fees, is 2
|
||||
const fillableTimesInFeeTokenBaseUnits = BigNumber.min(
|
||||
this._transferrableMakerFeeTokenAmount,
|
||||
this._remainingMakerFeeAmount,
|
||||
);
|
||||
// The number of times the Maker can fill the order, given the Maker Token Balance
|
||||
// Assuming a balance of 150 wei, and an orderToFeeRatio of 100:1, maker can fill this order 1 time.
|
||||
let fillableTimesInMakerTokenUnits = this._transferrableMakerTokenAmount.dividedBy(orderToFeeRatio);
|
||||
if (this._isMakerTokenZRX) {
|
||||
// If ZRX is the maker token, the Fee and the Maker amount need to be removed from the same pool;
|
||||
// 200 ZRXwei for 2ZRXwei fee can only be filled once (need 202 ZRXwei)
|
||||
const totalZRXTokenPooled = this._transferrableMakerTokenAmount;
|
||||
// The purchasing power here is less as the tokens are taken from the same Pool
|
||||
// For every one number of fills, we have to take an extra ZRX out of the pool
|
||||
fillableTimesInMakerTokenUnits = totalZRXTokenPooled.dividedBy(orderToFeeRatio.plus(new BigNumber(1)));
|
||||
}
|
||||
// When Ratio is not fully divisible there can be remainders which cannot be represented, so they are floored.
|
||||
// This can result in a RoundingError being thrown by the Exchange Contract.
|
||||
const partiallyFillableMakerTokenAmount = fillableTimesInMakerTokenUnits
|
||||
.times(this._signedOrder.makerTokenAmount)
|
||||
.dividedToIntegerBy(this._signedOrder.makerFee);
|
||||
const partiallyFillableFeeTokenAmount = fillableTimesInFeeTokenBaseUnits
|
||||
.times(this._signedOrder.makerTokenAmount)
|
||||
.dividedToIntegerBy(this._signedOrder.makerFee);
|
||||
const partiallyFillableAmount = BigNumber.min(
|
||||
partiallyFillableMakerTokenAmount,
|
||||
partiallyFillableFeeTokenAmount,
|
||||
);
|
||||
return partiallyFillableAmount;
|
||||
}
|
||||
}
|
||||
27
packages/0x.js/src/schemas/zero_ex_config_schema.ts
Normal file
27
packages/0x.js/src/schemas/zero_ex_config_schema.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export const zeroExConfigSchema = {
|
||||
id: '/ZeroExConfig',
|
||||
properties: {
|
||||
networkId: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
},
|
||||
gasPrice: { $ref: '/Number' },
|
||||
exchangeContractAddress: { $ref: '/Address' },
|
||||
tokenRegistryContractAddress: { $ref: '/Address' },
|
||||
orderWatcherConfig: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pollingIntervalMs: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
},
|
||||
numConfirmations: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
required: ['networkId'],
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { TokenWrapper } from '../contract_wrappers/token_wrapper';
|
||||
import { BlockParamLiteral } from '../types';
|
||||
|
||||
/**
|
||||
* Copy on read store for balances/proxyAllowances of tokens/accounts
|
||||
*/
|
||||
export class BalanceAndProxyAllowanceLazyStore {
|
||||
private _token: TokenWrapper;
|
||||
private _defaultBlock: BlockParamLiteral;
|
||||
private _balance: {
|
||||
[tokenAddress: string]: {
|
||||
[userAddress: string]: BigNumber;
|
||||
};
|
||||
};
|
||||
private _proxyAllowance: {
|
||||
[tokenAddress: string]: {
|
||||
[userAddress: string]: BigNumber;
|
||||
};
|
||||
};
|
||||
constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) {
|
||||
this._token = token;
|
||||
this._defaultBlock = defaultBlock;
|
||||
this._balance = {};
|
||||
this._proxyAllowance = {};
|
||||
}
|
||||
public async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> {
|
||||
if (_.isUndefined(this._balance[tokenAddress]) || _.isUndefined(this._balance[tokenAddress][userAddress])) {
|
||||
const methodOpts = {
|
||||
defaultBlock: this._defaultBlock,
|
||||
};
|
||||
const balance = await this._token.getBalanceAsync(tokenAddress, userAddress, methodOpts);
|
||||
this.setBalance(tokenAddress, userAddress, balance);
|
||||
}
|
||||
const cachedBalance = this._balance[tokenAddress][userAddress];
|
||||
return cachedBalance;
|
||||
}
|
||||
public setBalance(tokenAddress: string, userAddress: string, balance: BigNumber): void {
|
||||
if (_.isUndefined(this._balance[tokenAddress])) {
|
||||
this._balance[tokenAddress] = {};
|
||||
}
|
||||
this._balance[tokenAddress][userAddress] = balance;
|
||||
}
|
||||
public deleteBalance(tokenAddress: string, userAddress: string): void {
|
||||
if (!_.isUndefined(this._balance[tokenAddress])) {
|
||||
delete this._balance[tokenAddress][userAddress];
|
||||
if (_.isEmpty(this._balance[tokenAddress])) {
|
||||
delete this._balance[tokenAddress];
|
||||
}
|
||||
}
|
||||
}
|
||||
public async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> {
|
||||
if (
|
||||
_.isUndefined(this._proxyAllowance[tokenAddress]) ||
|
||||
_.isUndefined(this._proxyAllowance[tokenAddress][userAddress])
|
||||
) {
|
||||
const methodOpts = {
|
||||
defaultBlock: this._defaultBlock,
|
||||
};
|
||||
const proxyAllowance = await this._token.getProxyAllowanceAsync(tokenAddress, userAddress, methodOpts);
|
||||
this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance);
|
||||
}
|
||||
const cachedProxyAllowance = this._proxyAllowance[tokenAddress][userAddress];
|
||||
return cachedProxyAllowance;
|
||||
}
|
||||
public setProxyAllowance(tokenAddress: string, userAddress: string, proxyAllowance: BigNumber): void {
|
||||
if (_.isUndefined(this._proxyAllowance[tokenAddress])) {
|
||||
this._proxyAllowance[tokenAddress] = {};
|
||||
}
|
||||
this._proxyAllowance[tokenAddress][userAddress] = proxyAllowance;
|
||||
}
|
||||
public deleteProxyAllowance(tokenAddress: string, userAddress: string): void {
|
||||
if (!_.isUndefined(this._proxyAllowance[tokenAddress])) {
|
||||
delete this._proxyAllowance[tokenAddress][userAddress];
|
||||
if (_.isEmpty(this._proxyAllowance[tokenAddress])) {
|
||||
delete this._proxyAllowance[tokenAddress];
|
||||
}
|
||||
}
|
||||
}
|
||||
public deleteAll(): void {
|
||||
this._balance = {};
|
||||
this._proxyAllowance = {};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ExchangeWrapper } from '../contract_wrappers/exchange_wrapper';
|
||||
import { BlockParamLiteral } from '../types';
|
||||
|
||||
/**
|
||||
* Copy on read store for filled/cancelled taker amounts
|
||||
*/
|
||||
export class OrderFilledCancelledLazyStore {
|
||||
private _exchange: ExchangeWrapper;
|
||||
private _filledTakerAmount: {
|
||||
[orderHash: string]: BigNumber;
|
||||
};
|
||||
private _cancelledTakerAmount: {
|
||||
[orderHash: string]: BigNumber;
|
||||
};
|
||||
constructor(exchange: ExchangeWrapper) {
|
||||
this._exchange = exchange;
|
||||
this._filledTakerAmount = {};
|
||||
this._cancelledTakerAmount = {};
|
||||
}
|
||||
public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
|
||||
if (_.isUndefined(this._filledTakerAmount[orderHash])) {
|
||||
const methodOpts = {
|
||||
defaultBlock: BlockParamLiteral.Pending,
|
||||
};
|
||||
const filledTakerAmount = await this._exchange.getFilledTakerAmountAsync(orderHash, methodOpts);
|
||||
this.setFilledTakerAmount(orderHash, filledTakerAmount);
|
||||
}
|
||||
const cachedFilled = this._filledTakerAmount[orderHash];
|
||||
return cachedFilled;
|
||||
}
|
||||
public setFilledTakerAmount(orderHash: string, filledTakerAmount: BigNumber): void {
|
||||
this._filledTakerAmount[orderHash] = filledTakerAmount;
|
||||
}
|
||||
public deleteFilledTakerAmount(orderHash: string): void {
|
||||
delete this._filledTakerAmount[orderHash];
|
||||
}
|
||||
public async getCancelledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
|
||||
if (_.isUndefined(this._cancelledTakerAmount[orderHash])) {
|
||||
const methodOpts = {
|
||||
defaultBlock: BlockParamLiteral.Pending,
|
||||
};
|
||||
const cancelledTakerAmount = await this._exchange.getCancelledTakerAmountAsync(orderHash, methodOpts);
|
||||
this.setCancelledTakerAmount(orderHash, cancelledTakerAmount);
|
||||
}
|
||||
const cachedCancelled = this._cancelledTakerAmount[orderHash];
|
||||
return cachedCancelled;
|
||||
}
|
||||
public setCancelledTakerAmount(orderHash: string, cancelledTakerAmount: BigNumber): void {
|
||||
this._cancelledTakerAmount[orderHash] = cancelledTakerAmount;
|
||||
}
|
||||
public deleteCancelledTakerAmount(orderHash: string): void {
|
||||
delete this._cancelledTakerAmount[orderHash];
|
||||
}
|
||||
public deleteAll(): void {
|
||||
this._filledTakerAmount = {};
|
||||
this._cancelledTakerAmount = {};
|
||||
}
|
||||
}
|
||||
380
packages/0x.js/src/types.ts
Normal file
380
packages/0x.js/src/types.ts
Normal file
@@ -0,0 +1,380 @@
|
||||
import { ContractEventArg, LogWithDecodedArgs } from '@0xproject/types';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
export enum ZeroExError {
|
||||
ExchangeContractDoesNotExist = 'EXCHANGE_CONTRACT_DOES_NOT_EXIST',
|
||||
ZRXContractDoesNotExist = 'ZRX_CONTRACT_DOES_NOT_EXIST',
|
||||
EtherTokenContractDoesNotExist = 'ETHER_TOKEN_CONTRACT_DOES_NOT_EXIST',
|
||||
TokenTransferProxyContractDoesNotExist = 'TOKEN_TRANSFER_PROXY_CONTRACT_DOES_NOT_EXIST',
|
||||
TokenRegistryContractDoesNotExist = 'TOKEN_REGISTRY_CONTRACT_DOES_NOT_EXIST',
|
||||
TokenContractDoesNotExist = 'TOKEN_CONTRACT_DOES_NOT_EXIST',
|
||||
UnhandledError = 'UNHANDLED_ERROR',
|
||||
UserHasNoAssociatedAddress = 'USER_HAS_NO_ASSOCIATED_ADDRESSES',
|
||||
InvalidSignature = 'INVALID_SIGNATURE',
|
||||
ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK',
|
||||
InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER',
|
||||
InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER',
|
||||
InsufficientEthBalanceForDeposit = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT',
|
||||
InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL',
|
||||
InvalidJump = 'INVALID_JUMP',
|
||||
OutOfGas = 'OUT_OF_GAS',
|
||||
NoNetworkId = 'NO_NETWORK_ID',
|
||||
SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND',
|
||||
SubscriptionAlreadyPresent = 'SUBSCRIPTION_ALREADY_PRESENT',
|
||||
TransactionMiningTimeout = 'TRANSACTION_MINING_TIMEOUT',
|
||||
}
|
||||
|
||||
export enum InternalZeroExError {
|
||||
NoAbiDecoder = 'NO_ABI_DECODER',
|
||||
ZrxNotInTokenRegistry = 'ZRX_NOT_IN_TOKEN_REGISTRY',
|
||||
WethNotInTokenRegistry = 'WETH_NOT_IN_TOKEN_REGISTRY',
|
||||
}
|
||||
|
||||
/**
|
||||
* Elliptic Curve signature
|
||||
*/
|
||||
export interface ECSignature {
|
||||
v: number;
|
||||
r: string;
|
||||
s: string;
|
||||
}
|
||||
|
||||
export type OrderAddresses = [string, string, string, string, string];
|
||||
|
||||
export type OrderValues = [BigNumber, BigNumber, BigNumber, BigNumber, BigNumber, BigNumber];
|
||||
|
||||
export type LogEvent = Web3.LogEntryEvent;
|
||||
export interface DecodedLogEvent<ArgsType> {
|
||||
isRemoved: boolean;
|
||||
log: LogWithDecodedArgs<ArgsType>;
|
||||
}
|
||||
|
||||
export type EventCallback<ArgsType> = (err: null | Error, log?: DecodedLogEvent<ArgsType>) => void;
|
||||
export type EventWatcherCallback = (err: null | Error, log?: LogEvent) => void;
|
||||
|
||||
export enum ExchangeContractErrCodes {
|
||||
ERROR_FILL_EXPIRED, // Order has already expired
|
||||
ERROR_FILL_NO_VALUE, // Order has already been fully filled or cancelled
|
||||
ERROR_FILL_TRUNCATION, // Rounding error too large
|
||||
ERROR_FILL_BALANCE_ALLOWANCE, // Insufficient balance or allowance for token transfer
|
||||
ERROR_CANCEL_EXPIRED, // Order has already expired
|
||||
ERROR_CANCEL_NO_VALUE, // Order has already been fully filled or cancelled
|
||||
}
|
||||
|
||||
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',
|
||||
BatchOrdersMustHaveAtLeastOneItem = 'BATCH_ORDERS_MUST_HAVE_AT_LEAST_ONE_ITEM',
|
||||
}
|
||||
|
||||
export interface ContractEvent {
|
||||
logIndex: number;
|
||||
transactionIndex: number;
|
||||
transactionHash: string;
|
||||
blockHash: string;
|
||||
blockNumber: number;
|
||||
address: string;
|
||||
type: string;
|
||||
event: string;
|
||||
args: ContractEventArgs;
|
||||
}
|
||||
|
||||
export interface LogFillContractEventArgs {
|
||||
maker: string;
|
||||
taker: string;
|
||||
feeRecipient: string;
|
||||
makerToken: string;
|
||||
takerToken: string;
|
||||
filledMakerTokenAmount: BigNumber;
|
||||
filledTakerTokenAmount: BigNumber;
|
||||
paidMakerFee: BigNumber;
|
||||
paidTakerFee: BigNumber;
|
||||
tokens: string;
|
||||
orderHash: string;
|
||||
}
|
||||
export interface LogCancelContractEventArgs {
|
||||
maker: string;
|
||||
feeRecipient: string;
|
||||
makerToken: string;
|
||||
takerToken: string;
|
||||
cancelledMakerTokenAmount: BigNumber;
|
||||
cancelledTakerTokenAmount: BigNumber;
|
||||
tokens: string;
|
||||
orderHash: string;
|
||||
}
|
||||
export interface LogErrorContractEventArgs {
|
||||
errorId: BigNumber;
|
||||
orderHash: string;
|
||||
}
|
||||
export type ExchangeContractEventArgs =
|
||||
| LogFillContractEventArgs
|
||||
| LogCancelContractEventArgs
|
||||
| LogErrorContractEventArgs;
|
||||
export interface TransferContractEventArgs {
|
||||
_from: string;
|
||||
_to: string;
|
||||
_value: BigNumber;
|
||||
}
|
||||
export interface ApprovalContractEventArgs {
|
||||
_owner: string;
|
||||
_spender: string;
|
||||
_value: BigNumber;
|
||||
}
|
||||
export interface DepositContractEventArgs {
|
||||
_owner: string;
|
||||
_value: BigNumber;
|
||||
}
|
||||
export interface WithdrawalContractEventArgs {
|
||||
_owner: string;
|
||||
_value: BigNumber;
|
||||
}
|
||||
export type TokenContractEventArgs = TransferContractEventArgs | ApprovalContractEventArgs;
|
||||
export type EtherTokenContractEventArgs =
|
||||
| TokenContractEventArgs
|
||||
| DepositContractEventArgs
|
||||
| WithdrawalContractEventArgs;
|
||||
export type ContractEventArgs = ExchangeContractEventArgs | TokenContractEventArgs | EtherTokenContractEventArgs;
|
||||
|
||||
export interface Order {
|
||||
maker: string;
|
||||
taker: string;
|
||||
makerFee: BigNumber;
|
||||
takerFee: BigNumber;
|
||||
makerTokenAmount: BigNumber;
|
||||
takerTokenAmount: BigNumber;
|
||||
makerTokenAddress: string;
|
||||
takerTokenAddress: string;
|
||||
salt: BigNumber;
|
||||
exchangeContractAddress: string;
|
||||
feeRecipient: string;
|
||||
expirationUnixTimestampSec: BigNumber;
|
||||
}
|
||||
|
||||
export interface SignedOrder extends Order {
|
||||
ecSignature: ECSignature;
|
||||
}
|
||||
|
||||
// [address, name, symbol, decimals, ipfsHash, swarmHash]
|
||||
export type TokenMetadata = [string, string, string, BigNumber, string, string];
|
||||
|
||||
export interface Token {
|
||||
name: string;
|
||||
address: string;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
}
|
||||
|
||||
export interface TxOpts {
|
||||
from: string;
|
||||
gas?: number;
|
||||
value?: BigNumber;
|
||||
gasPrice?: BigNumber;
|
||||
}
|
||||
|
||||
export interface TokenAddressBySymbol {
|
||||
[symbol: string]: string;
|
||||
}
|
||||
|
||||
export enum ExchangeEvents {
|
||||
LogFill = 'LogFill',
|
||||
LogCancel = 'LogCancel',
|
||||
LogError = 'LogError',
|
||||
}
|
||||
|
||||
export enum TokenEvents {
|
||||
Transfer = 'Transfer',
|
||||
Approval = 'Approval',
|
||||
}
|
||||
|
||||
export enum EtherTokenEvents {
|
||||
Transfer = 'Transfer',
|
||||
Approval = 'Approval',
|
||||
Deposit = 'Deposit',
|
||||
Withdrawal = 'Withdrawal',
|
||||
}
|
||||
|
||||
export type ContractEvents = TokenEvents | ExchangeEvents | EtherTokenEvents;
|
||||
|
||||
export interface IndexedFilterValues {
|
||||
[index: string]: ContractEventArg;
|
||||
}
|
||||
|
||||
// Earliest is omitted by design. It is simply an alias for the `0` constant and
|
||||
// is thus not very helpful. Moreover, this type is used in places that only accept
|
||||
// `latest` or `pending`.
|
||||
export enum BlockParamLiteral {
|
||||
Latest = 'latest',
|
||||
Pending = 'pending',
|
||||
}
|
||||
|
||||
export type BlockParam = BlockParamLiteral | number;
|
||||
|
||||
export interface BlockRange {
|
||||
fromBlock: BlockParam;
|
||||
toBlock: BlockParam;
|
||||
}
|
||||
|
||||
export type DoneCallback = (err?: Error) => void;
|
||||
|
||||
export interface OrderCancellationRequest {
|
||||
order: Order | SignedOrder;
|
||||
takerTokenCancelAmount: BigNumber;
|
||||
}
|
||||
|
||||
export interface OrderFillRequest {
|
||||
signedOrder: SignedOrder;
|
||||
takerTokenFillAmount: BigNumber;
|
||||
}
|
||||
|
||||
export type AsyncMethod = (...args: any[]) => Promise<any>;
|
||||
export type SyncMethod = (...args: any[]) => any;
|
||||
|
||||
/**
|
||||
* 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. To learn more
|
||||
* about providers, visit https://0xproject.com/wiki#Web3-Provider-Explained
|
||||
*/
|
||||
export type Web3Provider = Web3.Provider;
|
||||
|
||||
/*
|
||||
* orderExpirationCheckingIntervalMs: How often to check for expired orders. Default: 50
|
||||
* eventPollingIntervalMs: How often to poll the Ethereum node for new events. Defaults: 200
|
||||
* expirationMarginMs: Amount of time before order expiry that you'd like to be notified
|
||||
* of an orders expiration. Defaults: 0
|
||||
* cleanupJobIntervalMs: How often to run a cleanup job which revalidates all the orders. Defaults: 1h
|
||||
*/
|
||||
export interface OrderStateWatcherConfig {
|
||||
orderExpirationCheckingIntervalMs?: number;
|
||||
eventPollingIntervalMs?: number;
|
||||
expirationMarginMs?: number;
|
||||
cleanupJobIntervalMs?: number;
|
||||
}
|
||||
|
||||
/*
|
||||
* networkId: The id of the underlying ethereum network your provider is connected to. (1-mainnet, 42-kovan, 50-testrpc)
|
||||
* gasPrice: Gas price to use with every transaction
|
||||
* exchangeContractAddress: The address of an exchange contract to use
|
||||
* zrxContractAddress: The address of the ZRX contract to use
|
||||
* tokenRegistryContractAddress: The address of a token registry contract to use
|
||||
* tokenTransferProxyContractAddress: The address of the token transfer proxy contract to use
|
||||
* orderWatcherConfig: All the configs related to the orderWatcher
|
||||
*/
|
||||
export interface ZeroExConfig {
|
||||
networkId: number;
|
||||
gasPrice?: BigNumber;
|
||||
exchangeContractAddress?: string;
|
||||
zrxContractAddress?: string;
|
||||
tokenRegistryContractAddress?: string;
|
||||
tokenTransferProxyContractAddress?: string;
|
||||
orderWatcherConfig?: OrderStateWatcherConfig;
|
||||
}
|
||||
|
||||
export type ArtifactContractName = 'ZRX' | 'TokenTransferProxy' | 'TokenRegistry' | 'Token' | 'Exchange' | 'EtherToken';
|
||||
|
||||
export interface Artifact {
|
||||
contract_name: ArtifactContractName;
|
||||
abi: Web3.ContractAbi;
|
||||
networks: {
|
||||
[networkId: number]: {
|
||||
address: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* expectedFillTakerTokenAmount: If specified, the validation method will ensure that the
|
||||
* supplied order maker has a sufficient allowance/balance to fill this amount of the order's
|
||||
* takerTokenAmount. If not specified, the validation method ensures that the maker has a sufficient
|
||||
* allowance/balance to fill the entire remaining order amount.
|
||||
*/
|
||||
export interface ValidateOrderFillableOpts {
|
||||
expectedFillTakerTokenAmount?: BigNumber;
|
||||
}
|
||||
|
||||
/*
|
||||
* defaultBlock: The block up to which to query the blockchain state. Setting this to a historical block number
|
||||
* let's the user query the blockchain's state at an arbitrary point in time. In order for this to work, the
|
||||
* backing Ethereum node must keep the entire historical state of the chain (e.g setting `--pruning=archive`
|
||||
* flag when running Parity).
|
||||
*/
|
||||
export interface MethodOpts {
|
||||
defaultBlock?: Web3.BlockParam;
|
||||
}
|
||||
|
||||
/*
|
||||
* gasPrice: Gas price in Wei to use for a transaction
|
||||
* gasLimit: The amount of gas to send with a transaction
|
||||
*/
|
||||
export interface TransactionOpts {
|
||||
gasPrice?: BigNumber;
|
||||
gasLimit?: number;
|
||||
}
|
||||
|
||||
/*
|
||||
* shouldValidate: Flag indicating whether the library should make attempts to validate a transaction before
|
||||
* broadcasting it. For example, order has a valid signature, maker has sufficient funds, etc. Default: true
|
||||
*/
|
||||
export interface OrderTransactionOpts extends TransactionOpts {
|
||||
shouldValidate?: boolean;
|
||||
}
|
||||
|
||||
export type FilterObject = Web3.FilterObject;
|
||||
|
||||
export enum TradeSide {
|
||||
Maker = 'maker',
|
||||
Taker = 'taker',
|
||||
}
|
||||
|
||||
export enum TransferType {
|
||||
Trade = 'trade',
|
||||
Fee = 'fee',
|
||||
}
|
||||
|
||||
export interface OrderRelevantState {
|
||||
makerBalance: BigNumber;
|
||||
makerProxyAllowance: BigNumber;
|
||||
makerFeeBalance: BigNumber;
|
||||
makerFeeProxyAllowance: BigNumber;
|
||||
filledTakerTokenAmount: BigNumber;
|
||||
cancelledTakerTokenAmount: BigNumber;
|
||||
remainingFillableMakerTokenAmount: BigNumber;
|
||||
remainingFillableTakerTokenAmount: BigNumber;
|
||||
}
|
||||
|
||||
export interface OrderStateValid {
|
||||
isValid: true;
|
||||
orderHash: string;
|
||||
orderRelevantState: OrderRelevantState;
|
||||
}
|
||||
|
||||
export interface OrderStateInvalid {
|
||||
isValid: false;
|
||||
orderHash: string;
|
||||
error: ExchangeContractErrs;
|
||||
}
|
||||
|
||||
export type OrderState = OrderStateValid | OrderStateInvalid;
|
||||
|
||||
export type OnOrderStateChangeCallback = (err: Error | null, orderState?: OrderState) => void;
|
||||
// tslint:disable:max-file-line-count
|
||||
35
packages/0x.js/src/utils/assert.ts
Normal file
35
packages/0x.js/src/utils/assert.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { assert as sharedAssert } from '@0xproject/assert';
|
||||
// We need those two unused imports because they're actually used by sharedAssert which gets injected here
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
import { Schema } from '@0xproject/json-schemas';
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ECSignature } from '../types';
|
||||
import { signatureUtils } from '../utils/signature_utils';
|
||||
|
||||
export const assert = {
|
||||
...sharedAssert,
|
||||
isValidSignature(orderHash: string, ecSignature: ECSignature, signerAddress: string) {
|
||||
const isValidSignature = signatureUtils.isValidSignature(orderHash, ecSignature, signerAddress);
|
||||
this.assert(isValidSignature, `Expected order with hash '${orderHash}' to have a valid signature`);
|
||||
},
|
||||
async isSenderAddressAsync(
|
||||
variableName: string,
|
||||
senderAddressHex: string,
|
||||
web3Wrapper: Web3Wrapper,
|
||||
): Promise<void> {
|
||||
sharedAssert.isETHAddressHex(variableName, senderAddressHex);
|
||||
const isSenderAddressAvailable = await web3Wrapper.isSenderAddressAvailableAsync(senderAddressHex);
|
||||
sharedAssert.assert(
|
||||
isSenderAddressAvailable,
|
||||
`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 provider');
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
|
||||
export const constants = {
|
||||
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
|
||||
@@ -6,6 +6,7 @@ export const constants = {
|
||||
MAX_DIGITS_IN_UNSIGNED_256_INT: 78,
|
||||
INVALID_JUMP_PATTERN: 'invalid JUMP at',
|
||||
OUT_OF_GAS_PATTERN: 'out of gas',
|
||||
INVALID_TAKER_FORMAT: 'instance.taker is not of a type(s) string',
|
||||
UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
|
||||
DEFAULT_BLOCK_POLLING_INTERVAL: 1000,
|
||||
};
|
||||
91
packages/0x.js/src/utils/decorators.ts
Normal file
91
packages/0x.js/src/utils/decorators.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { AsyncMethod, SyncMethod, ZeroExError } from '../types';
|
||||
|
||||
import { constants } from './constants';
|
||||
|
||||
type ErrorTransformer = (err: Error) => Error;
|
||||
|
||||
const contractCallErrorTransformer = (error: Error) => {
|
||||
if (_.includes(error.message, constants.INVALID_JUMP_PATTERN)) {
|
||||
return new Error(ZeroExError.InvalidJump);
|
||||
}
|
||||
if (_.includes(error.message, constants.OUT_OF_GAS_PATTERN)) {
|
||||
return new Error(ZeroExError.OutOfGas);
|
||||
}
|
||||
return error;
|
||||
};
|
||||
|
||||
const schemaErrorTransformer = (error: Error) => {
|
||||
if (_.includes(error.message, constants.INVALID_TAKER_FORMAT)) {
|
||||
const errMsg =
|
||||
'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS';
|
||||
return new Error(errMsg);
|
||||
}
|
||||
return error;
|
||||
};
|
||||
|
||||
/**
|
||||
* Source: https://stackoverflow.com/a/29837695/3546986
|
||||
*/
|
||||
const asyncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => {
|
||||
const asyncErrorHandlingDecorator = (
|
||||
target: object,
|
||||
key: string | symbol,
|
||||
descriptor: TypedPropertyDescriptor<AsyncMethod>,
|
||||
) => {
|
||||
const originalMethod = descriptor.value as AsyncMethod;
|
||||
|
||||
// Do not use arrow syntax here. Use a function expression in
|
||||
// order to use the correct value of `this` in this method
|
||||
// tslint:disable-next-line:only-arrow-functions
|
||||
descriptor.value = async function(...args: any[]) {
|
||||
try {
|
||||
const result = await originalMethod.apply(this, args);
|
||||
return result;
|
||||
} catch (error) {
|
||||
const transformedError = errorTransformer(error);
|
||||
throw transformedError;
|
||||
}
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
|
||||
return asyncErrorHandlingDecorator;
|
||||
};
|
||||
|
||||
const syncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => {
|
||||
const syncErrorHandlingDecorator = (
|
||||
target: object,
|
||||
key: string | symbol,
|
||||
descriptor: TypedPropertyDescriptor<SyncMethod>,
|
||||
) => {
|
||||
const originalMethod = descriptor.value as SyncMethod;
|
||||
|
||||
// Do not use arrow syntax here. Use a function expression in
|
||||
// order to use the correct value of `this` in this method
|
||||
// tslint:disable-next-line:only-arrow-functions
|
||||
descriptor.value = function(...args: any[]) {
|
||||
try {
|
||||
const result = originalMethod.apply(this, args);
|
||||
return result;
|
||||
} catch (error) {
|
||||
const transformedError = errorTransformer(error);
|
||||
throw transformedError;
|
||||
}
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
};
|
||||
|
||||
return syncErrorHandlingDecorator;
|
||||
};
|
||||
|
||||
// _.flow(f, g) = f ∘ g
|
||||
const zeroExErrorTransformer = _.flow(schemaErrorTransformer, contractCallErrorTransformer);
|
||||
|
||||
export const decorators = {
|
||||
asyncZeroExErrorHandler: asyncErrorHandlerFactory(zeroExErrorTransformer),
|
||||
syncZeroExErrorHandler: syncErrorHandlerFactory(zeroExErrorTransformer),
|
||||
};
|
||||
106
packages/0x.js/src/utils/exchange_transfer_simulator.ts
Normal file
106
packages/0x.js/src/utils/exchange_transfer_simulator.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { TokenWrapper } from '../contract_wrappers/token_wrapper';
|
||||
import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allowance_lazy_store';
|
||||
import { BlockParamLiteral, ExchangeContractErrs, TradeSide, TransferType } from '../types';
|
||||
|
||||
enum FailureReason {
|
||||
Balance = 'balance',
|
||||
ProxyAllowance = 'proxyAllowance',
|
||||
}
|
||||
|
||||
const ERR_MSG_MAPPING = {
|
||||
[FailureReason.Balance]: {
|
||||
[TradeSide.Maker]: {
|
||||
[TransferType.Trade]: ExchangeContractErrs.InsufficientMakerBalance,
|
||||
[TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeBalance,
|
||||
},
|
||||
[TradeSide.Taker]: {
|
||||
[TransferType.Trade]: ExchangeContractErrs.InsufficientTakerBalance,
|
||||
[TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeBalance,
|
||||
},
|
||||
},
|
||||
[FailureReason.ProxyAllowance]: {
|
||||
[TradeSide.Maker]: {
|
||||
[TransferType.Trade]: ExchangeContractErrs.InsufficientMakerAllowance,
|
||||
[TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeAllowance,
|
||||
},
|
||||
[TradeSide.Taker]: {
|
||||
[TransferType.Trade]: ExchangeContractErrs.InsufficientTakerAllowance,
|
||||
[TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeAllowance,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export class ExchangeTransferSimulator {
|
||||
private _store: BalanceAndProxyAllowanceLazyStore;
|
||||
private _UNLIMITED_ALLOWANCE_IN_BASE_UNITS: BigNumber;
|
||||
private static _throwValidationError(
|
||||
failureReason: FailureReason,
|
||||
tradeSide: TradeSide,
|
||||
transferType: TransferType,
|
||||
): never {
|
||||
const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType];
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) {
|
||||
this._store = new BalanceAndProxyAllowanceLazyStore(token, defaultBlock);
|
||||
this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS = token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
|
||||
}
|
||||
/**
|
||||
* Simulates transferFrom call performed by a proxy
|
||||
* @param tokenAddress Address of the token to be transferred
|
||||
* @param from Owner of the transferred tokens
|
||||
* @param to Recipient of the transferred tokens
|
||||
* @param amountInBaseUnits The amount of tokens being transferred
|
||||
* @param tradeSide Is Maker/Taker transferring
|
||||
* @param transferType Is it a fee payment or a value transfer
|
||||
*/
|
||||
public async transferFromAsync(
|
||||
tokenAddress: string,
|
||||
from: string,
|
||||
to: string,
|
||||
amountInBaseUnits: BigNumber,
|
||||
tradeSide: TradeSide,
|
||||
transferType: TransferType,
|
||||
): Promise<void> {
|
||||
const balance = await this._store.getBalanceAsync(tokenAddress, from);
|
||||
const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, from);
|
||||
if (proxyAllowance.lessThan(amountInBaseUnits)) {
|
||||
ExchangeTransferSimulator._throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType);
|
||||
}
|
||||
if (balance.lessThan(amountInBaseUnits)) {
|
||||
ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType);
|
||||
}
|
||||
await this._decreaseProxyAllowanceAsync(tokenAddress, from, amountInBaseUnits);
|
||||
await this._decreaseBalanceAsync(tokenAddress, from, amountInBaseUnits);
|
||||
await this._increaseBalanceAsync(tokenAddress, to, amountInBaseUnits);
|
||||
}
|
||||
private async _decreaseProxyAllowanceAsync(
|
||||
tokenAddress: string,
|
||||
userAddress: string,
|
||||
amountInBaseUnits: BigNumber,
|
||||
): Promise<void> {
|
||||
const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, userAddress);
|
||||
if (!proxyAllowance.eq(this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
|
||||
this._store.setProxyAllowance(tokenAddress, userAddress, proxyAllowance.minus(amountInBaseUnits));
|
||||
}
|
||||
}
|
||||
private async _increaseBalanceAsync(
|
||||
tokenAddress: string,
|
||||
userAddress: string,
|
||||
amountInBaseUnits: BigNumber,
|
||||
): Promise<void> {
|
||||
const balance = await this._store.getBalanceAsync(tokenAddress, userAddress);
|
||||
this._store.setBalance(tokenAddress, userAddress, balance.plus(amountInBaseUnits));
|
||||
}
|
||||
private async _decreaseBalanceAsync(
|
||||
tokenAddress: string,
|
||||
userAddress: string,
|
||||
amountInBaseUnits: BigNumber,
|
||||
): Promise<void> {
|
||||
const balance = await this._store.getBalanceAsync(tokenAddress, userAddress);
|
||||
this._store.setBalance(tokenAddress, userAddress, balance.minus(amountInBaseUnits));
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as Web3 from 'web3';
|
||||
import * as uuid from 'uuid/v4';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import * as jsSHA3 from 'js-sha3';
|
||||
import {ContractEvents, IndexedFilterValues, SubscriptionOpts} from '../types';
|
||||
import * as _ from 'lodash';
|
||||
import * as uuid from 'uuid/v4';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
import { BlockRange, ContractEvents, IndexedFilterValues } from '../types';
|
||||
|
||||
const TOPIC_LENGTH = 32;
|
||||
|
||||
@@ -11,10 +12,14 @@ export const filterUtils = {
|
||||
generateUUID(): string {
|
||||
return uuid();
|
||||
},
|
||||
getFilter(address: string, eventName: ContractEvents,
|
||||
indexFilterValues: IndexedFilterValues, abi: Web3.ContractAbi,
|
||||
subscriptionOpts?: SubscriptionOpts): Web3.FilterObject {
|
||||
const eventAbi = _.find(abi, {name: eventName}) as Web3.EventAbi;
|
||||
getFilter(
|
||||
address: string,
|
||||
eventName: ContractEvents,
|
||||
indexFilterValues: IndexedFilterValues,
|
||||
abi: Web3.ContractAbi,
|
||||
blockRange?: BlockRange,
|
||||
): Web3.FilterObject {
|
||||
const eventAbi = _.find(abi, { name: eventName }) as Web3.EventAbi;
|
||||
const eventSignature = filterUtils.getEventSignatureFromAbiByName(eventAbi, eventName);
|
||||
const topicForEventSignature = ethUtil.addHexPrefix(jsSHA3.keccak256(eventSignature));
|
||||
const topicsForIndexedArgs = filterUtils.getTopicsForIndexedArgs(eventAbi, indexFilterValues);
|
||||
@@ -23,9 +28,9 @@ export const filterUtils = {
|
||||
address,
|
||||
topics,
|
||||
};
|
||||
if (!_.isUndefined(subscriptionOpts)) {
|
||||
if (!_.isUndefined(blockRange)) {
|
||||
filter = {
|
||||
...subscriptionOpts,
|
||||
...blockRange,
|
||||
...filter,
|
||||
};
|
||||
}
|
||||
@@ -36,8 +41,8 @@ export const filterUtils = {
|
||||
const signature = `${eventAbi.name}(${types.join(',')})`;
|
||||
return signature;
|
||||
},
|
||||
getTopicsForIndexedArgs(abi: Web3.EventAbi, indexFilterValues: IndexedFilterValues): Array<string|null> {
|
||||
const topics: Array<string|null> = [];
|
||||
getTopicsForIndexedArgs(abi: Web3.EventAbi, indexFilterValues: IndexedFilterValues): Array<string | null> {
|
||||
const topics: Array<string | null> = [];
|
||||
for (const eventInput of abi.inputs) {
|
||||
if (!eventInput.indexed) {
|
||||
continue;
|
||||
@@ -64,12 +69,12 @@ export const filterUtils = {
|
||||
}
|
||||
return true;
|
||||
},
|
||||
matchesTopics(logTopics: string[], filterTopics: Array<string[]|string|null>): boolean {
|
||||
matchesTopics(logTopics: string[], filterTopics: Array<string[] | string | null>): boolean {
|
||||
const matchesTopic = _.zipWith(logTopics, filterTopics, filterUtils.matchesTopic.bind(filterUtils));
|
||||
const matchesTopics = _.every(matchesTopic);
|
||||
return matchesTopics;
|
||||
},
|
||||
matchesTopic(logTopic: string, filterTopic: string[]|string|null): boolean {
|
||||
matchesTopic(logTopic: string, filterTopic: string[] | string | null): boolean {
|
||||
if (_.isArray(filterTopic)) {
|
||||
return _.includes(filterTopic, logTopic);
|
||||
}
|
||||
144
packages/0x.js/src/utils/order_state_utils.ts
Normal file
144
packages/0x.js/src/utils/order_state_utils.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ZeroEx } from '../0x';
|
||||
import { ExchangeWrapper } from '../contract_wrappers/exchange_wrapper';
|
||||
import { RemainingFillableCalculator } from '../order_watcher/remaining_fillable_calculator';
|
||||
import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allowance_lazy_store';
|
||||
import { OrderFilledCancelledLazyStore } from '../stores/order_filled_cancelled_lazy_store';
|
||||
import {
|
||||
ExchangeContractErrs,
|
||||
OrderRelevantState,
|
||||
OrderState,
|
||||
OrderStateInvalid,
|
||||
OrderStateValid,
|
||||
SignedOrder,
|
||||
} from '../types';
|
||||
|
||||
const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001;
|
||||
|
||||
export class OrderStateUtils {
|
||||
private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore;
|
||||
private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore;
|
||||
private static _validateIfOrderIsValid(signedOrder: SignedOrder, orderRelevantState: OrderRelevantState): void {
|
||||
const unavailableTakerTokenAmount = orderRelevantState.cancelledTakerTokenAmount.add(
|
||||
orderRelevantState.filledTakerTokenAmount,
|
||||
);
|
||||
const availableTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
|
||||
if (availableTakerTokenAmount.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
}
|
||||
|
||||
if (orderRelevantState.makerBalance.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientMakerBalance);
|
||||
}
|
||||
if (orderRelevantState.makerProxyAllowance.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientMakerAllowance);
|
||||
}
|
||||
if (!signedOrder.makerFee.eq(0)) {
|
||||
if (orderRelevantState.makerFeeBalance.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientMakerFeeBalance);
|
||||
}
|
||||
if (orderRelevantState.makerFeeProxyAllowance.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance);
|
||||
}
|
||||
}
|
||||
const minFillableTakerTokenAmountWithinNoRoundingErrorRange = signedOrder.takerTokenAmount
|
||||
.dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
|
||||
.dividedBy(signedOrder.makerTokenAmount);
|
||||
if (
|
||||
orderRelevantState.remainingFillableTakerTokenAmount.lessThan(
|
||||
minFillableTakerTokenAmountWithinNoRoundingErrorRange,
|
||||
)
|
||||
) {
|
||||
throw new Error(ExchangeContractErrs.OrderFillRoundingError);
|
||||
}
|
||||
}
|
||||
constructor(
|
||||
balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore,
|
||||
orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore,
|
||||
) {
|
||||
this._balanceAndProxyAllowanceLazyStore = balanceAndProxyAllowanceLazyStore;
|
||||
this._orderFilledCancelledLazyStore = orderFilledCancelledLazyStore;
|
||||
}
|
||||
public async getOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> {
|
||||
const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
try {
|
||||
OrderStateUtils._validateIfOrderIsValid(signedOrder, orderRelevantState);
|
||||
const orderState: OrderStateValid = {
|
||||
isValid: true,
|
||||
orderHash,
|
||||
orderRelevantState,
|
||||
};
|
||||
return orderState;
|
||||
} catch (err) {
|
||||
const orderState: OrderStateInvalid = {
|
||||
isValid: false,
|
||||
orderHash,
|
||||
error: err.message,
|
||||
};
|
||||
return orderState;
|
||||
}
|
||||
}
|
||||
public async getOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> {
|
||||
// HACK: We access the private property here but otherwise the interface will be less nice.
|
||||
// If we pass it from the instantiator - there is no opportunity to get it there
|
||||
// because JS doesn't support async constructors.
|
||||
// Moreover - it's cached under the hood so it's equivalent to an async constructor.
|
||||
const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper;
|
||||
const zrxTokenAddress = exchange.getZRXTokenAddress();
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
const makerBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync(
|
||||
signedOrder.makerTokenAddress,
|
||||
signedOrder.maker,
|
||||
);
|
||||
const makerProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync(
|
||||
signedOrder.makerTokenAddress,
|
||||
signedOrder.maker,
|
||||
);
|
||||
const makerFeeBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync(
|
||||
zrxTokenAddress,
|
||||
signedOrder.maker,
|
||||
);
|
||||
const makerFeeProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync(
|
||||
zrxTokenAddress,
|
||||
signedOrder.maker,
|
||||
);
|
||||
const filledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getFilledTakerAmountAsync(orderHash);
|
||||
const cancelledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getCancelledTakerAmountAsync(
|
||||
orderHash,
|
||||
);
|
||||
const unavailableTakerTokenAmount = await exchange.getUnavailableTakerAmountAsync(orderHash);
|
||||
const totalMakerTokenAmount = signedOrder.makerTokenAmount;
|
||||
const totalTakerTokenAmount = signedOrder.takerTokenAmount;
|
||||
const remainingTakerTokenAmount = totalTakerTokenAmount.minus(unavailableTakerTokenAmount);
|
||||
const remainingMakerTokenAmount = remainingTakerTokenAmount
|
||||
.times(totalMakerTokenAmount)
|
||||
.dividedToIntegerBy(totalTakerTokenAmount);
|
||||
const transferrableMakerTokenAmount = BigNumber.min([makerProxyAllowance, makerBalance]);
|
||||
const transferrableFeeTokenAmount = BigNumber.min([makerFeeProxyAllowance, makerFeeBalance]);
|
||||
|
||||
const isMakerTokenZRX = signedOrder.makerTokenAddress === zrxTokenAddress;
|
||||
const remainingFillableCalculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
const remainingFillableMakerTokenAmount = remainingFillableCalculator.computeRemainingMakerFillable();
|
||||
const remainingFillableTakerTokenAmount = remainingFillableCalculator.computeRemainingTakerFillable();
|
||||
const orderRelevantState = {
|
||||
makerBalance,
|
||||
makerProxyAllowance,
|
||||
makerFeeBalance,
|
||||
makerFeeProxyAllowance,
|
||||
filledTakerTokenAmount,
|
||||
cancelledTakerTokenAmount,
|
||||
remainingFillableMakerTokenAmount,
|
||||
remainingFillableTakerTokenAmount,
|
||||
};
|
||||
return orderRelevantState;
|
||||
}
|
||||
}
|
||||
216
packages/0x.js/src/utils/order_validation_utils.ts
Normal file
216
packages/0x.js/src/utils/order_validation_utils.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { ZeroEx } from '../0x';
|
||||
import { ExchangeWrapper } from '../contract_wrappers/exchange_wrapper';
|
||||
import { ExchangeContractErrs, Order, SignedOrder, TradeSide, TransferType, ZeroExError } from '../types';
|
||||
import { constants } from '../utils/constants';
|
||||
import { utils } from '../utils/utils';
|
||||
|
||||
import { ExchangeTransferSimulator } from './exchange_transfer_simulator';
|
||||
|
||||
export class OrderValidationUtils {
|
||||
private _exchangeWrapper: ExchangeWrapper;
|
||||
public static validateCancelOrderThrowIfInvalid(
|
||||
order: Order,
|
||||
cancelTakerTokenAmount: BigNumber,
|
||||
unavailableTakerTokenAmount: BigNumber,
|
||||
): void {
|
||||
if (cancelTakerTokenAmount.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.OrderCancelAmountZero);
|
||||
}
|
||||
if (order.takerTokenAmount.eq(unavailableTakerTokenAmount)) {
|
||||
throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
|
||||
}
|
||||
const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
|
||||
if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
|
||||
throw new Error(ExchangeContractErrs.OrderCancelExpired);
|
||||
}
|
||||
}
|
||||
public static async validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator: ExchangeTransferSimulator,
|
||||
signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber,
|
||||
senderAddress: string,
|
||||
zrxTokenAddress: string,
|
||||
): Promise<void> {
|
||||
const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount(
|
||||
fillTakerTokenAmount,
|
||||
signedOrder.takerTokenAmount,
|
||||
signedOrder.makerTokenAmount,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
signedOrder.makerTokenAddress,
|
||||
signedOrder.maker,
|
||||
senderAddress,
|
||||
fillMakerTokenAmount,
|
||||
TradeSide.Maker,
|
||||
TransferType.Trade,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
signedOrder.takerTokenAddress,
|
||||
senderAddress,
|
||||
signedOrder.maker,
|
||||
fillTakerTokenAmount,
|
||||
TradeSide.Taker,
|
||||
TransferType.Trade,
|
||||
);
|
||||
const makerFeeAmount = OrderValidationUtils._getPartialAmount(
|
||||
fillTakerTokenAmount,
|
||||
signedOrder.takerTokenAmount,
|
||||
signedOrder.makerFee,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
zrxTokenAddress,
|
||||
signedOrder.maker,
|
||||
signedOrder.feeRecipient,
|
||||
makerFeeAmount,
|
||||
TradeSide.Maker,
|
||||
TransferType.Fee,
|
||||
);
|
||||
const takerFeeAmount = OrderValidationUtils._getPartialAmount(
|
||||
fillTakerTokenAmount,
|
||||
signedOrder.takerTokenAmount,
|
||||
signedOrder.takerFee,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
zrxTokenAddress,
|
||||
senderAddress,
|
||||
signedOrder.feeRecipient,
|
||||
takerFeeAmount,
|
||||
TradeSide.Taker,
|
||||
TransferType.Fee,
|
||||
);
|
||||
}
|
||||
private static _validateRemainingFillAmountNotZeroOrThrow(
|
||||
takerTokenAmount: BigNumber,
|
||||
unavailableTakerTokenAmount: BigNumber,
|
||||
) {
|
||||
if (takerTokenAmount.eq(unavailableTakerTokenAmount)) {
|
||||
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
}
|
||||
}
|
||||
private static _validateOrderNotExpiredOrThrow(expirationUnixTimestampSec: BigNumber) {
|
||||
const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
|
||||
if (expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
|
||||
throw new Error(ExchangeContractErrs.OrderFillExpired);
|
||||
}
|
||||
}
|
||||
private static _getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber {
|
||||
const fillMakerTokenAmount = numerator
|
||||
.mul(target)
|
||||
.div(denominator)
|
||||
.round(0);
|
||||
return fillMakerTokenAmount;
|
||||
}
|
||||
constructor(exchangeWrapper: ExchangeWrapper) {
|
||||
this._exchangeWrapper = exchangeWrapper;
|
||||
}
|
||||
public async validateOrderFillableOrThrowAsync(
|
||||
exchangeTradeEmulator: ExchangeTransferSimulator,
|
||||
signedOrder: SignedOrder,
|
||||
zrxTokenAddress: string,
|
||||
expectedFillTakerTokenAmount?: BigNumber,
|
||||
): Promise<void> {
|
||||
const orderHash = utils.getOrderHashHex(signedOrder);
|
||||
const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
|
||||
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
|
||||
signedOrder.takerTokenAmount,
|
||||
unavailableTakerTokenAmount,
|
||||
);
|
||||
OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
|
||||
let fillTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
|
||||
if (!_.isUndefined(expectedFillTakerTokenAmount)) {
|
||||
fillTakerTokenAmount = expectedFillTakerTokenAmount;
|
||||
}
|
||||
const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount(
|
||||
fillTakerTokenAmount,
|
||||
signedOrder.takerTokenAmount,
|
||||
signedOrder.makerTokenAmount,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
signedOrder.makerTokenAddress,
|
||||
signedOrder.maker,
|
||||
signedOrder.taker,
|
||||
fillMakerTokenAmount,
|
||||
TradeSide.Maker,
|
||||
TransferType.Trade,
|
||||
);
|
||||
const makerFeeAmount = OrderValidationUtils._getPartialAmount(
|
||||
fillTakerTokenAmount,
|
||||
signedOrder.takerTokenAmount,
|
||||
signedOrder.makerFee,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
zrxTokenAddress,
|
||||
signedOrder.maker,
|
||||
signedOrder.feeRecipient,
|
||||
makerFeeAmount,
|
||||
TradeSide.Maker,
|
||||
TransferType.Fee,
|
||||
);
|
||||
}
|
||||
public async validateFillOrderThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator: ExchangeTransferSimulator,
|
||||
signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber,
|
||||
takerAddress: string,
|
||||
zrxTokenAddress: string,
|
||||
): Promise<BigNumber> {
|
||||
if (fillTakerTokenAmount.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.OrderFillAmountZero);
|
||||
}
|
||||
const orderHash = utils.getOrderHashHex(signedOrder);
|
||||
if (!ZeroEx.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker)) {
|
||||
throw new Error(ZeroExError.InvalidSignature);
|
||||
}
|
||||
const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
|
||||
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
|
||||
signedOrder.takerTokenAmount,
|
||||
unavailableTakerTokenAmount,
|
||||
);
|
||||
if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== takerAddress) {
|
||||
throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
|
||||
}
|
||||
OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
|
||||
const remainingTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
|
||||
const filledTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerTokenAmount)
|
||||
? remainingTakerTokenAmount
|
||||
: fillTakerTokenAmount;
|
||||
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator,
|
||||
signedOrder,
|
||||
filledTakerTokenAmount,
|
||||
takerAddress,
|
||||
zrxTokenAddress,
|
||||
);
|
||||
|
||||
const wouldRoundingErrorOccur = await this._exchangeWrapper.isRoundingErrorAsync(
|
||||
filledTakerTokenAmount,
|
||||
signedOrder.takerTokenAmount,
|
||||
signedOrder.makerTokenAmount,
|
||||
);
|
||||
if (wouldRoundingErrorOccur) {
|
||||
throw new Error(ExchangeContractErrs.OrderFillRoundingError);
|
||||
}
|
||||
return filledTakerTokenAmount;
|
||||
}
|
||||
public async validateFillOrKillOrderThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator: ExchangeTransferSimulator,
|
||||
signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber,
|
||||
takerAddress: string,
|
||||
zrxTokenAddress: string,
|
||||
): Promise<void> {
|
||||
const filledTakerTokenAmount = await this.validateFillOrderThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator,
|
||||
signedOrder,
|
||||
fillTakerTokenAmount,
|
||||
takerAddress,
|
||||
zrxTokenAddress,
|
||||
);
|
||||
if (filledTakerTokenAmount !== fillTakerTokenAmount) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,24 @@
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import {ECSignature} from '../types';
|
||||
|
||||
import { ECSignature } from '../types';
|
||||
|
||||
export const signatureUtils = {
|
||||
isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
|
||||
const dataBuff = ethUtil.toBuffer(data);
|
||||
const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
|
||||
try {
|
||||
const pubKey = ethUtil.ecrecover(
|
||||
msgHashBuff,
|
||||
signature.v,
|
||||
ethUtil.toBuffer(signature.r),
|
||||
ethUtil.toBuffer(signature.s),
|
||||
);
|
||||
const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey));
|
||||
return retrievedAddress === signerAddress;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
parseSignatureHexAsVRS(signatureHex: string): ECSignature {
|
||||
const signatureBuffer = ethUtil.toBuffer(signatureHex);
|
||||
let v = signatureBuffer[0];
|
||||
@@ -18,7 +35,7 @@ export const signatureUtils = {
|
||||
return ecSignature;
|
||||
},
|
||||
parseSignatureHexAsRSV(signatureHex: string): ECSignature {
|
||||
const {v, r, s} = ethUtil.fromRpcSig(signatureHex);
|
||||
const { v, r, s } = ethUtil.fromRpcSig(signatureHex);
|
||||
const ecSignature: ECSignature = {
|
||||
v,
|
||||
r: ethUtil.bufferToHex(r),
|
||||
69
packages/0x.js/src/utils/utils.ts
Normal file
69
packages/0x.js/src/utils/utils.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { SolidityTypes } from '@0xproject/types';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import BN = require('bn.js');
|
||||
import * as ethABI from 'ethereumjs-abi';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { Order, SignedOrder } from '../types';
|
||||
|
||||
export const utils = {
|
||||
/**
|
||||
* Converts BigNumber instance to BN
|
||||
* The only reason we convert to BN is to remain compatible with `ethABI. soliditySHA3` that
|
||||
* expects values of Solidity type `uint` to be passed as type `BN`.
|
||||
* We do not use BN anywhere else in the codebase.
|
||||
*/
|
||||
bigNumberToBN(value: BigNumber) {
|
||||
return new BN(value.toString(), 10);
|
||||
},
|
||||
consoleLog(message: string): void {
|
||||
// tslint:disable-next-line: no-console
|
||||
console.log(message);
|
||||
},
|
||||
spawnSwitchErr(name: string, value: any): Error {
|
||||
return new Error(`Unexpected switch value: ${value} encountered for ${name}`);
|
||||
},
|
||||
getOrderHashHex(order: Order | SignedOrder): string {
|
||||
const orderParts = [
|
||||
{ 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);
|
||||
const hashBuff = ethABI.soliditySHA3(types, values);
|
||||
const hashHex = ethUtil.bufferToHex(hashBuff);
|
||||
return hashHex;
|
||||
},
|
||||
getCurrentUnixTimestampSec(): BigNumber {
|
||||
return new BigNumber(Date.now() / 1000).round();
|
||||
},
|
||||
getCurrentUnixTimestampMs(): BigNumber {
|
||||
return new BigNumber(Date.now());
|
||||
},
|
||||
};
|
||||
@@ -1,22 +1,28 @@
|
||||
import * as _ from 'lodash';
|
||||
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import {chaiSetup} from './utils/chai_setup';
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import * as Sinon from 'sinon';
|
||||
import {ZeroEx, Order, ZeroExError, LogWithDecodedArgs} from '../src';
|
||||
import {constants} from './utils/constants';
|
||||
import {TokenUtils} from './utils/token_utils';
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||
|
||||
import { ApprovalContractEventArgs, LogWithDecodedArgs, Order, TokenEvents, ZeroEx } from '../src';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
import { TokenUtils } from './utils/token_utils';
|
||||
|
||||
const blockchainLifecycle = new BlockchainLifecycle();
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false;
|
||||
|
||||
describe('ZeroEx library', () => {
|
||||
const web3 = web3Factory.create();
|
||||
const zeroEx = new ZeroEx(web3.currentProvider);
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
const zeroEx = new ZeroEx(web3.currentProvider, config);
|
||||
describe('#setProvider', () => {
|
||||
it('overrides provider in nested web3s and invalidates contractInstances', async () => {
|
||||
// Instantiate the contract instances with the current provider
|
||||
@@ -28,7 +34,7 @@ describe('ZeroEx library', () => {
|
||||
const newProvider = web3Factory.getRpcProvider();
|
||||
// Add property to newProvider so that we can differentiate it from old provider
|
||||
(newProvider as any).zeroExTestId = 1;
|
||||
await zeroEx.setProviderAsync(newProvider);
|
||||
zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID);
|
||||
|
||||
// Check that contractInstances with old provider are removed after provider update
|
||||
expect((zeroEx.exchange as any)._exchangeContractIfExists).to.be.undefined();
|
||||
@@ -36,11 +42,11 @@ describe('ZeroEx library', () => {
|
||||
|
||||
// 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');
|
||||
expect(nestedWeb3WrapperProvider.zeroExTestId).to.be.a('number');
|
||||
const exchangeWeb3WrapperProvider = (zeroEx.exchange as any)._web3Wrapper.getCurrentProvider();
|
||||
expect((exchangeWeb3WrapperProvider as any).zeroExTestId).to.be.a('number');
|
||||
expect(exchangeWeb3WrapperProvider.zeroExTestId).to.be.a('number');
|
||||
const tokenRegistryWeb3WrapperProvider = (zeroEx.tokenRegistry as any)._web3Wrapper.getCurrentProvider();
|
||||
expect((tokenRegistryWeb3WrapperProvider as any).zeroExTestId).to.be.a('number');
|
||||
expect(tokenRegistryWeb3WrapperProvider.zeroExTestId).to.be.a('number');
|
||||
});
|
||||
});
|
||||
describe('#isValidSignature', () => {
|
||||
@@ -53,22 +59,25 @@ describe('ZeroEx library', () => {
|
||||
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
|
||||
};
|
||||
const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
|
||||
it('should return false if the data doesn\'t pertain to the signature & address', async () => {
|
||||
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(
|
||||
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync('0x0', signature, address),
|
||||
).to.become(false);
|
||||
});
|
||||
it('should return false if the address doesn\'t pertain to the signature & data', async () => {
|
||||
it("should return false if the address doesn't pertain to the signature & data", async () => {
|
||||
const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42';
|
||||
expect(ZeroEx.isValidSignature(dataHex, signature, validUnrelatedAddress)).to.be.false();
|
||||
return expect(
|
||||
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, signature,
|
||||
validUnrelatedAddress),
|
||||
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(
|
||||
dataHex,
|
||||
signature,
|
||||
validUnrelatedAddress,
|
||||
),
|
||||
).to.become(false);
|
||||
});
|
||||
it('should return false if the signature doesn\'t pertain to the dataHex & address', async () => {
|
||||
const wrongSignature = _.assign({}, signature, {v: 28});
|
||||
it("should return false if the signature doesn't pertain to the dataHex & address", async () => {
|
||||
const wrongSignature = _.assign({}, signature, { v: 28 });
|
||||
expect(ZeroEx.isValidSignature(dataHex, wrongSignature, address)).to.be.false();
|
||||
return expect(
|
||||
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, wrongSignature, address),
|
||||
@@ -77,9 +86,9 @@ describe('ZeroEx library', () => {
|
||||
it('should return true if the signature does pertain to the dataHex & address', async () => {
|
||||
const isValidSignatureLocal = ZeroEx.isValidSignature(dataHex, signature, address);
|
||||
expect(isValidSignatureLocal).to.be.true();
|
||||
const isValidSignatureOnContract = await (zeroEx.exchange as any)
|
||||
._isValidSignatureUsingContractCallAsync(dataHex, signature, address);
|
||||
return expect(isValidSignatureOnContract).to.be.true();
|
||||
return expect(
|
||||
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, signature, address),
|
||||
).to.become(true);
|
||||
});
|
||||
});
|
||||
describe('#generateSalt', () => {
|
||||
@@ -109,6 +118,13 @@ describe('ZeroEx library', () => {
|
||||
});
|
||||
});
|
||||
describe('#toUnitAmount', () => {
|
||||
it('should throw if invalid baseUnit amount supplied as argument', () => {
|
||||
const invalidBaseUnitAmount = new BigNumber(1000000000.4);
|
||||
const decimals = 6;
|
||||
expect(() => ZeroEx.toUnitAmount(invalidBaseUnitAmount, decimals)).to.throw(
|
||||
'amount should be in baseUnits (no decimals), found value: 1000000000.4',
|
||||
);
|
||||
});
|
||||
it('Should return the expected unit amount for the decimals passed in', () => {
|
||||
const baseUnitAmount = new BigNumber(1000000000);
|
||||
const decimals = 6;
|
||||
@@ -125,6 +141,13 @@ describe('ZeroEx library', () => {
|
||||
const expectedUnitAmount = new BigNumber(1000000000);
|
||||
expect(baseUnitAmount).to.be.bignumber.equal(expectedUnitAmount);
|
||||
});
|
||||
it('should throw if unitAmount has more decimals then specified as the max decimal precision', () => {
|
||||
const unitAmount = new BigNumber(0.823091);
|
||||
const decimals = 5;
|
||||
expect(() => ZeroEx.toBaseUnitAmount(unitAmount, decimals)).to.throw(
|
||||
'Invalid unit amount: 0.823091 - Too many decimal places',
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('#getOrderHashHex', () => {
|
||||
const expectedOrderHash = '0x39da987067a3c9e5f1617694f1301326ba8c8b0498ebef5df4863bed394e3c83';
|
||||
@@ -147,6 +170,15 @@ describe('ZeroEx library', () => {
|
||||
const orderHash = ZeroEx.getOrderHashHex(order);
|
||||
expect(orderHash).to.be.equal(expectedOrderHash);
|
||||
});
|
||||
it('throws a readable error message if taker format is invalid', async () => {
|
||||
const orderWithInvalidtakerFormat = {
|
||||
...order,
|
||||
taker: (null as any) as string,
|
||||
};
|
||||
const expectedErrorMessage =
|
||||
'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS';
|
||||
expect(() => ZeroEx.getOrderHashHex(orderWithInvalidtakerFormat)).to.throw(expectedErrorMessage);
|
||||
});
|
||||
});
|
||||
describe('#signOrderHashAsync', () => {
|
||||
let stubs: Sinon.SinonStub[] = [];
|
||||
@@ -167,43 +199,53 @@ describe('ZeroEx library', () => {
|
||||
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
|
||||
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
|
||||
};
|
||||
const ecSignature = await zeroEx.signOrderHashAsync(orderHash, makerAddress);
|
||||
const ecSignature = await zeroEx.signOrderHashAsync(
|
||||
orderHash,
|
||||
makerAddress,
|
||||
SHOULD_ADD_PERSONAL_MESSAGE_PREFIX,
|
||||
);
|
||||
expect(ecSignature).to.deep.equal(expectedECSignature);
|
||||
});
|
||||
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';
|
||||
const signature =
|
||||
'0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb021b';
|
||||
const expectedECSignature = {
|
||||
v: 27,
|
||||
r: '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3',
|
||||
s: '0x050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb02',
|
||||
};
|
||||
stubs = [
|
||||
Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync')
|
||||
.returns(Promise.resolve(signature)),
|
||||
Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync').returns(Promise.resolve(signature)),
|
||||
Sinon.stub(ZeroEx, 'isValidSignature').returns(true),
|
||||
];
|
||||
|
||||
const ecSignature = await zeroEx.signOrderHashAsync(orderHash, makerAddress);
|
||||
const ecSignature = await zeroEx.signOrderHashAsync(
|
||||
orderHash,
|
||||
makerAddress,
|
||||
SHOULD_ADD_PERSONAL_MESSAGE_PREFIX,
|
||||
);
|
||||
expect(ecSignature).to.deep.equal(expectedECSignature);
|
||||
});
|
||||
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';
|
||||
const signature =
|
||||
'0x1bc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee02dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960';
|
||||
const expectedECSignature = {
|
||||
v: 27,
|
||||
r: '0xc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee0',
|
||||
s: '0x2dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960',
|
||||
};
|
||||
stubs = [
|
||||
Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync')
|
||||
.returns(Promise.resolve(signature)),
|
||||
Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync').returns(Promise.resolve(signature)),
|
||||
Sinon.stub(ZeroEx, 'isValidSignature').returns(true),
|
||||
];
|
||||
|
||||
const ecSignature = await zeroEx.signOrderHashAsync(orderHash, makerAddress);
|
||||
const ecSignature = await zeroEx.signOrderHashAsync(
|
||||
orderHash,
|
||||
makerAddress,
|
||||
SHOULD_ADD_PERSONAL_MESSAGE_PREFIX,
|
||||
);
|
||||
expect(ecSignature).to.deep.equal(expectedECSignature);
|
||||
});
|
||||
});
|
||||
@@ -220,11 +262,11 @@ describe('ZeroEx library', () => {
|
||||
const tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
const tokenUtils = new TokenUtils(tokens);
|
||||
const zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
|
||||
const proxyAddress = await zeroEx.proxy.getContractAddressAsync();
|
||||
const proxyAddress = zeroEx.proxy.getContractAddress();
|
||||
const txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(zrxTokenAddress, coinbase);
|
||||
const txReceiptWithDecodedLogs = await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const log = txReceiptWithDecodedLogs.logs[0] as LogWithDecodedArgs;
|
||||
expect(log.event).to.be.equal('Approval');
|
||||
const log = txReceiptWithDecodedLogs.logs[0] as LogWithDecodedArgs<ApprovalContractEventArgs>;
|
||||
expect(log.event).to.be.equal(TokenEvents.Approval);
|
||||
expect(log.args._owner).to.be.equal(coinbase);
|
||||
expect(log.args._spender).to.be.equal(proxyAddress);
|
||||
expect(log.args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
@@ -232,28 +274,22 @@ describe('ZeroEx library', () => {
|
||||
});
|
||||
describe('#config', () => {
|
||||
it('allows to specify exchange contract address', async () => {
|
||||
const config = {
|
||||
const zeroExConfig = {
|
||||
exchangeContractAddress: ZeroEx.NULL_ADDRESS,
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
const zeroExWithWrongExchangeAddress = new ZeroEx(web3.currentProvider, config);
|
||||
return expect(zeroExWithWrongExchangeAddress.exchange.getContractAddressAsync())
|
||||
.to.be.rejectedWith(ZeroExError.ContractDoesNotExist);
|
||||
});
|
||||
it('allows to specify ether token contract address', async () => {
|
||||
const config = {
|
||||
etherTokenContractAddress: ZeroEx.NULL_ADDRESS,
|
||||
};
|
||||
const zeroExWithWrongEtherTokenAddress = new ZeroEx(web3.currentProvider, config);
|
||||
return expect(zeroExWithWrongEtherTokenAddress.etherToken.getContractAddressAsync())
|
||||
.to.be.rejectedWith(ZeroExError.ContractDoesNotExist);
|
||||
const zeroExWithWrongExchangeAddress = new ZeroEx(web3.currentProvider, zeroExConfig);
|
||||
expect(zeroExWithWrongExchangeAddress.exchange.getContractAddress()).to.be.equal(ZeroEx.NULL_ADDRESS);
|
||||
});
|
||||
it('allows to specify token registry token contract address', async () => {
|
||||
const config = {
|
||||
const zeroExConfig = {
|
||||
tokenRegistryContractAddress: ZeroEx.NULL_ADDRESS,
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
const zeroExWithWrongTokenRegistryAddress = new ZeroEx(web3.currentProvider, config);
|
||||
return expect(zeroExWithWrongTokenRegistryAddress.tokenRegistry.getContractAddressAsync())
|
||||
.to.be.rejectedWith(ZeroExError.ContractDoesNotExist);
|
||||
const zeroExWithWrongTokenRegistryAddress = new ZeroEx(web3.currentProvider, zeroExConfig);
|
||||
expect(zeroExWithWrongTokenRegistryAddress.tokenRegistry.getContractAddress()).to.be.equal(
|
||||
ZeroEx.NULL_ADDRESS,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,12 @@
|
||||
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';
|
||||
|
||||
import { ZeroEx } from '../src';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
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;
|
||||
@@ -18,15 +18,18 @@ describe('Artifacts', () => {
|
||||
const packageJSON = JSON.parse(packageJSONContent);
|
||||
const mnemonic = packageJSON.config.mnemonic;
|
||||
const web3Provider = new HDWalletProvider(mnemonic, kovanRpcUrl);
|
||||
const zeroEx = new ZeroEx(web3Provider);
|
||||
const config = {
|
||||
networkId: constants.KOVAN_NETWORK_ID,
|
||||
};
|
||||
const zeroEx = new ZeroEx(web3Provider, config);
|
||||
it('token registry contract is deployed', async () => {
|
||||
await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync();
|
||||
}).timeout(TIMEOUT);
|
||||
it('proxy contract is deployed', async () => {
|
||||
await (zeroEx.token as any)._getTokenTransferProxyAddressAsync();
|
||||
await (zeroEx.proxy as any)._getTokenTransferProxyContractAsync();
|
||||
}).timeout(TIMEOUT);
|
||||
it('exchange contract is deployed', async () => {
|
||||
await zeroEx.exchange.getContractAddressAsync();
|
||||
await (zeroEx.exchange as any)._getExchangeContractAsync();
|
||||
}).timeout(TIMEOUT);
|
||||
});
|
||||
describe('contracts are deployed on ropsten', () => {
|
||||
@@ -35,15 +38,18 @@ describe('Artifacts', () => {
|
||||
const packageJSON = JSON.parse(packageJSONContent);
|
||||
const mnemonic = packageJSON.config.mnemonic;
|
||||
const web3Provider = new HDWalletProvider(mnemonic, ropstenRpcUrl);
|
||||
const zeroEx = new ZeroEx(web3Provider);
|
||||
const config = {
|
||||
networkId: constants.ROPSTEN_NETWORK_ID,
|
||||
};
|
||||
const zeroEx = new ZeroEx(web3Provider, config);
|
||||
it('token registry contract is deployed', async () => {
|
||||
await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync();
|
||||
}).timeout(TIMEOUT);
|
||||
it('proxy contract is deployed', async () => {
|
||||
await (zeroEx.token as any)._getTokenTransferProxyAddressAsync();
|
||||
await (zeroEx.proxy as any)._getTokenTransferProxyContractAsync();
|
||||
}).timeout(TIMEOUT);
|
||||
it('exchange contract is deployed', async () => {
|
||||
await zeroEx.exchange.getContractAddressAsync();
|
||||
await (zeroEx.exchange as any)._getExchangeContractAsync();
|
||||
}).timeout(TIMEOUT);
|
||||
});
|
||||
});
|
||||
43
packages/0x.js/test/assert_test.ts
Normal file
43
packages/0x.js/test/assert_test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { web3Factory } from '@0xproject/dev-utils';
|
||||
import * as chai from 'chai';
|
||||
import 'mocha';
|
||||
|
||||
import { ZeroEx } from '../src';
|
||||
import { assert } from '../src/utils/assert';
|
||||
|
||||
import { constants } from './utils/constants';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('Assertion library', () => {
|
||||
const web3 = web3Factory.create();
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
const zeroEx = new ZeroEx(web3.currentProvider, config);
|
||||
describe('#isSenderAddressHexAsync', () => {
|
||||
it('throws when address is invalid', async () => {
|
||||
const address = '0xdeadbeef';
|
||||
const varName = 'address';
|
||||
return expect(
|
||||
assert.isSenderAddressAsync(varName, address, (zeroEx as any)._web3Wrapper),
|
||||
).to.be.rejectedWith(`Expected ${varName} to be of type ETHAddressHex, encountered: ${address}`);
|
||||
});
|
||||
it('throws when address is unavailable', async () => {
|
||||
const validUnrelatedAddress = '0x8b0292b11a196601eddce54b665cafeca0347d42';
|
||||
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 provider`,
|
||||
);
|
||||
});
|
||||
it("doesn't throw if address is available", async () => {
|
||||
const availableAddress = (await zeroEx.getAvailableAddressesAsync())[0];
|
||||
const varName = 'address';
|
||||
return expect(
|
||||
assert.isSenderAddressAsync(varName, availableAddress, (zeroEx as any)._web3Wrapper),
|
||||
).to.become(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
374
packages/0x.js/test/ether_token_wrapper_test.ts
Normal file
374
packages/0x.js/test/ether_token_wrapper_test.ts
Normal file
@@ -0,0 +1,374 @@
|
||||
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import 'mocha';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
import {
|
||||
ApprovalContractEventArgs,
|
||||
BlockParamLiteral,
|
||||
BlockRange,
|
||||
DecodedLogEvent,
|
||||
DepositContractEventArgs,
|
||||
EtherTokenEvents,
|
||||
Token,
|
||||
TransferContractEventArgs,
|
||||
WithdrawalContractEventArgs,
|
||||
ZeroEx,
|
||||
ZeroExError,
|
||||
} from '../src';
|
||||
import { artifacts } from '../src/artifacts';
|
||||
import { DoneCallback } from '../src/types';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
import { reportNodeCallbackErrors } from './utils/report_callback_errors';
|
||||
import { TokenUtils } from './utils/token_utils';
|
||||
|
||||
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 = 62517;
|
||||
|
||||
describe('EtherTokenWrapper', () => {
|
||||
let web3: Web3;
|
||||
let zeroEx: ZeroEx;
|
||||
let tokens: Token[];
|
||||
let userAddresses: string[];
|
||||
let addressWithETH: string;
|
||||
let wethContractAddress: string;
|
||||
let depositWeiAmount: BigNumber;
|
||||
let decimalPlaces: number;
|
||||
let addressWithoutFunds: string;
|
||||
const gasPrice = new BigNumber(1);
|
||||
const zeroExConfig = {
|
||||
gasPrice,
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
const transferAmount = new BigNumber(42);
|
||||
const allowanceAmount = new BigNumber(42);
|
||||
const depositAmount = new BigNumber(42);
|
||||
const withdrawalAmount = new BigNumber(42);
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3.currentProvider, zeroExConfig);
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
addressWithETH = userAddresses[0];
|
||||
wethContractAddress = (zeroEx.etherToken as any)._getContractAddress(artifacts.EtherTokenArtifact);
|
||||
depositWeiAmount = (zeroEx as any)._web3Wrapper.toWei(new BigNumber(5));
|
||||
decimalPlaces = 7;
|
||||
addressWithoutFunds = userAddresses[1];
|
||||
});
|
||||
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);
|
||||
|
||||
const txHash = await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
|
||||
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(wethContractAddress, 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(wethContractAddress, 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);
|
||||
|
||||
const txHash = await zeroEx.etherToken.withdrawAsync(wethContractAddress, depositWeiAmount, addressWithETH);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
|
||||
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(wethContractAddress, overWETHBalance, addressWithETH),
|
||||
).to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal);
|
||||
});
|
||||
});
|
||||
describe('#subscribe', () => {
|
||||
const indexFilterValues = {};
|
||||
let etherTokenAddress: string;
|
||||
before(() => {
|
||||
const tokenUtils = new TokenUtils(tokens);
|
||||
const etherToken = tokenUtils.getWethTokenOrThrow();
|
||||
etherTokenAddress = etherToken.address;
|
||||
});
|
||||
afterEach(() => {
|
||||
zeroEx.etherToken._unsubscribeAll();
|
||||
});
|
||||
// Hack: Mocha does not allow a test to be both async and have a `done` callback
|
||||
// Since we need to await the receipt of the event in the `subscribe` callback,
|
||||
// we do need both. A hack is to make the top-level async 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 tokens are transfered', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<TransferContractEventArgs>) => {
|
||||
expect(logEvent).to.not.be.undefined();
|
||||
expect(logEvent.isRemoved).to.be.false();
|
||||
expect(logEvent.log.logIndex).to.be.equal(0);
|
||||
expect(logEvent.log.transactionIndex).to.be.equal(0);
|
||||
expect(logEvent.log.blockNumber).to.be.a('number');
|
||||
const args = logEvent.log.args;
|
||||
expect(args._from).to.be.equal(addressWithETH);
|
||||
expect(args._to).to.be.equal(addressWithoutFunds);
|
||||
expect(args._value).to.be.bignumber.equal(transferAmount);
|
||||
},
|
||||
);
|
||||
await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH);
|
||||
zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Transfer, indexFilterValues, callback);
|
||||
await zeroEx.token.transferAsync(
|
||||
etherTokenAddress,
|
||||
addressWithETH,
|
||||
addressWithoutFunds,
|
||||
transferAmount,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
expect(logEvent).to.not.be.undefined();
|
||||
expect(logEvent.isRemoved).to.be.false();
|
||||
const args = logEvent.log.args;
|
||||
expect(args._owner).to.be.equal(addressWithETH);
|
||||
expect(args._spender).to.be.equal(addressWithoutFunds);
|
||||
expect(args._value).to.be.bignumber.equal(allowanceAmount);
|
||||
},
|
||||
);
|
||||
zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Approval, indexFilterValues, callback);
|
||||
await zeroEx.token.setAllowanceAsync(
|
||||
etherTokenAddress,
|
||||
addressWithETH,
|
||||
addressWithoutFunds,
|
||||
allowanceAmount,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should receive the Deposit event when ether is being deposited', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<DepositContractEventArgs>) => {
|
||||
expect(logEvent).to.not.be.undefined();
|
||||
expect(logEvent.isRemoved).to.be.false();
|
||||
const args = logEvent.log.args;
|
||||
expect(args._owner).to.be.equal(addressWithETH);
|
||||
expect(args._value).to.be.bignumber.equal(depositAmount);
|
||||
},
|
||||
);
|
||||
zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Deposit, indexFilterValues, callback);
|
||||
await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should receive the Withdrawal event when ether is being withdrawn', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<WithdrawalContractEventArgs>) => {
|
||||
expect(logEvent).to.not.be.undefined();
|
||||
expect(logEvent.isRemoved).to.be.false();
|
||||
const args = logEvent.log.args;
|
||||
expect(args._owner).to.be.equal(addressWithETH);
|
||||
expect(args._value).to.be.bignumber.equal(depositAmount);
|
||||
},
|
||||
);
|
||||
await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH);
|
||||
zeroEx.etherToken.subscribe(
|
||||
etherTokenAddress,
|
||||
EtherTokenEvents.Withdrawal,
|
||||
indexFilterValues,
|
||||
callback,
|
||||
);
|
||||
await zeroEx.etherToken.withdrawAsync(etherTokenAddress, withdrawalAmount, addressWithETH);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should cancel outstanding subscriptions when ZeroEx.setProvider is called', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
},
|
||||
);
|
||||
zeroEx.etherToken.subscribe(
|
||||
etherTokenAddress,
|
||||
EtherTokenEvents.Transfer,
|
||||
indexFilterValues,
|
||||
callbackNeverToBeCalled,
|
||||
);
|
||||
const callbackToBeCalled = reportNodeCallbackErrors(done)();
|
||||
const newProvider = web3Factory.getRpcProvider();
|
||||
zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID);
|
||||
await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH);
|
||||
zeroEx.etherToken.subscribe(
|
||||
etherTokenAddress,
|
||||
EtherTokenEvents.Transfer,
|
||||
indexFilterValues,
|
||||
callbackToBeCalled,
|
||||
);
|
||||
await zeroEx.token.transferAsync(
|
||||
etherTokenAddress,
|
||||
addressWithETH,
|
||||
addressWithoutFunds,
|
||||
transferAmount,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
},
|
||||
);
|
||||
await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH);
|
||||
const subscriptionToken = zeroEx.etherToken.subscribe(
|
||||
etherTokenAddress,
|
||||
EtherTokenEvents.Transfer,
|
||||
indexFilterValues,
|
||||
callbackNeverToBeCalled,
|
||||
);
|
||||
zeroEx.etherToken.unsubscribe(subscriptionToken);
|
||||
await zeroEx.token.transferAsync(
|
||||
etherTokenAddress,
|
||||
addressWithETH,
|
||||
addressWithoutFunds,
|
||||
transferAmount,
|
||||
);
|
||||
done();
|
||||
})().catch(done);
|
||||
});
|
||||
});
|
||||
describe('#getLogsAsync', () => {
|
||||
let etherTokenAddress: string;
|
||||
let tokenTransferProxyAddress: string;
|
||||
const blockRange: BlockRange = {
|
||||
fromBlock: 0,
|
||||
toBlock: BlockParamLiteral.Latest,
|
||||
};
|
||||
let txHash: string;
|
||||
before(() => {
|
||||
addressWithETH = userAddresses[0];
|
||||
const tokenUtils = new TokenUtils(tokens);
|
||||
const etherToken = tokenUtils.getWethTokenOrThrow();
|
||||
etherTokenAddress = etherToken.address;
|
||||
tokenTransferProxyAddress = zeroEx.proxy.getContractAddress();
|
||||
});
|
||||
it('should get logs with decoded args emitted by Approval', async () => {
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const eventName = EtherTokenEvents.Approval;
|
||||
const indexFilterValues = {};
|
||||
const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>(
|
||||
etherTokenAddress,
|
||||
eventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(1);
|
||||
const args = logs[0].args;
|
||||
expect(logs[0].event).to.be.equal(eventName);
|
||||
expect(args._owner).to.be.equal(addressWithETH);
|
||||
expect(args._spender).to.be.equal(tokenTransferProxyAddress);
|
||||
expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
it('should get logs with decoded args emitted by Deposit', async () => {
|
||||
await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH);
|
||||
const eventName = EtherTokenEvents.Deposit;
|
||||
const indexFilterValues = {};
|
||||
const logs = await zeroEx.etherToken.getLogsAsync<DepositContractEventArgs>(
|
||||
etherTokenAddress,
|
||||
eventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(1);
|
||||
const args = logs[0].args;
|
||||
expect(logs[0].event).to.be.equal(eventName);
|
||||
expect(args._owner).to.be.equal(addressWithETH);
|
||||
expect(args._value).to.be.bignumber.equal(depositAmount);
|
||||
});
|
||||
it('should only get the logs with the correct event name', async () => {
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const differentEventName = EtherTokenEvents.Transfer;
|
||||
const indexFilterValues = {};
|
||||
const logs = await zeroEx.etherToken.getLogsAsync(
|
||||
etherTokenAddress,
|
||||
differentEventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(0);
|
||||
});
|
||||
it('should only get the logs with the correct indexed fields', async () => {
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithoutFunds);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const eventName = EtherTokenEvents.Approval;
|
||||
const indexFilterValues = {
|
||||
_owner: addressWithETH,
|
||||
};
|
||||
const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>(
|
||||
etherTokenAddress,
|
||||
eventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(1);
|
||||
const args = logs[0].args;
|
||||
expect(args._owner).to.be.equal(addressWithETH);
|
||||
});
|
||||
});
|
||||
});
|
||||
126
packages/0x.js/test/event_watcher_test.ts
Normal file
126
packages/0x.js/test/event_watcher_test.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { web3Factory } from '@0xproject/dev-utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
import * as Sinon from 'sinon';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
import { LogEvent } from '../src';
|
||||
import { EventWatcher } from '../src/order_watcher/event_watcher';
|
||||
import { DoneCallback } from '../src/types';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { reportNodeCallbackErrors } from './utils/report_callback_errors';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('EventWatcher', () => {
|
||||
let web3: Web3;
|
||||
let stubs: Sinon.SinonStub[] = [];
|
||||
let eventWatcher: EventWatcher;
|
||||
let web3Wrapper: Web3Wrapper;
|
||||
const logA: Web3.LogEntry = {
|
||||
address: '0x71d271f8b14adef568f8f28f1587ce7271ac4ca5',
|
||||
blockHash: null,
|
||||
blockNumber: null,
|
||||
data: '',
|
||||
logIndex: null,
|
||||
topics: [],
|
||||
transactionHash: '0x004881d38cd4a8f72f1a0d68c8b9b8124504706041ff37019c1d1ed6bfda8e17',
|
||||
transactionIndex: 0,
|
||||
};
|
||||
const logB: Web3.LogEntry = {
|
||||
address: '0x8d12a197cb00d4747a1fe03395095ce2a5cc6819',
|
||||
blockHash: null,
|
||||
blockNumber: null,
|
||||
data: '',
|
||||
logIndex: null,
|
||||
topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'],
|
||||
transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25',
|
||||
transactionIndex: 0,
|
||||
};
|
||||
const logC: Web3.LogEntry = {
|
||||
address: '0x1d271f8b174adef58f1587ce68f8f27271ac4ca5',
|
||||
blockHash: null,
|
||||
blockNumber: null,
|
||||
data: '',
|
||||
logIndex: null,
|
||||
topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'],
|
||||
transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25',
|
||||
transactionIndex: 0,
|
||||
};
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
const pollingIntervalMs = 10;
|
||||
web3Wrapper = new Web3Wrapper(web3.currentProvider);
|
||||
eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalMs);
|
||||
});
|
||||
afterEach(() => {
|
||||
// clean up any stubs after the test has completed
|
||||
_.each(stubs, s => s.restore());
|
||||
stubs = [];
|
||||
eventWatcher.unsubscribe();
|
||||
});
|
||||
it('correctly emits initial log events', (done: DoneCallback) => {
|
||||
const logs: Web3.LogEntry[] = [logA, logB];
|
||||
const expectedLogEvents = [
|
||||
{
|
||||
removed: false,
|
||||
...logA,
|
||||
},
|
||||
{
|
||||
removed: false,
|
||||
...logB,
|
||||
},
|
||||
];
|
||||
const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync');
|
||||
getLogsStub.onCall(0).returns(logs);
|
||||
stubs.push(getLogsStub);
|
||||
const expectedToBeCalledOnce = false;
|
||||
const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => {
|
||||
const expectedLogEvent = expectedLogEvents.shift();
|
||||
expect(event).to.be.deep.equal(expectedLogEvent);
|
||||
if (_.isEmpty(expectedLogEvents)) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
eventWatcher.subscribe(callback);
|
||||
});
|
||||
it('correctly computes the difference and emits only changes', (done: DoneCallback) => {
|
||||
const initialLogs: Web3.LogEntry[] = [logA, logB];
|
||||
const changedLogs: Web3.LogEntry[] = [logA, logC];
|
||||
const expectedLogEvents = [
|
||||
{
|
||||
removed: false,
|
||||
...logA,
|
||||
},
|
||||
{
|
||||
removed: false,
|
||||
...logB,
|
||||
},
|
||||
{
|
||||
removed: true,
|
||||
...logB,
|
||||
},
|
||||
{
|
||||
removed: false,
|
||||
...logC,
|
||||
},
|
||||
];
|
||||
const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync');
|
||||
getLogsStub.onCall(0).returns(initialLogs);
|
||||
getLogsStub.onCall(1).returns(changedLogs);
|
||||
stubs.push(getLogsStub);
|
||||
const expectedToBeCalledOnce = false;
|
||||
const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => {
|
||||
const expectedLogEvent = expectedLogEvents.shift();
|
||||
expect(event).to.be.deep.equal(expectedLogEvent);
|
||||
if (_.isEmpty(expectedLogEvents)) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
eventWatcher.subscribe(callback);
|
||||
});
|
||||
});
|
||||
116
packages/0x.js/test/exchange_transfer_simulator_test.ts
Normal file
116
packages/0x.js/test/exchange_transfer_simulator_test.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
|
||||
import { ExchangeContractErrs, Token, ZeroEx } from '../src';
|
||||
import { BlockParamLiteral, TradeSide, TransferType } from '../src/types';
|
||||
import { ExchangeTransferSimulator } from '../src/utils/exchange_transfer_simulator';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle();
|
||||
|
||||
describe('ExchangeTransferSimulator', () => {
|
||||
const web3 = web3Factory.create();
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
const zeroEx = new ZeroEx(web3.currentProvider, config);
|
||||
const transferAmount = new BigNumber(5);
|
||||
let userAddresses: string[];
|
||||
let tokens: Token[];
|
||||
let coinbase: string;
|
||||
let sender: string;
|
||||
let recipient: string;
|
||||
let exampleTokenAddress: string;
|
||||
let exchangeTransferSimulator: ExchangeTransferSimulator;
|
||||
let txHash: string;
|
||||
before(async () => {
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
[coinbase, sender, recipient] = userAddresses;
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
exampleTokenAddress = tokens[0].address;
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('#transferFromAsync', () => {
|
||||
beforeEach(() => {
|
||||
exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest);
|
||||
});
|
||||
it("throws if the user doesn't have enough allowance", async () => {
|
||||
return expect(
|
||||
exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress,
|
||||
sender,
|
||||
recipient,
|
||||
transferAmount,
|
||||
TradeSide.Taker,
|
||||
TransferType.Trade,
|
||||
),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerAllowance);
|
||||
});
|
||||
it("throws if the user doesn't have enough balance", async () => {
|
||||
txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
return expect(
|
||||
exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress,
|
||||
sender,
|
||||
recipient,
|
||||
transferAmount,
|
||||
TradeSide.Maker,
|
||||
TransferType.Trade,
|
||||
),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance);
|
||||
});
|
||||
it('updates balances and proxyAllowance after transfer', async () => {
|
||||
txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
await exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress,
|
||||
sender,
|
||||
recipient,
|
||||
transferAmount,
|
||||
TradeSide.Taker,
|
||||
TransferType.Trade,
|
||||
);
|
||||
const store = (exchangeTransferSimulator as any)._store;
|
||||
const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
|
||||
const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
|
||||
const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
|
||||
expect(senderBalance).to.be.bignumber.equal(0);
|
||||
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
|
||||
expect(senderProxyAllowance).to.be.bignumber.equal(0);
|
||||
});
|
||||
it("doesn't update proxyAllowance after transfer if unlimited", async () => {
|
||||
txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(exampleTokenAddress, sender);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
await exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress,
|
||||
sender,
|
||||
recipient,
|
||||
transferAmount,
|
||||
TradeSide.Taker,
|
||||
TransferType.Trade,
|
||||
);
|
||||
const store = (exchangeTransferSimulator as any)._store;
|
||||
const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
|
||||
const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
|
||||
const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
|
||||
expect(senderBalance).to.be.bignumber.equal(0);
|
||||
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
|
||||
expect(senderProxyAllowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
158
packages/0x.js/test/expiration_watcher_test.ts
Normal file
158
packages/0x.js/test/expiration_watcher_test.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
import * as Sinon from 'sinon';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
import { ZeroEx } from '../src/0x';
|
||||
import { ExpirationWatcher } from '../src/order_watcher/expiration_watcher';
|
||||
import { DoneCallback, Token } from '../src/types';
|
||||
import { constants } from '../src/utils/constants';
|
||||
import { utils } from '../src/utils/utils';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { FillScenarios } from './utils/fill_scenarios';
|
||||
import { reportNoErrorCallbackErrors } from './utils/report_callback_errors';
|
||||
import { TokenUtils } from './utils/token_utils';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle();
|
||||
|
||||
describe('ExpirationWatcher', () => {
|
||||
let web3: Web3;
|
||||
let zeroEx: ZeroEx;
|
||||
let tokenUtils: TokenUtils;
|
||||
let tokens: Token[];
|
||||
let userAddresses: string[];
|
||||
let zrxTokenAddress: string;
|
||||
let fillScenarios: FillScenarios;
|
||||
let exchangeContractAddress: string;
|
||||
let makerTokenAddress: string;
|
||||
let takerTokenAddress: string;
|
||||
let coinbase: string;
|
||||
let makerAddress: string;
|
||||
let takerAddress: string;
|
||||
let feeRecipient: string;
|
||||
const fillableAmount = new BigNumber(5);
|
||||
let currentUnixTimestampSec: BigNumber;
|
||||
let timer: Sinon.SinonFakeTimers;
|
||||
let expirationWatcher: ExpirationWatcher;
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
zeroEx = new ZeroEx(web3.currentProvider, config);
|
||||
exchangeContractAddress = zeroEx.exchange.getContractAddress();
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
tokenUtils = new TokenUtils(tokens);
|
||||
zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
|
||||
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress);
|
||||
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
const [makerToken, takerToken] = tokenUtils.getDummyTokens();
|
||||
makerTokenAddress = makerToken.address;
|
||||
takerTokenAddress = takerToken.address;
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
const sinonTimerConfig = { shouldAdvanceTime: true } as any;
|
||||
// This constructor has incorrect types
|
||||
timer = Sinon.useFakeTimers(sinonTimerConfig);
|
||||
currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
|
||||
expirationWatcher = new ExpirationWatcher();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
timer.restore();
|
||||
expirationWatcher.unsubscribe();
|
||||
});
|
||||
it('correctly emits events when order expires', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const orderLifetimeSec = 60;
|
||||
const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
expirationUnixTimestampSec,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000));
|
||||
const callbackAsync = reportNoErrorCallbackErrors(done)((hash: string) => {
|
||||
expect(hash).to.be.equal(orderHash);
|
||||
expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.gte(expirationUnixTimestampSec);
|
||||
});
|
||||
expirationWatcher.subscribe(callbackAsync);
|
||||
timer.tick(orderLifetimeSec * 1000);
|
||||
})().catch(done);
|
||||
});
|
||||
it("doesn't emit events before order expires", (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const orderLifetimeSec = 60;
|
||||
const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
expirationUnixTimestampSec,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000));
|
||||
const callbackAsync = reportNoErrorCallbackErrors(done)(async (hash: string) => {
|
||||
done(new Error('Emitted expiration went before the order actually expired'));
|
||||
});
|
||||
expirationWatcher.subscribe(callbackAsync);
|
||||
const notEnoughTime = orderLifetimeSec - 1;
|
||||
timer.tick(notEnoughTime * 1000);
|
||||
done();
|
||||
})().catch(done);
|
||||
});
|
||||
it('emits events in correct order', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const order1Lifetime = 60;
|
||||
const order2Lifetime = 120;
|
||||
const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime);
|
||||
const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime);
|
||||
const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
order1ExpirationUnixTimestampSec,
|
||||
);
|
||||
const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
order2ExpirationUnixTimestampSec,
|
||||
);
|
||||
const orderHash1 = ZeroEx.getOrderHashHex(signedOrder1);
|
||||
const orderHash2 = ZeroEx.getOrderHashHex(signedOrder2);
|
||||
expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000));
|
||||
expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000));
|
||||
const expirationOrder = [orderHash1, orderHash2];
|
||||
const expectToBeCalledOnce = false;
|
||||
const callbackAsync = reportNoErrorCallbackErrors(done, expectToBeCalledOnce)((hash: string) => {
|
||||
const orderHash = expirationOrder.shift();
|
||||
expect(hash).to.be.equal(orderHash);
|
||||
if (_.isEmpty(expirationOrder)) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
expirationWatcher.subscribe(callbackAsync);
|
||||
timer.tick(order2Lifetime * 1000);
|
||||
})().catch(done);
|
||||
});
|
||||
});
|
||||
557
packages/0x.js/test/order_state_watcher_test.ts
Normal file
557
packages/0x.js/test/order_state_watcher_test.ts
Normal file
@@ -0,0 +1,557 @@
|
||||
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
import {
|
||||
ExchangeContractErrs,
|
||||
OrderState,
|
||||
OrderStateInvalid,
|
||||
OrderStateValid,
|
||||
SignedOrder,
|
||||
Token,
|
||||
ZeroEx,
|
||||
ZeroExError,
|
||||
} from '../src';
|
||||
import { DoneCallback } from '../src/types';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
import { FillScenarios } from './utils/fill_scenarios';
|
||||
import { reportNodeCallbackErrors } from './utils/report_callback_errors';
|
||||
import { TokenUtils } from './utils/token_utils';
|
||||
|
||||
const TIMEOUT_MS = 150;
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle();
|
||||
|
||||
describe('OrderStateWatcher', () => {
|
||||
let web3: Web3;
|
||||
let zeroEx: ZeroEx;
|
||||
let tokens: Token[];
|
||||
let tokenUtils: TokenUtils;
|
||||
let fillScenarios: FillScenarios;
|
||||
let userAddresses: string[];
|
||||
let zrxTokenAddress: string;
|
||||
let exchangeContractAddress: string;
|
||||
let makerToken: Token;
|
||||
let takerToken: Token;
|
||||
let maker: string;
|
||||
let taker: string;
|
||||
let signedOrder: SignedOrder;
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
const decimals = constants.ZRX_DECIMALS;
|
||||
const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals);
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3.currentProvider, config);
|
||||
exchangeContractAddress = zeroEx.exchange.getContractAddress();
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
[, maker, taker] = userAddresses;
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
tokenUtils = new TokenUtils(tokens);
|
||||
zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
|
||||
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress);
|
||||
await fillScenarios.initTokenBalancesAsync();
|
||||
[makerToken, takerToken] = tokenUtils.getDummyTokens();
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('#removeOrder', async () => {
|
||||
it('should successfully remove existing order', async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.include({
|
||||
[orderHash]: signedOrder,
|
||||
});
|
||||
let dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes;
|
||||
expect(dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress]).to.have.keys(orderHash);
|
||||
zeroEx.orderStateWatcher.removeOrder(orderHash);
|
||||
expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.not.include({
|
||||
[orderHash]: signedOrder,
|
||||
});
|
||||
dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes;
|
||||
expect(dependentOrderHashes[signedOrder.maker]).to.be.undefined();
|
||||
});
|
||||
it('should no-op when removing a non-existing order', async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
const nonExistentOrderHash = `0x${orderHash
|
||||
.substr(2)
|
||||
.split('')
|
||||
.reverse()
|
||||
.join('')}`;
|
||||
zeroEx.orderStateWatcher.removeOrder(nonExistentOrderHash);
|
||||
});
|
||||
});
|
||||
describe('#subscribe', async () => {
|
||||
afterEach(async () => {
|
||||
zeroEx.orderStateWatcher.unsubscribe();
|
||||
});
|
||||
it('should fail when trying to subscribe twice', async () => {
|
||||
zeroEx.orderStateWatcher.subscribe(_.noop);
|
||||
expect(() => zeroEx.orderStateWatcher.subscribe(_.noop)).to.throw(ZeroExError.SubscriptionAlreadyPresent);
|
||||
});
|
||||
});
|
||||
describe('tests with cleanup', async () => {
|
||||
afterEach(async () => {
|
||||
zeroEx.orderStateWatcher.unsubscribe();
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
zeroEx.orderStateWatcher.removeOrder(orderHash);
|
||||
});
|
||||
it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.false();
|
||||
const invalidOrderState = orderState as OrderStateInvalid;
|
||||
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
||||
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, new BigNumber(0));
|
||||
})().catch(done);
|
||||
});
|
||||
it('should not emit an orderState event when irrelevant Transfer event received', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
throw new Error('OrderState callback fired for irrelevant order');
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
const notTheMaker = userAddresses[0];
|
||||
const anyRecipient = taker;
|
||||
const transferAmount = new BigNumber(2);
|
||||
await zeroEx.token.transferAsync(makerToken.address, notTheMaker, anyRecipient, transferAmount);
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, TIMEOUT_MS);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should emit orderStateInvalid when maker moves balance backing watched order', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.false();
|
||||
const invalidOrderState = orderState as OrderStateInvalid;
|
||||
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
||||
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance);
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
const anyRecipient = taker;
|
||||
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
|
||||
await zeroEx.token.transferAsync(makerToken.address, maker, anyRecipient, makerBalance);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should emit orderStateInvalid when watched order fully filled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.false();
|
||||
const invalidOrderState = orderState as OrderStateInvalid;
|
||||
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
||||
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder,
|
||||
fillableAmount,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
taker,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should emit orderStateValid when watched order partially filled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
|
||||
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
|
||||
const fillAmountInBaseUnits = new BigNumber(2);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.true();
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
expect(validOrderState.orderHash).to.be.equal(orderHash);
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
const remainingMakerBalance = makerBalance.sub(fillAmountInBaseUnits);
|
||||
const remainingFillable = fillableAmount.minus(fillAmountInBaseUnits);
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingFillable,
|
||||
);
|
||||
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingFillable,
|
||||
);
|
||||
expect(orderRelevantState.makerBalance).to.be.bignumber.equal(remainingMakerBalance);
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder,
|
||||
fillAmountInBaseUnits,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
taker,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should trigger the callback when orders backing ZRX allowance changes', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18);
|
||||
const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), 18);
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
makerFee,
|
||||
takerFee,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
taker,
|
||||
);
|
||||
const callback = reportNodeCallbackErrors(done)();
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, new BigNumber(0));
|
||||
})().catch(done);
|
||||
});
|
||||
describe('remainingFillable(M|T)akerTokenAmount', () => {
|
||||
it('should calculate correct remaining fillable', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const takerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(10), decimals);
|
||||
const makerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(20), decimals);
|
||||
signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
makerFillableAmount,
|
||||
takerFillableAmount,
|
||||
);
|
||||
const fillAmountInBaseUnits = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.true();
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
expect(validOrderState.orderHash).to.be.equal(orderHash);
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(16), decimals),
|
||||
);
|
||||
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(8), decimals),
|
||||
);
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder,
|
||||
fillAmountInBaseUnits,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
taker,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should equal approved amount when approved amount is lowest', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
|
||||
const changedMakerApprovalAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals);
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
changedMakerApprovalAmount,
|
||||
);
|
||||
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
|
||||
changedMakerApprovalAmount,
|
||||
);
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, changedMakerApprovalAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should equal balance amount when balance amount is lowest', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
|
||||
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
|
||||
|
||||
const remainingAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals);
|
||||
const transferAmount = makerBalance.sub(remainingAmount);
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.true();
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingAmount,
|
||||
);
|
||||
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingAmount,
|
||||
);
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.transferAsync(makerToken.address, maker, ZeroEx.NULL_ADDRESS, transferAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should equal remaining amount when partially cancelled and order has fees', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals);
|
||||
const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals);
|
||||
const feeRecipient = taker;
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
makerFee,
|
||||
takerFee,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
feeRecipient,
|
||||
);
|
||||
|
||||
const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals);
|
||||
const transferTokenAmount = makerFee.sub(remainingTokenAmount);
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.true();
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingTokenAmount,
|
||||
);
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, transferTokenAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should equal ratio amount when fee balance is lowered', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals);
|
||||
const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals);
|
||||
const feeRecipient = taker;
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
makerFee,
|
||||
takerFee,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
feeRecipient,
|
||||
);
|
||||
|
||||
const remainingFeeAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals);
|
||||
|
||||
const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals);
|
||||
const transferTokenAmount = makerFee.sub(remainingTokenAmount);
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingFeeAmount,
|
||||
);
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, remainingFeeAmount);
|
||||
await zeroEx.token.transferAsync(
|
||||
makerToken.address,
|
||||
maker,
|
||||
ZeroEx.NULL_ADDRESS,
|
||||
transferTokenAmount,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should calculate full amount when all available and non-divisible', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals);
|
||||
const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
|
||||
const feeRecipient = taker;
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
makerFee,
|
||||
takerFee,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
feeRecipient,
|
||||
);
|
||||
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
fillableAmount,
|
||||
);
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.setProxyAllowanceAsync(
|
||||
makerToken.address,
|
||||
maker,
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(100), decimals),
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
});
|
||||
it('should emit orderStateInvalid when watched order cancelled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.false();
|
||||
const invalidOrderState = orderState as OrderStateInvalid;
|
||||
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
||||
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should emit orderStateInvalid when within rounding error range', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const remainingFillableAmountInBaseUnits = new BigNumber(100);
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.false();
|
||||
const invalidOrderState = orderState as OrderStateInvalid;
|
||||
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
||||
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError);
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.exchange.cancelOrderAsync(
|
||||
signedOrder,
|
||||
fillableAmount.minus(remainingFillableAmountInBaseUnits),
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should emit orderStateValid when watched order partially cancelled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address,
|
||||
takerToken.address,
|
||||
maker,
|
||||
taker,
|
||||
fillableAmount,
|
||||
);
|
||||
|
||||
const cancelAmountInBaseUnits = new BigNumber(2);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
zeroEx.orderStateWatcher.addOrder(signedOrder);
|
||||
|
||||
const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.true();
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
expect(validOrderState.orderHash).to.be.equal(orderHash);
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.cancelledTakerTokenAmount).to.be.bignumber.equal(cancelAmountInBaseUnits);
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmountInBaseUnits);
|
||||
})().catch(done);
|
||||
});
|
||||
});
|
||||
}); // tslint:disable:max-file-line-count
|
||||
473
packages/0x.js/test/order_validation_test.ts
Normal file
473
packages/0x.js/test/order_validation_test.ts
Normal file
@@ -0,0 +1,473 @@
|
||||
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import * as Sinon from 'sinon';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
import { ExchangeContractErrs, SignedOrder, Token, ZeroEx, ZeroExError } from '../src';
|
||||
import { BlockParamLiteral, TradeSide, TransferType } from '../src/types';
|
||||
import { ExchangeTransferSimulator } from '../src/utils/exchange_transfer_simulator';
|
||||
import { OrderValidationUtils } from '../src/utils/order_validation_utils';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
import { FillScenarios } from './utils/fill_scenarios';
|
||||
import { TokenUtils } from './utils/token_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;
|
||||
const fillableAmount = new BigNumber(5);
|
||||
const fillTakerAmount = new BigNumber(5);
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3.currentProvider, config);
|
||||
exchangeContractAddress = zeroEx.exchange.getContractAddress();
|
||||
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.getDummyTokens();
|
||||
makerTokenAddress = makerToken.address;
|
||||
takerTokenAddress = takerToken.address;
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('validateOrderFillableOrThrowAsync', () => {
|
||||
it('should succeed if the order is fillable', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
);
|
||||
await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder);
|
||||
});
|
||||
it('should succeed if the order is asymmetric and fillable', async () => {
|
||||
const makerFillableAmount = fillableAmount;
|
||||
const takerFillableAmount = fillableAmount.minus(4);
|
||||
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
makerFillableAmount,
|
||||
takerFillableAmount,
|
||||
);
|
||||
await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder);
|
||||
});
|
||||
it('should throw when the order is fully filled or cancelled', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
);
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
|
||||
return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith(
|
||||
ExchangeContractErrs.OrderRemainingFillAmountZero,
|
||||
);
|
||||
});
|
||||
it('should throw when order is expired', async () => {
|
||||
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
expirationInPast,
|
||||
);
|
||||
return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith(
|
||||
ExchangeContractErrs.OrderFillExpired,
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('validateFillOrderAndThrowIfInvalidAsync', () => {
|
||||
it('should throw when the fill amount is zero', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
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 signature is invalid', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
);
|
||||
// 27 <--> 28
|
||||
signedOrder.ecSignature.v = 28 - signedOrder.ecSignature.v + 27;
|
||||
return expect(
|
||||
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillableAmount, takerAddress),
|
||||
).to.be.rejectedWith(ZeroExError.InvalidSignature);
|
||||
});
|
||||
it('should throw when the order is fully filled or cancelled', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
);
|
||||
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;
|
||||
const cancelAmount = new BigNumber(3);
|
||||
beforeEach(async () => {
|
||||
[coinbase, makerAddress, takerAddress] = userAddresses;
|
||||
const [makerToken, takerToken] = tokenUtils.getDummyTokens();
|
||||
makerTokenAddress = makerToken.address;
|
||||
takerTokenAddress = takerToken.address;
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
);
|
||||
});
|
||||
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,
|
||||
);
|
||||
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('#validateFillOrderBalancesAllowancesThrowIfInvalidAsync', () => {
|
||||
let exchangeTransferSimulator: ExchangeTransferSimulator;
|
||||
let transferFromAsync: Sinon.SinonSpy;
|
||||
const bigNumberMatch = (expected: BigNumber) => {
|
||||
return Sinon.match((value: BigNumber) => value.eq(expected));
|
||||
};
|
||||
beforeEach('create exchangeTransferSimulator', async () => {
|
||||
exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest);
|
||||
transferFromAsync = Sinon.spy();
|
||||
exchangeTransferSimulator.transferFromAsync = transferFromAsync as any;
|
||||
});
|
||||
it('should call exchangeTransferSimulator.transferFrom in a correct order', async () => {
|
||||
const makerFee = new BigNumber(2);
|
||||
const takerFee = new BigNumber(2);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerFee,
|
||||
takerFee,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
feeRecipient,
|
||||
);
|
||||
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTransferSimulator,
|
||||
signedOrder,
|
||||
fillableAmount,
|
||||
takerAddress,
|
||||
zrxTokenAddress,
|
||||
);
|
||||
expect(transferFromAsync.callCount).to.be.equal(4);
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(0)
|
||||
.calledWith(
|
||||
makerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
bigNumberMatch(fillableAmount),
|
||||
TradeSide.Maker,
|
||||
TransferType.Trade,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(1)
|
||||
.calledWith(
|
||||
takerTokenAddress,
|
||||
takerAddress,
|
||||
makerAddress,
|
||||
bigNumberMatch(fillableAmount),
|
||||
TradeSide.Taker,
|
||||
TransferType.Trade,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(2)
|
||||
.calledWith(
|
||||
zrxTokenAddress,
|
||||
makerAddress,
|
||||
feeRecipient,
|
||||
bigNumberMatch(makerFee),
|
||||
TradeSide.Maker,
|
||||
TransferType.Fee,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(3)
|
||||
.calledWith(
|
||||
zrxTokenAddress,
|
||||
takerAddress,
|
||||
feeRecipient,
|
||||
bigNumberMatch(takerFee),
|
||||
TradeSide.Taker,
|
||||
TransferType.Fee,
|
||||
),
|
||||
).to.be.true();
|
||||
});
|
||||
it('should call exchangeTransferSimulator.transferFrom with correct values for an open order', async () => {
|
||||
const makerFee = new BigNumber(2);
|
||||
const takerFee = new BigNumber(2);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerFee,
|
||||
takerFee,
|
||||
makerAddress,
|
||||
ZeroEx.NULL_ADDRESS,
|
||||
fillableAmount,
|
||||
feeRecipient,
|
||||
);
|
||||
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTransferSimulator,
|
||||
signedOrder,
|
||||
fillableAmount,
|
||||
takerAddress,
|
||||
zrxTokenAddress,
|
||||
);
|
||||
expect(transferFromAsync.callCount).to.be.equal(4);
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(0)
|
||||
.calledWith(
|
||||
makerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
bigNumberMatch(fillableAmount),
|
||||
TradeSide.Maker,
|
||||
TransferType.Trade,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(1)
|
||||
.calledWith(
|
||||
takerTokenAddress,
|
||||
takerAddress,
|
||||
makerAddress,
|
||||
bigNumberMatch(fillableAmount),
|
||||
TradeSide.Taker,
|
||||
TransferType.Trade,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(2)
|
||||
.calledWith(
|
||||
zrxTokenAddress,
|
||||
makerAddress,
|
||||
feeRecipient,
|
||||
bigNumberMatch(makerFee),
|
||||
TradeSide.Maker,
|
||||
TransferType.Fee,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync
|
||||
.getCall(3)
|
||||
.calledWith(
|
||||
zrxTokenAddress,
|
||||
takerAddress,
|
||||
feeRecipient,
|
||||
bigNumberMatch(takerFee),
|
||||
TradeSide.Taker,
|
||||
TransferType.Fee,
|
||||
),
|
||||
).to.be.true();
|
||||
});
|
||||
it('should correctly round the fillMakerTokenAmount', async () => {
|
||||
const makerTokenAmount = new BigNumber(3);
|
||||
const takerTokenAmount = new BigNumber(1);
|
||||
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
makerTokenAmount,
|
||||
takerTokenAmount,
|
||||
);
|
||||
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTransferSimulator,
|
||||
signedOrder,
|
||||
takerTokenAmount,
|
||||
takerAddress,
|
||||
zrxTokenAddress,
|
||||
);
|
||||
expect(transferFromAsync.callCount).to.be.equal(4);
|
||||
const makerFillAmount = transferFromAsync.getCall(0).args[3];
|
||||
expect(makerFillAmount).to.be.bignumber.equal(makerTokenAmount);
|
||||
});
|
||||
it('should correctly round the makerFeeAmount', async () => {
|
||||
const makerFee = new BigNumber(2);
|
||||
const takerFee = new BigNumber(4);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerFee,
|
||||
takerFee,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
ZeroEx.NULL_ADDRESS,
|
||||
);
|
||||
const fillTakerTokenAmount = fillableAmount.div(2).round(0);
|
||||
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTransferSimulator,
|
||||
signedOrder,
|
||||
fillTakerTokenAmount,
|
||||
takerAddress,
|
||||
zrxTokenAddress,
|
||||
);
|
||||
const makerPartialFee = makerFee.div(2);
|
||||
const takerPartialFee = takerFee.div(2);
|
||||
expect(transferFromAsync.callCount).to.be.equal(4);
|
||||
const partialMakerFee = transferFromAsync.getCall(2).args[3];
|
||||
expect(partialMakerFee).to.be.bignumber.equal(makerPartialFee);
|
||||
const partialTakerFee = transferFromAsync.getCall(3).args[3];
|
||||
expect(partialTakerFee).to.be.bignumber.equal(takerPartialFee);
|
||||
});
|
||||
});
|
||||
});
|
||||
234
packages/0x.js/test/remaining_fillable_calculator_test.ts
Normal file
234
packages/0x.js/test/remaining_fillable_calculator_test.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as chai from 'chai';
|
||||
import 'mocha';
|
||||
|
||||
import { ZeroEx } from '../src/0x';
|
||||
import { RemainingFillableCalculator } from '../src/order_watcher/remaining_fillable_calculator';
|
||||
import { ECSignature, SignedOrder } from '../src/types';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('RemainingFillableCalculator', () => {
|
||||
let calculator: RemainingFillableCalculator;
|
||||
let signedOrder: SignedOrder;
|
||||
let transferrableMakerTokenAmount: BigNumber;
|
||||
let transferrableMakerFeeTokenAmount: BigNumber;
|
||||
let remainingMakerTokenAmount: BigNumber;
|
||||
let makerAmount: BigNumber;
|
||||
let takerAmount: BigNumber;
|
||||
let makerFeeAmount: BigNumber;
|
||||
let isMakerTokenZRX: boolean;
|
||||
const makerToken: string = '0x1';
|
||||
const takerToken: string = '0x2';
|
||||
const decimals: number = 4;
|
||||
const zero: BigNumber = new BigNumber(0);
|
||||
const zeroAddress = '0x0';
|
||||
const signature: ECSignature = { v: 27, r: '', s: '' };
|
||||
beforeEach(async () => {
|
||||
[makerAmount, takerAmount, makerFeeAmount] = [
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals),
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals),
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals),
|
||||
];
|
||||
[transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount] = [
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals),
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals),
|
||||
];
|
||||
});
|
||||
function buildSignedOrder(): SignedOrder {
|
||||
return {
|
||||
ecSignature: signature,
|
||||
exchangeContractAddress: zeroAddress,
|
||||
feeRecipient: zeroAddress,
|
||||
maker: zeroAddress,
|
||||
taker: zeroAddress,
|
||||
makerFee: makerFeeAmount,
|
||||
takerFee: zero,
|
||||
makerTokenAmount: makerAmount,
|
||||
takerTokenAmount: takerAmount,
|
||||
makerTokenAddress: makerToken,
|
||||
takerTokenAddress: takerToken,
|
||||
salt: zero,
|
||||
expirationUnixTimestampSec: zero,
|
||||
};
|
||||
}
|
||||
describe('Maker token is NOT ZRX', () => {
|
||||
before(async () => {
|
||||
isMakerTokenZRX = false;
|
||||
});
|
||||
it('calculates the correct amount when unfilled and funds available', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
|
||||
});
|
||||
it('calculates the correct amount when partially filled and funds available', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals);
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
|
||||
});
|
||||
it('calculates the amount to be 0 when all fee funds are transferred', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
transferrableMakerFeeTokenAmount = zero;
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero);
|
||||
});
|
||||
it('calculates the correct amount when balance is less than remaining fillable', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount);
|
||||
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount);
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount);
|
||||
});
|
||||
describe('Order to Fee Ratio is < 1', () => {
|
||||
beforeEach(async () => {
|
||||
[makerAmount, takerAmount, makerFeeAmount] = [
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals),
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals),
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals),
|
||||
];
|
||||
});
|
||||
it('calculates the correct amount when funds unavailable', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
|
||||
const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
|
||||
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount);
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount);
|
||||
});
|
||||
});
|
||||
describe('Ratio is not evenly divisble', () => {
|
||||
beforeEach(async () => {
|
||||
[makerAmount, takerAmount, makerFeeAmount] = [
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals),
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals),
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals),
|
||||
];
|
||||
});
|
||||
it('calculates the correct amount when funds unavailable', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
|
||||
const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
|
||||
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount);
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
const calculatedFillableAmount = calculator.computeRemainingMakerFillable();
|
||||
expect(calculatedFillableAmount.lessThanOrEqualTo(transferrableMakerTokenAmount)).to.be.true();
|
||||
expect(calculatedFillableAmount).to.be.bignumber.greaterThan(new BigNumber(0));
|
||||
const orderToFeeRatio = signedOrder.makerTokenAmount.dividedBy(signedOrder.makerFee);
|
||||
const calculatedFeeAmount = calculatedFillableAmount.dividedBy(orderToFeeRatio);
|
||||
expect(calculatedFeeAmount).to.be.bignumber.lessThan(transferrableMakerFeeTokenAmount);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Maker Token is ZRX', () => {
|
||||
before(async () => {
|
||||
isMakerTokenZRX = true;
|
||||
});
|
||||
it('calculates the correct amount when unfilled and funds available', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
transferrableMakerTokenAmount = makerAmount.plus(makerFeeAmount);
|
||||
transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount;
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
|
||||
});
|
||||
it('calculates the correct amount when partially filled and funds available', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals);
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
|
||||
});
|
||||
it('calculates the amount to be 0 when all fee funds are transferred', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
transferrableMakerTokenAmount = zero;
|
||||
transferrableMakerFeeTokenAmount = zero;
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero);
|
||||
});
|
||||
it('calculates the correct amount when balance is less than remaining fillable', () => {
|
||||
signedOrder = buildSignedOrder();
|
||||
const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
|
||||
remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount);
|
||||
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount);
|
||||
transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount;
|
||||
|
||||
const orderToFeeRatio = signedOrder.makerTokenAmount.dividedToIntegerBy(signedOrder.makerFee);
|
||||
const expectedFillableAmount = new BigNumber(450980);
|
||||
calculator = new RemainingFillableCalculator(
|
||||
signedOrder,
|
||||
isMakerTokenZRX,
|
||||
transferrableMakerTokenAmount,
|
||||
transferrableMakerFeeTokenAmount,
|
||||
remainingMakerTokenAmount,
|
||||
);
|
||||
const calculatedFillableAmount = calculator.computeRemainingMakerFillable();
|
||||
const numberOfFillsInRatio = calculatedFillableAmount.dividedToIntegerBy(orderToFeeRatio);
|
||||
const calculatedFillableAmountPlusFees = calculatedFillableAmount.plus(numberOfFillsInRatio);
|
||||
expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(transferrableMakerTokenAmount);
|
||||
expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(remainingMakerTokenAmount);
|
||||
expect(calculatedFillableAmount).to.be.bignumber.equal(expectedFillableAmount);
|
||||
expect(numberOfFillsInRatio.decimalPlaces()).to.be.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
84
packages/0x.js/test/subscription_test.ts
Normal file
84
packages/0x.js/test/subscription_test.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
import * as Sinon from 'sinon';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
import { ApprovalContractEventArgs, DecodedLogEvent, Token, TokenEvents, ZeroEx } from '../src';
|
||||
import { DoneCallback } from '../src/types';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
import { assertNodeCallbackError } from './utils/report_callback_errors';
|
||||
|
||||
chaiSetup.configure();
|
||||
const blockchainLifecycle = new BlockchainLifecycle();
|
||||
|
||||
describe('SubscriptionTest', () => {
|
||||
let web3: Web3;
|
||||
let zeroEx: ZeroEx;
|
||||
let userAddresses: string[];
|
||||
let tokens: Token[];
|
||||
let coinbase: string;
|
||||
let addressWithoutFunds: string;
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3.currentProvider, config);
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
coinbase = userAddresses[0];
|
||||
addressWithoutFunds = userAddresses[1];
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('#subscribe', () => {
|
||||
const indexFilterValues = {};
|
||||
let tokenAddress: string;
|
||||
const allowanceAmount = new BigNumber(42);
|
||||
let stubs: Sinon.SinonStub[] = [];
|
||||
before(() => {
|
||||
const token = tokens[0];
|
||||
tokenAddress = token.address;
|
||||
});
|
||||
afterEach(() => {
|
||||
zeroEx.token._unsubscribeAll();
|
||||
_.each(stubs, s => s.restore());
|
||||
stubs = [];
|
||||
});
|
||||
it('Should receive the Error when an error occurs while fetching the block', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const errMsg = 'Error fetching block';
|
||||
const callback = assertNodeCallbackError(done, errMsg);
|
||||
stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error(errMsg))];
|
||||
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
|
||||
await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should receive the Error when an error occurs while reconciling the new block', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const errMsg = 'Error fetching logs';
|
||||
const callback = assertNodeCallbackError(done, errMsg);
|
||||
stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getLogsAsync').throws(new Error(errMsg))];
|
||||
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
|
||||
await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should allow unsubscribeAll to be called successfully after an error', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = (err: Error | null, logEvent?: DecodedLogEvent<ApprovalContractEventArgs>) => _.noop;
|
||||
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
|
||||
stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error('JSON RPC error'))];
|
||||
zeroEx.token._unsubscribeAll();
|
||||
done();
|
||||
})().catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,13 @@
|
||||
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
|
||||
import { schemas, SchemaValidator } from '@0xproject/json-schemas';
|
||||
import * as chai from 'chai';
|
||||
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, Token} from '../src';
|
||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||
|
||||
import { Token, ZeroEx } from '../src';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
@@ -16,17 +18,20 @@ 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 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';
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
before(async () => {
|
||||
const web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3.currentProvider);
|
||||
zeroEx = new ZeroEx(web3.currentProvider, config);
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
_.map(tokens, token => {
|
||||
tokenAddressBySymbol[token.symbol] = token.address;
|
||||
@@ -1,17 +1,22 @@
|
||||
import { web3Factory } from '@0xproject/dev-utils';
|
||||
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';
|
||||
|
||||
import { ZeroEx } from '../src';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('TokenTransferProxyWrapper', () => {
|
||||
let zeroEx: ZeroEx;
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
before(async () => {
|
||||
const web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3.currentProvider);
|
||||
zeroEx = new ZeroEx(web3.currentProvider, config);
|
||||
});
|
||||
describe('#isAuthorizedAsync', () => {
|
||||
it('should return false if the address is not authorized', async () => {
|
||||
@@ -1,25 +1,27 @@
|
||||
import 'mocha';
|
||||
import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils';
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as chai from 'chai';
|
||||
import {chaiSetup} from './utils/chai_setup';
|
||||
import 'mocha';
|
||||
import * as Web3 from 'web3';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import promisify = require('es6-promisify');
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
|
||||
import {
|
||||
ApprovalContractEventArgs,
|
||||
BlockParamLiteral,
|
||||
BlockRange,
|
||||
DecodedLogEvent,
|
||||
Token,
|
||||
TokenEvents,
|
||||
TransferContractEventArgs,
|
||||
ZeroEx,
|
||||
ZeroExError,
|
||||
Token,
|
||||
SubscriptionOpts,
|
||||
TokenEvents,
|
||||
ContractEvent,
|
||||
TransferContractEventArgs,
|
||||
ApprovalContractEventArgs,
|
||||
LogWithDecodedArgs,
|
||||
LogEvent,
|
||||
} from '../src';
|
||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||
import {TokenUtils} from './utils/token_utils';
|
||||
import {DoneCallback, BlockParamLiteral} from '../src/types';
|
||||
import { DoneCallback } from '../src/types';
|
||||
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { constants } from './utils/constants';
|
||||
import { reportNodeCallbackErrors } from './utils/report_callback_errors';
|
||||
import { TokenUtils } from './utils/token_utils';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
@@ -33,9 +35,14 @@ describe('TokenWrapper', () => {
|
||||
let tokenUtils: TokenUtils;
|
||||
let coinbase: string;
|
||||
let addressWithoutFunds: string;
|
||||
let web3Wrapper: Web3Wrapper;
|
||||
const config = {
|
||||
networkId: constants.TESTRPC_NETWORK_ID,
|
||||
};
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3.currentProvider);
|
||||
zeroEx = new ZeroEx(web3.currentProvider, config);
|
||||
web3Wrapper = new Web3Wrapper(web3.currentProvider);
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
tokenUtils = new TokenUtils(tokens);
|
||||
@@ -50,7 +57,7 @@ describe('TokenWrapper', () => {
|
||||
});
|
||||
describe('#transferAsync', () => {
|
||||
let token: Token;
|
||||
let transferAmount: BigNumber.BigNumber;
|
||||
let transferAmount: BigNumber;
|
||||
before(() => {
|
||||
token = tokens[0];
|
||||
transferAmount = new BigNumber(42);
|
||||
@@ -60,25 +67,24 @@ describe('TokenWrapper', () => {
|
||||
const toAddress = addressWithoutFunds;
|
||||
const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
|
||||
expect(preBalance).to.be.bignumber.equal(0);
|
||||
const txHash = await zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
await zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount);
|
||||
const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
|
||||
return expect(postBalance).to.be.bignumber.equal(transferAmount);
|
||||
});
|
||||
it('should fail to transfer tokens if fromAddress has an insufficient balance', async () => {
|
||||
const fromAddress = addressWithoutFunds;
|
||||
const toAddress = coinbase;
|
||||
return expect(zeroEx.token.transferAsync(
|
||||
token.address, fromAddress, toAddress, transferAmount,
|
||||
)).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer);
|
||||
return expect(
|
||||
zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount),
|
||||
).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer);
|
||||
});
|
||||
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
|
||||
const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065';
|
||||
const fromAddress = coinbase;
|
||||
const toAddress = coinbase;
|
||||
return expect(zeroEx.token.transferAsync(
|
||||
nonExistentTokenAddress, fromAddress, toAddress, transferAmount,
|
||||
)).to.be.rejectedWith(ZeroExError.ContractDoesNotExist);
|
||||
return expect(
|
||||
zeroEx.token.transferAsync(nonExistentTokenAddress, fromAddress, toAddress, transferAmount),
|
||||
).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist);
|
||||
});
|
||||
});
|
||||
describe('#transferFromAsync', () => {
|
||||
@@ -97,24 +103,22 @@ describe('TokenWrapper', () => {
|
||||
const fromAddressBalance = await zeroEx.token.getBalanceAsync(token.address, fromAddress);
|
||||
expect(fromAddressBalance).to.be.bignumber.greaterThan(transferAmount);
|
||||
|
||||
const fromAddressAllowance = await zeroEx.token.getAllowanceAsync(token.address, fromAddress,
|
||||
toAddress);
|
||||
const fromAddressAllowance = await zeroEx.token.getAllowanceAsync(token.address, fromAddress, toAddress);
|
||||
expect(fromAddressAllowance).to.be.bignumber.equal(0);
|
||||
|
||||
return expect(zeroEx.token.transferFromAsync(
|
||||
token.address, fromAddress, toAddress, senderAddress, transferAmount,
|
||||
)).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer);
|
||||
return expect(
|
||||
zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount),
|
||||
).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer);
|
||||
});
|
||||
it('[regression] should fail to transfer tokens if set allowance for toAddress instead of senderAddress',
|
||||
async () => {
|
||||
it('[regression] should fail to transfer tokens if set allowance for toAddress instead of senderAddress', async () => {
|
||||
const fromAddress = coinbase;
|
||||
const transferAmount = new BigNumber(42);
|
||||
|
||||
await zeroEx.token.setAllowanceAsync(token.address, fromAddress, toAddress, transferAmount);
|
||||
|
||||
return expect(zeroEx.token.transferFromAsync(
|
||||
token.address, fromAddress, toAddress, senderAddress, transferAmount,
|
||||
)).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer);
|
||||
return expect(
|
||||
zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount),
|
||||
).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer);
|
||||
});
|
||||
it('should fail to transfer tokens if fromAddress has insufficient balance', async () => {
|
||||
const fromAddress = addressWithoutFunds;
|
||||
@@ -124,13 +128,16 @@ describe('TokenWrapper', () => {
|
||||
expect(fromAddressBalance).to.be.bignumber.equal(0);
|
||||
|
||||
await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount);
|
||||
const fromAddressAllowance = await zeroEx.token.getAllowanceAsync(token.address, fromAddress,
|
||||
senderAddress);
|
||||
const fromAddressAllowance = await zeroEx.token.getAllowanceAsync(
|
||||
token.address,
|
||||
fromAddress,
|
||||
senderAddress,
|
||||
);
|
||||
expect(fromAddressAllowance).to.be.bignumber.equal(transferAmount);
|
||||
|
||||
return expect(zeroEx.token.transferFromAsync(
|
||||
token.address, fromAddress, toAddress, senderAddress, transferAmount,
|
||||
)).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer);
|
||||
return expect(
|
||||
zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount),
|
||||
).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer);
|
||||
});
|
||||
it('should successfully transfer tokens', async () => {
|
||||
const fromAddress = coinbase;
|
||||
@@ -141,17 +148,22 @@ describe('TokenWrapper', () => {
|
||||
const transferAmount = new BigNumber(42);
|
||||
await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount);
|
||||
|
||||
await zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress,
|
||||
transferAmount);
|
||||
await zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount);
|
||||
const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
|
||||
return expect(postBalance).to.be.bignumber.equal(transferAmount);
|
||||
});
|
||||
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
|
||||
const fromAddress = coinbase;
|
||||
const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065';
|
||||
return expect(zeroEx.token.transferFromAsync(
|
||||
nonExistentTokenAddress, fromAddress, toAddress, senderAddress, new BigNumber(42),
|
||||
)).to.be.rejectedWith(ZeroExError.ContractDoesNotExist);
|
||||
return expect(
|
||||
zeroEx.token.transferFromAsync(
|
||||
nonExistentTokenAddress,
|
||||
fromAddress,
|
||||
toAddress,
|
||||
senderAddress,
|
||||
new BigNumber(42),
|
||||
),
|
||||
).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist);
|
||||
});
|
||||
});
|
||||
describe('#getBalanceAsync', () => {
|
||||
@@ -160,14 +172,15 @@ describe('TokenWrapper', () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const balance = await zeroEx.token.getBalanceAsync(token.address, ownerAddress);
|
||||
const expectedBalance = new BigNumber('100000000000000000000000000');
|
||||
const expectedBalance = new BigNumber('1000000000000000000000000000');
|
||||
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);
|
||||
return expect(zeroEx.token.getBalanceAsync(nonExistentTokenAddress, ownerAddress)).to.be.rejectedWith(
|
||||
ZeroExError.TokenContractDoesNotExist,
|
||||
);
|
||||
});
|
||||
it('should return a balance of 0 for a non-existent owner address', async () => {
|
||||
const token = tokens[0];
|
||||
@@ -182,25 +195,28 @@ describe('TokenWrapper', () => {
|
||||
before(async () => {
|
||||
const hasAddresses = false;
|
||||
const web3WithoutAccounts = web3Factory.create(hasAddresses);
|
||||
zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider);
|
||||
zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config);
|
||||
});
|
||||
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);
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const balance = await zeroExWithoutAccounts.token.getBalanceAsync(token.address, ownerAddress);
|
||||
const expectedBalance = new BigNumber('1000000000000000000000000000');
|
||||
return expect(balance).to.be.bignumber.equal(expectedBalance);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#setAllowanceAsync', () => {
|
||||
it('should set the spender\'s allowance', async () => {
|
||||
it("should set the spender's allowance", async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const spenderAddress = addressWithoutFunds;
|
||||
|
||||
const allowanceBeforeSet = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress,
|
||||
spenderAddress);
|
||||
const allowanceBeforeSet = await zeroEx.token.getAllowanceAsync(
|
||||
token.address,
|
||||
ownerAddress,
|
||||
spenderAddress,
|
||||
);
|
||||
const expectedAllowanceBeforeAllowanceSet = new BigNumber(0);
|
||||
expect(allowanceBeforeSet).to.be.bignumber.equal(expectedAllowanceBeforeAllowanceSet);
|
||||
|
||||
@@ -213,7 +229,7 @@ describe('TokenWrapper', () => {
|
||||
});
|
||||
});
|
||||
describe('#setUnlimitedAllowanceAsync', () => {
|
||||
it('should set the unlimited spender\'s allowance', async () => {
|
||||
it("should set the unlimited spender's allowance", async () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const spenderAddress = addressWithoutFunds;
|
||||
@@ -229,18 +245,30 @@ describe('TokenWrapper', () => {
|
||||
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 initBalanceWithNormalAllowance = await web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance);
|
||||
const initBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync(
|
||||
userWithUnlimitedAllowance,
|
||||
);
|
||||
|
||||
const finalBalanceWithNormalAllowance = await promisify(web3.eth.getBalance)(userWithNormalAllowance);
|
||||
const finalBalanceWithUnlimitedAllowance = 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 web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance);
|
||||
const finalBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync(
|
||||
userWithUnlimitedAllowance,
|
||||
);
|
||||
|
||||
const normalGasCost = initBalanceWithNormalAllowance.minus(finalBalanceWithNormalAllowance);
|
||||
const unlimitedGasCost = initBalanceWithUnlimitedAllowance.minus(finalBalanceWithUnlimitedAllowance);
|
||||
@@ -279,7 +307,7 @@ describe('TokenWrapper', () => {
|
||||
before(async () => {
|
||||
const hasAddresses = false;
|
||||
const web3WithoutAccounts = web3Factory.create(hasAddresses);
|
||||
zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider);
|
||||
zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config);
|
||||
});
|
||||
it('should get the proxy allowance', async () => {
|
||||
const token = tokens[0];
|
||||
@@ -290,7 +318,9 @@ describe('TokenWrapper', () => {
|
||||
await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits);
|
||||
|
||||
const allowance = await zeroExWithoutAccounts.token.getAllowanceAsync(
|
||||
token.address, ownerAddress, spenderAddress,
|
||||
token.address,
|
||||
ownerAddress,
|
||||
spenderAddress,
|
||||
);
|
||||
const expectedAllowance = amountInBaseUnits;
|
||||
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
|
||||
@@ -339,7 +369,6 @@ describe('TokenWrapper', () => {
|
||||
});
|
||||
describe('#subscribe', () => {
|
||||
const indexFilterValues = {};
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
let tokenAddress: string;
|
||||
const transferAmount = new BigNumber(42);
|
||||
const allowanceAmount = new BigNumber(42);
|
||||
@@ -348,7 +377,7 @@ describe('TokenWrapper', () => {
|
||||
tokenAddress = token.address;
|
||||
});
|
||||
afterEach(() => {
|
||||
zeroEx.token.unsubscribeAll();
|
||||
zeroEx.token._unsubscribeAll();
|
||||
});
|
||||
// Hack: Mocha does not allow a test to be both async and have a `done` callback
|
||||
// Since we need to await the receipt of the event in the `subscribe` callback,
|
||||
@@ -357,60 +386,66 @@ describe('TokenWrapper', () => {
|
||||
// Source: https://github.com/mochajs/mocha/issues/2407
|
||||
it('Should receive the Transfer event when tokens are transfered', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = (logEvent: LogEvent) => {
|
||||
expect(logEvent).to.not.be.undefined();
|
||||
const args = logEvent.args as any 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();
|
||||
};
|
||||
zeroEx.token.subscribe(
|
||||
tokenAddress, TokenEvents.Transfer, indexFilterValues, callback);
|
||||
const callback = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<TransferContractEventArgs>) => {
|
||||
expect(logEvent.isRemoved).to.be.false();
|
||||
expect(logEvent.log.logIndex).to.be.equal(0);
|
||||
expect(logEvent.log.transactionIndex).to.be.equal(0);
|
||||
expect(logEvent.log.blockNumber).to.be.a('number');
|
||||
const args = logEvent.log.args;
|
||||
expect(args._from).to.be.equal(coinbase);
|
||||
expect(args._to).to.be.equal(addressWithoutFunds);
|
||||
expect(args._value).to.be.bignumber.equal(transferAmount);
|
||||
},
|
||||
);
|
||||
zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callback);
|
||||
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = (logEvent: LogEvent) => {
|
||||
expect(logEvent).to.not.be.undefined();
|
||||
const args = logEvent.args as any 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();
|
||||
};
|
||||
zeroEx.token.subscribe(
|
||||
tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
|
||||
const callback = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
expect(logEvent).to.not.be.undefined();
|
||||
expect(logEvent.isRemoved).to.be.false();
|
||||
const args = logEvent.log.args;
|
||||
expect(args._owner).to.be.equal(coinbase);
|
||||
expect(args._spender).to.be.equal(addressWithoutFunds);
|
||||
expect(args._value).to.be.bignumber.equal(allowanceAmount);
|
||||
},
|
||||
);
|
||||
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
|
||||
await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Outstanding subscriptions are cancelled when zeroEx.setProviderAsync called', (done: DoneCallback) => {
|
||||
it('Outstanding subscriptions are cancelled when zeroEx.setProvider called', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callbackNeverToBeCalled = (logEvent: LogEvent) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
};
|
||||
zeroEx.token.subscribe(
|
||||
tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled,
|
||||
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
},
|
||||
);
|
||||
const callbackToBeCalled = (logEvent: LogEvent) => {
|
||||
done();
|
||||
};
|
||||
zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled);
|
||||
const callbackToBeCalled = reportNodeCallbackErrors(done)();
|
||||
const newProvider = web3Factory.getRpcProvider();
|
||||
await zeroEx.setProviderAsync(newProvider);
|
||||
zeroEx.token.subscribe(
|
||||
tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackToBeCalled,
|
||||
);
|
||||
zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID);
|
||||
zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackToBeCalled);
|
||||
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callbackNeverToBeCalled = (logEvent: LogEvent) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
};
|
||||
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
|
||||
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
},
|
||||
);
|
||||
const subscriptionToken = zeroEx.token.subscribe(
|
||||
tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled);
|
||||
tokenAddress,
|
||||
TokenEvents.Transfer,
|
||||
indexFilterValues,
|
||||
callbackNeverToBeCalled,
|
||||
);
|
||||
zeroEx.token.unsubscribe(subscriptionToken);
|
||||
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
|
||||
done();
|
||||
@@ -420,29 +455,33 @@ describe('TokenWrapper', () => {
|
||||
describe('#getLogsAsync', () => {
|
||||
let tokenAddress: string;
|
||||
let tokenTransferProxyAddress: string;
|
||||
const subscriptionOpts: SubscriptionOpts = {
|
||||
fromBlock: BlockParamLiteral.Earliest,
|
||||
const blockRange: BlockRange = {
|
||||
fromBlock: 0,
|
||||
toBlock: BlockParamLiteral.Latest,
|
||||
};
|
||||
let txHash: string;
|
||||
before(async () => {
|
||||
before(() => {
|
||||
const token = tokens[0];
|
||||
tokenAddress = token.address;
|
||||
tokenTransferProxyAddress = await zeroEx.proxy.getContractAddressAsync();
|
||||
tokenTransferProxyAddress = zeroEx.proxy.getContractAddress();
|
||||
});
|
||||
it('should get logs with decoded args emitted by Approval', async () => {
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const eventName = TokenEvents.Approval;
|
||||
const indexFilterValues = {};
|
||||
const logs = await zeroEx.token.getLogsAsync(
|
||||
tokenAddress, eventName, subscriptionOpts, indexFilterValues,
|
||||
const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>(
|
||||
tokenAddress,
|
||||
eventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(1);
|
||||
const args = logs[0].args;
|
||||
expect(logs[0].event).to.be.equal(eventName);
|
||||
expect(logs[0].args._owner).to.be.equal(coinbase);
|
||||
expect(logs[0].args._spender).to.be.equal(tokenTransferProxyAddress);
|
||||
expect(logs[0].args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
expect(args._owner).to.be.equal(coinbase);
|
||||
expect(args._spender).to.be.equal(tokenTransferProxyAddress);
|
||||
expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
it('should only get the logs with the correct event name', async () => {
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
|
||||
@@ -450,7 +489,10 @@ describe('TokenWrapper', () => {
|
||||
const differentEventName = TokenEvents.Transfer;
|
||||
const indexFilterValues = {};
|
||||
const logs = await zeroEx.token.getLogsAsync(
|
||||
tokenAddress, differentEventName, subscriptionOpts, indexFilterValues,
|
||||
tokenAddress,
|
||||
differentEventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(0);
|
||||
});
|
||||
@@ -463,11 +505,16 @@ describe('TokenWrapper', () => {
|
||||
const indexFilterValues = {
|
||||
_owner: coinbase,
|
||||
};
|
||||
const logs = await zeroEx.token.getLogsAsync(
|
||||
tokenAddress, eventName, subscriptionOpts, indexFilterValues,
|
||||
const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>(
|
||||
tokenAddress,
|
||||
eventName,
|
||||
blockRange,
|
||||
indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(1);
|
||||
expect(logs[0].args._owner).to.be.equal(coinbase);
|
||||
const args = logs[0].args;
|
||||
expect(args._owner).to.be.equal(coinbase);
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:disable:max-file-line-count
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as chai from 'chai';
|
||||
import * as dirtyChai from 'dirty-chai';
|
||||
import ChaiBigNumber = require('chai-bignumber');
|
||||
import chaiAsPromised = require('chai-as-promised');
|
||||
import ChaiBigNumber = require('chai-bignumber');
|
||||
import * as dirtyChai from 'dirty-chai';
|
||||
|
||||
export const chaiSetup = {
|
||||
configure() {
|
||||
9
packages/0x.js/test/utils/constants.ts
Normal file
9
packages/0x.js/test/utils/constants.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export const constants = {
|
||||
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
|
||||
ROPSTEN_NETWORK_ID: 3,
|
||||
KOVAN_NETWORK_ID: 42,
|
||||
TESTRPC_NETWORK_ID: 50,
|
||||
KOVAN_RPC_URL: 'https://kovan.infura.io/',
|
||||
ROPSTEN_RPC_URL: 'https://ropsten.infura.io/',
|
||||
ZRX_DECIMALS: 18,
|
||||
};
|
||||
202
packages/0x.js/test/utils/fill_scenarios.ts
Normal file
202
packages/0x.js/test/utils/fill_scenarios.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
|
||||
import { SignedOrder, Token, ZeroEx } from '../../src';
|
||||
import { artifacts } from '../../src/artifacts';
|
||||
import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token';
|
||||
import { orderFactory } from '../utils/order_factory';
|
||||
|
||||
import { constants } from './constants';
|
||||
|
||||
const INITIAL_COINBASE_TOKEN_SUPPLY_IN_UNITS = new BigNumber(100);
|
||||
|
||||
export class FillScenarios {
|
||||
private _zeroEx: ZeroEx;
|
||||
private _userAddresses: string[];
|
||||
private _tokens: Token[];
|
||||
private _coinbase: string;
|
||||
private _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 initTokenBalancesAsync() {
|
||||
const web3Wrapper = (this._zeroEx as any)._web3Wrapper as Web3Wrapper;
|
||||
for (const token of this._tokens) {
|
||||
if (token.symbol !== 'ZRX' && token.symbol !== 'WETH') {
|
||||
const contractInstance = web3Wrapper.getContractInstance(
|
||||
artifacts.DummyTokenArtifact.abi,
|
||||
token.address,
|
||||
);
|
||||
const defaults = {};
|
||||
const dummyToken = new DummyTokenContract(contractInstance, defaults);
|
||||
const tokenSupply = ZeroEx.toBaseUnitAmount(INITIAL_COINBASE_TOKEN_SUPPLY_IN_UNITS, token.decimals);
|
||||
const txHash = await dummyToken.setBalance.sendTransactionAsync(this._coinbase, tokenSupply, {
|
||||
from: this._coinbase,
|
||||
});
|
||||
await this._zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
public async createFillableSignedOrderAsync(
|
||||
makerTokenAddress: string,
|
||||
takerTokenAddress: string,
|
||||
makerAddress: string,
|
||||
takerAddress: string,
|
||||
fillableAmount: BigNumber,
|
||||
expirationUnixTimestampSec?: BigNumber,
|
||||
): Promise<SignedOrder> {
|
||||
return this.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
fillableAmount,
|
||||
expirationUnixTimestampSec,
|
||||
);
|
||||
}
|
||||
public async createFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress: string,
|
||||
takerTokenAddress: string,
|
||||
makerFee: BigNumber,
|
||||
takerFee: BigNumber,
|
||||
makerAddress: string,
|
||||
takerAddress: string,
|
||||
fillableAmount: BigNumber,
|
||||
feeRecepient: string,
|
||||
expirationUnixTimestampSec?: BigNumber,
|
||||
): Promise<SignedOrder> {
|
||||
return this._createAsymmetricFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerFee,
|
||||
takerFee,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
fillableAmount,
|
||||
feeRecepient,
|
||||
expirationUnixTimestampSec,
|
||||
);
|
||||
}
|
||||
public async createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress: string,
|
||||
takerTokenAddress: string,
|
||||
makerAddress: string,
|
||||
takerAddress: string,
|
||||
makerFillableAmount: BigNumber,
|
||||
takerFillableAmount: BigNumber,
|
||||
expirationUnixTimestampSec?: BigNumber,
|
||||
): Promise<SignedOrder> {
|
||||
const makerFee = new BigNumber(0);
|
||||
const takerFee = new BigNumber(0);
|
||||
const feeRecepient = constants.NULL_ADDRESS;
|
||||
return this._createAsymmetricFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerFee,
|
||||
takerFee,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
makerFillableAmount,
|
||||
takerFillableAmount,
|
||||
feeRecepient,
|
||||
expirationUnixTimestampSec,
|
||||
);
|
||||
}
|
||||
public async createPartiallyFilledSignedOrderAsync(
|
||||
makerTokenAddress: string,
|
||||
takerTokenAddress: string,
|
||||
takerAddress: string,
|
||||
fillableAmount: BigNumber,
|
||||
partialFillAmount: BigNumber,
|
||||
) {
|
||||
const [makerAddress] = this._userAddresses;
|
||||
const signedOrder = await this.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress,
|
||||
takerTokenAddress,
|
||||
makerAddress,
|
||||
takerAddress,
|
||||
fillableAmount,
|
||||
fillableAmount,
|
||||
);
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = false;
|
||||
await this._zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder,
|
||||
partialFillAmount,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
takerAddress,
|
||||
);
|
||||
return signedOrder;
|
||||
}
|
||||
private async _createAsymmetricFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress: string,
|
||||
takerTokenAddress: string,
|
||||
makerFee: BigNumber,
|
||||
takerFee: BigNumber,
|
||||
makerAddress: string,
|
||||
takerAddress: string,
|
||||
makerFillableAmount: BigNumber,
|
||||
takerFillableAmount: BigNumber,
|
||||
feeRecepient: string,
|
||||
expirationUnixTimestampSec?: BigNumber,
|
||||
): Promise<SignedOrder> {
|
||||
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,
|
||||
this._exchangeContractAddress,
|
||||
feeRecepient,
|
||||
expirationUnixTimestampSec,
|
||||
);
|
||||
return signedOrder;
|
||||
}
|
||||
private async _increaseBalanceAndAllowanceAsync(
|
||||
tokenAddress: string,
|
||||
address: string,
|
||||
amount: BigNumber,
|
||||
): Promise<void> {
|
||||
if (amount.isZero() || address === ZeroEx.NULL_ADDRESS) {
|
||||
return; // noop
|
||||
}
|
||||
await Promise.all([
|
||||
this._increaseBalanceAsync(tokenAddress, address, amount),
|
||||
this._increaseAllowanceAsync(tokenAddress, address, amount),
|
||||
]);
|
||||
}
|
||||
private async _increaseBalanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> {
|
||||
await this._zeroEx.token.transferAsync(tokenAddress, this._coinbase, address, amount);
|
||||
}
|
||||
private async _increaseAllowanceAsync(tokenAddress: string, address: string, amount: 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,25 +1,29 @@
|
||||
import { BigNumber } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import {ZeroEx, SignedOrder} from '../../src';
|
||||
|
||||
import { SignedOrder, ZeroEx } from '../../src';
|
||||
|
||||
const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false;
|
||||
|
||||
export const orderFactory = {
|
||||
async createSignedOrderAsync(
|
||||
zeroEx: ZeroEx,
|
||||
maker: string,
|
||||
taker: string,
|
||||
makerFee: BigNumber.BigNumber,
|
||||
takerFee: BigNumber.BigNumber,
|
||||
makerTokenAmount: BigNumber.BigNumber,
|
||||
makerFee: BigNumber,
|
||||
takerFee: BigNumber,
|
||||
makerTokenAmount: BigNumber,
|
||||
makerTokenAddress: string,
|
||||
takerTokenAmount: BigNumber.BigNumber,
|
||||
takerTokenAmount: BigNumber,
|
||||
takerTokenAddress: string,
|
||||
exchangeContractAddress: string,
|
||||
feeRecipient: string,
|
||||
expirationUnixTimestampSec?: BigNumber.BigNumber): Promise<SignedOrder> {
|
||||
expirationUnixTimestampSecIfExists?: BigNumber,
|
||||
): Promise<SignedOrder> {
|
||||
const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite
|
||||
expirationUnixTimestampSec = _.isUndefined(expirationUnixTimestampSec) ?
|
||||
defaultExpirationUnixTimestampSec :
|
||||
expirationUnixTimestampSec;
|
||||
const expirationUnixTimestampSec = _.isUndefined(expirationUnixTimestampSecIfExists)
|
||||
? defaultExpirationUnixTimestampSec
|
||||
: expirationUnixTimestampSecIfExists;
|
||||
const order = {
|
||||
maker,
|
||||
taker,
|
||||
@@ -35,8 +39,8 @@ export const orderFactory = {
|
||||
expirationUnixTimestampSec,
|
||||
};
|
||||
const orderHash = ZeroEx.getOrderHashHex(order);
|
||||
const ecSignature = await zeroEx.signOrderHashAsync(orderHash, maker);
|
||||
const signedOrder: SignedOrder = _.assign(order, {ecSignature});
|
||||
const ecSignature = await zeroEx.signOrderHashAsync(orderHash, maker, SHOULD_ADD_PERSONAL_MESSAGE_PREFIX);
|
||||
const signedOrder: SignedOrder = _.assign(order, { ecSignature });
|
||||
return signedOrder;
|
||||
},
|
||||
};
|
||||
66
packages/0x.js/test/utils/report_callback_errors.ts
Normal file
66
packages/0x.js/test/utils/report_callback_errors.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { DoneCallback } from '../../src/types';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
export const reportNoErrorCallbackErrors = (done: DoneCallback, expectToBeCalledOnce = true) => {
|
||||
return <T>(f?: (value: T) => void) => {
|
||||
const wrapped = (value: T) => {
|
||||
if (_.isUndefined(f)) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
f(value);
|
||||
if (expectToBeCalledOnce) {
|
||||
done();
|
||||
}
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
};
|
||||
return wrapped;
|
||||
};
|
||||
};
|
||||
|
||||
export const reportNodeCallbackErrors = (done: DoneCallback, expectToBeCalledOnce = true) => {
|
||||
return <T>(f?: (value: T) => void) => {
|
||||
const wrapped = (error: Error | null, value: T | undefined) => {
|
||||
if (!_.isNull(error)) {
|
||||
done(error);
|
||||
} else {
|
||||
if (_.isUndefined(f)) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
f(value as T);
|
||||
if (expectToBeCalledOnce) {
|
||||
done();
|
||||
}
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
return wrapped;
|
||||
};
|
||||
};
|
||||
|
||||
export const assertNodeCallbackError = (done: DoneCallback, errMsg: string) => {
|
||||
const wrapped = <T>(error: Error | null, value: T | undefined) => {
|
||||
if (_.isNull(error)) {
|
||||
done(new Error('Expected callback to receive an error'));
|
||||
} else {
|
||||
try {
|
||||
expect(error.message).to.be.equal(errMsg);
|
||||
done();
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
return wrapped;
|
||||
};
|
||||
33
packages/0x.js/test/utils/token_utils.ts
Normal file
33
packages/0x.js/test/utils/token_utils.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { InternalZeroExError, Token } from '../../src/types';
|
||||
|
||||
const PROTOCOL_TOKEN_SYMBOL = 'ZRX';
|
||||
const WETH_TOKEN_SYMBOL = 'WETH';
|
||||
|
||||
export class TokenUtils {
|
||||
private _tokens: Token[];
|
||||
constructor(tokens: Token[]) {
|
||||
this._tokens = tokens;
|
||||
}
|
||||
public getProtocolTokenOrThrow(): Token {
|
||||
const zrxToken = _.find(this._tokens, { symbol: PROTOCOL_TOKEN_SYMBOL });
|
||||
if (_.isUndefined(zrxToken)) {
|
||||
throw new Error(InternalZeroExError.ZrxNotInTokenRegistry);
|
||||
}
|
||||
return zrxToken;
|
||||
}
|
||||
public getWethTokenOrThrow(): Token {
|
||||
const wethToken = _.find(this._tokens, { symbol: WETH_TOKEN_SYMBOL });
|
||||
if (_.isUndefined(wethToken)) {
|
||||
throw new Error(InternalZeroExError.WethNotInTokenRegistry);
|
||||
}
|
||||
return wethToken;
|
||||
}
|
||||
public getDummyTokens(): Token[] {
|
||||
const dummyTokens = _.filter(this._tokens, token => {
|
||||
return !_.includes([PROTOCOL_TOKEN_SYMBOL, WETH_TOKEN_SYMBOL], token.symbol);
|
||||
});
|
||||
return dummyTokens;
|
||||
}
|
||||
}
|
||||
16
packages/0x.js/tsconfig.json
Normal file
16
packages/0x.js/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"noImplicitThis": false
|
||||
},
|
||||
"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"
|
||||
]
|
||||
}
|
||||
3
packages/0x.js/tslint.json
Normal file
3
packages/0x.js/tslint.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["@0xproject/tslint-config"]
|
||||
}
|
||||
@@ -7,10 +7,10 @@ const path = require('path');
|
||||
const production = process.env.NODE_ENV === 'production';
|
||||
|
||||
let entry = {
|
||||
'index': './src/index.ts',
|
||||
index: './src/index.ts',
|
||||
};
|
||||
if (production) {
|
||||
entry = _.assign({}, entry, {'index.min': './src/index.ts'});
|
||||
entry = _.assign({}, entry, { 'index.min': './src/index.ts' });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
11
packages/abi-gen/CHANGELOG.md
Normal file
11
packages/abi-gen/CHANGELOG.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.2.0 - _???_
|
||||
|
||||
* Added CLI options for explicit specifying location of partials and main template (#346)
|
||||
|
||||
## v0.1.0 - _January 11, 2018_
|
||||
|
||||
* Fixed array typings with union types (#295)
|
||||
* Add event ABIs to context data passed to templates (#302)
|
||||
* Add constructor ABIs to context data passed to templates (#304)
|
||||
50
packages/abi-gen/README.md
Normal file
50
packages/abi-gen/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# ABI Gen
|
||||
|
||||
This package allows you to generate TypeScript contract wrappers from ABI files.
|
||||
It's heavily inspired by [Geth abigen](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts) but takes a different approach.
|
||||
You can write your custom handlebars templates which will allow you to seamlessly integrate the generated code into your existing codebase with existing conventions.
|
||||
|
||||
For an example of the generated [wrapper files](https://github.com/0xProject/0x.js/tree/development/packages/0x.js/src/contract_wrappers/generated) check out 0x.js.
|
||||
[Here](https://github.com/0xProject/0x.js/tree/development/packages/0x.js/contract_templates) are the templates used to generate those files.
|
||||
|
||||
## Installation
|
||||
|
||||
`yarn add -g @0xproject/abi-gen`
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
abi-gen
|
||||
Options:
|
||||
--help Show help [boolean]
|
||||
--version Show version number [boolean]
|
||||
--abiGlob Glob pattern to search for ABI JSON files [string] [required]
|
||||
--templates Folder where to search for templates [string] [required]
|
||||
--output Folder where to put the output files [string] [required]
|
||||
```
|
||||
|
||||
## ABI files
|
||||
|
||||
You're required to pass a [glob](<https://en.wikipedia.org/wiki/Glob_(programming)>) template where your abi files are located.
|
||||
TL;DR - here is the example from 0x.js.
|
||||
|
||||
`--abiGlob 'src/artifacts/@(Exchange|Token|TokenTransferProxy|EtherToken|TokenRegistry).json`
|
||||
|
||||
We could've just used `--abiGlob 'src/artifacts/*.json` but we wanted to exclude some of the abi files.
|
||||
|
||||
The abi file should be either a [Truffle](http://truffleframework.com/) contract artifact (a JSON object with an abi key) or a JSON abi array.
|
||||
|
||||
## How to write custom templates?
|
||||
|
||||
The best way to get started is to copy [0x.js templates](https://github.com/0xProject/0x.js/tree/development/packages/0x.js/contract_templates) and start adjusting them for your needs.
|
||||
We use [handlebars](http://handlebarsjs.com/) template engine under the hood.
|
||||
You need to have a master template called `contract.mustache`. it will be used to generate each contract wrapper. Although - you don't need and probably shouldn't write all your logic in a single template file. You can write [partial templates](http://handlebarsjs.com/partials.html) and as long as they are within a partials folder - they will be registered and available.
|
||||
|
||||
## Which data/context do I get in my templates?
|
||||
|
||||
For now you don't get much on top of methods abi, some useful helpers and a contract name because it was enough for our use-case, but if you need something else - create a PR.
|
||||
See the [type definition](https://github.com/0xProject/0x.js/tree/development/packages/abi-gen/src/types.ts) of what we pass to the render method.
|
||||
|
||||
## Output files
|
||||
|
||||
Output files will be generated within an output folder with names converted to camel case and taken from abi file names. If you already have some files in that folder they will be overwritten.
|
||||
48
packages/abi-gen/package.json
Normal file
48
packages/abi-gen/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@0xproject/abi-gen",
|
||||
"version": "0.1.5",
|
||||
"description": "Generate contract wrappers from ABI and handlebars templates",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"scripts": {
|
||||
"lint": "tslint --project . 'src/**/*.ts'",
|
||||
"clean": "shx rm -rf lib",
|
||||
"build": "tsc"
|
||||
},
|
||||
"bin": {
|
||||
"abi-gen": "lib/index.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/0xProject/0x.js.git"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/0xProject/0x.js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x.js/packages/abi-gen/README.md",
|
||||
"dependencies": {
|
||||
"@0xproject/utils": "^0.2.3",
|
||||
"chalk": "^2.3.0",
|
||||
"glob": "^7.1.2",
|
||||
"handlebars": "^4.0.11",
|
||||
"lodash": "^4.17.4",
|
||||
"mkdirp": "^0.5.1",
|
||||
"to-snake-case": "^1.0.0",
|
||||
"web3": "^0.20.0",
|
||||
"yargs": "^10.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@0xproject/tslint-config": "^0.4.5",
|
||||
"@types/glob": "^5.0.33",
|
||||
"@types/handlebars": "^4.0.36",
|
||||
"@types/mkdirp": "^0.5.1",
|
||||
"@types/node": "^8.0.53",
|
||||
"@types/yargs": "^10.0.0",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"shx": "^0.2.2",
|
||||
"tslint": "5.8.0",
|
||||
"typescript": "~2.6.1",
|
||||
"web3-typescript-typings": "^0.9.7"
|
||||
}
|
||||
}
|
||||
15
packages/abi-gen/scripts/postpublish.js
Normal file
15
packages/abi-gen/scripts/postpublish.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const postpublish_utils = require('../../../scripts/postpublish_utils');
|
||||
const packageJSON = require('../package.json');
|
||||
|
||||
const subPackageName = packageJSON.name;
|
||||
|
||||
postpublish_utils
|
||||
.getLatestTagAndVersionAsync(subPackageName)
|
||||
.then(function(result) {
|
||||
const releaseName = postpublish_utils.getReleaseName(subPackageName, result.version);
|
||||
const assets = [];
|
||||
return postpublish_utils.publishReleaseNotes(result.tag, releaseName, assets);
|
||||
})
|
||||
.catch(function(err) {
|
||||
throw err;
|
||||
});
|
||||
4
packages/abi-gen/src/globals.d.ts
vendored
Normal file
4
packages/abi-gen/src/globals.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare function toSnakeCase(str: string): string;
|
||||
declare module 'to-snake-case' {
|
||||
export = toSnakeCase;
|
||||
}
|
||||
130
packages/abi-gen/src/index.ts
Normal file
130
packages/abi-gen/src/index.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import chalk from 'chalk';
|
||||
import * as fs from 'fs';
|
||||
import { sync as globSync } from 'glob';
|
||||
import * as Handlebars from 'handlebars';
|
||||
import * as _ from 'lodash';
|
||||
import * as mkdirp from 'mkdirp';
|
||||
import * as yargs from 'yargs';
|
||||
|
||||
import toSnakeCase = require('to-snake-case');
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
import { ContextData, ParamKind } from './types';
|
||||
import { utils } from './utils';
|
||||
|
||||
const ABI_TYPE_CONSTRUCTOR = 'constructor';
|
||||
const ABI_TYPE_METHOD = 'function';
|
||||
const ABI_TYPE_EVENT = 'event';
|
||||
|
||||
const args = yargs
|
||||
.option('abis', {
|
||||
describe: 'Glob pattern to search for ABI JSON files',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
})
|
||||
.option('output', {
|
||||
alias: ['o', 'out'],
|
||||
describe: 'Folder where to put the output files',
|
||||
type: 'string',
|
||||
normalize: true,
|
||||
demandOption: true,
|
||||
})
|
||||
.option('partials', {
|
||||
describe: 'Glob pattern for the partial template files',
|
||||
type: 'string',
|
||||
implies: 'template',
|
||||
})
|
||||
.option('template', {
|
||||
describe: 'Path for the main template file that will be used to generate each contract',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
normalize: true,
|
||||
})
|
||||
.example(
|
||||
"$0 --abis 'src/artifacts/**/*.json' --out 'src/contracts/generated/' --partials 'src/templates/partials/**/*.handlebars' --template 'src/templates/contract.handlebars'",
|
||||
'Full usage example',
|
||||
).argv;
|
||||
|
||||
function registerPartials(partialsGlob: string) {
|
||||
const partialTemplateFileNames = globSync(partialsGlob);
|
||||
utils.log(`Found ${chalk.green(`${partialTemplateFileNames.length}`)} ${chalk.bold('partial')} templates`);
|
||||
for (const partialTemplateFileName of partialTemplateFileNames) {
|
||||
const namedContent = utils.getNamedContent(partialTemplateFileName);
|
||||
Handlebars.registerPartial(namedContent.name, namedContent.content);
|
||||
}
|
||||
return partialsGlob;
|
||||
}
|
||||
|
||||
function writeOutputFile(name: string, renderedTsCode: string): void {
|
||||
const fileName = toSnakeCase(name);
|
||||
const filePath = `${args.output}/${fileName}.ts`;
|
||||
fs.writeFileSync(filePath, renderedTsCode);
|
||||
utils.log(`Created: ${chalk.bold(filePath)}`);
|
||||
}
|
||||
|
||||
Handlebars.registerHelper('parameterType', utils.solTypeToTsType.bind(utils, ParamKind.Input));
|
||||
Handlebars.registerHelper('returnType', utils.solTypeToTsType.bind(utils, ParamKind.Output));
|
||||
|
||||
if (args.partials) {
|
||||
registerPartials(args.partials);
|
||||
}
|
||||
const mainTemplate = utils.getNamedContent(args.template);
|
||||
const template = Handlebars.compile<ContextData>(mainTemplate.content);
|
||||
const abiFileNames = globSync(args.abis);
|
||||
|
||||
if (_.isEmpty(abiFileNames)) {
|
||||
utils.log(`${chalk.red(`No ABI files found.`)}`);
|
||||
utils.log(`Please make sure you've passed the correct folder name and that the files have
|
||||
${chalk.bold('*.json')} extensions`);
|
||||
process.exit(1);
|
||||
} else {
|
||||
utils.log(`Found ${chalk.green(`${abiFileNames.length}`)} ${chalk.bold('ABI')} files`);
|
||||
mkdirp.sync(args.output);
|
||||
}
|
||||
for (const abiFileName of abiFileNames) {
|
||||
const namedContent = utils.getNamedContent(abiFileName);
|
||||
utils.log(`Processing: ${chalk.bold(namedContent.name)}...`);
|
||||
const parsedContent = JSON.parse(namedContent.content);
|
||||
const ABI = _.isArray(parsedContent)
|
||||
? parsedContent // ABI file
|
||||
: parsedContent.abi; // Truffle contracts file
|
||||
if (_.isUndefined(ABI)) {
|
||||
utils.log(`${chalk.red(`ABI not found in ${abiFileName}.`)}`);
|
||||
utils.log(`Please make sure your ABI file is either an array with ABI entries or an object with the abi key`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let ctor = ABI.find((abi: Web3.AbiDefinition) => abi.type === ABI_TYPE_CONSTRUCTOR) as Web3.ConstructorAbi;
|
||||
if (_.isUndefined(ctor)) {
|
||||
ctor = utils.getEmptyConstructor(); // The constructor exists, but it's implicit in JSON's ABI definition
|
||||
}
|
||||
|
||||
const methodAbis = ABI.filter((abi: Web3.AbiDefinition) => abi.type === ABI_TYPE_METHOD) as Web3.MethodAbi[];
|
||||
const methodsData = _.map(methodAbis, methodAbi => {
|
||||
_.map(methodAbi.inputs, input => {
|
||||
if (_.isEmpty(input.name)) {
|
||||
// Auto-generated getters don't have parameter names
|
||||
input.name = 'index';
|
||||
}
|
||||
});
|
||||
// This will make templates simpler
|
||||
const methodData = {
|
||||
...methodAbi,
|
||||
singleReturnValue: methodAbi.outputs.length === 1,
|
||||
};
|
||||
return methodData;
|
||||
});
|
||||
|
||||
const eventAbis = ABI.filter((abi: Web3.AbiDefinition) => abi.type === ABI_TYPE_EVENT) as Web3.EventAbi[];
|
||||
|
||||
const contextData = {
|
||||
contractName: namedContent.name,
|
||||
ctor,
|
||||
methods: methodsData,
|
||||
events: eventAbis,
|
||||
};
|
||||
const renderedTsCode = template(contextData);
|
||||
writeOutputFile(namedContent.name, renderedTsCode);
|
||||
}
|
||||
23
packages/abi-gen/src/types.ts
Normal file
23
packages/abi-gen/src/types.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
export enum ParamKind {
|
||||
Input = 'input',
|
||||
Output = 'output',
|
||||
}
|
||||
|
||||
export enum AbiType {
|
||||
Function = 'function',
|
||||
Constructor = 'constructor',
|
||||
Event = 'event',
|
||||
Fallback = 'fallback',
|
||||
}
|
||||
|
||||
export interface Method extends Web3.MethodAbi {
|
||||
singleReturnValue: boolean;
|
||||
}
|
||||
|
||||
export interface ContextData {
|
||||
contractName: string;
|
||||
methods: Method[];
|
||||
events: Web3.EventAbi[];
|
||||
}
|
||||
73
packages/abi-gen/src/utils.ts
Normal file
73
packages/abi-gen/src/utils.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import * as fs from 'fs';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
import { AbiType, ParamKind } from './types';
|
||||
|
||||
export const utils = {
|
||||
solTypeToTsType(paramKind: ParamKind, solType: string): string {
|
||||
const trailingArrayRegex = /\[\d*\]$/;
|
||||
if (solType.match(trailingArrayRegex)) {
|
||||
const arrayItemSolType = solType.replace(trailingArrayRegex, '');
|
||||
const arrayItemTsType = utils.solTypeToTsType(paramKind, arrayItemSolType);
|
||||
const arrayTsType = utils.isUnionType(arrayItemTsType)
|
||||
? `Array<${arrayItemTsType}>`
|
||||
: `${arrayItemTsType}[]`;
|
||||
return arrayTsType;
|
||||
} else {
|
||||
const solTypeRegexToTsType = [
|
||||
{ regex: '^string$', tsType: 'string' },
|
||||
{ regex: '^address$', tsType: 'string' },
|
||||
{ regex: '^bool$', tsType: 'boolean' },
|
||||
{ regex: '^u?int\\d*$', tsType: 'BigNumber' },
|
||||
{ regex: '^bytes\\d*$', tsType: 'string' },
|
||||
];
|
||||
if (paramKind === ParamKind.Input) {
|
||||
// web3 allows to pass those an non-bignumbers and that's nice
|
||||
// but it always returns stuff as BigNumbers
|
||||
solTypeRegexToTsType.unshift({
|
||||
regex: '^u?int(8|16|32)?$',
|
||||
tsType: 'number|BigNumber',
|
||||
});
|
||||
}
|
||||
for (const regexAndTxType of solTypeRegexToTsType) {
|
||||
const { regex, tsType } = regexAndTxType;
|
||||
if (solType.match(regex)) {
|
||||
return tsType;
|
||||
}
|
||||
}
|
||||
throw new Error(`Unknown Solidity type found: ${solType}`);
|
||||
}
|
||||
},
|
||||
isUnionType(tsType: string): boolean {
|
||||
return tsType === 'number|BigNumber';
|
||||
},
|
||||
log(...args: any[]): void {
|
||||
console.log(...args); // tslint:disable-line:no-console
|
||||
},
|
||||
getPartialNameFromFileName(filename: string): string {
|
||||
const name = path.parse(filename).name;
|
||||
return name;
|
||||
},
|
||||
getNamedContent(filename: string): { name: string; content: string } {
|
||||
const name = utils.getPartialNameFromFileName(filename);
|
||||
try {
|
||||
const content = fs.readFileSync(filename).toString();
|
||||
return {
|
||||
name,
|
||||
content,
|
||||
};
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to read ${filename}: ${err}`);
|
||||
}
|
||||
},
|
||||
getEmptyConstructor(): Web3.ConstructorAbi {
|
||||
return {
|
||||
type: AbiType.Constructor,
|
||||
stateMutability: 'nonpayable',
|
||||
payable: false,
|
||||
inputs: [],
|
||||
};
|
||||
},
|
||||
};
|
||||
7
packages/abi-gen/tsconfig.json
Normal file
7
packages/abi-gen/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["./src/**/*", "./test/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"]
|
||||
}
|
||||
3
packages/abi-gen/tslint.json
Normal file
3
packages/abi-gen/tslint.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["@0xproject/tslint-config"]
|
||||
}
|
||||
6
packages/assert/CHANGELOG.md
Normal file
6
packages/assert/CHANGELOG.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.0.4 - _Nov. 14, 2017_
|
||||
|
||||
* Re-publish Assert previously published under NPM package @0xproject/0x-assert
|
||||
* Added assertion isValidBaseUnitAmount which checks both that the value is a valid bigNumber and that it does not contain decimals.
|
||||
55
packages/assert/README.md
Normal file
55
packages/assert/README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
## @0xproject/assert
|
||||
|
||||
Standard type and schema assertions to be used across all 0x projects and packages
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
yarn add @0xproject/assert
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
import { assert } from '@0xproject/assert';
|
||||
|
||||
assert.isValidBaseUnitAmount('baseUnitAmount', baseUnitAmount);
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
We strongly encourage that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository.
|
||||
|
||||
Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them:
|
||||
|
||||
```bash
|
||||
yarn config set workspaces-experimental true
|
||||
```
|
||||
|
||||
Then install dependencies
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Lint
|
||||
|
||||
```bash
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
45
packages/assert/package.json
Normal file
45
packages/assert/package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@0xproject/assert",
|
||||
"version": "0.0.14",
|
||||
"description": "Provides a standard way of performing type and schema validation across 0x projects",
|
||||
"main": "lib/src/index.js",
|
||||
"types": "lib/src/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "shx rm -rf _bundles lib test_temp",
|
||||
"lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'",
|
||||
"run_mocha": "mocha lib/test/**/*_test.js",
|
||||
"prepublishOnly": "run-p build",
|
||||
"test": "run-s clean build run_mocha",
|
||||
"test:circleci": "yarn test"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/0xProject/0x.js.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/0xProject/0x.js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x.js/packages/assert/README.md",
|
||||
"devDependencies": {
|
||||
"@0xproject/tslint-config": "^0.4.5",
|
||||
"@types/lodash": "^4.14.86",
|
||||
"@types/mocha": "^2.2.42",
|
||||
"@types/valid-url": "^1.0.2",
|
||||
"chai": "^4.0.1",
|
||||
"chai-typescript-typings": "^0.0.2",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"mocha": "^4.0.1",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"shx": "^0.2.2",
|
||||
"tslint": "5.8.0",
|
||||
"typescript": "~2.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0xproject/json-schemas": "^0.7.6",
|
||||
"@0xproject/utils": "^0.2.3",
|
||||
"lodash": "^4.17.4",
|
||||
"valid-url": "^1.0.9"
|
||||
}
|
||||
}
|
||||
15
packages/assert/scripts/postpublish.js
Normal file
15
packages/assert/scripts/postpublish.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const postpublish_utils = require('../../../scripts/postpublish_utils');
|
||||
const packageJSON = require('../package.json');
|
||||
|
||||
const subPackageName = packageJSON.name;
|
||||
|
||||
postpublish_utils
|
||||
.getLatestTagAndVersionAsync(subPackageName)
|
||||
.then(function(result) {
|
||||
const releaseName = postpublish_utils.getReleaseName(subPackageName, result.version);
|
||||
const assets = [];
|
||||
return postpublish_utils.publishReleaseNotes(result.tag, releaseName, assets);
|
||||
})
|
||||
.catch(function(err) {
|
||||
throw err;
|
||||
});
|
||||
1
packages/assert/src/globals.d.ts
vendored
Normal file
1
packages/assert/src/globals.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare module 'dirty-chai';
|
||||
@@ -1,18 +1,24 @@
|
||||
import { Schema, SchemaValidator } from '@0xproject/json-schemas';
|
||||
import { addressUtils, BigNumber } from '@0xproject/utils';
|
||||
import * as _ from 'lodash';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import * as Web3 from 'web3';
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {SchemaValidator, Schema} from '0x-json-schemas';
|
||||
import * as validUrl from 'valid-url';
|
||||
|
||||
const HEX_REGEX = /^0x[0-9A-F]*$/i;
|
||||
|
||||
export const assert = {
|
||||
isBigNumber(variableName: string, value: BigNumber.BigNumber): void {
|
||||
const isBigNumber = _.isObject(value) && value.isBigNumber;
|
||||
isBigNumber(variableName: string, value: BigNumber): void {
|
||||
const isBigNumber = _.isObject(value) && (value as any).isBigNumber;
|
||||
this.assert(isBigNumber, this.typeAssertionMessage(variableName, 'BigNumber', value));
|
||||
},
|
||||
isUndefined(value: any, variableName?: string): void {
|
||||
this.assert(_.isUndefined(value), this.typeAssertionMessage(variableName, 'undefined', value));
|
||||
isValidBaseUnitAmount(variableName: string, value: BigNumber) {
|
||||
assert.isBigNumber(variableName, value);
|
||||
const isNegative = value.lessThan(0);
|
||||
this.assert(!isNegative, `${variableName} cannot be a negative number, found value: ${value.toNumber()}`);
|
||||
const hasDecimals = value.decimalPlaces() !== 0;
|
||||
this.assert(
|
||||
!hasDecimals,
|
||||
`${variableName} should be in baseUnits (no decimals), found value: ${value.toNumber()}`,
|
||||
);
|
||||
},
|
||||
isString(variableName: string, value: string): void {
|
||||
this.assert(_.isString(value), this.typeAssertionMessage(variableName, 'string', value));
|
||||
@@ -21,19 +27,23 @@ export const assert = {
|
||||
this.assert(_.isFunction(value), this.typeAssertionMessage(variableName, 'function', value));
|
||||
},
|
||||
isHexString(variableName: string, value: string): void {
|
||||
this.assert(_.isString(value) && HEX_REGEX.test(value),
|
||||
this.typeAssertionMessage(variableName, 'HexString', value));
|
||||
this.assert(
|
||||
_.isString(value) && HEX_REGEX.test(value),
|
||||
this.typeAssertionMessage(variableName, 'HexString', value),
|
||||
);
|
||||
},
|
||||
isETHAddressHex(variableName: string, value: string): void {
|
||||
const web3 = new Web3();
|
||||
this.assert(web3.isAddress(value), this.typeAssertionMessage(variableName, 'ETHAddressHex', value));
|
||||
this.assert(addressUtils.isAddress(value), this.typeAssertionMessage(variableName, 'ETHAddressHex', value));
|
||||
this.assert(
|
||||
web3.isAddress(value) && value.toLowerCase() === value,
|
||||
addressUtils.isAddress(value) && value.toLowerCase() === 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 {
|
||||
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}'`);
|
||||
@@ -43,18 +53,6 @@ export const assert = {
|
||||
`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 provider`,
|
||||
);
|
||||
},
|
||||
async isUserAddressAvailableAsync(web3Wrapper: Web3Wrapper): Promise<void> {
|
||||
const availableAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
||||
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);
|
||||
},
|
||||
@@ -64,8 +62,8 @@ export const assert = {
|
||||
isBoolean(variableName: string, value: boolean): void {
|
||||
this.assert(_.isBoolean(value), this.typeAssertionMessage(variableName, 'boolean', value));
|
||||
},
|
||||
isWeb3Provider(variableName: string, value: Web3.Provider): void {
|
||||
const isWeb3Provider = _.isFunction((value as any).send) || _.isFunction((value as any).sendAsync);
|
||||
isWeb3Provider(variableName: string, value: any): void {
|
||||
const isWeb3Provider = _.isFunction(value.send) || _.isFunction(value.sendAsync);
|
||||
this.assert(isWeb3Provider, this.typeAssertionMessage(variableName, 'Web3.Provider', value));
|
||||
},
|
||||
doesConformToSchema(variableName: string, value: any, schema: Schema): void {
|
||||
@@ -77,6 +75,14 @@ Encountered: ${JSON.stringify(value, null, '\t')}
|
||||
Validation errors: ${validationResult.errors.join(', ')}`;
|
||||
this.assert(!hasValidationErrors, msg);
|
||||
},
|
||||
isHttpUrl(variableName: string, value: any): void {
|
||||
const isValidUrl = !_.isUndefined(validUrl.isWebUri(value));
|
||||
this.assert(isValidUrl, this.typeAssertionMessage(variableName, 'http url', value));
|
||||
},
|
||||
isUri(variableName: string, value: any): void {
|
||||
const isValidUri = !_.isUndefined(validUrl.isUri(value));
|
||||
this.assert(isValidUri, this.typeAssertionMessage(variableName, 'uri', value));
|
||||
},
|
||||
assert(condition: boolean, message: string): void {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user