Compare commits
911 Commits
@0x/contra
...
@0x/contra
Author | SHA1 | Date | |
---|---|---|---|
|
b6b618e5ce | ||
|
0bb8887027 | ||
|
fabfdd0aa2 | ||
|
8f8336b344 | ||
|
d10659f986 | ||
|
e853555165 | ||
|
2ce09d73ac | ||
|
14f48a5f4f | ||
|
8da4e4a830 | ||
|
88ae8311c8 | ||
|
eabf6a466a | ||
|
989f691d06 | ||
|
52a3dae7cb | ||
|
8a6dfacf71 | ||
|
19ca6c13ad | ||
|
9196f122dd | ||
|
55e3d81c58 | ||
|
80caa16718 | ||
|
ddc5aaacdb | ||
|
38825865cc | ||
|
157b2efa1d | ||
|
212a2d229a | ||
|
ccb89fb26a | ||
|
3a0b0c0973 | ||
|
c159ed9ebb | ||
|
e37dbcc273 | ||
|
841e660b1f | ||
|
391aba5f37 | ||
|
f5d30f4a07 | ||
|
230cf7dfb7 | ||
|
8734b70f83 | ||
|
ff9a2b2d9a | ||
|
a41dab2922 | ||
|
f4504106a1 | ||
|
1a5b8041c7 | ||
|
1e2170f8bb | ||
|
0a4a973a7f | ||
|
67137cadac | ||
|
ead4afa06b | ||
|
e39ef95191 | ||
|
3d7585671f | ||
|
44806106db | ||
|
6a8197a4e8 | ||
|
4d4b4e0f2b | ||
|
82da33d742 | ||
|
1c6130a492 | ||
|
dabe5e939f | ||
|
b4ac6d3439 | ||
|
d0ea74e180 | ||
|
a5f77f3964 | ||
|
9401bb53e8 | ||
|
347c6d02cf | ||
|
64a0080616 | ||
|
288a7d4cea | ||
|
1207b68f57 | ||
|
2b82187fe0 | ||
|
3e2dbfc83c | ||
|
6691f490bc | ||
|
a7db900e51 | ||
|
cfa2a90dae | ||
|
feebb45e9d | ||
|
13a2f3a330 | ||
|
1da8801084 | ||
|
7a7b17c4f3 | ||
|
2afb06de13 | ||
|
ac771e2865 | ||
|
4b09204936 | ||
|
e4a5518a7c | ||
|
e83507ba98 | ||
|
6e5b77edb2 | ||
|
98167da8fa | ||
|
11d5fec59b | ||
|
ed02f4ca88 | ||
|
e7c612971d | ||
|
242a2e21b5 | ||
|
92a1e5413b | ||
|
e1ab9aa690 | ||
|
222f7e6fd4 | ||
|
59001f827b | ||
|
549bfe98f1 | ||
|
64e3b6f5ee | ||
|
2fdc0426ff | ||
|
e6c70fda66 | ||
|
50da1354f3 | ||
|
e503cacf57 | ||
|
6a1e0edc78 | ||
|
048f5c2771 | ||
|
d9378e9a8f | ||
|
f77aaaf2e0 | ||
|
380835b151 | ||
|
857d91df85 | ||
|
cd9a6b0de7 | ||
|
86333f4928 | ||
|
d1975bd5dd | ||
|
08b9f27eac | ||
|
5b2cf8a776 | ||
|
7394106880 | ||
|
9134a7ae1e | ||
|
c617e9f483 | ||
|
545fcef716 | ||
|
e20fa3a1bf | ||
|
9d01b47d5b | ||
|
fa4c34df41 | ||
|
9f8ab4d626 | ||
|
1971c9cecd | ||
|
82f730831d | ||
|
035167e5b7 | ||
|
323fb0a965 | ||
|
45aacf122f | ||
|
5dcf7919fc | ||
|
f15560bd89 | ||
|
bf3ae730d6 | ||
|
92ce258bb4 | ||
|
5810e7df82 | ||
|
f2cbf4a561 | ||
|
0c8bb2e675 | ||
|
f51c4f9617 | ||
|
5345f7c983 | ||
|
7d9e43b2e1 | ||
|
570c1e1809 | ||
|
720d335b09 | ||
|
f0ecda1a48 | ||
|
4ed111a7d4 | ||
|
fc257523c7 | ||
|
9775f8d83c | ||
|
01a1b19556 | ||
|
bd228034fd | ||
|
2672a5c59f | ||
|
5f383430eb | ||
|
66ec3e9f4d | ||
|
c269c427a6 | ||
|
212855ebd8 | ||
|
8f78945a73 | ||
|
86d50cf597 | ||
|
8986d506a5 | ||
|
3d2c945c35 | ||
|
8206a3969f | ||
|
43afe67593 | ||
|
ecf939f8c8 | ||
|
f0c4ccfa1e | ||
|
718d48b7d5 | ||
|
d5e88677ae | ||
|
b1cfdc7a6a | ||
|
5c5f815b20 | ||
|
d9edb9675f | ||
|
be6fce5a89 | ||
|
12afeb30ae | ||
|
23df406ff0 | ||
|
c9ecef4fc3 | ||
|
62f0a867a8 | ||
|
f68b8d82e0 | ||
|
6b7cb13e9a | ||
|
613af6013a | ||
|
0db56a781e | ||
|
fe7674b184 | ||
|
44a6fe7310 | ||
|
46690f0c35 | ||
|
cfc4e345cc | ||
|
c68278d824 | ||
|
09600a71cd | ||
|
d66ba70f5e | ||
|
e7c4120d24 | ||
|
1212e534a8 | ||
|
899d0f1e42 | ||
|
dde4ed3754 | ||
|
b596e02752 | ||
|
d5afe696da | ||
|
976be66dee | ||
|
98c8a6387a | ||
|
be1b636e30 | ||
|
8bae0b81aa | ||
|
7ee19e1306 | ||
|
626d0dfa93 | ||
|
8453c616a5 | ||
|
56bc2944d0 | ||
|
b2cf701e30 | ||
|
9a3b29acb7 | ||
|
85b217f167 | ||
|
a7700d6c22 | ||
|
1e274518dd | ||
|
f4f8927c79 | ||
|
60b7890f16 | ||
|
4d46290ef6 | ||
|
00f7e2cfc3 | ||
|
e742708261 | ||
|
8c9de31c5c | ||
|
2bf992f0ac | ||
|
52a01bcc11 | ||
|
f250e03c40 | ||
|
75acbfb042 | ||
|
740fd6f5d9 | ||
|
c58306cd49 | ||
|
985696631c | ||
|
d8a3d7f920 | ||
|
b6b96f0eee | ||
|
3f037ef3cc | ||
|
5c2d3fb3da | ||
|
84d38ea878 | ||
|
6226aa0b23 | ||
|
72af35834b | ||
|
3019e3817f | ||
|
da9e90faf0 | ||
|
e737395c2b | ||
|
5a33167594 | ||
|
dfb7e50948 | ||
|
b43c9f075c | ||
|
f42ce6adb5 | ||
|
bf533bbdbb | ||
|
9328729c4c | ||
|
2e23a044ca | ||
|
691a3a1e72 | ||
|
fc40f9634b | ||
|
4969441f21 | ||
|
d04d50794e | ||
|
5e1fbe34a9 | ||
|
2b75829a0c | ||
|
3b3fd0a3a2 | ||
|
1e4b61008a | ||
|
ffb71e9fd5 | ||
|
c1aeb4bde6 | ||
|
66bca5cd81 | ||
|
b68df3a067 | ||
|
066f3bb646 | ||
|
661c334928 | ||
|
a1895d4157 | ||
|
1e7efe026a | ||
|
02e7da979a | ||
|
bd2839110b | ||
|
1bcb2697e7 | ||
|
fdb9a664f5 | ||
|
8099c334c7 | ||
|
344d28ab78 | ||
|
5e41168305 | ||
|
dd97669cdd | ||
|
af75581659 | ||
|
3c08f5b86a | ||
|
7809cad6cb | ||
|
3e59029966 | ||
|
24249bcb4d | ||
|
543011c3de | ||
|
4d9f2586d9 | ||
|
7f94ebe362 | ||
|
c9bf1eda54 | ||
|
2cd4d9004c | ||
|
35cd0319d4 | ||
|
899030e966 | ||
|
15e3944d23 | ||
|
f1fbaedb0f | ||
|
cf04062a19 | ||
|
2e922bf7db | ||
|
741a731ecb | ||
|
91bff1976c | ||
|
fad500042a | ||
|
bdb2a01385 | ||
|
f372cd2d13 | ||
|
a435da910f | ||
|
f1d96d9673 | ||
|
45226ee1f6 | ||
|
03b8d29740 | ||
|
80a4af249f | ||
|
900d444946 | ||
|
7ca9393d2d | ||
|
b22d7bb310 | ||
|
c742cdfe5c | ||
|
2d3fe02cd7 | ||
|
7d61cb6bac | ||
|
a804433a7a | ||
|
11082320c2 | ||
|
9f9797b123 | ||
|
05d34616b7 | ||
|
c7f474ada1 | ||
|
c7328a63d0 | ||
|
db818794d5 | ||
|
b4b34e4890 | ||
|
eec016380d | ||
|
dcab272be0 | ||
|
418d033a5c | ||
|
e7a7713b80 | ||
|
52a580d845 | ||
|
ea42a0b5f5 | ||
|
a0b63da9a6 | ||
|
8d9932fc42 | ||
|
54be45bedc | ||
|
3c9d2a562b | ||
|
654bd5175c | ||
|
9db660cd3f | ||
|
9aeadff9bd | ||
|
cd631b789b | ||
|
4d98408aaf | ||
|
490aafba4e | ||
|
3cd31d49bb | ||
|
6dd78b8d35 | ||
|
bd51c34098 | ||
|
0e758fadee | ||
|
a67659ff8e | ||
|
d1203f90da | ||
|
c5f6ebee18 | ||
|
41cc5234c4 | ||
|
c2d3e5f052 | ||
|
f944b95e32 | ||
|
0a5b919cd9 | ||
|
b5f85c11fe | ||
|
5d44b5e913 | ||
|
dd1961d86c | ||
|
f3539bf448 | ||
|
d831e559f0 | ||
|
cc079660f3 | ||
|
4460dac697 | ||
|
31190f921c | ||
|
22515d8dce | ||
|
a099d970a4 | ||
|
95bb2c5504 | ||
|
0b1262cc4d | ||
|
f9244f6f7e | ||
|
9dd4ea1584 | ||
|
9d085cdb61 | ||
|
f00a2dbc59 | ||
|
20862d47ab | ||
|
cfa1bf36de | ||
|
c362b33b5a | ||
|
222a151eff | ||
|
d6115cff25 | ||
|
6459522617 | ||
|
16e1f0eea1 | ||
|
0564ac1530 | ||
|
b896f82282 | ||
|
d737d419d9 | ||
|
0de2b6983b | ||
|
25628c34ee | ||
|
54fdccd397 | ||
|
27af4a988d | ||
|
fddad131a2 | ||
|
4d493eeebd | ||
|
14c1495a1a | ||
|
49c029ddb3 | ||
|
ddbe142c4c | ||
|
4710b40e45 | ||
|
be0b296769 | ||
|
0a0ee67740 | ||
|
943d648225 | ||
|
6d835f5cc1 | ||
|
a59cd67acf | ||
|
bbc06be091 | ||
|
d303e9f347 | ||
|
85ee923d89 | ||
|
fe6ba20ff5 | ||
|
18acf50b12 | ||
|
509fabb61c | ||
|
cc3378b4cd | ||
|
8d3ccf333d | ||
|
520919b165 | ||
|
600b86dd31 | ||
|
9c3fdd2584 | ||
|
73974cd90f | ||
|
8a60b3b402 | ||
|
59ed5ae6b7 | ||
|
c687385974 | ||
|
2f72e15ea7 | ||
|
7ea99baeb2 | ||
|
14f3d20772 | ||
|
72f51df25f | ||
|
a94b58e304 | ||
|
d327fabf9c | ||
|
9e02888c74 | ||
|
98c9a847f3 | ||
|
a159f4c9d6 | ||
|
b197731ed2 | ||
|
028f54fdf0 | ||
|
de62a0f8ed | ||
|
a0fcc50a5f | ||
|
4f186e843c | ||
|
df64c20587 | ||
|
c78a602990 | ||
|
0acec57ba9 | ||
|
7423028fea | ||
|
49d951b7be | ||
|
a554c9518d | ||
|
f3c7ba445e | ||
|
3abff41385 | ||
|
6c36832f0e | ||
|
310c18707b | ||
|
422a4a5578 | ||
|
a995b2e1ae | ||
|
90ad681a9e | ||
|
2ff5c39712 | ||
|
ded849fd6d | ||
|
02f7064953 | ||
|
1ddadfce1e | ||
|
7720e5007c | ||
|
ccfd021796 | ||
|
fdcad84cee | ||
|
ad3e3b8421 | ||
|
f67b4f8902 | ||
|
3c0d7319ba | ||
|
7f0aab6ec6 | ||
|
669578a926 | ||
|
fa1db64a8e | ||
|
7cf60fa927 | ||
|
75a0d3e494 | ||
|
a98218ae22 | ||
|
10952e9dc4 | ||
|
b36e23471b | ||
|
f45bf1c95b | ||
|
d4db2587aa | ||
|
62e6336a7d | ||
|
2d8acd4711 | ||
|
d7d95be042 | ||
|
e3c97f0681 | ||
|
1a4fa015b9 | ||
|
63dacfaac5 | ||
|
f7263ac2c6 | ||
|
c7d7e1f0e3 | ||
|
9c54b615f5 | ||
|
69fe1aa981 | ||
|
bfb3d19e3b | ||
|
0b4e62a63e | ||
|
5cdbc03e71 | ||
|
16cd6dd25d | ||
|
fc71e7f99f | ||
|
e740380731 | ||
|
f9921d2c91 | ||
|
d8bfc92cc5 | ||
|
1f2214f891 | ||
|
50835e317f | ||
|
6c8d4dcc1e | ||
|
85a7efbd61 | ||
|
6b5ef10467 | ||
|
0bf46bfcb5 | ||
|
8b8dc7ac78 | ||
|
a017122c44 | ||
|
86a9a892d2 | ||
|
f31a141d78 | ||
|
fa67997424 | ||
|
a5f06c577d | ||
|
e575672877 | ||
|
a34d5b29e8 | ||
|
91ec65da1b | ||
|
ee8d40a66e | ||
|
38ac2e80ed | ||
|
8b70762e34 | ||
|
18c613a611 | ||
|
957f8c56a1 | ||
|
b16446877e | ||
|
9164d58dc7 | ||
|
0beb2f9d3c | ||
|
ccdd66052a | ||
|
518fd814b6 | ||
|
f49bb78dba | ||
|
a54aa77d28 | ||
|
f85e443c9c | ||
|
399e004e7f | ||
|
3099ba71eb | ||
|
951fcf384c | ||
|
c750368a3e | ||
|
3149d86855 | ||
|
3aaf21e34e | ||
|
519c375a42 | ||
|
3ed2c732bd | ||
|
0aa5550d0f | ||
|
1bc8dd83d3 | ||
|
c642cd6fed | ||
|
f72918362d | ||
|
28c4ca73ab | ||
|
a256494ec8 | ||
|
5fd359a64f | ||
|
e043735362 | ||
|
7010b1adb9 | ||
|
17ff262729 | ||
|
1c18838cd8 | ||
|
55b87ae78d | ||
|
e446e902f3 | ||
|
095b52e0b2 | ||
|
4f25ff6a50 | ||
|
6242e0aeec | ||
|
fde9fc9dd4 | ||
|
c481e42673 | ||
|
5e228d7232 | ||
|
6b8e40fdc9 | ||
|
bca44bf9e3 | ||
|
08e49dcf2e | ||
|
0f45409b4d | ||
|
5469d3ec13 | ||
|
d12f8410b9 | ||
|
704c52d229 | ||
|
7dcdda14f5 | ||
|
0f59256ca7 | ||
|
ddee04e98c | ||
|
50f5002b71 | ||
|
e866add4b0 | ||
|
39b93b88c5 | ||
|
deb7e95567 | ||
|
91500501ce | ||
|
6a1caeb9a1 | ||
|
cee7803c37 | ||
|
4e8d0ac7cb | ||
|
47bbcb9935 | ||
|
c0288c5f26 | ||
|
18c2013625 | ||
|
548089888d | ||
|
180c65cfeb | ||
|
6a28e41bc8 | ||
|
f9ef942a98 | ||
|
8272c7a74e | ||
|
4e745489db | ||
|
fc2625a0c0 | ||
|
d0e43ebaf1 | ||
|
879805e316 | ||
|
6845d2e0ef | ||
|
542d1c1c41 | ||
|
31568e7abb | ||
|
4b2488b124 | ||
|
d4c37ecfa3 | ||
|
3f9fd7c060 | ||
|
6ed1412bdd | ||
|
78b9a45158 | ||
|
b8925baa88 | ||
|
87fd3f2a82 | ||
|
0490ef5900 | ||
|
8af164dbd2 | ||
|
c994afbf3c | ||
|
fa36c91bd6 | ||
|
843caf86fb | ||
|
5b8f294aaf | ||
|
44eef5b0e0 | ||
|
aad75840d4 | ||
|
31faef7030 | ||
|
941877a05a | ||
|
4fd4c1e8e1 | ||
|
7cd27cd9c8 | ||
|
54dbef2b4f | ||
|
05424c9f33 | ||
|
346c6fc590 | ||
|
7ed3afe9f0 | ||
|
d29ed6cd49 | ||
|
8c3abf3473 | ||
|
384114d3c7 | ||
|
e8be5a0a8f | ||
|
98c6fa10e6 | ||
|
30d0bdec07 | ||
|
7024f29865 | ||
|
29ebed9514 | ||
|
c0260bc44a | ||
|
6992bff0e0 | ||
|
6957e6e8f7 | ||
|
31dbca7efc | ||
|
82b6dad1ba | ||
|
5063c17e6b | ||
|
3fff3d9c60 | ||
|
2fbe0aed32 | ||
|
3bdc1802cb | ||
|
2675833b0d | ||
|
939a5b477a | ||
|
dc57e7a5b3 | ||
|
a401b8f475 | ||
|
b008fabdac | ||
|
caf286b8cb | ||
|
aea278c022 | ||
|
4e20fe1602 | ||
|
849ca58228 | ||
|
9991fca2e5 | ||
|
9c7bdcfeef | ||
|
b5463d522b | ||
|
af0d830103 | ||
|
d4187dffa3 | ||
|
a7f06f2be5 | ||
|
7ff7d1a185 | ||
|
d3ab612a89 | ||
|
e3bc80e027 | ||
|
a8720806f1 | ||
|
5aac4c2e5d | ||
|
6fa645aab9 | ||
|
842ea4645b | ||
|
e1cfbcd4f6 | ||
|
6f17ff55fa | ||
|
d88af4dfa6 | ||
|
bb0ba21e92 | ||
|
f0753c8e58 | ||
|
4c55004b08 | ||
|
ae24119c09 | ||
|
b8753d8f20 | ||
|
bab368b956 | ||
|
22bc1fb21e | ||
|
63ba764de8 | ||
|
6a7530d741 | ||
|
eb9bf7c4f9 | ||
|
b6571d0ca3 | ||
|
9207d1c680 | ||
|
f783c9bb25 | ||
|
df5786deda | ||
|
de971e6c46 | ||
|
2a6ed0c96e | ||
|
9ec380777a | ||
|
8916d0d367 | ||
|
1e5648111e | ||
|
ae51cfe8b9 | ||
|
05ef250ab4 | ||
|
38e4871f32 | ||
|
a86ba7af2e | ||
|
5704afc54c | ||
|
55c4fc9aca | ||
|
888c17353b | ||
|
054c0e91a3 | ||
|
4dfb610eba | ||
|
008eb8dd8b | ||
|
2882c4bb89 | ||
|
4473851f5b | ||
|
15d9e2d3d5 | ||
|
d5d9df383e | ||
|
243a04b756 | ||
|
b77dcbd39b | ||
|
ad5d4bdfc5 | ||
|
54c17b0068 | ||
|
616907eff8 | ||
|
afaabb3673 | ||
|
1c1f625352 | ||
|
d3f45d2148 | ||
|
7326dbd108 | ||
|
4bf311a282 | ||
|
7f5a3f12ca | ||
|
6a9b71466d | ||
|
70e550a25f | ||
|
1328882ab6 | ||
|
b378a0608d | ||
|
e28c6d6f9c | ||
|
f57f29e426 | ||
|
f2857452e3 | ||
|
af04c294b9 | ||
|
65c8630534 | ||
|
095882d016 | ||
|
1693506f80 | ||
|
667c22169f | ||
|
c53edf6bd9 | ||
|
b601220845 | ||
|
e77a608f45 | ||
|
2822e77716 | ||
|
98227928af | ||
|
430afbdc80 | ||
|
dfdb48ce7d | ||
|
257d1b2b52 | ||
|
07200437b6 | ||
|
ebfa00d555 | ||
|
421f555d57 | ||
|
4b60d941cc | ||
|
60d24ada62 | ||
|
a1c121e2fe | ||
|
b3775a3ca5 | ||
|
5d36c97a46 | ||
|
4bdd412c15 | ||
|
46e3bcd6b9 | ||
|
68a85ddf90 | ||
|
6eba9273cb | ||
|
9de151cc14 | ||
|
b8c9a5dd1f | ||
|
4dfb3507c4 | ||
|
0cac2d407b | ||
|
b3106cd932 | ||
|
c4a467fa96 | ||
|
cf7afbe7f6 | ||
|
a8d58aeac4 | ||
|
28209e9413 | ||
|
68ed56f2d9 | ||
|
0c03747807 | ||
|
dfe7ecbb5b | ||
|
3039d8edfa | ||
|
7c850cc082 | ||
|
0e07ee3d81 | ||
|
14b820f2c1 | ||
|
9366fa3b45 | ||
|
d356e9e65f | ||
|
230ebffd0e | ||
|
5eedc1edca | ||
|
fa6db9411b | ||
|
b88e42a52d | ||
|
8d1b27d130 | ||
|
1148d37102 | ||
|
88704ce417 | ||
|
9f69d2cb76 | ||
|
644f54bfba | ||
|
755c4da8cc | ||
|
483c77fba8 | ||
|
55fd71c5e1 | ||
|
1991bd437f | ||
|
22af796302 | ||
|
a70931ffbf | ||
|
2f7dd177c1 | ||
|
d35a053efd | ||
|
87cc1f9415 | ||
|
e7bb524362 | ||
|
9e03e1c742 | ||
|
6fbfcef1fa | ||
|
9471510086 | ||
|
56320468fd | ||
|
53a70bbffb | ||
|
4f2e547bd6 | ||
|
af82fa7b62 | ||
|
6bd1a5ab80 | ||
|
93568f6fd0 | ||
|
1a3bf81c19 | ||
|
2eeabe3998 | ||
|
b1d86a7a2d | ||
|
6cfc9ba47b | ||
|
350474540b | ||
|
1136e58de7 | ||
|
558ce4713c | ||
|
69c6f50dfb | ||
|
babe01321c | ||
|
ff61dc4391 | ||
|
4da6ede9d0 | ||
|
0b1bab873e | ||
|
6c60341ce8 | ||
|
e1c1878130 | ||
|
fea5a39740 | ||
|
9f2221a885 | ||
|
977cd2505e | ||
|
e3f85a7c0f | ||
|
d81339722e | ||
|
f5c09e02de | ||
|
5e102526ec | ||
|
3c2efd4b67 | ||
|
7bb93a7c32 | ||
|
ac8f4ae2f9 | ||
|
1ff49188b1 | ||
|
2c329023c2 | ||
|
4457a300bf | ||
|
397496aff0 | ||
|
7458fe0d81 | ||
|
453c81f634 | ||
|
eebce4b54d | ||
|
f7976e18f1 | ||
|
d951fe9988 | ||
|
fd4da78075 | ||
|
a025ae3f54 | ||
|
a75ba0d903 | ||
|
99318ae2ba | ||
|
8369bcb605 | ||
|
338cc69034 | ||
|
4526c52fe8 | ||
|
81f9bda502 | ||
|
46bc5463ca | ||
|
bb346537ba | ||
|
3b5f0d5c30 | ||
|
808ce969d9 | ||
|
16f8339f3c | ||
|
25d68c3904 | ||
|
77ad8e1a80 | ||
|
9e8c18075a | ||
|
4278cdfd29 | ||
|
142c2bd0f0 | ||
|
13ee8686bb | ||
|
5c06df2635 | ||
|
d69bf76341 | ||
|
d0d1b295b4 | ||
|
e450191548 | ||
|
3aee83f3d8 | ||
|
3610a2bc8d | ||
|
ab559d4620 | ||
|
15d308d4c5 | ||
|
e7ea66afb5 | ||
|
d9a1d8bde6 | ||
|
9ac4486403 | ||
|
428afabaa3 | ||
|
9162189fa6 | ||
|
154ca9b760 | ||
|
9fcead3973 | ||
|
2fd9d0359c | ||
|
4cf9e030a2 | ||
|
5570d14179 | ||
|
fd3c546994 | ||
|
e81ae05df9 | ||
|
cb394f3a1c | ||
|
03ed057ff6 | ||
|
807290ff38 | ||
|
40a4b4fa7a | ||
|
b7d2ad3651 | ||
|
faac286f70 | ||
|
c6c7f6f907 | ||
|
ccd0da58cb | ||
|
500e5f1b5d | ||
|
ebb6177271 | ||
|
951c256980 | ||
|
a134ef03dd | ||
|
f21a9d16d0 | ||
|
42c3bb00ec | ||
|
0d0fcfe49a | ||
|
3f0db92be6 | ||
|
ec24c79da1 | ||
|
f5fffbea04 | ||
|
fb0a2ef248 | ||
|
240f482e8e | ||
|
885031d3ce | ||
|
eb212de70e | ||
|
68323d6def | ||
|
7c492071f1 | ||
|
8fbdef2a1d | ||
|
8f64784781 | ||
|
1bd6095c60 | ||
|
e133a5f0f3 | ||
|
b47886416e | ||
|
c522d611d1 | ||
|
f6d9b6b7aa | ||
|
1de6bca12d | ||
|
2de9b862d8 | ||
|
3c649df3df | ||
|
9bf38d9e4d | ||
|
dee40f038d | ||
|
d0aa907418 | ||
|
8dbdffc9b4 | ||
|
f409780455 | ||
|
bebcd99b3b | ||
|
99aeaddf42 | ||
|
793398216f | ||
|
b353ed3157 | ||
|
8637212a17 | ||
|
c42ce38e1c | ||
|
76d228a603 | ||
|
5cb52faa10 | ||
|
e67e822845 | ||
|
a52686ca3b | ||
|
7a1e6cccfd | ||
|
1166b6c2fb | ||
|
7a6693694c | ||
|
6ed423d1af | ||
|
8b69444602 | ||
|
7de5e8d9c8 | ||
|
844e3d1934 | ||
|
9eafbbc0ae | ||
|
74677e3d54 | ||
|
57f4638742 | ||
|
c7a32f2d56 | ||
|
d2dc64aa2d | ||
|
edb2a34c51 | ||
|
dbd9b1c5c4 | ||
|
c3b758845d | ||
|
4d770549fc | ||
|
a67674bae1 | ||
|
462a61da73 | ||
|
ae8a7f6320 | ||
|
a23c6a0996 | ||
|
e6594cecce | ||
|
85ed5d27f5 | ||
|
6ac9e11245 | ||
|
9fbd809344 | ||
|
63a098d757 | ||
|
b68d9ed672 | ||
|
cf65d4a909 | ||
|
079f627b34 | ||
|
f6c6cbc343 | ||
|
c6cdea77b6 | ||
|
3191de68b8 | ||
|
f3da56773e | ||
|
5197758579 | ||
|
d48af7c4c2 | ||
|
bbafe0fc46 | ||
|
9a91f917e0 | ||
|
1f681f02ae | ||
|
778a86e7eb | ||
|
6eb923d22f | ||
|
e602afcd5f | ||
|
bf1115d417 | ||
|
f5d668af31 | ||
|
62373f969c | ||
|
30f2e3b606 | ||
|
7d3d997083 | ||
|
3955e2c84a | ||
|
6a6c41df26 | ||
|
1dfd2aec50 | ||
|
8885f543ae | ||
|
8804e6c2ca | ||
|
88b7c214f7 | ||
|
9a7ccc20e8 | ||
|
1bff790628 | ||
|
0010ca3e03 | ||
|
9edb5dae88 | ||
|
85b49096dc | ||
|
a7eaa10220 | ||
|
5a46ce55b6 | ||
|
dbebbecab1 | ||
|
c7593e66bf | ||
|
d52dc69279 | ||
|
1c65fa212d | ||
|
be88eb00f8 | ||
|
65c60f5386 | ||
|
83a043e639 | ||
|
8175192f60 | ||
|
7d2c975d73 | ||
|
e5153737d8 | ||
|
88766a02c7 | ||
|
8162394797 | ||
|
fd001186dd | ||
|
128fef8838 | ||
|
1e25a0654a | ||
|
82b0ff6008 | ||
|
ca7c3630f9 | ||
|
260bb8218f | ||
|
a691de7d55 | ||
|
5674c484e2 | ||
|
9286dc284c | ||
|
624b71bd39 | ||
|
b7ea605a3b | ||
|
489a787de8 | ||
|
84150036ed | ||
|
619123720c | ||
|
cc98ff9fe6 | ||
|
39d159f38c | ||
|
ca8eb90cec | ||
|
f8e3e28d85 | ||
|
4d868489bd | ||
|
b06f09f6af | ||
|
bf00f5f945 | ||
|
6ceb6cc301 | ||
|
00a6afaa8e |
@@ -11,9 +11,14 @@ jobs:
|
||||
steps:
|
||||
- checkout
|
||||
- run: echo 'export PATH=$HOME/CIRCLE_PROJECT_REPONAME/node_modules/.bin:$PATH' >> $BASH_ENV
|
||||
- run:
|
||||
# HACK(albrow): Without this, yarn commands will sometimes
|
||||
# fail with a "permission denied" error.
|
||||
name: Set npm path
|
||||
command: npm set prefix=/home/circleci/npm && echo 'export PATH=$HOME/circleci/npm/bin:$PATH' >> /home/circleci/.bashrc
|
||||
- run:
|
||||
name: install-yarn
|
||||
command: sudo npm install --global yarn@1.9.4
|
||||
command: npm install --global yarn@1.9.4
|
||||
- run:
|
||||
name: yarn
|
||||
command: yarn --frozen-lockfile --ignore-engines install || yarn --frozen-lockfile --ignore-engines install
|
||||
@@ -24,6 +29,10 @@ jobs:
|
||||
key: repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
paths:
|
||||
- ~/repo
|
||||
- save_cache:
|
||||
key: python-contract-wrappers-{{ .Environment.CIRCLE_SHA1 }}
|
||||
paths:
|
||||
- ~/repo/packages/python-contract-wrappers/generated
|
||||
build-website:
|
||||
resource_class: medium+
|
||||
docker:
|
||||
@@ -42,15 +51,7 @@ jobs:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run: yarn wsrun test:circleci @0x/contracts-multisig
|
||||
- run: yarn wsrun test:circleci @0x/contracts-utils
|
||||
- run: yarn wsrun test:circleci @0x/contracts-exchange-libs
|
||||
- run: yarn wsrun test:circleci @0x/contracts-erc20
|
||||
- run: yarn wsrun test:circleci @0x/contracts-erc721
|
||||
- run: yarn wsrun test:circleci @0x/contracts-extensions
|
||||
- run: yarn wsrun test:circleci @0x/contracts-asset-proxy
|
||||
- run: yarn wsrun test:circleci @0x/contracts-exchange
|
||||
- run: yarn wsrun test:circleci @0x/contracts-exchange-forwarder
|
||||
- run: yarn wsrun test:circleci @0x/contracts-multisig @0x/contracts-utils @0x/contracts-exchange-libs @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-extensions @0x/contracts-asset-proxy @0x/contracts-exchange @0x/contracts-exchange-forwarder @0x/contracts-coordinator @0x/contracts-dev-utils
|
||||
test-contracts-geth:
|
||||
docker:
|
||||
- image: circleci/node:9-browsers
|
||||
@@ -62,15 +63,7 @@ jobs:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
# HACK(albrow): we need to sleep 10 seconds to ensure the devnet is
|
||||
# initialized
|
||||
- run: sleep 10 && TEST_PROVIDER=geth yarn wsrun test @0x/contracts-multisig
|
||||
- run: TEST_PROVIDER=geth yarn wsrun test:circleci @0x/contracts-utils
|
||||
- run: TEST_PROVIDER=geth yarn wsrun test:circleci @0x/contracts-exchange-libs
|
||||
- run: TEST_PROVIDER=geth yarn wsrun test:circleci @0x/contracts-erc20
|
||||
- run: TEST_PROVIDER=geth yarn wsrun test:circleci @0x/contracts-erc721
|
||||
- run: TEST_PROVIDER=geth yarn wsrun test:circleci @0x/contracts-extensions
|
||||
- run: TEST_PROVIDER=geth yarn wsrun test:circleci @0x/contracts-asset-proxy
|
||||
- run: TEST_PROVIDER=geth yarn wsrun test:circleci @0x/contracts-exchange
|
||||
- run: TEST_PROVIDER=geth yarn wsrun test:circleci @0x/contracts-exchange-forwarder
|
||||
- run: sleep 10 && TEST_PROVIDER=geth yarn wsrun test:circleci @0x/contracts-multisig @0x/contracts-utils @0x/contracts-exchange-libs @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-extensions @0x/contracts-asset-proxy @0x/contracts-exchange @0x/contracts-exchange-forwarder @0x/contracts-coordinator @0x/contracts-dev-utils
|
||||
test-publish:
|
||||
resource_class: medium+
|
||||
docker:
|
||||
@@ -91,20 +84,6 @@ jobs:
|
||||
keys:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run: yarn test:generate_docs:circleci
|
||||
test-pipeline:
|
||||
docker:
|
||||
- image: circleci/node:9
|
||||
- image: postgres:11-alpine
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run: ZEROEX_DATA_PIPELINE_TEST_DB_URL='postgresql://postgres@localhost/postgres' yarn wsrun test:circleci @0x/pipeline
|
||||
- save_cache:
|
||||
key: coverage-pipeline-{{ .Environment.CIRCLE_SHA1 }}
|
||||
paths:
|
||||
- ~/repo/packages/pipeline/coverage/lcov.info
|
||||
test-rest:
|
||||
docker:
|
||||
- image: circleci/node:9-browsers
|
||||
@@ -115,6 +94,8 @@ jobs:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run: yarn wsrun test:circleci @0x/contracts-test-utils
|
||||
- run: yarn wsrun test:circleci @0x/abi-gen
|
||||
- run: yarn wsrun test:circleci @0x/asset-buyer
|
||||
- run: yarn wsrun test:circleci @0x/contract-artifacts
|
||||
- run: yarn wsrun test:circleci @0x/assert
|
||||
- run: yarn wsrun test:circleci @0x/base-contract
|
||||
- run: yarn wsrun test:circleci @0x/connect
|
||||
@@ -139,6 +120,10 @@ jobs:
|
||||
key: coverage-assert-{{ .Environment.CIRCLE_SHA1 }}
|
||||
paths:
|
||||
- ~/repo/packages/assert/coverage/lcov.info
|
||||
- save_cache:
|
||||
key: coverage-asset-buyer-{{ .Environment.CIRCLE_SHA1 }}
|
||||
paths:
|
||||
- ~/repo/packages/asset-buyer/coverage/lcov.info
|
||||
- save_cache:
|
||||
key: coverage-base-contract-{{ .Environment.CIRCLE_SHA1 }}
|
||||
paths:
|
||||
@@ -197,21 +182,27 @@ jobs:
|
||||
- image: circleci/python
|
||||
- image: 0xorg/ganache-cli:2.2.2
|
||||
- image: 0xorg/launch-kit-ci
|
||||
command: |
|
||||
yarn start:ts -p 3000:3000
|
||||
environment:
|
||||
RPC_URL: http://localhost:8545
|
||||
NETWORK_ID: 50
|
||||
WHITELIST_ALL_TOKENS: True
|
||||
command: bash -c "until curl -sfd'{\"method\":\"net_listening\"}' http://localhost:8545 | grep true; do continue; done; forever ts/lib/index.js"
|
||||
steps:
|
||||
- checkout
|
||||
- run: sudo chown -R circleci:circleci /usr/local/bin
|
||||
- run: sudo chown -R circleci:circleci /usr/local/lib/python3.7
|
||||
- restore_cache:
|
||||
key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
|
||||
key: installed-py-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- restore_cache:
|
||||
key: python-contract-wrappers-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
command: |
|
||||
cd python-packages
|
||||
python -m ensurepip
|
||||
./pre_install
|
||||
./install
|
||||
- save_cache:
|
||||
key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
|
||||
key: installed-py-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
|
||||
paths:
|
||||
- '/usr/local/bin'
|
||||
- '/usr/local/lib/python3.7/site-packages'
|
||||
@@ -252,14 +243,14 @@ jobs:
|
||||
- run: sudo chown -R circleci:circleci /usr/local/bin
|
||||
- run: sudo chown -R circleci:circleci /usr/local/lib/python3.7
|
||||
- restore_cache:
|
||||
key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
|
||||
key: installed-py-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
command: |
|
||||
cd python-packages/order_utils
|
||||
python -m ensurepip
|
||||
python -m pip install .
|
||||
- save_cache:
|
||||
key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
|
||||
key: installed-py-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
|
||||
paths:
|
||||
- '/usr/local/bin'
|
||||
- '/usr/local/lib/python3.7/site-packages'
|
||||
@@ -280,11 +271,14 @@ jobs:
|
||||
- run: sudo chown -R circleci:circleci /usr/local/bin
|
||||
- run: sudo chown -R circleci:circleci /usr/local/lib/python3.7
|
||||
- restore_cache:
|
||||
key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
|
||||
key: installed-py-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- restore_cache:
|
||||
key: python-contract-wrappers-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run:
|
||||
command: |
|
||||
python -m ensurepip
|
||||
cd python-packages
|
||||
./pre_install
|
||||
./install
|
||||
./lint
|
||||
static-tests:
|
||||
@@ -314,6 +308,9 @@ jobs:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- coverage-assert-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- coverage-asset-buyer-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- coverage-base-contract-{{ .Environment.CIRCLE_SHA1 }}
|
||||
@@ -386,12 +383,11 @@ workflows:
|
||||
- test-contracts-ganache:
|
||||
requires:
|
||||
- build
|
||||
- test-contracts-geth:
|
||||
requires:
|
||||
- build
|
||||
- test-pipeline:
|
||||
requires:
|
||||
- build
|
||||
# TODO(albrow): Tests always fail on Geth right now because our fork
|
||||
# is outdated. Uncomment once we have updated our Geth fork.
|
||||
# - test-contracts-geth:
|
||||
# requires:
|
||||
# - build
|
||||
- test-rest:
|
||||
requires:
|
||||
- build
|
||||
@@ -411,6 +407,8 @@ workflows:
|
||||
- static-tests-python:
|
||||
requires:
|
||||
- test-python
|
||||
- test-python
|
||||
- test-python:
|
||||
requires:
|
||||
- build
|
||||
# skip python tox run for now, as we don't yet have multiple test environments to support.
|
||||
#- test-rest-python
|
||||
|
6
.gitattributes
vendored
6
.gitattributes
vendored
@@ -1,7 +1,7 @@
|
||||
*.sol linguist-language=Solidity
|
||||
|
||||
# Automatically collapse generated files in GitHub.
|
||||
*.svg linguist-generated
|
||||
packages/contract-artifacts/artifacts/*json linguist-generated
|
||||
packages/abi-gen-wrappers/wrappers/*.ts liguist-generated
|
||||
*.svg linguist-generated=true
|
||||
packages/contract-artifacts/artifacts/*json linguist-generated=true
|
||||
packages/abi-gen-wrappers/src/generated-wrappers/*.ts liguist-generated=true
|
||||
|
||||
|
80
.github/autolabeler.yml
vendored
80
.github/autolabeler.yml
vendored
@@ -1,43 +1,43 @@
|
||||
python: ['python-packages']
|
||||
contracts: ['contracts']
|
||||
sol-doc: ['packages/sol-doc']
|
||||
sol-resolver: ['packages/sol-resolver']
|
||||
contracts-gen: ['packages/contracts-gen']
|
||||
sra-spec: ['packages/sra-spec']
|
||||
subproviders: ['packages/subproviders']
|
||||
contract-addresses: ['packages/contract-addresses']
|
||||
migrations: ['packages/migrations']
|
||||
web3-wrapper: ['packages/web3-wrapper']
|
||||
sol-compiler: ['packages/sol-compiler']
|
||||
types: ['packages/types']
|
||||
instant: ['packages/instant']
|
||||
abi-gen-templates: ['packages/abi-gen-templates']
|
||||
abi-gen: ['packages/abi-gen']
|
||||
website: ['packages/website']
|
||||
sol-coverage: ['packages/sol-coverage']
|
||||
sol-profiler: ['packages/sol-profiler']
|
||||
sol-trace: ['packages/sol-trace']
|
||||
sol-tracing-utils: ['packages/sol-tracing-utils']
|
||||
utils: ['packages/utils']
|
||||
tslint-config: ['packages/tslint-config']
|
||||
asset-buyer: ['packages/asset-buyer']
|
||||
order-watcher: ['packages/order-watcher']
|
||||
react-docs: ['packages/react-docs']
|
||||
order-utils: ['packages/order-utils']
|
||||
react-shared: ['packages/react-shared']
|
||||
assert: ['packages/assert']
|
||||
base-contract: ['packages/base-contract']
|
||||
typescript-typings: ['packages/typescript-typings']
|
||||
@0x/sol-doc: ['packages/sol-doc']
|
||||
@0x/sol-resolver: ['packages/sol-resolver']
|
||||
@0x/contracts-gen: ['packages/contracts-gen']
|
||||
@0x/sra-spec: ['packages/sra-spec']
|
||||
@0x/subproviders: ['packages/subproviders']
|
||||
@0x/contract-addresses: ['packages/contract-addresses']
|
||||
@0x/migrations: ['packages/migrations']
|
||||
@0x/web3-wrapper: ['packages/web3-wrapper']
|
||||
@0x/sol-compiler: ['packages/sol-compiler']
|
||||
@0x/types: ['packages/types']
|
||||
@0x/instant: ['packages/instant']
|
||||
@0x/abi-gen-templates: ['packages/abi-gen-templates']
|
||||
@0x/abi-gen: ['packages/abi-gen']
|
||||
@0x/website: ['packages/website']
|
||||
@0x/sol-coverage: ['packages/sol-coverage']
|
||||
@0x/sol-profiler: ['packages/sol-profiler']
|
||||
@0x/sol-trace: ['packages/sol-trace']
|
||||
@0x/sol-tracing-utils: ['packages/sol-tracing-utils']
|
||||
@0x/utils: ['packages/utils']
|
||||
@0x/tslint-config: ['packages/tslint-config']
|
||||
@0x/asset-buyer: ['packages/asset-buyer']
|
||||
@0x/order-watcher: ['packages/order-watcher']
|
||||
@0x/react-docs: ['packages/react-docs']
|
||||
@0x/order-utils: ['packages/order-utils']
|
||||
@0x/react-shared: ['packages/react-shared']
|
||||
@0x/assert: ['packages/assert']
|
||||
@0x/base-contract: ['packages/base-contract']
|
||||
@0x/typescript-typings: ['packages/typescript-typings']
|
||||
0x.js: ['packages/0x.js']
|
||||
abi-gen-wrappers: ['packages/abi-gen-wrappers']
|
||||
metacoin: ['packages/metacoin']
|
||||
contract-artifacts: ['packages/contract-artifacts']
|
||||
dev-utils: ['packages/dev-utils']
|
||||
contract-wrappers: ['packages/contract-wrappers']
|
||||
json-schemas: ['packages/json-schemas']
|
||||
ethereum-types: ['ethereum-types']
|
||||
connect: ['packages/connect']
|
||||
fill-scenarios: ['packages/fill-scenarios']
|
||||
dev-tools-pages: ['packages/dev-tools-pages']
|
||||
testnet-faucets: ['packages/testnet-faucets']
|
||||
monorepo-scripts: ['packages/monorepo-scripts']
|
||||
@0x/abi-gen-wrappers: ['packages/abi-gen-wrappers']
|
||||
@0x/metacoin: ['packages/metacoin']
|
||||
@0x/contract-artifacts: ['packages/contract-artifacts']
|
||||
@0x/dev-utils: ['packages/dev-utils']
|
||||
@0x/contract-wrappers: ['packages/contract-wrappers']
|
||||
@0x/json-schemas: ['packages/json-schemas']
|
||||
@0x/ethereum-types: ['ethereum-types']
|
||||
@0x/connect: ['packages/connect']
|
||||
@0x/fill-scenarios: ['packages/fill-scenarios']
|
||||
@0x/dev-tools-pages: ['packages/dev-tools-pages']
|
||||
@0x/testnet-faucets: ['packages/testnet-faucets']
|
||||
@0x/monorepo-scripts: ['packages/monorepo-scripts']
|
||||
|
13
.gitignore
vendored
13
.gitignore
vendored
@@ -83,6 +83,7 @@ packages/react-docs/example/public/bundle*
|
||||
packages/testnet-faucets/server/
|
||||
|
||||
# generated contract artifacts/
|
||||
contracts/coordinator/generated-artifacts/
|
||||
contracts/exchange/generated-artifacts/
|
||||
contracts/asset-proxy/generated-artifacts/
|
||||
contracts/multisig/generated-artifacts/
|
||||
@@ -90,13 +91,18 @@ contracts/utils/generated-artifacts/
|
||||
contracts/exchange-libs/generated-artifacts/
|
||||
contracts/erc20/generated-artifacts/
|
||||
contracts/erc721/generated-artifacts/
|
||||
contracts/erc1155/generated-artifacts/
|
||||
contracts/extensions/generated-artifacts/
|
||||
contracts/exchange-forwarder/generated-artifacts/
|
||||
contracts/dev-utils/generated-artifacts/
|
||||
packages/sol-tracing-utils/test/fixtures/artifacts/
|
||||
packages/metacoin/artifacts/
|
||||
python-packages/contract_artifacts/src/zero_ex/contract_artifacts/artifacts/
|
||||
|
||||
# generated contract wrappers
|
||||
packages/abi-gen-wrappers/wrappers
|
||||
packages/abi-gen-wrappers/src/generated-wrappers/
|
||||
packages/python-contract-wrappers/generated/
|
||||
contracts/coordinator/generated-wrappers/
|
||||
contracts/exchange/generated-wrappers/
|
||||
contracts/asset-proxy/generated-wrappers/
|
||||
contracts/multisig/generated-wrappers/
|
||||
@@ -104,10 +110,15 @@ contracts/utils/generated-wrappers/
|
||||
contracts/exchange-libs/generated-wrappers/
|
||||
contracts/erc20/generated-wrappers/
|
||||
contracts/erc721/generated-wrappers/
|
||||
contracts/erc1155/generated-wrappers/
|
||||
contracts/extensions/generated-wrappers/
|
||||
contracts/exchange-forwarder/generated-wrappers/
|
||||
contracts/dev-utils/generated-wrappers/
|
||||
packages/metacoin/src/contract_wrappers
|
||||
|
||||
# cli test output
|
||||
packages/abi-gen/test-cli/output
|
||||
|
||||
# solc-bin in sol-compiler
|
||||
packages/sol-compiler/solc_bin/
|
||||
|
||||
|
@@ -1,5 +1,7 @@
|
||||
lib
|
||||
.nyc_output
|
||||
/contracts/coordinator/generated-wrappers
|
||||
/contracts/coordinator/generated-artifacts
|
||||
/contracts/exchange/generated-wrappers
|
||||
/contracts/exchange/generated-artifacts
|
||||
/contracts/asset-proxy/generated-wrappers
|
||||
@@ -14,13 +16,15 @@ lib
|
||||
/contracts/erc20/generated-artifacts
|
||||
/contracts/erc721/generated-wrappers
|
||||
/contracts/erc721/generated-artifacts
|
||||
/contracts/erc1155/generated-wrappers
|
||||
/contracts/erc1155/generated-artifacts
|
||||
/contracts/extensions/generated-wrappers
|
||||
/contracts/extensions/generated-artifacts
|
||||
/contracts/exchange-forwarder/generated-wrappers
|
||||
/contracts/exchange-forwarder/generated-artifacts
|
||||
/packages/abi-gen-wrappers/src/generated-wrappers
|
||||
/packages/contract-artifacts/artifacts
|
||||
/python-packages/contract_artifacts/src/zero_ex/contract_artifacts/artifacts
|
||||
/contracts/dev-utils/generated-wrappers
|
||||
/contracts/dev-utils/generated-artifacts
|
||||
/packages/abi-gen/test-cli/output
|
||||
/packages/json-schemas/schemas
|
||||
/python-packages/json_schemas/src/zero_ex/json_schemas/schemas
|
||||
/packages/metacoin/src/contract_wrappers
|
||||
|
10
CODEOWNERS
10
CODEOWNERS
@@ -11,10 +11,10 @@ packages/website/ @BMillman19 @fragosti @fabioberger @steveklebanoff
|
||||
|
||||
# Dev tools & setup
|
||||
.circleci/ @LogvinovLeon
|
||||
packages/abi-gen/ @LogvinovLeon
|
||||
packages/base-contract/ @LogvinovLeon
|
||||
packages/abi-gen/ @feuGeneA
|
||||
packages/base-contract/ @xianny
|
||||
packages/connect/ @fragosti
|
||||
packages/abi-gen-templates/ @LogvinovLeon
|
||||
packages/abi-gen-templates/ @feuGeneA @xianny
|
||||
packages/contract-addresses/ @albrow
|
||||
packages/contract-artifacts/ @albrow
|
||||
packages/dev-utils/ @LogvinovLeon @fabioberger
|
||||
@@ -23,6 +23,7 @@ packages/ethereum-types/ @LogvinovLeon
|
||||
packages/metacoin/ @LogvinovLeon
|
||||
packages/monorepo-scripts/ @fabioberger
|
||||
packages/order-utils/ @fabioberger @LogvinovLeon
|
||||
packages/python-contract-wrappers/ @feuGeneA
|
||||
packages/sol-compiler/ @LogvinovLeon
|
||||
packages/sol-coverage/ @LogvinovLeon
|
||||
packages/sol-profiler/ @LogvinovLeon
|
||||
@@ -33,6 +34,7 @@ packages/subproviders/ @fabioberger @dekz
|
||||
packages/verdaccio/ @albrow
|
||||
packages/web3-wrapper/ @LogvinovLeon @fabioberger
|
||||
python-packages/ @feuGeneA
|
||||
packages/utils/ @hysz
|
||||
|
||||
# Protocol/smart contracts
|
||||
contracts/core/test/ @albrow
|
||||
contracts/ @abandeali1 @hysz
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
<!--- Before submitting please check to see if this issue was already reported -->
|
||||
|
||||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
<!--- Prefix your issue title with the package name it relates to (e.g., `0x.js: ` or `general:`) -->
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
|
@@ -34,18 +34,23 @@ Visit our [developer portal](https://0xproject.com/docs/order-utils) for a compr
|
||||
|
||||
### Solidity Packages
|
||||
|
||||
These packages are all under development. See [/contracts/README.md](/contracts/README.md) for a list of deployed packages.
|
||||
|
||||
| Package | Version | Description |
|
||||
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`@0x/contracts-asset-proxy`](/contracts/asset-proxy) | [](https://www.npmjs.com/package/@0x/contracts-asset-proxy) | [`AssetProxy`](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#assetproxy) contracts used within the protocol |
|
||||
| [`@0x/contracts-erc20`](/contracts/erc20) | [](https://www.npmjs.com/package/@0x/contracts-erc20) | Implementations of various ERC20 tokens |
|
||||
| [`@0x/contracts-erc721`](/contracts/erc721) | [](https://www.npmjs.com/package/@0x/contracts-erc721) | Implementations of various ERC721 tokens |
|
||||
| [`@0x/contracts-erc1155`](/contracts/erc1155) | [](https://www.npmjs.com/package/@0x/contracts-erc1155) | Implementations of various ERC1155 tokens |
|
||||
| [`@0x/contracts-exchange`](/contracts/exchange) | [](https://www.npmjs.com/package/@0x/contracts-exchange) | The [`Exchange`](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#exchange) contract used for settling trades within the protocol |
|
||||
| [`@0x/contracts-exchange-forwarder`](/contracts/exchange-forwarder) | [](https://www.npmjs.com/package/@0x/contracts-exchange-forwarder) | A [`Forwarder`](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/forwarder-specification.md) contract used to simplify UX for interacting with the protocol |
|
||||
| [`@0x/contracts-exchange-libs`](/contracts/exchange-libs) | [](https://www.npmjs.com/package/@0x/contracts-exchange-libs) | Protocol specific Llbraries used within the [`Exchange`](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#exchange) contract |
|
||||
| [`@0x/contracts-exchange-libs`](/contracts/exchange-libs) | [](https://www.npmjs.com/package/@0x/contracts-exchange-libs) | Protocol specific libraries used within the [`Exchange`](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#exchange) contract |
|
||||
| [`@0x/contracts-extensions`](/contracts/extensions) | [](https://www.npmjs.com/package/@0x/contracts-extensions) | Contracts that interact with and extend the functionality of the core protocol |
|
||||
| [`@0x/contracts-multisig`](/contracts/multisig) | [](https://www.npmjs.com/package/@0x/contracts-multisig) | Various implementations of multisignature wallets, including the [`AssetProxyOwner`](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md#assetproxyowner) contract that has permissions to upgrade the protocol |
|
||||
| [`@0x/contracts-test-utils`](/contracts/test-utils) | [](https://www.npmjs.com/package/@0x/contracts-test-utils) | Typescript/Javascript shared utilities used for testing contracts |
|
||||
| [`@0x/contracts-utils`](/contracts/utils) | [](https://www.npmjs.com/package/@0x/contracts-utils) | Generic libraries and utilities used throughout all of the contracts |
|
||||
| [`@0x/contracts-coordinator`](/contracts/coordinator) | [](https://www.npmjs.com/package/@0x/contracts-coordinator) | A contract that allows users to execute 0x transactions with permission from a Coordinator |
|
||||
| [`@0x/contracts-dev-utils`](/contracts/dev-utils) | [](https://www.npmjs.com/package/@0x/contracts-dev-utils) | A contract contains utility functions for developers (such as validating many orders using a single eth_call) |
|
||||
|
||||
### Typescript/Javascript Packages
|
||||
|
||||
|
16
contracts/README.md
Normal file
16
contracts/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
#### Deployed Contract Packages
|
||||
|
||||
| Contract | Package | Version | Git Tag |
|
||||
| --------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| AssetProxyOwner | [`@0x/contracts-multisig`](/contracts/multisig) | [v1.0.2](https://www.npmjs.com/package/@0x/contracts-multisig/v/1.0.2) | [@0x/contracts-multisig@1.0.2](https://github.com/0xProject/0x-monorepo/releases/tag/@0x/contracts-multisig@1.0.2) |
|
||||
| ERC20Proxy | [`@0x/contracts-asset-proxy`](/contracts/asset-proxy) | [v1.0.1](https://www.npmjs.com/package/@0x/contracts-asset-proxy/v/1.0.1) | [@0x/contracts-asset-proxy@1.0.1](https://github.com/0xProject/0x-monorepo/releases/tag/@0x/contracts-asset-proxy@1.0.1) |
|
||||
| ERC721Proxy | [`@0x/contracts-asset-proxy`](/contracts/asset-proxy) | [v1.0.1](https://www.npmjs.com/package/@0x/contracts-asset-proxy/v/1.0.1) | [@0x/contracts-asset-proxy@1.0.1](https://github.com/0xProject/0x-monorepo/releases/tag/@0x/contracts-asset-proxy@1.0.1) |
|
||||
| Exchange | [`@0x/contracts-exchange`](/contracts/exchange) | [v1.0.1](https://www.npmjs.com/package/@0x/contracts-exchange/v/1.0.1) | [@0x/contracts-exchange@1.0.1](https://github.com/0xProject/0x-monorepo/releases/tag/@0x/contracts-exchange@1.0.1) |
|
||||
| DutchAuction | [`@0x/contracts-extensions`](/contracts/extensions) | [v1.0.2](https://www.npmjs.com/package/@0x/contracts-extensions/v/1.0.2) | [@0x/contracts-extensions@1.0.2](https://github.com/0xProject/0x-monorepo/releases/tag/@0x/contracts-extensions@1.0.2) |
|
||||
| Forwarder | [`@0x/contracts-exchange-forwarder`](/contracts/exchange-forwarder) | [v1.0.1](https://www.npmjs.com/package/@0x/contracts-exchange-forwarder/v/1.0.1) | [@0x/contracts-exchange-forwarder@1.0.1](https://github.com/0xProject/0x-monorepo/releases/tag/@0x/contracts-exchange-forwarder@1.0.1) |
|
||||
| MultiAssetProxy | [`@0x/contracts-asset-proxy`](/contracts/asset-proxy) | [v1.0.1](https://www.npmjs.com/package/@0x/contracts-asset-proxy/v/1.0.1) | [@0x/contracts-asset-proxy@1.0.1](https://github.com/0xProject/0x-monorepo/releases/tag/@0x/contracts-asset-proxy@1.0.1) |
|
||||
| ZRXToken | [`@0x/contracts-erc20`](/contracts/erc20) | [v1.0.1](https://www.npmjs.com/package/@0x/contracts-erc20/v/1.0.1) | [@0x/contracts-erc20@1.0.1](https://github.com/0xProject/0x-monorepo/releases/tag/@0x/contracts-erc20@1.0.1) |
|
||||
|
||||
#### Development
|
||||
|
||||
Building solidity files will update the contract artifact in `{package-name}/generated-artifacts/{contract}.json`, but does not automatically update the `abi-gen-wrappers` package, which are generated from the artifact JSON. To ensure consistency, clean and rebuild `abi-gen-wrappers` after any changes to the artifact JSON.
|
@@ -1,4 +1,136 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1563047529,
|
||||
"version": "2.2.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "2.2.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add `LibAssetProxyIds` contract",
|
||||
"pr": 1835
|
||||
},
|
||||
{
|
||||
"note": "Updated ERC1155 Asset Proxy. Less optimization. More explicit handling of edge cases.",
|
||||
"pr": 1852
|
||||
},
|
||||
{
|
||||
"note": "Implement StaticCallProxy",
|
||||
"pr": 1863
|
||||
}
|
||||
],
|
||||
"timestamp": 1563006338
|
||||
},
|
||||
{
|
||||
"timestamp": 1558712885,
|
||||
"version": "2.1.5",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1557961111,
|
||||
"version": "2.1.4",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1557799313,
|
||||
"version": "2.1.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "2.1.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Update tests to use contract-built-in `awaitTransactionSuccessAsync`",
|
||||
"pr": 1797
|
||||
}
|
||||
],
|
||||
"timestamp": 1557507213
|
||||
},
|
||||
{
|
||||
"version": "2.1.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
],
|
||||
"timestamp": 1554997931
|
||||
},
|
||||
{
|
||||
"version": "2.1.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Run Web3ProviderEngine without excess block polling",
|
||||
"pr": 1695
|
||||
}
|
||||
],
|
||||
"timestamp": 1553183790
|
||||
},
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Do not reexport external dependencies",
|
||||
"pr": 1682
|
||||
},
|
||||
{
|
||||
"note": "Add ERC1155Proxy",
|
||||
"pr": 1661
|
||||
},
|
||||
{
|
||||
"note": "Bumped solidity version to ^0.5.5",
|
||||
"pr": 1701
|
||||
},
|
||||
{
|
||||
"note": "Integration testing for ERC1155Proxy",
|
||||
"pr": 1673
|
||||
}
|
||||
],
|
||||
"timestamp": 1553091633
|
||||
},
|
||||
{
|
||||
"timestamp": 1551479279,
|
||||
"version": "1.0.9",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1551299797,
|
||||
"version": "1.0.8",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1551220833,
|
||||
"version": "1.0.7",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1551130135,
|
||||
"version": "1.0.6",
|
||||
|
@@ -5,6 +5,59 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v2.2.1 - _July 13, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.2.0 - _July 13, 2019_
|
||||
|
||||
* Add `LibAssetProxyIds` contract (#1835)
|
||||
* Updated ERC1155 Asset Proxy. Less optimization. More explicit handling of edge cases. (#1852)
|
||||
* Implement StaticCallProxy (#1863)
|
||||
|
||||
## v2.1.5 - _May 24, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.1.4 - _May 15, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.1.3 - _May 14, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.1.2 - _May 10, 2019_
|
||||
|
||||
* Update tests to use contract-built-in `awaitTransactionSuccessAsync` (#1797)
|
||||
|
||||
## v2.1.1 - _April 11, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.1.0 - _March 21, 2019_
|
||||
|
||||
* Run Web3ProviderEngine without excess block polling (#1695)
|
||||
|
||||
## v2.0.0 - _March 20, 2019_
|
||||
|
||||
* Do not reexport external dependencies (#1682)
|
||||
* Add ERC1155Proxy (#1661)
|
||||
* Bumped solidity version to ^0.5.5 (#1701)
|
||||
* Integration testing for ERC1155Proxy (#1673)
|
||||
|
||||
## v1.0.9 - _March 1, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.0.8 - _February 27, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.0.7 - _February 26, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.0.6 - _February 25, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
@@ -1,14 +1,20 @@
|
||||
{
|
||||
"artifactsDir": "./generated-artifacts",
|
||||
"contractsDir": "./contracts",
|
||||
"useDockerisedSolc": true,
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"compilerSettings": {
|
||||
"optimizer": { "enabled": true, "runs": 1000000 },
|
||||
"evmVersion": "constantinople",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
|
||||
},
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"devdoc",
|
||||
"evm.bytecode.object",
|
||||
"evm.bytecode.sourceMap",
|
||||
"evm.deployedBytecode.object",
|
||||
@@ -18,17 +24,15 @@
|
||||
}
|
||||
},
|
||||
"contracts": [
|
||||
"@0x/contracts-erc20/contracts/test/DummyERC20Token.sol",
|
||||
"@0x/contracts-erc20/contracts/test/DummyMultipleReturnERC20Token.sol",
|
||||
"@0x/contracts-erc20/contracts/test/DummyNoReturnERC20Token.sol",
|
||||
"@0x/contracts-erc721/contracts/test/DummyERC721Receiver.sol",
|
||||
"@0x/contracts-erc721/contracts/test/DummyERC721Token.sol",
|
||||
"src/ERC1155Proxy.sol",
|
||||
"src/ERC20Proxy.sol",
|
||||
"src/ERC721Proxy.sol",
|
||||
"src/MixinAuthorizable.sol",
|
||||
"src/MultiAssetProxy.sol",
|
||||
"src/StaticCallProxy.sol",
|
||||
"src/interfaces/IAssetData.sol",
|
||||
"src/interfaces/IAssetProxy.sol",
|
||||
"src/interfaces/IAuthorizable.sol"
|
||||
"src/interfaces/IAuthorizable.sol",
|
||||
"test/TestStaticCallTarget.sol"
|
||||
]
|
||||
}
|
||||
|
375
contracts/asset-proxy/contracts/src/ERC1155Proxy.sol
Normal file
375
contracts/asset-proxy/contracts/src/ERC1155Proxy.sol
Normal file
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
|
||||
import "./MixinAuthorizable.sol";
|
||||
|
||||
|
||||
contract ERC1155Proxy is
|
||||
MixinAuthorizable
|
||||
{
|
||||
|
||||
// Id of this proxy.
|
||||
bytes4 constant internal PROXY_ID = bytes4(keccak256("ERC1155Assets(address,uint256[],uint256[],bytes)"));
|
||||
|
||||
// solhint-disable-next-line payable-fallback
|
||||
function ()
|
||||
external
|
||||
{
|
||||
// Input calldata to this function is encoded as follows:
|
||||
// -- TABLE #1 --
|
||||
// | Area | Offset (**) | Length | Contents |
|
||||
// |----------|-------------|-------------|---------------------------------|
|
||||
// | Header | 0 | 4 | function selector |
|
||||
// | Params | | 4 * 32 | function parameters: |
|
||||
// | | 4 | | 1. offset to assetData (*) |
|
||||
// | | 36 | | 2. from |
|
||||
// | | 68 | | 3. to |
|
||||
// | | 100 | | 4. amount |
|
||||
// | Data | | | assetData: |
|
||||
// | | 132 | 32 | assetData Length |
|
||||
// | | 164 | (see below) | assetData Contents |
|
||||
//
|
||||
//
|
||||
// Asset data is encoded as follows:
|
||||
// -- TABLE #2 --
|
||||
// | Area | Offset | Length | Contents |
|
||||
// |----------|-------------|---------|-------------------------------------|
|
||||
// | Header | 0 | 4 | assetProxyId |
|
||||
// | Params | | 4 * 32 | function parameters: |
|
||||
// | | 4 | | 1. address of ERC1155 contract |
|
||||
// | | 36 | | 2. offset to ids (*) |
|
||||
// | | 68 | | 3. offset to values (*) |
|
||||
// | | 100 | | 4. offset to data (*) |
|
||||
// | Data | | | ids: |
|
||||
// | | 132 | 32 | 1. ids Length |
|
||||
// | | 164 | a | 2. ids Contents |
|
||||
// | | | | values: |
|
||||
// | | 164 + a | 32 | 1. values Length |
|
||||
// | | 196 + a | b | 2. values Contents |
|
||||
// | | | | data: |
|
||||
// | | 196 + a + b | 32 | 1. data Length |
|
||||
// | | 228 + a + b | c | 2. data Contents |
|
||||
//
|
||||
//
|
||||
// Calldata for target ERC155 asset is encoded for safeBatchTransferFrom:
|
||||
// -- TABLE #3 --
|
||||
// | Area | Offset (**) | Length | Contents |
|
||||
// |----------|-------------|---------|-------------------------------------|
|
||||
// | Header | 0 | 4 | safeBatchTransferFrom selector |
|
||||
// | Params | | 5 * 32 | function parameters: |
|
||||
// | | 4 | | 1. from address |
|
||||
// | | 36 | | 2. to address |
|
||||
// | | 68 | | 3. offset to ids (*) |
|
||||
// | | 100 | | 4. offset to values (*) |
|
||||
// | | 132 | | 5. offset to data (*) |
|
||||
// | Data | | | ids: |
|
||||
// | | 164 | 32 | 1. ids Length |
|
||||
// | | 196 | a | 2. ids Contents |
|
||||
// | | | | values: |
|
||||
// | | 196 + a | 32 | 1. values Length |
|
||||
// | | 228 + a | b | 2. values Contents |
|
||||
// | | | | data: |
|
||||
// | | 228 + a + b | 32 | 1. data Length |
|
||||
// | | 260 + a + b | c | 2. data Contents |
|
||||
//
|
||||
//
|
||||
// (*): offset is computed from start of function parameters, so offset
|
||||
// by an additional 4 bytes in the calldata.
|
||||
//
|
||||
// (**): the `Offset` column is computed assuming no calldata compression;
|
||||
// offsets in the Data Area are dynamic and should be evaluated in
|
||||
// real-time.
|
||||
//
|
||||
// WARNING: The ABIv2 specification allows additional padding between
|
||||
// the Params and Data section. This will result in a larger
|
||||
// offset to assetData.
|
||||
//
|
||||
// Note: Table #1 and Table #2 exist in Calldata. We construct Table #3 in memory.
|
||||
//
|
||||
//
|
||||
assembly {
|
||||
// The first 4 bytes of calldata holds the function selector
|
||||
let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000)
|
||||
|
||||
// `transferFrom` will be called with the following parameters:
|
||||
// assetData Encoded byte array.
|
||||
// from Address to transfer asset from.
|
||||
// to Address to transfer asset to.
|
||||
// amount Amount of asset to transfer.
|
||||
// bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4
|
||||
if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) {
|
||||
|
||||
// To lookup a value in a mapping, we load from the storage location keccak256(k, p),
|
||||
// where k is the key left padded to 32 bytes and p is the storage slot
|
||||
mstore(0, caller)
|
||||
mstore(32, authorized_slot)
|
||||
|
||||
// Revert if authorized[msg.sender] == false
|
||||
if iszero(sload(keccak256(0, 64))) {
|
||||
// Revert with `Error("SENDER_NOT_AUTHORIZED")`
|
||||
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(64, 0x0000001553454e4445525f4e4f545f415554484f52495a454400000000000000)
|
||||
mstore(96, 0)
|
||||
revert(0, 100)
|
||||
}
|
||||
|
||||
// Construct Table #3 in memory, starting at memory offset 0.
|
||||
// The algorithm below maps calldata (Table #1) and assetData (Table #2) to memory (Table #3).
|
||||
// Once Table #3 ha been constructed in memory, the destination erc1155 contract is called using this
|
||||
// as its calldata. This process is divided into three steps, below.
|
||||
|
||||
////////// STEP 1/3 - Map calldata to memory (Table #1 -> Table #3) //////////
|
||||
|
||||
// Store the safeBatchTransferFrom function selector, which is computed using:
|
||||
// bytes4(keccak256("safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"))
|
||||
mstore(0, 0x2eb2c2d600000000000000000000000000000000000000000000000000000000)
|
||||
|
||||
// Copy `from` and `to` fields from calldata (Table #1) into memory (Table #3)
|
||||
calldatacopy(
|
||||
4, // aligned such that `from` and `to` are at the correct location for Table #3
|
||||
36, // beginning of `from` field from Table #1
|
||||
64 // 32 bytes for `from` + 32 bytes for `to` field
|
||||
)
|
||||
|
||||
////////// STEP 2/3 - Map assetData to memory (Table #2 -> Table #3) //////////
|
||||
|
||||
// Map relevant fields from assetData (Table #2) into memory (Table #3)
|
||||
// The Contents column of Table #2 is the same as Table #3,
|
||||
// beginning from parameter 3 - `offset to ids (*)`
|
||||
// The `values` from assetData (Table #2) are multiplied by `amount` (Table #1)
|
||||
// when they are copied into memory.
|
||||
|
||||
// Load offset to `assetData`
|
||||
let assetDataOffset := add(calldataload(4), 4)
|
||||
|
||||
// Load length in bytes of `assetData`
|
||||
let assetDataLength := calldataload(assetDataOffset)
|
||||
|
||||
// Assert that the length of asset data:
|
||||
// 1. Must be at least 132 bytes (Table #2)
|
||||
// 2. Must be a multiple of 32 (excluding the 4-byte selector)
|
||||
if or(lt(assetDataLength, 132), mod(sub(assetDataLength, 4), 32)) {
|
||||
// Revert with `Error("INVALID_ASSET_DATA_LENGTH")`
|
||||
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(64, 0x00000019494e56414c49445f41535345545f444154415f4c454e475448000000)
|
||||
mstore(96, 0)
|
||||
revert(0, 100)
|
||||
}
|
||||
|
||||
// End of asset data in calldata
|
||||
// +32 for length field
|
||||
let assetDataEnd := add(assetDataOffset, add(assetDataLength, 32))
|
||||
if gt(assetDataEnd, calldatasize()) {
|
||||
// Revert with `Error("INVALID_ASSET_DATA_END")`
|
||||
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(64, 0x00000016494e56414c49445f41535345545f444154415f454e44000000000000)
|
||||
mstore(96, 0)
|
||||
revert(0, 100)
|
||||
}
|
||||
|
||||
// Load offset to parameters section in asset data
|
||||
let paramsInAssetDataOffset := add(assetDataOffset, 36)
|
||||
|
||||
// Offset of end of Data Area in memory.
|
||||
// This value will grow as we construct Table #3.
|
||||
let dataAreaEndOffset := 164
|
||||
|
||||
// Load amount by which to scale values
|
||||
let amount := calldataload(100)
|
||||
|
||||
// Store pointer to `ids` (Table #3)
|
||||
// Subtract 4 for `safeBatchTransferFrom` selector
|
||||
mstore(68, sub(dataAreaEndOffset, 4))
|
||||
|
||||
// Ensure length of `ids` does not overflow
|
||||
let idsOffset := add(paramsInAssetDataOffset, calldataload(add(assetDataOffset, 68)))
|
||||
let idsLength := calldataload(idsOffset)
|
||||
let idsLengthInBytes := mul(idsLength, 32)
|
||||
if sub(div(idsLengthInBytes, 32), idsLength) {
|
||||
// Revert with `Error("UINT256_OVERFLOW")`
|
||||
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(64, 0x0000001055494e543235365f4f564552464c4f57000000000000000000000000)
|
||||
mstore(96, 0)
|
||||
revert(0, 100)
|
||||
}
|
||||
|
||||
// Ensure `ids` does not resolve to outside of `assetData`
|
||||
let idsBegin := add(idsOffset, 32)
|
||||
let idsEnd := add(idsBegin, idsLengthInBytes)
|
||||
if gt(idsEnd, assetDataEnd) {
|
||||
// Revert with `Error("INVALID_IDS_OFFSET")`
|
||||
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(64, 0x00000012494e56414c49445f4944535f4f464653455400000000000000000000)
|
||||
mstore(96, 0)
|
||||
revert(0, 100)
|
||||
}
|
||||
|
||||
// Copy `ids` from `assetData` (Table #2) to memory (Table #3)
|
||||
calldatacopy(
|
||||
dataAreaEndOffset,
|
||||
idsOffset,
|
||||
add(idsLengthInBytes, 32)
|
||||
)
|
||||
dataAreaEndOffset := add(dataAreaEndOffset, add(idsLengthInBytes, 32))
|
||||
|
||||
// Store pointer to `values` (Table #3)
|
||||
// Subtract 4 for `safeBatchTrasferFrom` selector
|
||||
mstore(100, sub(dataAreaEndOffset, 4))
|
||||
|
||||
// Ensure length of `values` does not overflow
|
||||
let valuesOffset := add(paramsInAssetDataOffset, calldataload(add(assetDataOffset, 100)))
|
||||
let valuesLength := calldataload(valuesOffset)
|
||||
let valuesLengthInBytes := mul(valuesLength, 32)
|
||||
if sub(div(valuesLengthInBytes, 32), valuesLength) {
|
||||
// Revert with `Error("UINT256_OVERFLOW")`
|
||||
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(64, 0x0000001055494e543235365f4f564552464c4f57000000000000000000000000)
|
||||
mstore(96, 0)
|
||||
revert(0, 100)
|
||||
}
|
||||
|
||||
// Ensure `values` does not resolve to outside of `assetData`
|
||||
let valuesBegin := add(valuesOffset, 32)
|
||||
let valuesEnd := add(valuesBegin, valuesLengthInBytes)
|
||||
if gt(valuesEnd, assetDataEnd) {
|
||||
// Revert with `Error("INVALID_VALUES_OFFSET")`
|
||||
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(64, 0x00000015494e56414c49445f56414c5545535f4f464653455400000000000000)
|
||||
mstore(96, 0)
|
||||
revert(0, 100)
|
||||
}
|
||||
|
||||
// Store length of `values`
|
||||
mstore(dataAreaEndOffset, valuesLength)
|
||||
dataAreaEndOffset := add(dataAreaEndOffset, 32)
|
||||
|
||||
// Scale and store elements of `values`
|
||||
for { let currentValueOffset := valuesBegin }
|
||||
lt(currentValueOffset, valuesEnd)
|
||||
{ currentValueOffset := add(currentValueOffset, 32) }
|
||||
{
|
||||
// Load value and generate scaled value
|
||||
let currentValue := calldataload(currentValueOffset)
|
||||
let currentValueScaled := mul(currentValue, amount)
|
||||
|
||||
// Revert if `amount` != 0 and multiplication resulted in an overflow
|
||||
if iszero(or(
|
||||
iszero(amount),
|
||||
eq(div(currentValueScaled, amount), currentValue)
|
||||
)) {
|
||||
// Revert with `Error("UINT256_OVERFLOW")`
|
||||
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(64, 0x0000001055494e543235365f4f564552464c4f57000000000000000000000000)
|
||||
mstore(96, 0)
|
||||
revert(0, 100)
|
||||
}
|
||||
|
||||
// There was no overflow, store the scaled token value
|
||||
mstore(dataAreaEndOffset, currentValueScaled)
|
||||
dataAreaEndOffset := add(dataAreaEndOffset, 32)
|
||||
}
|
||||
|
||||
// Store pointer to `data` (Table #3)
|
||||
// Subtract 4 for `safeBatchTrasferFrom` selector
|
||||
mstore(132, sub(dataAreaEndOffset, 4))
|
||||
|
||||
// Ensure `data` does not resolve to outside of `assetData`
|
||||
let dataOffset := add(paramsInAssetDataOffset, calldataload(add(assetDataOffset, 132)))
|
||||
let dataLengthInBytes := calldataload(dataOffset)
|
||||
let dataBegin := add(dataOffset, 32)
|
||||
let dataEnd := add(dataBegin, dataLengthInBytes)
|
||||
if gt(dataEnd, assetDataEnd) {
|
||||
// Revert with `Error("INVALID_DATA_OFFSET")`
|
||||
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(64, 0x00000013494e56414c49445f444154415f4f4646534554000000000000000000)
|
||||
mstore(96, 0)
|
||||
revert(0, 100)
|
||||
}
|
||||
|
||||
// Copy `data` from `assetData` (Table #2) to memory (Table #3)
|
||||
calldatacopy(
|
||||
dataAreaEndOffset,
|
||||
dataOffset,
|
||||
add(dataLengthInBytes, 32)
|
||||
)
|
||||
|
||||
// Update the end of data offset to be word-aligned
|
||||
let dataLengthInWords := div(add(dataLengthInBytes, 31), 32)
|
||||
let dataLengthInBytesWordAligned := mul(dataLengthInWords, 32)
|
||||
dataAreaEndOffset := add(dataAreaEndOffset, add(dataLengthInBytesWordAligned, 32))
|
||||
|
||||
////////// STEP 3/3 - Execute Transfer //////////
|
||||
// Load the address of the destination erc1155 contract from asset data (Table #2)
|
||||
// +32 bytes for assetData Length
|
||||
// +4 bytes for assetProxyId
|
||||
let assetAddress := and(
|
||||
calldataload(add(assetDataOffset, 36)),
|
||||
0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff
|
||||
)
|
||||
|
||||
// Call into the destination erc1155 contract using as calldata Table #3 (constructed in-memory above)
|
||||
let success := call(
|
||||
gas, // forward all gas
|
||||
assetAddress, // call address of erc1155 asset
|
||||
0, // don't send any ETH
|
||||
0, // pointer to start of input
|
||||
dataAreaEndOffset, // length of input is the end of the Data Area (Table #3)
|
||||
0, // write output over memory that won't be reused
|
||||
0 // don't copy output to memory
|
||||
)
|
||||
|
||||
// Revert with reason given by AssetProxy if `transferFrom` call failed
|
||||
if iszero(success) {
|
||||
returndatacopy(
|
||||
0, // copy to memory at 0
|
||||
0, // copy from return data at 0
|
||||
returndatasize() // copy all return data
|
||||
)
|
||||
revert(0, returndatasize())
|
||||
}
|
||||
|
||||
// Return if call was successful
|
||||
return(0, 0)
|
||||
}
|
||||
|
||||
// Revert if undefined function is called
|
||||
revert(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Gets the proxy id associated with the proxy address.
|
||||
/// @return Proxy id.
|
||||
function getProxyId()
|
||||
external
|
||||
pure
|
||||
returns (bytes4)
|
||||
{
|
||||
return PROXY_ID;
|
||||
}
|
||||
}
|
@@ -16,7 +16,7 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.4.24;
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
import "./MixinAuthorizable.sol";
|
||||
|
||||
|
@@ -16,7 +16,7 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.4.24;
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
import "./MixinAuthorizable.sol";
|
||||
|
||||
|
@@ -16,7 +16,7 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.4.24;
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/Ownable.sol";
|
||||
import "./mixins/MAssetProxyDispatcher.sol";
|
||||
@@ -28,7 +28,7 @@ contract MixinAssetProxyDispatcher is
|
||||
MAssetProxyDispatcher
|
||||
{
|
||||
// Mapping from Asset Proxy Id's to their respective Asset Proxy
|
||||
mapping (bytes4 => IAssetProxy) public assetProxies;
|
||||
mapping (bytes4 => address) public assetProxies;
|
||||
|
||||
/// @dev Registers an asset proxy to its asset proxy id.
|
||||
/// Once an asset proxy is registered, it cannot be unregistered.
|
||||
@@ -37,10 +37,8 @@ contract MixinAssetProxyDispatcher is
|
||||
external
|
||||
onlyOwner
|
||||
{
|
||||
IAssetProxy assetProxyContract = IAssetProxy(assetProxy);
|
||||
|
||||
// Ensure that no asset proxy exists with current id.
|
||||
bytes4 assetProxyId = assetProxyContract.getProxyId();
|
||||
bytes4 assetProxyId = IAssetProxy(assetProxy).getProxyId();
|
||||
address currentAssetProxy = assetProxies[assetProxyId];
|
||||
require(
|
||||
currentAssetProxy == address(0),
|
||||
@@ -48,7 +46,7 @@ contract MixinAssetProxyDispatcher is
|
||||
);
|
||||
|
||||
// Add asset proxy and log registration.
|
||||
assetProxies[assetProxyId] = assetProxyContract;
|
||||
assetProxies[assetProxyId] = assetProxy;
|
||||
emit AssetProxyRegistered(
|
||||
assetProxyId,
|
||||
assetProxy
|
||||
|
@@ -16,7 +16,7 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.4.24;
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/Ownable.sol";
|
||||
import "./mixins/MAuthorizable.sol";
|
||||
|
@@ -16,7 +16,7 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.4.24;
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
import "./MixinAssetProxyDispatcher.sol";
|
||||
import "./MixinAuthorizable.sol";
|
||||
|
207
contracts/asset-proxy/contracts/src/StaticCallProxy.sol
Normal file
207
contracts/asset-proxy/contracts/src/StaticCallProxy.sol
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
|
||||
|
||||
contract StaticCallProxy {
|
||||
|
||||
// Id of this proxy.
|
||||
bytes4 constant internal PROXY_ID = bytes4(keccak256("StaticCall(address,bytes,bytes32)"));
|
||||
|
||||
// solhint-disable-next-line payable-fallback
|
||||
function ()
|
||||
external
|
||||
{
|
||||
assembly {
|
||||
// The first 4 bytes of calldata holds the function selector
|
||||
let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000)
|
||||
|
||||
// `transferFrom` will be called with the following parameters:
|
||||
// assetData Encoded byte array.
|
||||
// from Address to transfer asset from.
|
||||
// to Address to transfer asset to.
|
||||
// amount Amount of asset to transfer.
|
||||
// bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4
|
||||
if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) {
|
||||
|
||||
// `transferFrom`.
|
||||
// The function is marked `external`, so no abi decoding is done for
|
||||
// us. Instead, we expect the `calldata` memory to contain the
|
||||
// following:
|
||||
//
|
||||
// | Area | Offset | Length | Contents |
|
||||
// |----------|--------|---------|-------------------------------------|
|
||||
// | Header | 0 | 4 | function selector |
|
||||
// | Params | | 4 * 32 | function parameters: |
|
||||
// | | 4 | | 1. offset to assetData (*) |
|
||||
// | | 36 | | 2. from |
|
||||
// | | 68 | | 3. to |
|
||||
// | | 100 | | 4. amount |
|
||||
// | Data | | | assetData: |
|
||||
// | | 132 | 32 | assetData Length |
|
||||
// | | 164 | ** | assetData Contents |
|
||||
//
|
||||
// (*): offset is computed from start of function parameters, so offset
|
||||
// by an additional 4 bytes in the calldata.
|
||||
//
|
||||
// (**): see table below to compute length of assetData Contents
|
||||
// (***): Note that the `from`, `to`, and `amount` params in calldata are ignored in this function.
|
||||
//
|
||||
// WARNING: The ABIv2 specification allows additional padding between
|
||||
// the Params and Data section. This will result in a larger
|
||||
// offset to assetData.
|
||||
|
||||
// Load offset to `assetData`
|
||||
let assetDataOffset := add(calldataload(4), 4)
|
||||
|
||||
// Validate length of `assetData`
|
||||
let assetDataLen := calldataload(assetDataOffset)
|
||||
if or(lt(assetDataLen, 100), mod(sub(assetDataLen, 4), 32)) {
|
||||
// Revert with `Error("INVALID_ASSET_DATA_LENGTH")`
|
||||
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(64, 0x00000019494e56414c49445f41535345545f444154415f4c454e475448000000)
|
||||
mstore(96, 0)
|
||||
revert(0, 100)
|
||||
}
|
||||
|
||||
// Ensure that `assetData` ends inside of calldata
|
||||
let assetDataEnd := add(assetDataOffset, add(assetDataLen, 32))
|
||||
if gt(assetDataEnd, calldatasize()) {
|
||||
// Revert with `Error("INVALID_ASSET_DATA_END")`
|
||||
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(64, 0x00000016494e56414c49445f41535345545f444154415f454e44000000000000)
|
||||
mstore(96, 0)
|
||||
revert(0, 100)
|
||||
}
|
||||
|
||||
// Asset data is encoded as follows:
|
||||
// | Area | Offset | Length | Contents |
|
||||
// |----------|-------------|---------|--------------------------------------|
|
||||
// | Header | 0 | 4 | assetProxyId |
|
||||
// | Params | | 4 * 32 | function parameters: |
|
||||
// | | 4 | | 1. address of callTarget |
|
||||
// | | 36 | | 2. offset to staticCallData (*) |
|
||||
// | | 68 | | 3. expected 32 byte hash of output |
|
||||
// | Data | | | staticCallData: |
|
||||
// | | 100 | 32 | 1. staticCallData Length |
|
||||
// | | 132 | a | 2. staticCallData Contents |
|
||||
|
||||
// In order to find the offset to `staticCallData`, we must add:
|
||||
// assetDataOffset
|
||||
// + 32 (assetData len)
|
||||
// + 4 (proxyId)
|
||||
// + 32 (callTarget)
|
||||
let paramsInAssetDataOffset := add(assetDataOffset, 36)
|
||||
let staticCallDataOffset := add(paramsInAssetDataOffset, calldataload(add(assetDataOffset, 68)))
|
||||
|
||||
// Load length of `staticCallData`
|
||||
let staticCallDataLen := calldataload(staticCallDataOffset)
|
||||
|
||||
// Ensure `staticCallData` does not begin to outside of `assetData`
|
||||
let staticCallDataBegin := add(staticCallDataOffset, 32)
|
||||
let staticCallDataEnd := add(staticCallDataBegin, staticCallDataLen)
|
||||
if gt(staticCallDataEnd, assetDataEnd) {
|
||||
// Revert with `Error("INVALID_STATIC_CALL_DATA_OFFSET")`
|
||||
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(64, 0x0000001f494e56414c49445f5354415449435f43414c4c5f444154415f4f4646)
|
||||
mstore(96, 0x5345540000000000000000000000000000000000000000000000000000000000)
|
||||
revert(0, 100)
|
||||
}
|
||||
|
||||
// Copy `staticCallData` into memory
|
||||
calldatacopy(
|
||||
0, // memory can be safely overwritten from beginning
|
||||
staticCallDataBegin, // start of `staticCallData`
|
||||
staticCallDataLen // copy the entire `staticCallData`
|
||||
)
|
||||
|
||||
// In order to find the offset to `callTarget`, we must add:
|
||||
// assetDataOffset
|
||||
// + 32 (assetData len)
|
||||
// + 4 (proxyId)
|
||||
let callTarget := and(
|
||||
calldataload(add(assetDataOffset, 36)),
|
||||
0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff
|
||||
)
|
||||
|
||||
// Perform `callTarget.staticcall(staticCallData)`
|
||||
let success := staticcall(
|
||||
gas, // forward all gas
|
||||
callTarget, // call address `callTarget`
|
||||
0, // pointer to start of input
|
||||
staticCallDataLen, // length of input
|
||||
0, // start of memory can be safely overwritten
|
||||
0 // don't copy output to memory
|
||||
)
|
||||
|
||||
// Copy entire output to start of memory
|
||||
let outputLen := returndatasize()
|
||||
returndatacopy(
|
||||
0, // copy to memory at 0
|
||||
0, // copy from return data at 0
|
||||
outputLen // copy all return data
|
||||
)
|
||||
|
||||
// Revert with reason given by `callTarget` if staticcall is unsuccessful
|
||||
if iszero(success) {
|
||||
revert(0, outputLen)
|
||||
}
|
||||
|
||||
// Calculate hash of output
|
||||
let callResultHash := keccak256(0, outputLen)
|
||||
|
||||
// In order to find the offset to `expectedCallResultHash`, we must add:
|
||||
// assetDataOffset
|
||||
// + 32 (assetData len)
|
||||
// + 4 (proxyId)
|
||||
// + 32 (callTarget)
|
||||
// + 32 (staticCallDataOffset)
|
||||
let expectedResultHash := calldataload(add(assetDataOffset, 100))
|
||||
|
||||
if sub(callResultHash, expectedResultHash) {
|
||||
// Revert with `Error("UNEXPECTED_STATIC_CALL_RESULT")`
|
||||
mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)
|
||||
mstore(64, 0x0000001d554e45585045435445445f5354415449435f43414c4c5f524553554c)
|
||||
mstore(96, 0x5400000000000000000000000000000000000000000000000000000000000000)
|
||||
revert(0, 100)
|
||||
}
|
||||
|
||||
// Return if output matched expected output
|
||||
return(0, 0)
|
||||
}
|
||||
|
||||
// Revert if undefined function is called
|
||||
revert(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Gets the proxy id associated with the proxy address.
|
||||
/// @return Proxy id.
|
||||
function getProxyId()
|
||||
external
|
||||
pure
|
||||
returns (bytes4)
|
||||
{
|
||||
return PROXY_ID;
|
||||
}
|
||||
}
|
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
// solhint-disable
|
||||
pragma solidity ^0.4.24;
|
||||
pragma solidity ^0.5.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
|
||||
@@ -26,19 +26,33 @@ pragma experimental ABIEncoderV2;
|
||||
// This argument is ABI encoded as one of the methods of this interface.
|
||||
interface IAssetData {
|
||||
|
||||
function ERC20Token(address tokenContract)
|
||||
function ERC20Token(address tokenAddress)
|
||||
external;
|
||||
|
||||
function ERC721Token(
|
||||
address tokenContract,
|
||||
address tokenAddress,
|
||||
uint256 tokenId
|
||||
)
|
||||
external;
|
||||
|
||||
function MultiAsset(
|
||||
uint256[] amounts,
|
||||
bytes[] nestedAssetData
|
||||
function ERC1155Assets(
|
||||
address tokenAddress,
|
||||
uint256[] calldata tokenIds,
|
||||
uint256[] calldata tokenValues,
|
||||
bytes calldata callbackData
|
||||
)
|
||||
external;
|
||||
|
||||
function MultiAsset(
|
||||
uint256[] calldata amounts,
|
||||
bytes[] calldata nestedAssetData
|
||||
)
|
||||
external;
|
||||
|
||||
function StaticCall(
|
||||
address callTarget,
|
||||
bytes calldata staticCallData,
|
||||
bytes32 callResultHash
|
||||
)
|
||||
external;
|
||||
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.4.24;
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
import "./IAuthorizable.sol";
|
||||
|
||||
@@ -30,7 +30,7 @@ contract IAssetProxy is
|
||||
/// @param to Address to transfer asset to.
|
||||
/// @param amount Amount of asset to transfer.
|
||||
function transferFrom(
|
||||
bytes assetData,
|
||||
bytes calldata assetData,
|
||||
address from,
|
||||
address to,
|
||||
uint256 amount
|
||||
|
@@ -16,7 +16,7 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.4.24;
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
|
||||
contract IAssetProxyDispatcher {
|
||||
|
@@ -16,7 +16,7 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.4.24;
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/interfaces/IOwnable.sol";
|
||||
|
||||
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
|
||||
contract LibAssetProxyIds {
|
||||
|
||||
// AssetProxy Ids are equiavalent the first 4 bytes of the keccak256 hash of the function signature assigned to each AssetProxy.
|
||||
|
||||
// ERC20Token(address)
|
||||
bytes4 constant public ERC20_PROXY_ID = 0xf47261b0;
|
||||
|
||||
// ERC721Token(address,uint256)
|
||||
bytes4 constant public ERC721_PROXY_ID = 0x02571792;
|
||||
|
||||
// ERC1155Assets(address,uint256[],uint256[],bytes)
|
||||
bytes4 constant public ERC1155_PROXY_ID = 0xa7cb5fb7;
|
||||
|
||||
// MultiAsset(uint256[],bytes[])
|
||||
bytes4 constant public MULTI_ASSET_PROXY_ID = 0x94cfcdd7;
|
||||
|
||||
// StaticCall(address,bytes,bytes32)
|
||||
bytes4 constant public STATIC_CALL_PROXY_ID = 0xc339d10a;
|
||||
}
|
@@ -16,7 +16,7 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.4.24;
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
import "../interfaces/IAssetProxyDispatcher.sol";
|
||||
|
||||
|
@@ -16,7 +16,7 @@
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.4.24;
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
import "../interfaces/IAuthorizable.sol";
|
||||
|
||||
|
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.9;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
|
||||
|
||||
contract TestStaticCallTarget {
|
||||
|
||||
using LibBytes for bytes;
|
||||
|
||||
uint256 internal _state;
|
||||
|
||||
function updateState()
|
||||
external
|
||||
{
|
||||
_state++;
|
||||
}
|
||||
|
||||
function assertEvenNumber(uint256 target)
|
||||
external
|
||||
pure
|
||||
{
|
||||
require(
|
||||
target % 2 == 0,
|
||||
"TARGET_NOT_EVEN"
|
||||
);
|
||||
}
|
||||
|
||||
function isOddNumber(uint256 target)
|
||||
external
|
||||
pure
|
||||
returns (bool isOdd)
|
||||
{
|
||||
isOdd = target % 2 == 1;
|
||||
return isOdd;
|
||||
}
|
||||
|
||||
function noInputFunction()
|
||||
external
|
||||
pure
|
||||
{
|
||||
assert(msg.data.length == 4 && msg.data.readBytes4(0) == bytes4(keccak256("noInputFunction()")));
|
||||
}
|
||||
|
||||
function dynamicInputFunction(bytes calldata a)
|
||||
external
|
||||
pure
|
||||
{
|
||||
bytes memory abiEncodedData = abi.encodeWithSignature("dynamicInputFunction(bytes)", a);
|
||||
assert(msg.data.equals(abiEncodedData));
|
||||
}
|
||||
|
||||
function returnComplexType(uint256 a, uint256 b)
|
||||
external
|
||||
view
|
||||
returns (bytes memory result)
|
||||
{
|
||||
result = abi.encodePacked(
|
||||
address(this),
|
||||
a,
|
||||
b
|
||||
);
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-asset-proxy",
|
||||
"version": "1.0.6",
|
||||
"version": "2.2.1",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -24,6 +24,7 @@
|
||||
"clean": "shx rm -rf lib generated-artifacts generated-wrappers",
|
||||
"generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../../node_modules/@0x/abi-gen-templates/contract.handlebars --partials '../../node_modules/@0x/abi-gen-templates/partials/**/*.handlebars' --output generated-wrappers --backend ethers",
|
||||
"lint": "tslint --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts",
|
||||
"fix": "tslint --fix --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts",
|
||||
"coverage:report:text": "istanbul report text",
|
||||
"coverage:report:html": "istanbul report html && open coverage/index.html",
|
||||
"profiler:report:html": "istanbul report html && open coverage/index.html",
|
||||
@@ -33,7 +34,7 @@
|
||||
"lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
|
||||
},
|
||||
"config": {
|
||||
"abis": "./generated-artifacts/@(DummyERC20Token|DummyERC721Receiver|DummyERC721Token|DummyMultipleReturnERC20Token|DummyNoReturnERC20Token|ERC20Proxy|ERC721Proxy|IAssetData|IAssetProxy|IAuthorizable|MixinAuthorizable|MultiAssetProxy).json",
|
||||
"abis": "./generated-artifacts/@(ERC1155Proxy|ERC20Proxy|ERC721Proxy|IAssetData|IAssetProxy|IAuthorizable|MixinAuthorizable|MultiAssetProxy|StaticCallProxy|TestStaticCallTarget).json",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
||||
},
|
||||
"repository": {
|
||||
@@ -46,11 +47,12 @@
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^2.0.4",
|
||||
"@0x/contracts-gen": "^1.0.3",
|
||||
"@0x/dev-utils": "^2.1.1",
|
||||
"@0x/sol-compiler": "^3.1.0",
|
||||
"@0x/tslint-config": "^3.0.0",
|
||||
"@0x/abi-gen": "^2.1.1",
|
||||
"@0x/contracts-gen": "^1.0.10",
|
||||
"@0x/contracts-test-utils": "^3.1.9",
|
||||
"@0x/dev-utils": "^2.2.4",
|
||||
"@0x/sol-compiler": "^3.1.9",
|
||||
"@0x/tslint-config": "^3.0.1",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/node": "*",
|
||||
"chai": "^4.0.1",
|
||||
@@ -66,17 +68,18 @@
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^5.0.0",
|
||||
"@0x/contracts-erc20": "1.0.2",
|
||||
"@0x/contracts-erc721": "1.0.2",
|
||||
"@0x/contracts-test-utils": "^3.0.5",
|
||||
"@0x/contracts-utils": "2.0.1",
|
||||
"@0x/order-utils": "^7.0.0",
|
||||
"@0x/types": "^2.1.0",
|
||||
"@0x/typescript-typings": "^4.1.0",
|
||||
"@0x/utils": "^4.2.0",
|
||||
"@0x/web3-wrapper": "^6.0.0",
|
||||
"ethereum-types": "^2.1.0",
|
||||
"@0x/base-contract": "^5.1.1",
|
||||
"@0x/contracts-erc1155": "^1.1.8",
|
||||
"@0x/contracts-erc20": "^2.2.7",
|
||||
"@0x/contracts-erc721": "^2.1.8",
|
||||
"@0x/contracts-utils": "^3.1.8",
|
||||
"@0x/order-utils": "^8.2.1",
|
||||
"@0x/types": "^2.4.0",
|
||||
"@0x/typescript-typings": "^4.2.3",
|
||||
"@0x/utils": "^4.4.0",
|
||||
"@0x/web3-wrapper": "^6.0.7",
|
||||
"ethereum-types": "^2.1.3",
|
||||
"ethereumjs-util": "^5.1.1",
|
||||
"lodash": "^4.17.11"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
@@ -5,11 +5,7 @@
|
||||
*/
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as DummyERC20Token from '../generated-artifacts/DummyERC20Token.json';
|
||||
import * as DummyERC721Receiver from '../generated-artifacts/DummyERC721Receiver.json';
|
||||
import * as DummyERC721Token from '../generated-artifacts/DummyERC721Token.json';
|
||||
import * as DummyMultipleReturnERC20Token from '../generated-artifacts/DummyMultipleReturnERC20Token.json';
|
||||
import * as DummyNoReturnERC20Token from '../generated-artifacts/DummyNoReturnERC20Token.json';
|
||||
import * as ERC1155Proxy from '../generated-artifacts/ERC1155Proxy.json';
|
||||
import * as ERC20Proxy from '../generated-artifacts/ERC20Proxy.json';
|
||||
import * as ERC721Proxy from '../generated-artifacts/ERC721Proxy.json';
|
||||
import * as IAssetData from '../generated-artifacts/IAssetData.json';
|
||||
@@ -17,17 +13,17 @@ import * as IAssetProxy from '../generated-artifacts/IAssetProxy.json';
|
||||
import * as IAuthorizable from '../generated-artifacts/IAuthorizable.json';
|
||||
import * as MixinAuthorizable from '../generated-artifacts/MixinAuthorizable.json';
|
||||
import * as MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json';
|
||||
import * as StaticCallProxy from '../generated-artifacts/StaticCallProxy.json';
|
||||
import * as TestStaticCallTarget from '../generated-artifacts/TestStaticCallTarget.json';
|
||||
export const artifacts = {
|
||||
DummyERC20Token: DummyERC20Token as ContractArtifact,
|
||||
DummyMultipleReturnERC20Token: DummyMultipleReturnERC20Token as ContractArtifact,
|
||||
DummyNoReturnERC20Token: DummyNoReturnERC20Token as ContractArtifact,
|
||||
DummyERC721Receiver: DummyERC721Receiver as ContractArtifact,
|
||||
DummyERC721Token: DummyERC721Token as ContractArtifact,
|
||||
ERC1155Proxy: ERC1155Proxy as ContractArtifact,
|
||||
ERC20Proxy: ERC20Proxy as ContractArtifact,
|
||||
ERC721Proxy: ERC721Proxy as ContractArtifact,
|
||||
MixinAuthorizable: MixinAuthorizable as ContractArtifact,
|
||||
MultiAssetProxy: MultiAssetProxy as ContractArtifact,
|
||||
StaticCallProxy: StaticCallProxy as ContractArtifact,
|
||||
IAssetData: IAssetData as ContractArtifact,
|
||||
IAssetProxy: IAssetProxy as ContractArtifact,
|
||||
IAuthorizable: IAuthorizable as ContractArtifact,
|
||||
TestStaticCallTarget: TestStaticCallTarget as ContractArtifact,
|
||||
};
|
||||
|
@@ -3,11 +3,7 @@
|
||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../generated-wrappers/dummy_erc20_token';
|
||||
export * from '../generated-wrappers/dummy_erc721_receiver';
|
||||
export * from '../generated-wrappers/dummy_erc721_token';
|
||||
export * from '../generated-wrappers/dummy_multiple_return_erc20_token';
|
||||
export * from '../generated-wrappers/dummy_no_return_erc20_token';
|
||||
export * from '../generated-wrappers/erc1155_proxy';
|
||||
export * from '../generated-wrappers/erc20_proxy';
|
||||
export * from '../generated-wrappers/erc721_proxy';
|
||||
export * from '../generated-wrappers/i_asset_data';
|
||||
@@ -15,3 +11,5 @@ export * from '../generated-wrappers/i_asset_proxy';
|
||||
export * from '../generated-wrappers/i_authorizable';
|
||||
export * from '../generated-wrappers/mixin_authorizable';
|
||||
export * from '../generated-wrappers/multi_asset_proxy';
|
||||
export * from '../generated-wrappers/static_call_proxy';
|
||||
export * from '../generated-wrappers/test_static_call_target';
|
||||
|
@@ -53,16 +53,18 @@ describe('Authorizable', () => {
|
||||
);
|
||||
});
|
||||
it('should allow owner to add an authorized address', async () => {
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
|
||||
await authorizable.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
const isAuthorized = await authorizable.authorized.callAsync(address);
|
||||
expect(isAuthorized).to.be.true();
|
||||
});
|
||||
it('should throw if owner attempts to authorize a duplicate address', async () => {
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
|
||||
await authorizable.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
return expectTransactionFailedAsync(
|
||||
@@ -74,8 +76,9 @@ describe('Authorizable', () => {
|
||||
|
||||
describe('removeAuthorizedAddress', () => {
|
||||
it('should throw if not called by owner', async () => {
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
|
||||
await authorizable.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
return expectTransactionFailedAsync(
|
||||
@@ -87,14 +90,14 @@ describe('Authorizable', () => {
|
||||
});
|
||||
|
||||
it('should allow owner to remove an authorized address', async () => {
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
|
||||
await authorizable.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await authorizable.removeAuthorizedAddress.sendTransactionAsync(address, {
|
||||
from: owner,
|
||||
}),
|
||||
await authorizable.removeAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
const isAuthorized = await authorizable.authorized.callAsync(address);
|
||||
@@ -113,8 +116,9 @@ describe('Authorizable', () => {
|
||||
|
||||
describe('removeAuthorizedAddressAtIndex', () => {
|
||||
it('should throw if not called by owner', async () => {
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
|
||||
await authorizable.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
const index = new BigNumber(0);
|
||||
@@ -126,8 +130,9 @@ describe('Authorizable', () => {
|
||||
);
|
||||
});
|
||||
it('should throw if index is >= authorities.length', async () => {
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
|
||||
await authorizable.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
const index = new BigNumber(1);
|
||||
@@ -150,12 +155,14 @@ describe('Authorizable', () => {
|
||||
it('should throw if address at index does not match target', async () => {
|
||||
const address1 = address;
|
||||
const address2 = notOwner;
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await authorizable.addAuthorizedAddress.sendTransactionAsync(address1, { from: owner }),
|
||||
await authorizable.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
address1,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await authorizable.addAuthorizedAddress.sendTransactionAsync(address2, { from: owner }),
|
||||
await authorizable.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
address2,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
const address1Index = new BigNumber(0);
|
||||
@@ -167,15 +174,16 @@ describe('Authorizable', () => {
|
||||
);
|
||||
});
|
||||
it('should allow owner to remove an authorized address', async () => {
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }),
|
||||
await authorizable.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
const index = new BigNumber(0);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, {
|
||||
from: owner,
|
||||
}),
|
||||
await authorizable.removeAuthorizedAddressAtIndex.awaitTransactionSuccessAsync(
|
||||
address,
|
||||
index,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
const isAuthorized = await authorizable.authorized.callAsync(address);
|
||||
@@ -187,20 +195,17 @@ describe('Authorizable', () => {
|
||||
it('should return all authorized addresses', async () => {
|
||||
const initial = await authorizable.getAuthorizedAddresses.callAsync();
|
||||
expect(initial).to.have.length(0);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await authorizable.addAuthorizedAddress.sendTransactionAsync(address, {
|
||||
from: owner,
|
||||
}),
|
||||
await authorizable.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
const afterAdd = await authorizable.getAuthorizedAddresses.callAsync();
|
||||
expect(afterAdd).to.have.length(1);
|
||||
expect(afterAdd).to.include(address);
|
||||
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await authorizable.removeAuthorizedAddress.sendTransactionAsync(address, {
|
||||
from: owner,
|
||||
}),
|
||||
await authorizable.removeAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
const afterRemove = await authorizable.getAuthorizedAddresses.callAsync();
|
||||
|
1948
contracts/asset-proxy/test/erc1155_proxy.ts
Normal file
1948
contracts/asset-proxy/test/erc1155_proxy.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,10 @@
|
||||
import { env, EnvVars } from '@0x/dev-utils';
|
||||
|
||||
import { coverage, profiler, provider } from '@0x/contracts-test-utils';
|
||||
import { providerUtils } from '@0x/utils';
|
||||
|
||||
before('start web3 provider', () => {
|
||||
provider.start();
|
||||
providerUtils.startProviderEngine(provider);
|
||||
});
|
||||
after('generate coverage report', async () => {
|
||||
if (env.parseBoolean(EnvVars.SolidityCoverage)) {
|
||||
|
@@ -1,3 +1,16 @@
|
||||
import { ERC1155MintableContract, Erc1155Wrapper } from '@0x/contracts-erc1155';
|
||||
import {
|
||||
artifacts as erc20Artifacts,
|
||||
DummyERC20TokenContract,
|
||||
DummyERC20TokenTransferEventArgs,
|
||||
DummyMultipleReturnERC20TokenContract,
|
||||
DummyNoReturnERC20TokenContract,
|
||||
} from '@0x/contracts-erc20';
|
||||
import {
|
||||
artifacts as erc721Artifacts,
|
||||
DummyERC721ReceiverContract,
|
||||
DummyERC721TokenContract,
|
||||
} from '@0x/contracts-erc721';
|
||||
import {
|
||||
chaiSetup,
|
||||
constants,
|
||||
@@ -18,12 +31,7 @@ import * as _ from 'lodash';
|
||||
|
||||
import {
|
||||
artifacts,
|
||||
DummyERC20TokenContract,
|
||||
DummyERC20TokenTransferEventArgs,
|
||||
DummyERC721ReceiverContract,
|
||||
DummyERC721TokenContract,
|
||||
DummyMultipleReturnERC20TokenContract,
|
||||
DummyNoReturnERC20TokenContract,
|
||||
ERC1155ProxyWrapper,
|
||||
ERC20ProxyContract,
|
||||
ERC20Wrapper,
|
||||
ERC721ProxyContract,
|
||||
@@ -36,16 +44,8 @@ import {
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
const assetProxyInterface = new IAssetProxyContract(
|
||||
artifacts.IAssetProxy.compilerOutput.abi,
|
||||
constants.NULL_ADDRESS,
|
||||
provider,
|
||||
);
|
||||
const assetDataInterface = new IAssetDataContract(
|
||||
artifacts.IAssetData.compilerOutput.abi,
|
||||
constants.NULL_ADDRESS,
|
||||
provider,
|
||||
);
|
||||
const assetProxyInterface = new IAssetProxyContract(constants.NULL_ADDRESS, provider);
|
||||
const assetDataInterface = new IAssetDataContract(constants.NULL_ADDRESS, provider);
|
||||
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
describe('Asset Transfer Proxies', () => {
|
||||
@@ -71,6 +71,15 @@ describe('Asset Transfer Proxies', () => {
|
||||
let erc721AFromTokenId: BigNumber;
|
||||
let erc721BFromTokenId: BigNumber;
|
||||
|
||||
let erc1155Proxy: ERC721ProxyContract;
|
||||
let erc1155ProxyWrapper: ERC1155ProxyWrapper;
|
||||
let erc1155Contract: ERC1155MintableContract;
|
||||
let erc1155Contract2: ERC1155MintableContract;
|
||||
let erc1155Wrapper: Erc1155Wrapper;
|
||||
let erc1155Wrapper2: Erc1155Wrapper;
|
||||
let erc1155FungibleTokens: BigNumber[];
|
||||
let erc1155NonFungibleTokensOwnedBySpender: BigNumber[];
|
||||
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
@@ -94,50 +103,62 @@ describe('Asset Transfer Proxies', () => {
|
||||
);
|
||||
|
||||
// Configure ERC20Proxy
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, {
|
||||
from: owner,
|
||||
}),
|
||||
await erc20Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
authorized,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, {
|
||||
from: owner,
|
||||
}),
|
||||
await erc20Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
multiAssetProxy.address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
|
||||
// Configure ERC721Proxy
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, {
|
||||
from: owner,
|
||||
}),
|
||||
await erc721Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
authorized,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, {
|
||||
from: owner,
|
||||
}),
|
||||
await erc721Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
multiAssetProxy.address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
|
||||
// Configure ERC115Proxy
|
||||
erc1155ProxyWrapper = new ERC1155ProxyWrapper(provider, usedAddresses, owner);
|
||||
erc1155Proxy = await erc1155ProxyWrapper.deployProxyAsync();
|
||||
await erc1155Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
authorized,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await erc1155Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
multiAssetProxy.address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
|
||||
// Configure MultiAssetProxy
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await multiAssetProxy.addAuthorizedAddress.sendTransactionAsync(authorized, {
|
||||
from: owner,
|
||||
}),
|
||||
await multiAssetProxy.addAuthorizedAddress.awaitTransactionSuccessAsync(
|
||||
authorized,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, {
|
||||
from: owner,
|
||||
}),
|
||||
await multiAssetProxy.registerAssetProxy.awaitTransactionSuccessAsync(
|
||||
erc20Proxy.address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc721Proxy.address, {
|
||||
from: owner,
|
||||
}),
|
||||
await multiAssetProxy.registerAssetProxy.awaitTransactionSuccessAsync(
|
||||
erc721Proxy.address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await multiAssetProxy.registerAssetProxy.awaitTransactionSuccessAsync(
|
||||
erc1155Proxy.address,
|
||||
{ from: owner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
|
||||
@@ -148,7 +169,7 @@ describe('Asset Transfer Proxies', () => {
|
||||
constants.DUMMY_TOKEN_DECIMALS,
|
||||
);
|
||||
noReturnErc20Token = await DummyNoReturnERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
artifacts.DummyNoReturnERC20Token,
|
||||
erc20Artifacts.DummyNoReturnERC20Token,
|
||||
provider,
|
||||
txDefaults,
|
||||
constants.DUMMY_TOKEN_NAME,
|
||||
@@ -157,7 +178,7 @@ describe('Asset Transfer Proxies', () => {
|
||||
constants.DUMMY_TOKEN_TOTAL_SUPPLY,
|
||||
);
|
||||
multipleReturnErc20Token = await DummyMultipleReturnERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
artifacts.DummyMultipleReturnERC20Token,
|
||||
erc20Artifacts.DummyMultipleReturnERC20Token,
|
||||
provider,
|
||||
txDefaults,
|
||||
constants.DUMMY_TOKEN_NAME,
|
||||
@@ -167,38 +188,39 @@ describe('Asset Transfer Proxies', () => {
|
||||
);
|
||||
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await noReturnErc20Token.setBalance.sendTransactionAsync(fromAddress, constants.INITIAL_ERC20_BALANCE),
|
||||
await noReturnErc20Token.setBalance.awaitTransactionSuccessAsync(
|
||||
fromAddress,
|
||||
constants.INITIAL_ERC20_BALANCE,
|
||||
{
|
||||
from: owner,
|
||||
},
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await noReturnErc20Token.approve.sendTransactionAsync(
|
||||
erc20Proxy.address,
|
||||
constants.INITIAL_ERC20_ALLOWANCE,
|
||||
{ from: fromAddress },
|
||||
),
|
||||
await noReturnErc20Token.approve.awaitTransactionSuccessAsync(
|
||||
erc20Proxy.address,
|
||||
constants.INITIAL_ERC20_ALLOWANCE,
|
||||
{ from: fromAddress },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await multipleReturnErc20Token.setBalance.sendTransactionAsync(
|
||||
fromAddress,
|
||||
constants.INITIAL_ERC20_BALANCE,
|
||||
),
|
||||
await multipleReturnErc20Token.setBalance.awaitTransactionSuccessAsync(
|
||||
fromAddress,
|
||||
constants.INITIAL_ERC20_BALANCE,
|
||||
{
|
||||
from: owner,
|
||||
},
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await multipleReturnErc20Token.approve.sendTransactionAsync(
|
||||
erc20Proxy.address,
|
||||
constants.INITIAL_ERC20_ALLOWANCE,
|
||||
{ from: fromAddress },
|
||||
),
|
||||
await multipleReturnErc20Token.approve.awaitTransactionSuccessAsync(
|
||||
erc20Proxy.address,
|
||||
constants.INITIAL_ERC20_ALLOWANCE,
|
||||
{ from: fromAddress },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
|
||||
// Deploy and configure ERC721 tokens and receiver
|
||||
[erc721TokenA, erc721TokenB] = await erc721Wrapper.deployDummyTokensAsync();
|
||||
erc721Receiver = await DummyERC721ReceiverContract.deployFrom0xArtifactAsync(
|
||||
artifacts.DummyERC721Receiver,
|
||||
erc721Artifacts.DummyERC721Receiver,
|
||||
provider,
|
||||
txDefaults,
|
||||
);
|
||||
@@ -207,6 +229,22 @@ describe('Asset Transfer Proxies', () => {
|
||||
const erc721Balances = await erc721Wrapper.getBalancesAsync();
|
||||
erc721AFromTokenId = erc721Balances[fromAddress][erc721TokenA.address][0];
|
||||
erc721BFromTokenId = erc721Balances[fromAddress][erc721TokenB.address][0];
|
||||
|
||||
// Deploy & configure ERC1155 tokens and receiver
|
||||
[erc1155Wrapper, erc1155Wrapper2] = await erc1155ProxyWrapper.deployDummyContractsAsync();
|
||||
erc1155Contract = erc1155Wrapper.getContract();
|
||||
erc1155Contract2 = erc1155Wrapper2.getContract();
|
||||
await erc1155ProxyWrapper.setBalancesAndAllowancesAsync();
|
||||
erc1155FungibleTokens = erc1155ProxyWrapper.getFungibleTokenIds();
|
||||
const nonFungibleTokens = erc1155ProxyWrapper.getNonFungibleTokenIds();
|
||||
const tokenBalances = await erc1155ProxyWrapper.getBalancesAsync();
|
||||
erc1155NonFungibleTokensOwnedBySpender = [];
|
||||
_.each(nonFungibleTokens, (nonFungibleToken: BigNumber) => {
|
||||
const nonFungibleTokenAsString = nonFungibleToken.toString();
|
||||
const nonFungibleTokenHeldBySpender =
|
||||
tokenBalances.nonFungible[fromAddress][erc1155Contract.address][nonFungibleTokenAsString][0];
|
||||
erc1155NonFungibleTokensOwnedBySpender.push(nonFungibleTokenHeldBySpender);
|
||||
});
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
@@ -364,10 +402,10 @@ describe('Asset Transfer Proxies', () => {
|
||||
toAddress,
|
||||
amount,
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, allowance, {
|
||||
from: fromAddress,
|
||||
}),
|
||||
await erc20TokenA.approve.awaitTransactionSuccessAsync(
|
||||
erc20Proxy.address,
|
||||
allowance,
|
||||
{ from: fromAddress },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
const erc20Balances = await erc20Wrapper.getBalancesAsync();
|
||||
@@ -396,10 +434,10 @@ describe('Asset Transfer Proxies', () => {
|
||||
toAddress,
|
||||
amount,
|
||||
);
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await noReturnErc20Token.approve.sendTransactionAsync(erc20Proxy.address, allowance, {
|
||||
from: fromAddress,
|
||||
}),
|
||||
await noReturnErc20Token.approve.awaitTransactionSuccessAsync(
|
||||
erc20Proxy.address,
|
||||
allowance,
|
||||
{ from: fromAddress },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
const initialFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress);
|
||||
@@ -562,7 +600,7 @@ describe('Asset Transfer Proxies', () => {
|
||||
erc721Receiver.address,
|
||||
amount,
|
||||
);
|
||||
const logDecoder = new LogDecoder(web3Wrapper, artifacts);
|
||||
const logDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...erc721Artifacts });
|
||||
const tx = await logDecoder.getTxWithDecodedLogsAsync(
|
||||
await web3Wrapper.sendTransactionAsync({
|
||||
to: erc721Proxy.address,
|
||||
@@ -637,10 +675,10 @@ describe('Asset Transfer Proxies', () => {
|
||||
const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
|
||||
expect(ownerFromAsset).to.be.equal(fromAddress);
|
||||
// Remove transfer approval for fromAddress.
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await erc721TokenA.approve.sendTransactionAsync(constants.NULL_ADDRESS, erc721AFromTokenId, {
|
||||
from: fromAddress,
|
||||
}),
|
||||
await erc721TokenA.approve.awaitTransactionSuccessAsync(
|
||||
constants.NULL_ADDRESS,
|
||||
erc721AFromTokenId,
|
||||
{ from: fromAddress },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
// Perform a transfer; expect this to fail.
|
||||
@@ -754,7 +792,7 @@ describe('Asset Transfer Proxies', () => {
|
||||
inputAmount,
|
||||
);
|
||||
const erc20Balances = await erc20Wrapper.getBalancesAsync();
|
||||
const logDecoder = new LogDecoder(web3Wrapper, artifacts);
|
||||
const logDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...erc20Artifacts });
|
||||
const tx = await logDecoder.getTxWithDecodedLogsAsync(
|
||||
await web3Wrapper.sendTransactionAsync({
|
||||
to: multiAssetProxy.address,
|
||||
@@ -938,6 +976,314 @@ describe('Asset Transfer Proxies', () => {
|
||||
expect(newOwnerFromAsset1).to.be.equal(toAddress);
|
||||
expect(newOwnerFromAsset2).to.be.equal(toAddress);
|
||||
});
|
||||
it('should transfer a fungible ERC1155 token', async () => {
|
||||
// setup test parameters
|
||||
const tokenHolders = [fromAddress, toAddress];
|
||||
const tokensToTransfer = erc1155FungibleTokens.slice(0, 1);
|
||||
const valuesToTransfer = [new BigNumber(25)];
|
||||
const valueMultiplier = new BigNumber(23);
|
||||
const receiverCallbackData = '0x0102030405';
|
||||
// check balances before transfer
|
||||
const expectedInitialBalances = [
|
||||
// from
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE,
|
||||
// to
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE,
|
||||
];
|
||||
await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedInitialBalances);
|
||||
// encode erc1155 asset data
|
||||
const erc1155AssetData = assetDataUtils.encodeERC1155AssetData(
|
||||
erc1155Contract.address,
|
||||
tokensToTransfer,
|
||||
valuesToTransfer,
|
||||
receiverCallbackData,
|
||||
);
|
||||
// encode multi-asset data
|
||||
const multiAssetAmount = new BigNumber(5);
|
||||
const amounts = [valueMultiplier];
|
||||
const nestedAssetData = [erc1155AssetData];
|
||||
const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
|
||||
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
|
||||
assetData,
|
||||
fromAddress,
|
||||
toAddress,
|
||||
multiAssetAmount,
|
||||
);
|
||||
// execute transfer
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await web3Wrapper.sendTransactionAsync({
|
||||
to: multiAssetProxy.address,
|
||||
data,
|
||||
from: authorized,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
// check balances
|
||||
const totalValueTransferred = valuesToTransfer[0].times(valueMultiplier).times(multiAssetAmount);
|
||||
const expectedFinalBalances = [
|
||||
// from
|
||||
expectedInitialBalances[0].minus(totalValueTransferred),
|
||||
// to
|
||||
expectedInitialBalances[1].plus(totalValueTransferred),
|
||||
];
|
||||
await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedFinalBalances);
|
||||
});
|
||||
it('should successfully transfer multiple fungible tokens of the same ERC1155 contract', async () => {
|
||||
// setup test parameters
|
||||
const tokenHolders = [fromAddress, toAddress];
|
||||
const tokensToTransfer = erc1155FungibleTokens.slice(0, 3);
|
||||
const valuesToTransfer = [new BigNumber(25), new BigNumber(35), new BigNumber(45)];
|
||||
const valueMultiplier = new BigNumber(23);
|
||||
const receiverCallbackData = '0x0102030405';
|
||||
// check balances before transfer
|
||||
const expectedInitialBalances = [
|
||||
// from
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE,
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE,
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE,
|
||||
// to
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE,
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE,
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE,
|
||||
];
|
||||
await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedInitialBalances);
|
||||
// encode erc1155 asset data
|
||||
const erc1155AssetData = assetDataUtils.encodeERC1155AssetData(
|
||||
erc1155Contract.address,
|
||||
tokensToTransfer,
|
||||
valuesToTransfer,
|
||||
receiverCallbackData,
|
||||
);
|
||||
// encode multi-asset data
|
||||
const multiAssetAmount = new BigNumber(5);
|
||||
const amounts = [valueMultiplier];
|
||||
const nestedAssetData = [erc1155AssetData];
|
||||
const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
|
||||
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
|
||||
assetData,
|
||||
fromAddress,
|
||||
toAddress,
|
||||
multiAssetAmount,
|
||||
);
|
||||
// execute transfer
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await web3Wrapper.sendTransactionAsync({
|
||||
to: multiAssetProxy.address,
|
||||
data,
|
||||
from: authorized,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
// check balances
|
||||
const totalValuesTransferred = _.map(valuesToTransfer, (value: BigNumber) => {
|
||||
return value.times(valueMultiplier).times(multiAssetAmount);
|
||||
});
|
||||
const expectedFinalBalances = [
|
||||
// from
|
||||
expectedInitialBalances[0].minus(totalValuesTransferred[0]),
|
||||
expectedInitialBalances[1].minus(totalValuesTransferred[1]),
|
||||
expectedInitialBalances[2].minus(totalValuesTransferred[2]),
|
||||
// to
|
||||
expectedInitialBalances[3].plus(totalValuesTransferred[0]),
|
||||
expectedInitialBalances[4].plus(totalValuesTransferred[1]),
|
||||
expectedInitialBalances[5].plus(totalValuesTransferred[2]),
|
||||
];
|
||||
await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedFinalBalances);
|
||||
});
|
||||
it('should successfully transfer multiple fungible/non-fungible tokens of the same ERC1155 contract', async () => {
|
||||
// setup test parameters
|
||||
const tokenHolders = [fromAddress, toAddress];
|
||||
const fungibleTokensToTransfer = erc1155FungibleTokens.slice(0, 1);
|
||||
const nonFungibleTokensToTransfer = erc1155NonFungibleTokensOwnedBySpender.slice(0, 1);
|
||||
const tokensToTransfer = fungibleTokensToTransfer.concat(nonFungibleTokensToTransfer);
|
||||
const valuesToTransfer = [new BigNumber(25), new BigNumber(1)];
|
||||
const valueMultiplier = new BigNumber(1);
|
||||
const receiverCallbackData = '0x0102030405';
|
||||
// check balances before transfer
|
||||
const nftOwnerBalance = new BigNumber(1);
|
||||
const nftNotOwnerBalance = new BigNumber(0);
|
||||
const expectedInitialBalances = [
|
||||
// from
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE,
|
||||
nftOwnerBalance,
|
||||
// to
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE,
|
||||
nftNotOwnerBalance,
|
||||
];
|
||||
await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedInitialBalances);
|
||||
// encode erc1155 asset data
|
||||
const erc1155AssetData = assetDataUtils.encodeERC1155AssetData(
|
||||
erc1155Contract.address,
|
||||
tokensToTransfer,
|
||||
valuesToTransfer,
|
||||
receiverCallbackData,
|
||||
);
|
||||
// encode multi-asset data
|
||||
const multiAssetAmount = new BigNumber(1);
|
||||
const amounts = [valueMultiplier];
|
||||
const nestedAssetData = [erc1155AssetData];
|
||||
const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
|
||||
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
|
||||
assetData,
|
||||
fromAddress,
|
||||
toAddress,
|
||||
multiAssetAmount,
|
||||
);
|
||||
// execute transfer
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await web3Wrapper.sendTransactionAsync({
|
||||
to: multiAssetProxy.address,
|
||||
data,
|
||||
from: authorized,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
// check balances
|
||||
const totalValuesTransferred = _.map(valuesToTransfer, (value: BigNumber) => {
|
||||
return value.times(valueMultiplier).times(multiAssetAmount);
|
||||
});
|
||||
const expectedFinalBalances = [
|
||||
// from
|
||||
expectedInitialBalances[0].minus(totalValuesTransferred[0]),
|
||||
expectedInitialBalances[1].minus(totalValuesTransferred[1]),
|
||||
// to
|
||||
expectedInitialBalances[2].plus(totalValuesTransferred[0]),
|
||||
expectedInitialBalances[3].plus(totalValuesTransferred[1]),
|
||||
];
|
||||
await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedFinalBalances);
|
||||
});
|
||||
it('should successfully transfer multiple different ERC1155 tokens', async () => {
|
||||
// setup test parameters
|
||||
const tokenHolders = [fromAddress, toAddress];
|
||||
const tokensToTransfer = erc1155FungibleTokens.slice(0, 1);
|
||||
const valuesToTransfer = [new BigNumber(25)];
|
||||
const valueMultiplier = new BigNumber(23);
|
||||
const receiverCallbackData = '0x0102030405';
|
||||
// check balances before transfer
|
||||
const expectedInitialBalances = [
|
||||
// from
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE,
|
||||
// to
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE,
|
||||
];
|
||||
await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedInitialBalances);
|
||||
await erc1155Wrapper2.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedInitialBalances);
|
||||
// encode erc1155 asset data
|
||||
const erc1155AssetData1 = assetDataUtils.encodeERC1155AssetData(
|
||||
erc1155Contract.address,
|
||||
tokensToTransfer,
|
||||
valuesToTransfer,
|
||||
receiverCallbackData,
|
||||
);
|
||||
const erc1155AssetData2 = assetDataUtils.encodeERC1155AssetData(
|
||||
erc1155Contract2.address,
|
||||
tokensToTransfer,
|
||||
valuesToTransfer,
|
||||
receiverCallbackData,
|
||||
);
|
||||
// encode multi-asset data
|
||||
const multiAssetAmount = new BigNumber(5);
|
||||
const amounts = [valueMultiplier, valueMultiplier];
|
||||
const nestedAssetData = [erc1155AssetData1, erc1155AssetData2];
|
||||
const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
|
||||
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
|
||||
assetData,
|
||||
fromAddress,
|
||||
toAddress,
|
||||
multiAssetAmount,
|
||||
);
|
||||
// execute transfer
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await web3Wrapper.sendTransactionAsync({
|
||||
to: multiAssetProxy.address,
|
||||
data,
|
||||
from: authorized,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
// check balances
|
||||
const totalValueTransferred = valuesToTransfer[0].times(valueMultiplier).times(multiAssetAmount);
|
||||
const expectedFinalBalances = [
|
||||
// from
|
||||
expectedInitialBalances[0].minus(totalValueTransferred),
|
||||
// to
|
||||
expectedInitialBalances[1].plus(totalValueTransferred),
|
||||
];
|
||||
await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedFinalBalances);
|
||||
await erc1155Wrapper2.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedFinalBalances);
|
||||
});
|
||||
it('should successfully transfer a combination of ERC20, ERC721, and ERC1155 tokens', async () => {
|
||||
// setup test parameters
|
||||
const inputAmount = new BigNumber(1);
|
||||
const erc20Amount = new BigNumber(10);
|
||||
const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address);
|
||||
const erc721Amount = new BigNumber(1);
|
||||
const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId);
|
||||
const erc1155TokenHolders = [fromAddress, toAddress];
|
||||
const erc1155TokensToTransfer = erc1155FungibleTokens.slice(0, 1);
|
||||
const erc1155ValuesToTransfer = [new BigNumber(25)];
|
||||
const erc1155Amount = new BigNumber(23);
|
||||
const erc1155ReceiverCallbackData = '0x0102030405';
|
||||
const erc1155AssetData = assetDataUtils.encodeERC1155AssetData(
|
||||
erc1155Contract.address,
|
||||
erc1155TokensToTransfer,
|
||||
erc1155ValuesToTransfer,
|
||||
erc1155ReceiverCallbackData,
|
||||
);
|
||||
const amounts = [erc20Amount, erc721Amount, erc1155Amount];
|
||||
const nestedAssetData = [erc20AssetData, erc721AssetData, erc1155AssetData];
|
||||
const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData);
|
||||
const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData(
|
||||
assetData,
|
||||
fromAddress,
|
||||
toAddress,
|
||||
inputAmount,
|
||||
);
|
||||
// check balances before transfer
|
||||
const erc20Balances = await erc20Wrapper.getBalancesAsync();
|
||||
const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
|
||||
expect(ownerFromAsset).to.be.equal(fromAddress);
|
||||
const erc1155ExpectedInitialBalances = [
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE,
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE,
|
||||
];
|
||||
await erc1155Wrapper.assertBalancesAsync(
|
||||
erc1155TokenHolders,
|
||||
erc1155TokensToTransfer,
|
||||
erc1155ExpectedInitialBalances,
|
||||
);
|
||||
// execute transfer
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await web3Wrapper.sendTransactionAsync({
|
||||
to: multiAssetProxy.address,
|
||||
data,
|
||||
from: authorized,
|
||||
gas: 1000000,
|
||||
}),
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
// check balances after transfer
|
||||
const newBalances = await erc20Wrapper.getBalancesAsync();
|
||||
const totalAmount = inputAmount.times(erc20Amount);
|
||||
expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal(
|
||||
erc20Balances[fromAddress][erc20TokenA.address].minus(totalAmount),
|
||||
);
|
||||
expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal(
|
||||
erc20Balances[toAddress][erc20TokenA.address].plus(totalAmount),
|
||||
);
|
||||
const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId);
|
||||
expect(newOwnerFromAsset).to.be.equal(toAddress);
|
||||
const erc1155TotalValueTransferred = erc1155ValuesToTransfer[0].times(erc1155Amount).times(inputAmount);
|
||||
const expectedFinalBalances = [
|
||||
erc1155ExpectedInitialBalances[0].minus(erc1155TotalValueTransferred),
|
||||
erc1155ExpectedInitialBalances[1].plus(erc1155TotalValueTransferred),
|
||||
];
|
||||
await erc1155Wrapper.assertBalancesAsync(
|
||||
erc1155TokenHolders,
|
||||
erc1155TokensToTransfer,
|
||||
expectedFinalBalances,
|
||||
);
|
||||
});
|
||||
it('should successfully transfer a combination of ERC20 and ERC721 tokens', async () => {
|
||||
const inputAmount = new BigNumber(1);
|
||||
const erc20Amount = new BigNumber(10);
|
||||
|
250
contracts/asset-proxy/test/static_call_proxy.ts
Normal file
250
contracts/asset-proxy/test/static_call_proxy.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import {
|
||||
chaiSetup,
|
||||
constants,
|
||||
expectTransactionFailedAsync,
|
||||
expectTransactionFailedWithoutReasonAsync,
|
||||
provider,
|
||||
txDefaults,
|
||||
web3Wrapper,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { BlockchainLifecycle } from '@0x/dev-utils';
|
||||
import { assetDataUtils } from '@0x/order-utils';
|
||||
import { AssetProxyId, RevertReason } from '@0x/types';
|
||||
import { AbiEncoder, BigNumber } from '@0x/utils';
|
||||
import * as chai from 'chai';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
|
||||
import { artifacts, IAssetProxyContract, StaticCallProxyContract, TestStaticCallTargetContract } from '../src';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
describe('StaticCallProxy', () => {
|
||||
const amount = constants.ZERO_AMOUNT;
|
||||
let fromAddress: string;
|
||||
let toAddress: string;
|
||||
|
||||
let staticCallProxy: IAssetProxyContract;
|
||||
let staticCallTarget: TestStaticCallTargetContract;
|
||||
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
after(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
before(async () => {
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
[fromAddress, toAddress] = accounts.slice(0, 2);
|
||||
const staticCallProxyWithoutTransferFrom = await StaticCallProxyContract.deployFrom0xArtifactAsync(
|
||||
artifacts.StaticCallProxy,
|
||||
provider,
|
||||
txDefaults,
|
||||
);
|
||||
staticCallProxy = new IAssetProxyContract(staticCallProxyWithoutTransferFrom.address, provider, txDefaults);
|
||||
staticCallTarget = await TestStaticCallTargetContract.deployFrom0xArtifactAsync(
|
||||
artifacts.TestStaticCallTarget,
|
||||
provider,
|
||||
txDefaults,
|
||||
);
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
|
||||
describe('general', () => {
|
||||
it('should revert if undefined function is called', async () => {
|
||||
const undefinedSelector = '0x01020304';
|
||||
await expectTransactionFailedWithoutReasonAsync(
|
||||
web3Wrapper.sendTransactionAsync({
|
||||
from: fromAddress,
|
||||
to: staticCallProxy.address,
|
||||
value: constants.ZERO_AMOUNT,
|
||||
data: undefinedSelector,
|
||||
}),
|
||||
);
|
||||
});
|
||||
it('should have an id of 0xc339d10a', async () => {
|
||||
const proxyId = await staticCallProxy.getProxyId.callAsync();
|
||||
const expectedProxyId = AssetProxyId.StaticCall;
|
||||
expect(proxyId).to.equal(expectedProxyId);
|
||||
});
|
||||
});
|
||||
describe('transferFrom', () => {
|
||||
it('should revert if assetData lies outside the bounds of calldata', async () => {
|
||||
const staticCallData = staticCallTarget.noInputFunction.getABIEncodedTransactionData();
|
||||
const expectedResultHash = constants.KECCAK256_NULL;
|
||||
const assetData = assetDataUtils.encodeStaticCallAssetData(
|
||||
staticCallTarget.address,
|
||||
staticCallData,
|
||||
expectedResultHash,
|
||||
);
|
||||
const txData = staticCallProxy.transferFrom.getABIEncodedTransactionData(
|
||||
assetData,
|
||||
fromAddress,
|
||||
toAddress,
|
||||
amount,
|
||||
);
|
||||
const offsetToAssetData = '0000000000000000000000000000000000000000000000000000000000000080';
|
||||
const txDataEndBuffer = ethUtil.toBuffer((txData.length - 2) / 2 - 4);
|
||||
const paddedTxDataEndBuffer = ethUtil.setLengthLeft(txDataEndBuffer, 32);
|
||||
const invalidOffsetToAssetData = ethUtil.bufferToHex(paddedTxDataEndBuffer).slice(2);
|
||||
const newAssetData = '0000000000000000000000000000000000000000000000000000000000000304';
|
||||
const badTxData = `${txData.replace(offsetToAssetData, invalidOffsetToAssetData)}${newAssetData}`;
|
||||
await expectTransactionFailedAsync(
|
||||
web3Wrapper.sendTransactionAsync({
|
||||
to: staticCallProxy.address,
|
||||
from: fromAddress,
|
||||
data: badTxData,
|
||||
}),
|
||||
RevertReason.InvalidAssetDataEnd,
|
||||
);
|
||||
});
|
||||
it('should revert if the length of assetData, excluding the proxyId, is not a multiple of 32', async () => {
|
||||
const staticCallData = staticCallTarget.noInputFunction.getABIEncodedTransactionData();
|
||||
const expectedResultHash = constants.KECCAK256_NULL;
|
||||
const assetData = `${assetDataUtils.encodeStaticCallAssetData(
|
||||
staticCallTarget.address,
|
||||
staticCallData,
|
||||
expectedResultHash,
|
||||
)}01`;
|
||||
await expectTransactionFailedAsync(
|
||||
staticCallProxy.transferFrom.sendTransactionAsync(assetData, fromAddress, toAddress, amount),
|
||||
RevertReason.InvalidAssetDataLength,
|
||||
);
|
||||
});
|
||||
it('should revert if the length of assetData is less than 100 bytes', async () => {
|
||||
const staticCallData = constants.NULL_BYTES;
|
||||
const expectedResultHash = constants.KECCAK256_NULL;
|
||||
const assetData = assetDataUtils
|
||||
.encodeStaticCallAssetData(staticCallTarget.address, staticCallData, expectedResultHash)
|
||||
.slice(0, -128);
|
||||
const assetDataByteLen = (assetData.length - 2) / 2;
|
||||
expect((assetDataByteLen - 4) % 32).to.equal(0);
|
||||
await expectTransactionFailedAsync(
|
||||
staticCallProxy.transferFrom.sendTransactionAsync(assetData, fromAddress, toAddress, amount),
|
||||
RevertReason.InvalidAssetDataLength,
|
||||
);
|
||||
});
|
||||
it('should revert if the offset to `staticCallData` points to outside of assetData', async () => {
|
||||
const staticCallData = staticCallTarget.noInputFunction.getABIEncodedTransactionData();
|
||||
const expectedResultHash = constants.KECCAK256_NULL;
|
||||
const assetData = assetDataUtils.encodeStaticCallAssetData(
|
||||
staticCallTarget.address,
|
||||
staticCallData,
|
||||
expectedResultHash,
|
||||
);
|
||||
const offsetToStaticCallData = '0000000000000000000000000000000000000000000000000000000000000060';
|
||||
const assetDataEndBuffer = ethUtil.toBuffer((assetData.length - 2) / 2 - 4);
|
||||
const paddedAssetDataEndBuffer = ethUtil.setLengthLeft(assetDataEndBuffer, 32);
|
||||
const invalidOffsetToStaticCallData = ethUtil.bufferToHex(paddedAssetDataEndBuffer).slice(2);
|
||||
const newStaticCallData = '0000000000000000000000000000000000000000000000000000000000000304';
|
||||
const badAssetData = `${assetData.replace(
|
||||
offsetToStaticCallData,
|
||||
invalidOffsetToStaticCallData,
|
||||
)}${newStaticCallData}`;
|
||||
await expectTransactionFailedAsync(
|
||||
staticCallProxy.transferFrom.sendTransactionAsync(badAssetData, fromAddress, toAddress, amount),
|
||||
RevertReason.InvalidStaticCallDataOffset,
|
||||
);
|
||||
});
|
||||
it('should revert if the callTarget attempts to write to state', async () => {
|
||||
const staticCallData = staticCallTarget.updateState.getABIEncodedTransactionData();
|
||||
const expectedResultHash = constants.KECCAK256_NULL;
|
||||
const assetData = assetDataUtils.encodeStaticCallAssetData(
|
||||
staticCallTarget.address,
|
||||
staticCallData,
|
||||
expectedResultHash,
|
||||
);
|
||||
await expectTransactionFailedWithoutReasonAsync(
|
||||
staticCallProxy.transferFrom.sendTransactionAsync(assetData, fromAddress, toAddress, amount),
|
||||
);
|
||||
});
|
||||
it('should revert with data provided by the callTarget if the staticcall reverts', async () => {
|
||||
const staticCallData = staticCallTarget.assertEvenNumber.getABIEncodedTransactionData(new BigNumber(1));
|
||||
const expectedResultHash = constants.KECCAK256_NULL;
|
||||
const assetData = assetDataUtils.encodeStaticCallAssetData(
|
||||
staticCallTarget.address,
|
||||
staticCallData,
|
||||
expectedResultHash,
|
||||
);
|
||||
await expectTransactionFailedAsync(
|
||||
staticCallProxy.transferFrom.sendTransactionAsync(assetData, fromAddress, toAddress, amount),
|
||||
RevertReason.TargetNotEven,
|
||||
);
|
||||
});
|
||||
it('should revert if the hash of the output is different than expected expected', async () => {
|
||||
const staticCallData = staticCallTarget.isOddNumber.getABIEncodedTransactionData(new BigNumber(0));
|
||||
const trueAsBuffer = ethUtil.toBuffer('0x0000000000000000000000000000000000000000000000000000000000000001');
|
||||
const expectedResultHash = ethUtil.bufferToHex(ethUtil.sha3(trueAsBuffer));
|
||||
const assetData = assetDataUtils.encodeStaticCallAssetData(
|
||||
staticCallTarget.address,
|
||||
staticCallData,
|
||||
expectedResultHash,
|
||||
);
|
||||
await expectTransactionFailedAsync(
|
||||
staticCallProxy.transferFrom.sendTransactionAsync(assetData, fromAddress, toAddress, amount),
|
||||
RevertReason.UnexpectedStaticCallResult,
|
||||
);
|
||||
});
|
||||
it('should be successful if a function call with no inputs is successful', async () => {
|
||||
const staticCallData = staticCallTarget.noInputFunction.getABIEncodedTransactionData();
|
||||
const expectedResultHash = constants.KECCAK256_NULL;
|
||||
const assetData = assetDataUtils.encodeStaticCallAssetData(
|
||||
staticCallTarget.address,
|
||||
staticCallData,
|
||||
expectedResultHash,
|
||||
);
|
||||
await staticCallProxy.transferFrom.awaitTransactionSuccessAsync(assetData, fromAddress, toAddress, amount);
|
||||
});
|
||||
it('should be successful if a function call with one static input returns the correct value', async () => {
|
||||
const staticCallData = staticCallTarget.isOddNumber.getABIEncodedTransactionData(new BigNumber(1));
|
||||
const trueAsBuffer = ethUtil.toBuffer('0x0000000000000000000000000000000000000000000000000000000000000001');
|
||||
const expectedResultHash = ethUtil.bufferToHex(ethUtil.sha3(trueAsBuffer));
|
||||
const assetData = assetDataUtils.encodeStaticCallAssetData(
|
||||
staticCallTarget.address,
|
||||
staticCallData,
|
||||
expectedResultHash,
|
||||
);
|
||||
await staticCallProxy.transferFrom.awaitTransactionSuccessAsync(assetData, fromAddress, toAddress, amount);
|
||||
});
|
||||
it('should be successful if a function with one dynamic input is successful', async () => {
|
||||
const dynamicInput = '0x0102030405060708';
|
||||
const staticCallData = staticCallTarget.dynamicInputFunction.getABIEncodedTransactionData(dynamicInput);
|
||||
const expectedResultHash = constants.KECCAK256_NULL;
|
||||
const assetData = assetDataUtils.encodeStaticCallAssetData(
|
||||
staticCallTarget.address,
|
||||
staticCallData,
|
||||
expectedResultHash,
|
||||
);
|
||||
await staticCallProxy.transferFrom.awaitTransactionSuccessAsync(assetData, fromAddress, toAddress, amount);
|
||||
});
|
||||
it('should be successful if a function call returns a complex type', async () => {
|
||||
const a = new BigNumber(1);
|
||||
const b = new BigNumber(2);
|
||||
const staticCallData = staticCallTarget.returnComplexType.getABIEncodedTransactionData(a, b);
|
||||
const abiEncoder = new AbiEncoder.DynamicBytes({
|
||||
name: '',
|
||||
type: 'bytes',
|
||||
});
|
||||
const aHex = '0000000000000000000000000000000000000000000000000000000000000001';
|
||||
const bHex = '0000000000000000000000000000000000000000000000000000000000000002';
|
||||
const expectedResults = `${staticCallTarget.address}${aHex}${bHex}`;
|
||||
const offset = '0000000000000000000000000000000000000000000000000000000000000020';
|
||||
const encodedExpectedResultWithOffset = `0x${offset}${abiEncoder.encode(expectedResults).slice(2)}`;
|
||||
const expectedResultHash = ethUtil.bufferToHex(
|
||||
ethUtil.sha3(ethUtil.toBuffer(encodedExpectedResultWithOffset)),
|
||||
);
|
||||
const assetData = assetDataUtils.encodeStaticCallAssetData(
|
||||
staticCallTarget.address,
|
||||
staticCallData,
|
||||
expectedResultHash,
|
||||
);
|
||||
await staticCallProxy.transferFrom.awaitTransactionSuccessAsync(assetData, fromAddress, toAddress, amount);
|
||||
});
|
||||
});
|
||||
});
|
401
contracts/asset-proxy/test/utils/erc1155_proxy_wrapper.ts
Normal file
401
contracts/asset-proxy/test/utils/erc1155_proxy_wrapper.ts
Normal file
@@ -0,0 +1,401 @@
|
||||
import { artifacts as erc1155Artifacts, ERC1155MintableContract, Erc1155Wrapper } from '@0x/contracts-erc1155';
|
||||
import {
|
||||
constants,
|
||||
ERC1155FungibleHoldingsByOwner,
|
||||
ERC1155HoldingsByOwner,
|
||||
ERC1155NonFungibleHoldingsByOwner,
|
||||
LogDecoder,
|
||||
txDefaults,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils } from '@0x/order-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts, ERC1155ProxyContract, IAssetProxyContract } from '../../src';
|
||||
|
||||
export class ERC1155ProxyWrapper {
|
||||
private readonly _tokenOwnerAddresses: string[];
|
||||
private readonly _fungibleTokenIds: string[];
|
||||
private readonly _nonFungibleTokenIds: string[];
|
||||
private readonly _nfts: Array<{ id: BigNumber; tokenId: BigNumber }>;
|
||||
private readonly _contractOwnerAddress: string;
|
||||
private readonly _web3Wrapper: Web3Wrapper;
|
||||
private readonly _provider: Provider;
|
||||
private readonly _logDecoder: LogDecoder;
|
||||
private readonly _dummyTokenWrappers: Erc1155Wrapper[];
|
||||
private readonly _assetProxyInterface: IAssetProxyContract;
|
||||
private _proxyContract?: ERC1155ProxyContract;
|
||||
private _proxyIdIfExists?: string;
|
||||
private _initialTokenIdsByOwner: ERC1155HoldingsByOwner = { fungible: {}, nonFungible: {} };
|
||||
|
||||
constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
this._provider = provider;
|
||||
const allArtifacts = _.merge(artifacts, erc1155Artifacts);
|
||||
this._logDecoder = new LogDecoder(this._web3Wrapper, allArtifacts);
|
||||
this._dummyTokenWrappers = [];
|
||||
this._assetProxyInterface = new IAssetProxyContract(constants.NULL_ADDRESS, provider);
|
||||
this._tokenOwnerAddresses = tokenOwnerAddresses;
|
||||
this._contractOwnerAddress = contractOwnerAddress;
|
||||
this._fungibleTokenIds = [];
|
||||
this._nonFungibleTokenIds = [];
|
||||
this._nfts = [];
|
||||
}
|
||||
/**
|
||||
* @dev Deploys dummy ERC1155 contracts
|
||||
* @return An array of ERC1155 wrappers; one for each deployed contract.
|
||||
*/
|
||||
public async deployDummyContractsAsync(): Promise<Erc1155Wrapper[]> {
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
for (const i of _.times(constants.NUM_DUMMY_ERC1155_CONTRACTS_TO_DEPLOY)) {
|
||||
const erc1155Contract = await ERC1155MintableContract.deployFrom0xArtifactAsync(
|
||||
erc1155Artifacts.ERC1155Mintable,
|
||||
this._provider,
|
||||
txDefaults,
|
||||
);
|
||||
const erc1155Wrapper = new Erc1155Wrapper(erc1155Contract, this._provider, this._contractOwnerAddress);
|
||||
this._dummyTokenWrappers.push(erc1155Wrapper);
|
||||
}
|
||||
return this._dummyTokenWrappers;
|
||||
}
|
||||
/**
|
||||
* @dev Deploys the ERC1155 proxy
|
||||
* @return Deployed ERC1155 proxy contract instance
|
||||
*/
|
||||
public async deployProxyAsync(): Promise<ERC1155ProxyContract> {
|
||||
this._proxyContract = await ERC1155ProxyContract.deployFrom0xArtifactAsync(
|
||||
artifacts.ERC1155Proxy,
|
||||
this._provider,
|
||||
txDefaults,
|
||||
);
|
||||
this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync();
|
||||
return this._proxyContract;
|
||||
}
|
||||
/**
|
||||
* @dev Gets the ERC1155 proxy id
|
||||
*/
|
||||
public getProxyId(): string {
|
||||
this._validateProxyContractExistsOrThrow();
|
||||
return this._proxyIdIfExists as string;
|
||||
}
|
||||
/**
|
||||
* @dev generates abi-encoded tx data for transferring erc1155 fungible/non-fungible tokens.
|
||||
* @param from source address
|
||||
* @param to destination address
|
||||
* @param contractAddress address of erc155 contract
|
||||
* @param tokensToTransfer array of erc1155 tokens to transfer
|
||||
* @param valuesToTransfer array of corresponding values for each erc1155 token to transfer
|
||||
* @param valueMultiplier each value in `valuesToTransfer` is multiplied by this
|
||||
* @param receiverCallbackData callback data if `to` is a contract
|
||||
* @param authorizedSender sender of `transferFrom` transaction
|
||||
* @param extraData extra data to append to `transferFrom` transaction. Optional.
|
||||
* @return abi encoded tx data.
|
||||
*/
|
||||
public getTransferFromAbiEncodedTxData(
|
||||
from: string,
|
||||
to: string,
|
||||
contractAddress: string,
|
||||
tokensToTransfer: BigNumber[],
|
||||
valuesToTransfer: BigNumber[],
|
||||
valueMultiplier: BigNumber,
|
||||
receiverCallbackData: string,
|
||||
authorizedSender: string,
|
||||
assetData_?: string,
|
||||
): string {
|
||||
this._validateProxyContractExistsOrThrow();
|
||||
const assetData =
|
||||
assetData_ === undefined
|
||||
? assetDataUtils.encodeERC1155AssetData(
|
||||
contractAddress,
|
||||
tokensToTransfer,
|
||||
valuesToTransfer,
|
||||
receiverCallbackData,
|
||||
)
|
||||
: assetData_;
|
||||
const data = this._assetProxyInterface.transferFrom.getABIEncodedTransactionData(
|
||||
assetData,
|
||||
from,
|
||||
to,
|
||||
valueMultiplier,
|
||||
);
|
||||
return data;
|
||||
}
|
||||
/**
|
||||
* @dev transfers erc1155 fungible/non-fungible tokens.
|
||||
* @param txData: abi-encoded tx data
|
||||
* @param authorizedSender sender of `transferFrom` transaction
|
||||
*/
|
||||
public async transferFromRawAsync(
|
||||
txData: string,
|
||||
authorizedSender: string,
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const txHash = await this._web3Wrapper.sendTransactionAsync({
|
||||
to: (this._proxyContract as ERC1155ProxyContract).address,
|
||||
data: txData,
|
||||
from: authorizedSender,
|
||||
gas: 300000,
|
||||
});
|
||||
const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return txReceipt;
|
||||
}
|
||||
/**
|
||||
* @dev transfers erc1155 fungible/non-fungible tokens.
|
||||
* @param from source address
|
||||
* @param to destination address
|
||||
* @param contractAddress address of erc155 contract
|
||||
* @param tokensToTransfer array of erc1155 tokens to transfer
|
||||
* @param valuesToTransfer array of corresponding values for each erc1155 token to transfer
|
||||
* @param valueMultiplier each value in `valuesToTransfer` is multiplied by this
|
||||
* @param receiverCallbackData callback data if `to` is a contract
|
||||
* @param authorizedSender sender of `transferFrom` transaction
|
||||
* @param extraData extra data to append to `transferFrom` transaction. Optional.
|
||||
* @return tranasction hash.
|
||||
*/
|
||||
public async transferFromAsync(
|
||||
from: string,
|
||||
to: string,
|
||||
contractAddress: string,
|
||||
tokensToTransfer: BigNumber[],
|
||||
valuesToTransfer: BigNumber[],
|
||||
valueMultiplier: BigNumber,
|
||||
receiverCallbackData: string,
|
||||
authorizedSender: string,
|
||||
assetData_?: string,
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
this._validateProxyContractExistsOrThrow();
|
||||
const assetData =
|
||||
assetData_ === undefined
|
||||
? assetDataUtils.encodeERC1155AssetData(
|
||||
contractAddress,
|
||||
tokensToTransfer,
|
||||
valuesToTransfer,
|
||||
receiverCallbackData,
|
||||
)
|
||||
: assetData_;
|
||||
const data = this._assetProxyInterface.transferFrom.getABIEncodedTransactionData(
|
||||
assetData,
|
||||
from,
|
||||
to,
|
||||
valueMultiplier,
|
||||
);
|
||||
const txHash = await this._web3Wrapper.sendTransactionAsync({
|
||||
to: (this._proxyContract as ERC1155ProxyContract).address,
|
||||
data,
|
||||
from: authorizedSender,
|
||||
gas: 300000,
|
||||
});
|
||||
const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash);
|
||||
return txReceipt;
|
||||
}
|
||||
/**
|
||||
* @dev For each deployed ERC1155 contract, this function mints a set of fungible/non-fungible
|
||||
* tokens for each token owner address (`_tokenOwnerAddresses`).
|
||||
* @return Balances of each token owner, across all ERC1155 contracts and tokens.
|
||||
*/
|
||||
public async setBalancesAndAllowancesAsync(): Promise<ERC1155HoldingsByOwner> {
|
||||
this._validateDummyTokenContractsExistOrThrow();
|
||||
this._validateProxyContractExistsOrThrow();
|
||||
this._initialTokenIdsByOwner = {
|
||||
fungible: {},
|
||||
nonFungible: {},
|
||||
};
|
||||
const fungibleHoldingsByOwner: ERC1155FungibleHoldingsByOwner = {};
|
||||
const nonFungibleHoldingsByOwner: ERC1155NonFungibleHoldingsByOwner = {};
|
||||
// Set balances accordingly
|
||||
for (const dummyWrapper of this._dummyTokenWrappers) {
|
||||
const dummyAddress = dummyWrapper.getContract().address;
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
for (const i of _.times(constants.NUM_ERC1155_FUNGIBLE_TOKENS_MINT)) {
|
||||
// Create a fungible token
|
||||
const tokenId = await dummyWrapper.mintFungibleTokensAsync(
|
||||
this._tokenOwnerAddresses,
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE,
|
||||
);
|
||||
const tokenIdAsString = tokenId.toString();
|
||||
this._fungibleTokenIds.push(tokenIdAsString);
|
||||
// Mint tokens for each owner for this token
|
||||
for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
if (fungibleHoldingsByOwner[tokenOwnerAddress] === undefined) {
|
||||
fungibleHoldingsByOwner[tokenOwnerAddress] = {};
|
||||
}
|
||||
if (fungibleHoldingsByOwner[tokenOwnerAddress][dummyAddress] === undefined) {
|
||||
fungibleHoldingsByOwner[tokenOwnerAddress][dummyAddress] = {};
|
||||
}
|
||||
fungibleHoldingsByOwner[tokenOwnerAddress][dummyAddress][tokenIdAsString] =
|
||||
constants.INITIAL_ERC1155_FUNGIBLE_BALANCE;
|
||||
await dummyWrapper.setApprovalForAllAsync(
|
||||
tokenOwnerAddress,
|
||||
(this._proxyContract as ERC1155ProxyContract).address,
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Non-fungible tokens
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
for (const j of _.times(constants.NUM_ERC1155_NONFUNGIBLE_TOKENS_MINT)) {
|
||||
const [tokenId, nftIds] = await dummyWrapper.mintNonFungibleTokensAsync(this._tokenOwnerAddresses);
|
||||
const tokenIdAsString = tokenId.toString();
|
||||
this._nonFungibleTokenIds.push(tokenIdAsString);
|
||||
_.each(this._tokenOwnerAddresses, async (tokenOwnerAddress: string, i: number) => {
|
||||
if (nonFungibleHoldingsByOwner[tokenOwnerAddress] === undefined) {
|
||||
nonFungibleHoldingsByOwner[tokenOwnerAddress] = {};
|
||||
}
|
||||
if (nonFungibleHoldingsByOwner[tokenOwnerAddress][dummyAddress] === undefined) {
|
||||
nonFungibleHoldingsByOwner[tokenOwnerAddress][dummyAddress] = {};
|
||||
}
|
||||
if (nonFungibleHoldingsByOwner[tokenOwnerAddress][dummyAddress][tokenIdAsString] === undefined) {
|
||||
nonFungibleHoldingsByOwner[tokenOwnerAddress][dummyAddress][tokenIdAsString] = [];
|
||||
}
|
||||
this._nfts.push({ id: nftIds[i], tokenId });
|
||||
nonFungibleHoldingsByOwner[tokenOwnerAddress][dummyAddress][tokenIdAsString].push(nftIds[i]);
|
||||
await dummyWrapper.setApprovalForAllAsync(
|
||||
tokenOwnerAddress,
|
||||
(this._proxyContract as ERC1155ProxyContract).address,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
this._initialTokenIdsByOwner = {
|
||||
fungible: fungibleHoldingsByOwner,
|
||||
nonFungible: nonFungibleHoldingsByOwner,
|
||||
};
|
||||
return this._initialTokenIdsByOwner;
|
||||
}
|
||||
/**
|
||||
* @dev For each deployed ERC1155 contract, this function quieries the set of fungible/non-fungible
|
||||
* tokens for each token owner address (`_tokenOwnerAddresses`).
|
||||
* @return Balances of each token owner, across all ERC1155 contracts and tokens.
|
||||
*/
|
||||
public async getBalancesAsync(): Promise<ERC1155HoldingsByOwner> {
|
||||
this._validateDummyTokenContractsExistOrThrow();
|
||||
this._validateBalancesAndAllowancesSetOrThrow();
|
||||
const tokenHoldingsByOwner: ERC1155FungibleHoldingsByOwner = {};
|
||||
const nonFungibleHoldingsByOwner: ERC1155NonFungibleHoldingsByOwner = {};
|
||||
for (const dummyTokenWrapper of this._dummyTokenWrappers) {
|
||||
const tokenContract = dummyTokenWrapper.getContract();
|
||||
const tokenAddress = tokenContract.address;
|
||||
// Construct batch balance call
|
||||
const tokenOwners: string[] = [];
|
||||
const tokenIds: BigNumber[] = [];
|
||||
for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
|
||||
for (const tokenId of this._fungibleTokenIds) {
|
||||
tokenOwners.push(tokenOwnerAddress);
|
||||
tokenIds.push(new BigNumber(tokenId));
|
||||
}
|
||||
for (const nft of this._nfts) {
|
||||
tokenOwners.push(tokenOwnerAddress);
|
||||
tokenIds.push(nft.id);
|
||||
}
|
||||
}
|
||||
const balances = await dummyTokenWrapper.getBalancesAsync(tokenOwners, tokenIds);
|
||||
// Parse out balances into fungible / non-fungible token holdings
|
||||
let i = 0;
|
||||
for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
|
||||
// Fungible tokens
|
||||
for (const tokenId of this._fungibleTokenIds) {
|
||||
if (tokenHoldingsByOwner[tokenOwnerAddress] === undefined) {
|
||||
tokenHoldingsByOwner[tokenOwnerAddress] = {};
|
||||
}
|
||||
if (tokenHoldingsByOwner[tokenOwnerAddress][tokenAddress] === undefined) {
|
||||
tokenHoldingsByOwner[tokenOwnerAddress][tokenAddress] = {};
|
||||
}
|
||||
tokenHoldingsByOwner[tokenOwnerAddress][tokenAddress][tokenId] = balances[i++];
|
||||
}
|
||||
// Non-fungible tokens
|
||||
for (const nft of this._nfts) {
|
||||
if (nonFungibleHoldingsByOwner[tokenOwnerAddress] === undefined) {
|
||||
nonFungibleHoldingsByOwner[tokenOwnerAddress] = {};
|
||||
}
|
||||
if (nonFungibleHoldingsByOwner[tokenOwnerAddress][tokenAddress] === undefined) {
|
||||
nonFungibleHoldingsByOwner[tokenOwnerAddress][tokenAddress] = {};
|
||||
}
|
||||
if (
|
||||
nonFungibleHoldingsByOwner[tokenOwnerAddress][tokenAddress][nft.tokenId.toString()] ===
|
||||
undefined
|
||||
) {
|
||||
nonFungibleHoldingsByOwner[tokenOwnerAddress][tokenAddress][nft.tokenId.toString()] = [];
|
||||
}
|
||||
const isOwner = balances[i++];
|
||||
if (isOwner.isEqualTo(1)) {
|
||||
nonFungibleHoldingsByOwner[tokenOwnerAddress][tokenAddress][nft.tokenId.toString()].push(
|
||||
nft.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const holdingsByOwner = {
|
||||
fungible: tokenHoldingsByOwner,
|
||||
nonFungible: nonFungibleHoldingsByOwner,
|
||||
};
|
||||
return holdingsByOwner;
|
||||
}
|
||||
/**
|
||||
* @dev Checks if proxy is approved to transfer tokens on behalf of `userAddress`.
|
||||
* @param userAddress owner of ERC1155 tokens.
|
||||
* @param contractAddress address of ERC1155 contract.
|
||||
* @return True iff the proxy is approved for all. False otherwise.
|
||||
*/
|
||||
public async isProxyApprovedForAllAsync(userAddress: string, contractAddress: string): Promise<boolean> {
|
||||
this._validateProxyContractExistsOrThrow();
|
||||
const tokenContract = this._getContractFromAddress(contractAddress);
|
||||
const operator = (this._proxyContract as ERC1155ProxyContract).address;
|
||||
const didApproveAll = await tokenContract.isApprovedForAll.callAsync(userAddress, operator);
|
||||
return didApproveAll;
|
||||
}
|
||||
public getFungibleTokenIds(): BigNumber[] {
|
||||
const fungibleTokenIds = _.map(this._fungibleTokenIds, (tokenIdAsString: string) => {
|
||||
return new BigNumber(tokenIdAsString);
|
||||
});
|
||||
return fungibleTokenIds;
|
||||
}
|
||||
public getNonFungibleTokenIds(): BigNumber[] {
|
||||
const nonFungibleTokenIds = _.map(this._nonFungibleTokenIds, (tokenIdAsString: string) => {
|
||||
return new BigNumber(tokenIdAsString);
|
||||
});
|
||||
return nonFungibleTokenIds;
|
||||
}
|
||||
public getTokenOwnerAddresses(): string[] {
|
||||
return this._tokenOwnerAddresses;
|
||||
}
|
||||
public getContractWrapper(contractAddress: string): Erc1155Wrapper {
|
||||
const tokenWrapper = _.find(this._dummyTokenWrappers, (wrapper: Erc1155Wrapper) => {
|
||||
return wrapper.getContract().address === contractAddress;
|
||||
});
|
||||
if (tokenWrapper === undefined) {
|
||||
throw new Error(`Contract: ${contractAddress} was not deployed through ERC1155ProxyWrapper`);
|
||||
}
|
||||
return tokenWrapper;
|
||||
}
|
||||
private _getContractFromAddress(tokenAddress: string): ERC1155MintableContract {
|
||||
const tokenContractIfExists = _.find(this._dummyTokenWrappers, c => c.getContract().address === tokenAddress);
|
||||
if (tokenContractIfExists === undefined) {
|
||||
throw new Error(`Token: ${tokenAddress} was not deployed through ERC1155ProxyWrapper`);
|
||||
}
|
||||
return tokenContractIfExists.getContract();
|
||||
}
|
||||
private _validateDummyTokenContractsExistOrThrow(): void {
|
||||
if (this._dummyTokenWrappers === undefined) {
|
||||
throw new Error('Dummy ERC1155 tokens not yet deployed, please call "deployDummyTokensAsync"');
|
||||
}
|
||||
}
|
||||
private _validateProxyContractExistsOrThrow(): void {
|
||||
if (this._proxyContract === undefined) {
|
||||
throw new Error('ERC1155 proxy contract not yet deployed, please call "deployProxyAsync"');
|
||||
}
|
||||
}
|
||||
private _validateBalancesAndAllowancesSetOrThrow(): void {
|
||||
if (
|
||||
_.keys(this._initialTokenIdsByOwner.fungible).length === 0 ||
|
||||
_.keys(this._initialTokenIdsByOwner.nonFungible).length === 0
|
||||
) {
|
||||
throw new Error(
|
||||
'Dummy ERC1155 balances and allowances not yet set, please call "setBalancesAndAllowancesAsync"',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,16 +1,16 @@
|
||||
import { constants, ERC20BalancesByOwner, txDefaults, Web3ProviderEngine } from '@0x/contracts-test-utils';
|
||||
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { constants, ERC20BalancesByOwner, txDefaults } from '@0x/contracts-test-utils';
|
||||
import { assetDataUtils } from '@0x/order-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { ZeroExProvider } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts, DummyERC20TokenContract, ERC20ProxyContract } from '../../src';
|
||||
import { artifacts, ERC20ProxyContract } from '../../src';
|
||||
|
||||
export class ERC20Wrapper {
|
||||
private readonly _tokenOwnerAddresses: string[];
|
||||
private readonly _contractOwnerAddress: string;
|
||||
private readonly _web3Wrapper: Web3Wrapper;
|
||||
private readonly _provider: Web3ProviderEngine;
|
||||
private readonly _provider: ZeroExProvider;
|
||||
private readonly _dummyTokenContracts: DummyERC20TokenContract[];
|
||||
private _proxyContract?: ERC20ProxyContract;
|
||||
private _proxyIdIfExists?: string;
|
||||
@@ -21,9 +21,8 @@ export class ERC20Wrapper {
|
||||
* @param contractOwnerAddress Desired owner of the contract
|
||||
* Instance of ERC20Wrapper
|
||||
*/
|
||||
constructor(provider: Web3ProviderEngine, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
|
||||
constructor(provider: ZeroExProvider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
|
||||
this._dummyTokenContracts = [];
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
this._provider = provider;
|
||||
this._tokenOwnerAddresses = tokenOwnerAddresses;
|
||||
this._contractOwnerAddress = contractOwnerAddress;
|
||||
@@ -35,7 +34,7 @@ export class ERC20Wrapper {
|
||||
for (let i = 0; i < numberToDeploy; i++) {
|
||||
this._dummyTokenContracts.push(
|
||||
await DummyERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
artifacts.DummyERC20Token,
|
||||
erc20Artifacts.DummyERC20Token,
|
||||
this._provider,
|
||||
txDefaults,
|
||||
constants.DUMMY_TOKEN_NAME,
|
||||
@@ -65,20 +64,16 @@ export class ERC20Wrapper {
|
||||
this._validateProxyContractExistsOrThrow();
|
||||
for (const dummyTokenContract of this._dummyTokenContracts) {
|
||||
for (const tokenOwnerAddress of this._tokenOwnerAddresses) {
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await dummyTokenContract.setBalance.sendTransactionAsync(
|
||||
tokenOwnerAddress,
|
||||
constants.INITIAL_ERC20_BALANCE,
|
||||
{ from: this._contractOwnerAddress },
|
||||
),
|
||||
await dummyTokenContract.setBalance.awaitTransactionSuccessAsync(
|
||||
tokenOwnerAddress,
|
||||
constants.INITIAL_ERC20_BALANCE,
|
||||
{ from: this._contractOwnerAddress },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await dummyTokenContract.approve.sendTransactionAsync(
|
||||
(this._proxyContract as ERC20ProxyContract).address,
|
||||
constants.INITIAL_ERC20_ALLOWANCE,
|
||||
{ from: tokenOwnerAddress },
|
||||
),
|
||||
await dummyTokenContract.approve.awaitTransactionSuccessAsync(
|
||||
(this._proxyContract as ERC20ProxyContract).address,
|
||||
constants.INITIAL_ERC20_ALLOWANCE,
|
||||
{ from: tokenOwnerAddress },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
@@ -91,10 +86,10 @@ export class ERC20Wrapper {
|
||||
}
|
||||
public async setBalanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise<void> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(assetData);
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await tokenContract.setBalance.sendTransactionAsync(userAddress, amount, {
|
||||
from: this._contractOwnerAddress,
|
||||
}),
|
||||
await tokenContract.setBalance.awaitTransactionSuccessAsync(
|
||||
userAddress,
|
||||
amount,
|
||||
{ from: this._contractOwnerAddress },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
@@ -107,10 +102,10 @@ export class ERC20Wrapper {
|
||||
public async setAllowanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise<void> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(assetData);
|
||||
const proxyAddress = (this._proxyContract as ERC20ProxyContract).address;
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await tokenContract.approve.sendTransactionAsync(proxyAddress, amount, {
|
||||
from: userAddress,
|
||||
}),
|
||||
await tokenContract.approve.awaitTransactionSuccessAsync(
|
||||
proxyAddress,
|
||||
amount,
|
||||
{ from: userAddress },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
@@ -131,7 +126,7 @@ export class ERC20Wrapper {
|
||||
_.forEach(balances, (balance, balanceIndex) => {
|
||||
const tokenAddress = balanceInfo[balanceIndex].tokenAddress;
|
||||
const tokenOwnerAddress = balanceInfo[balanceIndex].tokenOwnerAddress;
|
||||
if (_.isUndefined(balancesByOwner[tokenOwnerAddress])) {
|
||||
if (balancesByOwner[tokenOwnerAddress] === undefined) {
|
||||
balancesByOwner[tokenOwnerAddress] = {};
|
||||
}
|
||||
const wrappedBalance = new BigNumber(balance);
|
||||
@@ -140,7 +135,7 @@ export class ERC20Wrapper {
|
||||
return balancesByOwner;
|
||||
}
|
||||
public addDummyTokenContract(dummy: DummyERC20TokenContract): void {
|
||||
if (!_.isUndefined(this._dummyTokenContracts)) {
|
||||
if (this._dummyTokenContracts !== undefined) {
|
||||
this._dummyTokenContracts.push(dummy);
|
||||
}
|
||||
}
|
||||
@@ -158,18 +153,18 @@ export class ERC20Wrapper {
|
||||
const erc20ProxyData = assetDataUtils.decodeERC20AssetData(assetData);
|
||||
const tokenAddress = erc20ProxyData.tokenAddress;
|
||||
const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress);
|
||||
if (_.isUndefined(tokenContractIfExists)) {
|
||||
if (tokenContractIfExists === undefined) {
|
||||
throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`);
|
||||
}
|
||||
return tokenContractIfExists;
|
||||
}
|
||||
private _validateDummyTokenContractsExistOrThrow(): void {
|
||||
if (_.isUndefined(this._dummyTokenContracts)) {
|
||||
if (this._dummyTokenContracts === undefined) {
|
||||
throw new Error('Dummy ERC20 tokens not yet deployed, please call "deployDummyTokensAsync"');
|
||||
}
|
||||
}
|
||||
private _validateProxyContractExistsOrThrow(): void {
|
||||
if (_.isUndefined(this._proxyContract)) {
|
||||
if (this._proxyContract === undefined) {
|
||||
throw new Error('ERC20 proxy contract not yet deployed, please call "deployProxyAsync"');
|
||||
}
|
||||
}
|
||||
|
@@ -1,22 +1,21 @@
|
||||
import { constants, ERC721TokenIdsByOwner, txDefaults, Web3ProviderEngine } from '@0x/contracts-test-utils';
|
||||
import { artifacts as erc721Artifacts, DummyERC721TokenContract } from '@0x/contracts-erc721';
|
||||
import { constants, ERC721TokenIdsByOwner, txDefaults } from '@0x/contracts-test-utils';
|
||||
import { generatePseudoRandomSalt } from '@0x/order-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { ZeroExProvider } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { artifacts, DummyERC721TokenContract, ERC721ProxyContract } from '../../src';
|
||||
import { artifacts, ERC721ProxyContract } from '../../src';
|
||||
|
||||
export class ERC721Wrapper {
|
||||
private readonly _tokenOwnerAddresses: string[];
|
||||
private readonly _contractOwnerAddress: string;
|
||||
private readonly _web3Wrapper: Web3Wrapper;
|
||||
private readonly _provider: Web3ProviderEngine;
|
||||
private readonly _provider: ZeroExProvider;
|
||||
private readonly _dummyTokenContracts: DummyERC721TokenContract[];
|
||||
private _proxyContract?: ERC721ProxyContract;
|
||||
private _proxyIdIfExists?: string;
|
||||
private _initialTokenIdsByOwner: ERC721TokenIdsByOwner = {};
|
||||
constructor(provider: Web3ProviderEngine, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
constructor(provider: ZeroExProvider, tokenOwnerAddresses: string[], contractOwnerAddress: string) {
|
||||
this._provider = provider;
|
||||
this._dummyTokenContracts = [];
|
||||
this._tokenOwnerAddresses = tokenOwnerAddresses;
|
||||
@@ -27,7 +26,7 @@ export class ERC721Wrapper {
|
||||
for (const i of _.times(constants.NUM_DUMMY_ERC721_TO_DEPLOY)) {
|
||||
this._dummyTokenContracts.push(
|
||||
await DummyERC721TokenContract.deployFrom0xArtifactAsync(
|
||||
artifacts.DummyERC721Token,
|
||||
erc721Artifacts.DummyERC721Token,
|
||||
this._provider,
|
||||
txDefaults,
|
||||
constants.DUMMY_TOKEN_NAME,
|
||||
@@ -60,12 +59,12 @@ export class ERC721Wrapper {
|
||||
for (const i of _.times(constants.NUM_ERC721_TOKENS_TO_MINT)) {
|
||||
const tokenId = generatePseudoRandomSalt();
|
||||
await this.mintAsync(dummyTokenContract.address, tokenId, tokenOwnerAddress);
|
||||
if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress])) {
|
||||
if (this._initialTokenIdsByOwner[tokenOwnerAddress] === undefined) {
|
||||
this._initialTokenIdsByOwner[tokenOwnerAddress] = {
|
||||
[dummyTokenContract.address]: [],
|
||||
};
|
||||
}
|
||||
if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address])) {
|
||||
if (this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address] === undefined) {
|
||||
this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address] = [];
|
||||
}
|
||||
this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address].push(tokenId);
|
||||
@@ -89,20 +88,20 @@ export class ERC721Wrapper {
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId);
|
||||
const proxyAddress = (this._proxyContract as ERC721ProxyContract).address;
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await tokenContract.setApprovalForAll.sendTransactionAsync(proxyAddress, isApproved, {
|
||||
from: tokenOwner,
|
||||
}),
|
||||
await tokenContract.setApprovalForAll.awaitTransactionSuccessAsync(
|
||||
proxyAddress,
|
||||
isApproved,
|
||||
{ from: tokenOwner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
public async approveAsync(to: string, tokenAddress: string, tokenId: BigNumber): Promise<void> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId);
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await tokenContract.approve.sendTransactionAsync(to, tokenId, {
|
||||
from: tokenOwner,
|
||||
}),
|
||||
await tokenContract.approve.awaitTransactionSuccessAsync(
|
||||
to,
|
||||
tokenId,
|
||||
{ from: tokenOwner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
@@ -113,28 +112,29 @@ export class ERC721Wrapper {
|
||||
userAddress: string,
|
||||
): Promise<void> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await tokenContract.transferFrom.sendTransactionAsync(currentOwner, userAddress, tokenId, {
|
||||
from: currentOwner,
|
||||
}),
|
||||
await tokenContract.transferFrom.awaitTransactionSuccessAsync(
|
||||
currentOwner,
|
||||
userAddress,
|
||||
tokenId,
|
||||
{ from: currentOwner },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
public async mintAsync(tokenAddress: string, tokenId: BigNumber, userAddress: string): Promise<void> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await tokenContract.mint.sendTransactionAsync(userAddress, tokenId, {
|
||||
from: this._contractOwnerAddress,
|
||||
}),
|
||||
await tokenContract.mint.awaitTransactionSuccessAsync(
|
||||
userAddress,
|
||||
tokenId,
|
||||
{ from: this._contractOwnerAddress },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
public async burnAsync(tokenAddress: string, tokenId: BigNumber, owner: string): Promise<void> {
|
||||
const tokenContract = this._getTokenContractFromAssetData(tokenAddress);
|
||||
await this._web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await tokenContract.burn.sendTransactionAsync(owner, tokenId, {
|
||||
from: this._contractOwnerAddress,
|
||||
}),
|
||||
await tokenContract.burn.awaitTransactionSuccessAsync(
|
||||
owner,
|
||||
tokenId,
|
||||
{ from: this._contractOwnerAddress },
|
||||
constants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
}
|
||||
@@ -187,12 +187,12 @@ export class ERC721Wrapper {
|
||||
_.forEach(tokenOwnerAddresses, (tokenOwnerAddress, ownerIndex) => {
|
||||
const tokenAddress = tokenInfo[ownerIndex].tokenAddress;
|
||||
const tokenId = tokenInfo[ownerIndex].tokenId;
|
||||
if (_.isUndefined(tokenIdsByOwner[tokenOwnerAddress])) {
|
||||
if (tokenIdsByOwner[tokenOwnerAddress] === undefined) {
|
||||
tokenIdsByOwner[tokenOwnerAddress] = {
|
||||
[tokenAddress]: [],
|
||||
};
|
||||
}
|
||||
if (_.isUndefined(tokenIdsByOwner[tokenOwnerAddress][tokenAddress])) {
|
||||
if (tokenIdsByOwner[tokenOwnerAddress][tokenAddress] === undefined) {
|
||||
tokenIdsByOwner[tokenOwnerAddress][tokenAddress] = [];
|
||||
}
|
||||
tokenIdsByOwner[tokenOwnerAddress][tokenAddress].push(tokenId);
|
||||
@@ -208,18 +208,18 @@ export class ERC721Wrapper {
|
||||
}
|
||||
private _getTokenContractFromAssetData(tokenAddress: string): DummyERC721TokenContract {
|
||||
const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress);
|
||||
if (_.isUndefined(tokenContractIfExists)) {
|
||||
if (tokenContractIfExists === undefined) {
|
||||
throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`);
|
||||
}
|
||||
return tokenContractIfExists;
|
||||
}
|
||||
private _validateDummyTokenContractsExistOrThrow(): void {
|
||||
if (_.isUndefined(this._dummyTokenContracts)) {
|
||||
if (this._dummyTokenContracts === undefined) {
|
||||
throw new Error('Dummy ERC721 tokens not yet deployed, please call "deployDummyTokensAsync"');
|
||||
}
|
||||
}
|
||||
private _validateProxyContractExistsOrThrow(): void {
|
||||
if (_.isUndefined(this._proxyContract)) {
|
||||
if (this._proxyContract === undefined) {
|
||||
throw new Error('ERC721 proxy contract not yet deployed, please call "deployProxyAsync"');
|
||||
}
|
||||
}
|
||||
|
@@ -1,2 +1,3 @@
|
||||
export * from './erc20_wrapper';
|
||||
export * from './erc721_wrapper';
|
||||
export * from './erc1155_proxy_wrapper';
|
||||
|
@@ -3,18 +3,16 @@
|
||||
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
|
||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
||||
"files": [
|
||||
"generated-artifacts/DummyERC20Token.json",
|
||||
"generated-artifacts/DummyERC721Receiver.json",
|
||||
"generated-artifacts/DummyERC721Token.json",
|
||||
"generated-artifacts/DummyMultipleReturnERC20Token.json",
|
||||
"generated-artifacts/DummyNoReturnERC20Token.json",
|
||||
"generated-artifacts/ERC1155Proxy.json",
|
||||
"generated-artifacts/ERC20Proxy.json",
|
||||
"generated-artifacts/ERC721Proxy.json",
|
||||
"generated-artifacts/IAssetData.json",
|
||||
"generated-artifacts/IAssetProxy.json",
|
||||
"generated-artifacts/IAuthorizable.json",
|
||||
"generated-artifacts/MixinAuthorizable.json",
|
||||
"generated-artifacts/MultiAssetProxy.json"
|
||||
"generated-artifacts/MultiAssetProxy.json",
|
||||
"generated-artifacts/StaticCallProxy.json",
|
||||
"generated-artifacts/TestStaticCallTarget.json"
|
||||
],
|
||||
"exclude": ["./deploy/solc/solc_bin"]
|
||||
}
|
||||
|
101
contracts/coordinator/CHANGELOG.json
Normal file
101
contracts/coordinator/CHANGELOG.json
Normal file
@@ -0,0 +1,101 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1563047529,
|
||||
"version": "2.0.6",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1563006338,
|
||||
"version": "2.0.5",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1558712885,
|
||||
"version": "2.0.4",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1557961111,
|
||||
"version": "2.0.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1557799313,
|
||||
"version": "2.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1557507213,
|
||||
"version": "2.0.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Make `decodeOrdersFromFillData`, `getCoordinatorApprovalHash`, and `getTransactionHash` public",
|
||||
"pr": 1729
|
||||
},
|
||||
{
|
||||
"note": "Make `assertValidTransactionOrdersApproval` internal",
|
||||
"pr": 1729
|
||||
}
|
||||
],
|
||||
"timestamp": 1554997931
|
||||
},
|
||||
{
|
||||
"version": "1.1.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Run Web3ProviderEngine without excess block polling",
|
||||
"pr": 1695
|
||||
}
|
||||
],
|
||||
"timestamp": 1553183790
|
||||
},
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Created Coordinator package"
|
||||
},
|
||||
{
|
||||
"note": "Use separate EIP712 domains for transactions and approvals",
|
||||
"pr": 1705
|
||||
},
|
||||
{
|
||||
"note": "Add `SignatureType.Invalid`",
|
||||
"pr": 1705
|
||||
},
|
||||
{
|
||||
"note": "Set `evmVersion` to `constantinople`",
|
||||
"pr": 1707
|
||||
}
|
||||
],
|
||||
"timestamp": 1553091633
|
||||
}
|
||||
]
|
46
contracts/coordinator/CHANGELOG.md
Normal file
46
contracts/coordinator/CHANGELOG.md
Normal file
@@ -0,0 +1,46 @@
|
||||
<!--
|
||||
changelogUtils.file is auto-generated using the monorepo-scripts package. Don't edit directly.
|
||||
Edit the package's CHANGELOG.json file only.
|
||||
-->
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v2.0.6 - _July 13, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.0.5 - _July 13, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.0.4 - _May 24, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.0.3 - _May 15, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.0.2 - _May 14, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.0.1 - _May 10, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.0.0 - _April 11, 2019_
|
||||
|
||||
* Make `decodeOrdersFromFillData`, `getCoordinatorApprovalHash`, and `getTransactionHash` public (#1729)
|
||||
* Make `assertValidTransactionOrdersApproval` internal (#1729)
|
||||
|
||||
## v1.1.0 - _March 21, 2019_
|
||||
|
||||
* Run Web3ProviderEngine without excess block polling (#1695)
|
||||
|
||||
## v1.0.0 - _March 20, 2019_
|
||||
|
||||
* Created Coordinator package
|
||||
* Use separate EIP712 domains for transactions and approvals (#1705)
|
||||
* Add `SignatureType.Invalid` (#1705)
|
||||
* Set `evmVersion` to `constantinople` (#1707)
|
1
contracts/coordinator/DEPLOYS.json
Normal file
1
contracts/coordinator/DEPLOYS.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
73
contracts/coordinator/README.md
Normal file
73
contracts/coordinator/README.md
Normal file
@@ -0,0 +1,73 @@
|
||||
## Coordinator
|
||||
|
||||
This package contains a contract that allows users to call arbitrary functions on the Exchange contract with permission from one or more Coordinators. Addresses of the deployed contracts can be found in the 0x [wiki](https://0xproject.com/wiki#Deployed-Addresses) or the [DEPLOYS](./DEPLOYS.json) file within this package.
|
||||
|
||||
## Installation
|
||||
|
||||
**Install**
|
||||
|
||||
```bash
|
||||
npm install @0x/contracts-coordinator --save
|
||||
```
|
||||
|
||||
## Bug bounty
|
||||
|
||||
A bug bounty for the 2.0.0 contracts is ongoing! Instructions can be found [here](https://0xproject.com/wiki#Bug-Bounty).
|
||||
|
||||
## 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.
|
||||
|
||||
For proposals regarding the 0x protocol's smart contract architecture, message format, or additional functionality, go to the [0x Improvement Proposals (ZEIPs)](https://github.com/0xProject/ZEIPs) repository and follow the contribution guidelines provided therein.
|
||||
|
||||
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
|
||||
|
||||
To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory:
|
||||
|
||||
```bash
|
||||
PKG=@0x/contracts-coordinator yarn build
|
||||
```
|
||||
|
||||
Or continuously rebuild on change:
|
||||
|
||||
```bash
|
||||
PKG=@0x/contracts-coordinator yarn watch
|
||||
```
|
||||
|
||||
### Clean
|
||||
|
||||
```bash
|
||||
yarn clean
|
||||
```
|
||||
|
||||
### Lint
|
||||
|
||||
```bash
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
|
||||
#### Testing options
|
||||
|
||||
Contracts testing options like coverage, profiling, revert traces or backing node choosing - are described [here](../TESTING.md).
|
26
contracts/coordinator/compiler.json
Normal file
26
contracts/coordinator/compiler.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"artifactsDir": "./generated-artifacts",
|
||||
"contractsDir": "./contracts",
|
||||
"useDockerisedSolc": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
|
||||
},
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"devdoc",
|
||||
"evm.bytecode.object",
|
||||
"evm.bytecode.sourceMap",
|
||||
"evm.deployedBytecode.object",
|
||||
"evm.deployedBytecode.sourceMap"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"contracts": ["src/Coordinator.sol", "src/registry/CoordinatorRegistry.sol"]
|
||||
}
|
39
contracts/coordinator/contracts/src/Coordinator.sol
Normal file
39
contracts/coordinator/contracts/src/Coordinator.sol
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma experimental "ABIEncoderV2";
|
||||
|
||||
import "./libs/LibConstants.sol";
|
||||
import "./MixinSignatureValidator.sol";
|
||||
import "./MixinCoordinatorApprovalVerifier.sol";
|
||||
import "./MixinCoordinatorCore.sol";
|
||||
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
contract Coordinator is
|
||||
LibConstants,
|
||||
MixinSignatureValidator,
|
||||
MixinCoordinatorApprovalVerifier,
|
||||
MixinCoordinatorCore
|
||||
{
|
||||
constructor (address _exchange)
|
||||
public
|
||||
LibConstants(_exchange)
|
||||
{}
|
||||
}
|
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma experimental "ABIEncoderV2";
|
||||
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibExchangeSelectors.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibAddressArray.sol";
|
||||
import "./libs/LibCoordinatorApproval.sol";
|
||||
import "./libs/LibZeroExTransaction.sol";
|
||||
import "./mixins/MSignatureValidator.sol";
|
||||
import "./mixins/MCoordinatorApprovalVerifier.sol";
|
||||
|
||||
|
||||
// solhint-disable avoid-tx-origin
|
||||
contract MixinCoordinatorApprovalVerifier is
|
||||
LibExchangeSelectors,
|
||||
LibCoordinatorApproval,
|
||||
LibZeroExTransaction,
|
||||
MSignatureValidator,
|
||||
MCoordinatorApprovalVerifier
|
||||
{
|
||||
using LibBytes for bytes;
|
||||
using LibAddressArray for address[];
|
||||
|
||||
/// @dev Validates that the 0x transaction has been approved by all of the feeRecipients
|
||||
/// that correspond to each order in the transaction's Exchange calldata.
|
||||
/// @param transaction 0x transaction containing salt, signerAddress, and data.
|
||||
/// @param txOrigin Required signer of Ethereum transaction calling this function.
|
||||
/// @param transactionSignature Proof that the transaction has been signed by the signer.
|
||||
/// @param approvalExpirationTimeSeconds Array of expiration times in seconds for which each corresponding approval signature expires.
|
||||
/// @param approvalSignatures Array of signatures that correspond to the feeRecipients of each order in the transaction's Exchange calldata.
|
||||
function assertValidCoordinatorApprovals(
|
||||
LibZeroExTransaction.ZeroExTransaction memory transaction,
|
||||
address txOrigin,
|
||||
bytes memory transactionSignature,
|
||||
uint256[] memory approvalExpirationTimeSeconds,
|
||||
bytes[] memory approvalSignatures
|
||||
)
|
||||
public
|
||||
view
|
||||
{
|
||||
// Get the orders from the the Exchange calldata in the 0x transaction
|
||||
LibOrder.Order[] memory orders = decodeOrdersFromFillData(transaction.data);
|
||||
|
||||
// No approval is required for non-fill methods
|
||||
if (orders.length > 0) {
|
||||
// Revert if approval is invalid for transaction orders
|
||||
assertValidTransactionOrdersApproval(
|
||||
transaction,
|
||||
orders,
|
||||
txOrigin,
|
||||
transactionSignature,
|
||||
approvalExpirationTimeSeconds,
|
||||
approvalSignatures
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Decodes the orders from Exchange calldata representing any fill method.
|
||||
/// @param data Exchange calldata representing a fill method.
|
||||
/// @return The orders from the Exchange calldata.
|
||||
function decodeOrdersFromFillData(bytes memory data)
|
||||
public
|
||||
pure
|
||||
returns (LibOrder.Order[] memory orders)
|
||||
{
|
||||
bytes4 selector = data.readBytes4(0);
|
||||
if (
|
||||
selector == FILL_ORDER_SELECTOR ||
|
||||
selector == FILL_ORDER_NO_THROW_SELECTOR ||
|
||||
selector == FILL_OR_KILL_ORDER_SELECTOR
|
||||
) {
|
||||
// Decode single order
|
||||
(LibOrder.Order memory order) = abi.decode(
|
||||
data.slice(4, data.length),
|
||||
(LibOrder.Order)
|
||||
);
|
||||
orders = new LibOrder.Order[](1);
|
||||
orders[0] = order;
|
||||
} else if (
|
||||
selector == BATCH_FILL_ORDERS_SELECTOR ||
|
||||
selector == BATCH_FILL_ORDERS_NO_THROW_SELECTOR ||
|
||||
selector == BATCH_FILL_OR_KILL_ORDERS_SELECTOR ||
|
||||
selector == MARKET_BUY_ORDERS_SELECTOR ||
|
||||
selector == MARKET_BUY_ORDERS_NO_THROW_SELECTOR ||
|
||||
selector == MARKET_SELL_ORDERS_SELECTOR ||
|
||||
selector == MARKET_SELL_ORDERS_NO_THROW_SELECTOR
|
||||
) {
|
||||
// Decode all orders
|
||||
// solhint-disable indent
|
||||
(orders) = abi.decode(
|
||||
data.slice(4, data.length),
|
||||
(LibOrder.Order[])
|
||||
);
|
||||
} else if (selector == MATCH_ORDERS_SELECTOR) {
|
||||
// Decode left and right orders
|
||||
(LibOrder.Order memory leftOrder, LibOrder.Order memory rightOrder) = abi.decode(
|
||||
data.slice(4, data.length),
|
||||
(LibOrder.Order, LibOrder.Order)
|
||||
);
|
||||
|
||||
// Create array of orders
|
||||
orders = new LibOrder.Order[](2);
|
||||
orders[0] = leftOrder;
|
||||
orders[1] = rightOrder;
|
||||
}
|
||||
return orders;
|
||||
}
|
||||
|
||||
/// @dev Validates that the feeRecipients of a batch of order have approved a 0x transaction.
|
||||
/// @param transaction 0x transaction containing salt, signerAddress, and data.
|
||||
/// @param orders Array of order structs containing order specifications.
|
||||
/// @param txOrigin Required signer of Ethereum transaction calling this function.
|
||||
/// @param transactionSignature Proof that the transaction has been signed by the signer.
|
||||
/// @param approvalExpirationTimeSeconds Array of expiration times in seconds for which each corresponding approval signature expires.
|
||||
/// @param approvalSignatures Array of signatures that correspond to the feeRecipients of each order.
|
||||
function assertValidTransactionOrdersApproval(
|
||||
LibZeroExTransaction.ZeroExTransaction memory transaction,
|
||||
LibOrder.Order[] memory orders,
|
||||
address txOrigin,
|
||||
bytes memory transactionSignature,
|
||||
uint256[] memory approvalExpirationTimeSeconds,
|
||||
bytes[] memory approvalSignatures
|
||||
)
|
||||
internal
|
||||
view
|
||||
{
|
||||
// Verify that Ethereum tx signer is the same as the approved txOrigin
|
||||
require(
|
||||
tx.origin == txOrigin,
|
||||
"INVALID_ORIGIN"
|
||||
);
|
||||
|
||||
// Hash 0x transaction
|
||||
bytes32 transactionHash = getTransactionHash(transaction);
|
||||
|
||||
// Create empty list of approval signers
|
||||
address[] memory approvalSignerAddresses = new address[](0);
|
||||
|
||||
uint256 signaturesLength = approvalSignatures.length;
|
||||
for (uint256 i = 0; i != signaturesLength; i++) {
|
||||
// Create approval message
|
||||
uint256 currentApprovalExpirationTimeSeconds = approvalExpirationTimeSeconds[i];
|
||||
CoordinatorApproval memory approval = CoordinatorApproval({
|
||||
txOrigin: txOrigin,
|
||||
transactionHash: transactionHash,
|
||||
transactionSignature: transactionSignature,
|
||||
approvalExpirationTimeSeconds: currentApprovalExpirationTimeSeconds
|
||||
});
|
||||
|
||||
// Ensure approval has not expired
|
||||
require(
|
||||
// solhint-disable-next-line not-rely-on-time
|
||||
currentApprovalExpirationTimeSeconds > block.timestamp,
|
||||
"APPROVAL_EXPIRED"
|
||||
);
|
||||
|
||||
// Hash approval message and recover signer address
|
||||
bytes32 approvalHash = getCoordinatorApprovalHash(approval);
|
||||
address approvalSignerAddress = getSignerAddress(approvalHash, approvalSignatures[i]);
|
||||
|
||||
// Add approval signer to list of signers
|
||||
approvalSignerAddresses = approvalSignerAddresses.append(approvalSignerAddress);
|
||||
}
|
||||
|
||||
// Ethereum transaction signer gives implicit signature of approval
|
||||
approvalSignerAddresses = approvalSignerAddresses.append(tx.origin);
|
||||
|
||||
uint256 ordersLength = orders.length;
|
||||
for (uint256 i = 0; i != ordersLength; i++) {
|
||||
// Do not check approval if the order's senderAddress is null
|
||||
if (orders[i].senderAddress == address(0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure feeRecipient of order has approved this 0x transaction
|
||||
address approverAddress = orders[i].feeRecipientAddress;
|
||||
bool isOrderApproved = approvalSignerAddresses.contains(approverAddress);
|
||||
require(
|
||||
isOrderApproved,
|
||||
"INVALID_APPROVAL_SIGNATURE"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
65
contracts/coordinator/contracts/src/MixinCoordinatorCore.sol
Normal file
65
contracts/coordinator/contracts/src/MixinCoordinatorCore.sol
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma experimental "ABIEncoderV2";
|
||||
|
||||
import "./libs/LibZeroExTransaction.sol";
|
||||
import "./libs/LibConstants.sol";
|
||||
import "./mixins/MCoordinatorApprovalVerifier.sol";
|
||||
import "./interfaces/ICoordinatorCore.sol";
|
||||
|
||||
|
||||
contract MixinCoordinatorCore is
|
||||
LibConstants,
|
||||
MCoordinatorApprovalVerifier,
|
||||
ICoordinatorCore
|
||||
{
|
||||
/// @dev Executes a 0x transaction that has been signed by the feeRecipients that correspond to each order in the transaction's Exchange calldata.
|
||||
/// @param transaction 0x transaction containing salt, signerAddress, and data.
|
||||
/// @param txOrigin Required signer of Ethereum transaction calling this function.
|
||||
/// @param transactionSignature Proof that the transaction has been signed by the signer.
|
||||
/// @param approvalExpirationTimeSeconds Array of expiration times in seconds for which each corresponding approval signature expires.
|
||||
/// @param approvalSignatures Array of signatures that correspond to the feeRecipients of each order in the transaction's Exchange calldata.
|
||||
function executeTransaction(
|
||||
LibZeroExTransaction.ZeroExTransaction memory transaction,
|
||||
address txOrigin,
|
||||
bytes memory transactionSignature,
|
||||
uint256[] memory approvalExpirationTimeSeconds,
|
||||
bytes[] memory approvalSignatures
|
||||
)
|
||||
public
|
||||
{
|
||||
// Validate that the 0x transaction has been approves by each feeRecipient
|
||||
assertValidCoordinatorApprovals(
|
||||
transaction,
|
||||
txOrigin,
|
||||
transactionSignature,
|
||||
approvalExpirationTimeSeconds,
|
||||
approvalSignatures
|
||||
);
|
||||
|
||||
// Execute the transaction
|
||||
EXCHANGE.executeTransaction(
|
||||
transaction.salt,
|
||||
transaction.signerAddress,
|
||||
transaction.data,
|
||||
transactionSignature
|
||||
);
|
||||
}
|
||||
}
|
118
contracts/coordinator/contracts/src/MixinSignatureValidator.sol
Normal file
118
contracts/coordinator/contracts/src/MixinSignatureValidator.sol
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "./mixins/MSignatureValidator.sol";
|
||||
|
||||
|
||||
contract MixinSignatureValidator is
|
||||
MSignatureValidator
|
||||
{
|
||||
using LibBytes for bytes;
|
||||
|
||||
/// @dev Recovers the address of a signer given a hash and signature.
|
||||
/// @param hash Any 32 byte hash.
|
||||
/// @param signature Proof that the hash has been signed by signer.
|
||||
function getSignerAddress(bytes32 hash, bytes memory signature)
|
||||
public
|
||||
pure
|
||||
returns (address signerAddress)
|
||||
{
|
||||
require(
|
||||
signature.length > 0,
|
||||
"LENGTH_GREATER_THAN_0_REQUIRED"
|
||||
);
|
||||
|
||||
// Pop last byte off of signature byte array.
|
||||
uint8 signatureTypeRaw = uint8(signature.popLastByte());
|
||||
|
||||
// Ensure signature is supported
|
||||
require(
|
||||
signatureTypeRaw < uint8(SignatureType.NSignatureTypes),
|
||||
"SIGNATURE_UNSUPPORTED"
|
||||
);
|
||||
|
||||
SignatureType signatureType = SignatureType(signatureTypeRaw);
|
||||
|
||||
// Always illegal signature.
|
||||
// This is always an implicit option since a signer can create a
|
||||
// signature array with invalid type or length. We may as well make
|
||||
// it an explicit option. This aids testing and analysis. It is
|
||||
// also the initialization value for the enum type.
|
||||
if (signatureType == SignatureType.Illegal) {
|
||||
revert("SIGNATURE_ILLEGAL");
|
||||
|
||||
// Always invalid signature.
|
||||
// Like Illegal, this is always implicitly available and therefore
|
||||
// offered explicitly. It can be implicitly created by providing
|
||||
// a correctly formatted but incorrect signature.
|
||||
} else if (signatureType == SignatureType.Invalid) {
|
||||
require(
|
||||
signature.length == 0,
|
||||
"LENGTH_0_REQUIRED"
|
||||
);
|
||||
revert("SIGNATURE_INVALID");
|
||||
|
||||
// Signature using EIP712
|
||||
} else if (signatureType == SignatureType.EIP712) {
|
||||
require(
|
||||
signature.length == 65,
|
||||
"LENGTH_65_REQUIRED"
|
||||
);
|
||||
uint8 v = uint8(signature[0]);
|
||||
bytes32 r = signature.readBytes32(1);
|
||||
bytes32 s = signature.readBytes32(33);
|
||||
signerAddress = ecrecover(
|
||||
hash,
|
||||
v,
|
||||
r,
|
||||
s
|
||||
);
|
||||
return signerAddress;
|
||||
|
||||
// Signed using web3.eth_sign
|
||||
} else if (signatureType == SignatureType.EthSign) {
|
||||
require(
|
||||
signature.length == 65,
|
||||
"LENGTH_65_REQUIRED"
|
||||
);
|
||||
uint8 v = uint8(signature[0]);
|
||||
bytes32 r = signature.readBytes32(1);
|
||||
bytes32 s = signature.readBytes32(33);
|
||||
signerAddress = ecrecover(
|
||||
keccak256(abi.encodePacked(
|
||||
"\x19Ethereum Signed Message:\n32",
|
||||
hash
|
||||
)),
|
||||
v,
|
||||
r,
|
||||
s
|
||||
);
|
||||
return signerAddress;
|
||||
}
|
||||
|
||||
// Anything else is illegal (We do not return false because
|
||||
// the signature may actually be valid, just not in a format
|
||||
// that we currently support. In this case returning false
|
||||
// may lead the caller to incorrectly believe that the
|
||||
// signature was invalid.)
|
||||
revert("SIGNATURE_UNSUPPORTED");
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma experimental "ABIEncoderV2";
|
||||
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "../libs/LibZeroExTransaction.sol";
|
||||
|
||||
|
||||
contract ICoordinatorApprovalVerifier {
|
||||
|
||||
/// @dev Validates that the 0x transaction has been approved by all of the feeRecipients
|
||||
/// that correspond to each order in the transaction's Exchange calldata.
|
||||
/// @param transaction 0x transaction containing salt, signerAddress, and data.
|
||||
/// @param txOrigin Required signer of Ethereum transaction calling this function.
|
||||
/// @param transactionSignature Proof that the transaction has been signed by the signer.
|
||||
/// @param approvalExpirationTimeSeconds Array of expiration times in seconds for which each corresponding approval signature expires.
|
||||
/// @param approvalSignatures Array of signatures that correspond to the feeRecipients of each order in the transaction's Exchange calldata.
|
||||
function assertValidCoordinatorApprovals(
|
||||
LibZeroExTransaction.ZeroExTransaction memory transaction,
|
||||
address txOrigin,
|
||||
bytes memory transactionSignature,
|
||||
uint256[] memory approvalExpirationTimeSeconds,
|
||||
bytes[] memory approvalSignatures
|
||||
)
|
||||
public
|
||||
view;
|
||||
|
||||
/// @dev Decodes the orders from Exchange calldata representing any fill method.
|
||||
/// @param data Exchange calldata representing a fill method.
|
||||
/// @return The orders from the Exchange calldata.
|
||||
function decodeOrdersFromFillData(bytes memory data)
|
||||
public
|
||||
pure
|
||||
returns (LibOrder.Order[] memory orders);
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma experimental "ABIEncoderV2";
|
||||
|
||||
import "../libs/LibZeroExTransaction.sol";
|
||||
|
||||
|
||||
contract ICoordinatorCore {
|
||||
|
||||
/// @dev Executes a 0x transaction that has been signed by the feeRecipients that correspond to each order in the transaction's Exchange calldata.
|
||||
/// @param transaction 0x transaction containing salt, signerAddress, and data.
|
||||
/// @param txOrigin Required signer of Ethereum transaction calling this function.
|
||||
/// @param transactionSignature Proof that the transaction has been signed by the signer.
|
||||
/// @param approvalExpirationTimeSeconds Array of expiration times in seconds for which each corresponding approval signature expires.
|
||||
/// @param approvalSignatures Array of signatures that correspond to the feeRecipients of each order in the transaction's Exchange calldata.
|
||||
function executeTransaction(
|
||||
LibZeroExTransaction.ZeroExTransaction memory transaction,
|
||||
address txOrigin,
|
||||
bytes memory transactionSignature,
|
||||
uint256[] memory approvalExpirationTimeSeconds,
|
||||
bytes[] memory approvalSignatures
|
||||
)
|
||||
public;
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
|
||||
contract ISignatureValidator {
|
||||
|
||||
/// @dev Recovers the address of a signer given a hash and signature.
|
||||
/// @param hash Any 32 byte hash.
|
||||
/// @param signature Proof that the hash has been signed by signer.
|
||||
function getSignerAddress(bytes32 hash, bytes memory signature)
|
||||
public
|
||||
pure
|
||||
returns (address signerAddress);
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
|
||||
contract ITransactions {
|
||||
|
||||
/// @dev Executes an exchange method call in the context of signer.
|
||||
/// @param salt Arbitrary number to ensure uniqueness of transaction hash.
|
||||
/// @param signerAddress Address of transaction signer.
|
||||
/// @param data AbiV2 encoded calldata.
|
||||
/// @param signature Proof of signer transaction by signer.
|
||||
function executeTransaction(
|
||||
uint256 salt,
|
||||
address signerAddress,
|
||||
bytes calldata data,
|
||||
bytes calldata signature
|
||||
)
|
||||
external;
|
||||
}
|
34
contracts/coordinator/contracts/src/libs/LibConstants.sol
Normal file
34
contracts/coordinator/contracts/src/libs/LibConstants.sol
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
import "../interfaces/ITransactions.sol";
|
||||
|
||||
|
||||
contract LibConstants {
|
||||
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
ITransactions internal EXCHANGE;
|
||||
|
||||
constructor (address _exchange)
|
||||
public
|
||||
{
|
||||
EXCHANGE = ITransactions(_exchange);
|
||||
}
|
||||
}
|
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma experimental "ABIEncoderV2";
|
||||
|
||||
import "./LibEIP712Domain.sol";
|
||||
|
||||
|
||||
contract LibCoordinatorApproval is
|
||||
LibEIP712Domain
|
||||
{
|
||||
// Hash for the EIP712 Coordinator approval message
|
||||
// keccak256(abi.encodePacked(
|
||||
// "CoordinatorApproval(",
|
||||
// "address txOrigin,",
|
||||
// "bytes32 transactionHash,",
|
||||
// "bytes transactionSignature,",
|
||||
// "uint256 approvalExpirationTimeSeconds",
|
||||
// ")"
|
||||
// ));
|
||||
bytes32 constant internal EIP712_COORDINATOR_APPROVAL_SCHEMA_HASH = 0x2fbcdbaa76bc7589916958ae919dfbef04d23f6bbf26de6ff317b32c6cc01e05;
|
||||
|
||||
struct CoordinatorApproval {
|
||||
address txOrigin; // Required signer of Ethereum transaction that is submitting approval.
|
||||
bytes32 transactionHash; // EIP712 hash of the transaction.
|
||||
bytes transactionSignature; // Signature of the 0x transaction.
|
||||
uint256 approvalExpirationTimeSeconds; // Timestamp in seconds for which the approval expires.
|
||||
}
|
||||
|
||||
/// @dev Calculated the EIP712 hash of the Coordinator approval mesasage using the domain separator of this contract.
|
||||
/// @param approval Coordinator approval message containing the transaction hash, transaction signature, and expiration of the approval.
|
||||
/// @return EIP712 hash of the Coordinator approval message with the domain separator of this contract.
|
||||
function getCoordinatorApprovalHash(CoordinatorApproval memory approval)
|
||||
public
|
||||
view
|
||||
returns (bytes32 approvalHash)
|
||||
{
|
||||
approvalHash = hashEIP712CoordinatorMessage(hashCoordinatorApproval(approval));
|
||||
return approvalHash;
|
||||
}
|
||||
|
||||
/// @dev Calculated the EIP712 hash of the Coordinator approval mesasage with no domain separator.
|
||||
/// @param approval Coordinator approval message containing the transaction hash, transaction signature, and expiration of the approval.
|
||||
/// @return EIP712 hash of the Coordinator approval message with no domain separator.
|
||||
function hashCoordinatorApproval(CoordinatorApproval memory approval)
|
||||
internal
|
||||
pure
|
||||
returns (bytes32 result)
|
||||
{
|
||||
bytes32 schemaHash = EIP712_COORDINATOR_APPROVAL_SCHEMA_HASH;
|
||||
bytes memory transactionSignature = approval.transactionSignature;
|
||||
address txOrigin = approval.txOrigin;
|
||||
bytes32 transactionHash = approval.transactionHash;
|
||||
uint256 approvalExpirationTimeSeconds = approval.approvalExpirationTimeSeconds;
|
||||
|
||||
// Assembly for more efficiently computing:
|
||||
// keccak256(abi.encodePacked(
|
||||
// EIP712_COORDINATOR_APPROVAL_SCHEMA_HASH,
|
||||
// approval.txOrigin,
|
||||
// approval.transactionHash,
|
||||
// keccak256(approval.transactionSignature)
|
||||
// approval.approvalExpirationTimeSeconds,
|
||||
// ));
|
||||
|
||||
assembly {
|
||||
// Compute hash of transaction signature
|
||||
let transactionSignatureHash := keccak256(add(transactionSignature, 32), mload(transactionSignature))
|
||||
|
||||
// Load free memory pointer
|
||||
let memPtr := mload(64)
|
||||
|
||||
mstore(memPtr, schemaHash) // hash of schema
|
||||
mstore(add(memPtr, 32), txOrigin) // txOrigin
|
||||
mstore(add(memPtr, 64), transactionHash) // transactionHash
|
||||
mstore(add(memPtr, 96), transactionSignatureHash) // transactionSignatureHash
|
||||
mstore(add(memPtr, 128), approvalExpirationTimeSeconds) // approvalExpirationTimeSeconds
|
||||
// Compute hash
|
||||
result := keccak256(memPtr, 160)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
131
contracts/coordinator/contracts/src/libs/LibEIP712Domain.sol
Normal file
131
contracts/coordinator/contracts/src/libs/LibEIP712Domain.sol
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
import "./LibConstants.sol";
|
||||
|
||||
|
||||
contract LibEIP712Domain is
|
||||
LibConstants
|
||||
{
|
||||
|
||||
// EIP191 header for EIP712 prefix
|
||||
string constant internal EIP191_HEADER = "\x19\x01";
|
||||
|
||||
// EIP712 Domain Name value for the Coordinator
|
||||
string constant internal EIP712_COORDINATOR_DOMAIN_NAME = "0x Protocol Coordinator";
|
||||
|
||||
// EIP712 Domain Version value for the Coordinator
|
||||
string constant internal EIP712_COORDINATOR_DOMAIN_VERSION = "1.0.0";
|
||||
|
||||
// EIP712 Domain Name value for the Exchange
|
||||
string constant internal EIP712_EXCHANGE_DOMAIN_NAME = "0x Protocol";
|
||||
|
||||
// EIP712 Domain Version value for the Exchange
|
||||
string constant internal EIP712_EXCHANGE_DOMAIN_VERSION = "2";
|
||||
|
||||
// Hash of the EIP712 Domain Separator Schema
|
||||
bytes32 constant internal EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked(
|
||||
"EIP712Domain(",
|
||||
"string name,",
|
||||
"string version,",
|
||||
"address verifyingContract",
|
||||
")"
|
||||
));
|
||||
|
||||
// Hash of the EIP712 Domain Separator data for the Coordinator
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
bytes32 public EIP712_COORDINATOR_DOMAIN_HASH;
|
||||
|
||||
// Hash of the EIP712 Domain Separator data for the Exchange
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
bytes32 public EIP712_EXCHANGE_DOMAIN_HASH;
|
||||
|
||||
constructor ()
|
||||
public
|
||||
{
|
||||
EIP712_COORDINATOR_DOMAIN_HASH = keccak256(abi.encodePacked(
|
||||
EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH,
|
||||
keccak256(bytes(EIP712_COORDINATOR_DOMAIN_NAME)),
|
||||
keccak256(bytes(EIP712_COORDINATOR_DOMAIN_VERSION)),
|
||||
uint256(address(this))
|
||||
));
|
||||
|
||||
EIP712_EXCHANGE_DOMAIN_HASH = keccak256(abi.encodePacked(
|
||||
EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH,
|
||||
keccak256(bytes(EIP712_EXCHANGE_DOMAIN_NAME)),
|
||||
keccak256(bytes(EIP712_EXCHANGE_DOMAIN_VERSION)),
|
||||
uint256(address(EXCHANGE))
|
||||
));
|
||||
}
|
||||
|
||||
/// @dev Calculates EIP712 encoding for a hash struct in the EIP712 domain
|
||||
/// of this contract.
|
||||
/// @param hashStruct The EIP712 hash struct.
|
||||
/// @return EIP712 hash applied to this EIP712 Domain.
|
||||
function hashEIP712CoordinatorMessage(bytes32 hashStruct)
|
||||
internal
|
||||
view
|
||||
returns (bytes32 result)
|
||||
{
|
||||
return hashEIP712Message(EIP712_COORDINATOR_DOMAIN_HASH, hashStruct);
|
||||
}
|
||||
|
||||
/// @dev Calculates EIP712 encoding for a hash struct in the EIP712 domain
|
||||
/// of the Exchange contract.
|
||||
/// @param hashStruct The EIP712 hash struct.
|
||||
/// @return EIP712 hash applied to the Exchange EIP712 Domain.
|
||||
function hashEIP712ExchangeMessage(bytes32 hashStruct)
|
||||
internal
|
||||
view
|
||||
returns (bytes32 result)
|
||||
{
|
||||
return hashEIP712Message(EIP712_EXCHANGE_DOMAIN_HASH, hashStruct);
|
||||
}
|
||||
|
||||
/// @dev Calculates EIP712 encoding for a hash struct with a given domain hash.
|
||||
/// @param eip712DomainHash Hash of the domain domain separator data.
|
||||
/// @param hashStruct The EIP712 hash struct.
|
||||
/// @return EIP712 hash applied to the Exchange EIP712 Domain.
|
||||
function hashEIP712Message(bytes32 eip712DomainHash, bytes32 hashStruct)
|
||||
internal
|
||||
pure
|
||||
returns (bytes32 result)
|
||||
{
|
||||
// Assembly for more efficient computing:
|
||||
// keccak256(abi.encodePacked(
|
||||
// EIP191_HEADER,
|
||||
// EIP712_DOMAIN_HASH,
|
||||
// hashStruct
|
||||
// ));
|
||||
|
||||
assembly {
|
||||
// Load free memory pointer
|
||||
let memPtr := mload(64)
|
||||
|
||||
mstore(memPtr, 0x1901000000000000000000000000000000000000000000000000000000000000) // EIP191 header
|
||||
mstore(add(memPtr, 2), eip712DomainHash) // EIP712 domain hash
|
||||
mstore(add(memPtr, 34), hashStruct) // Hash of struct
|
||||
|
||||
// Compute hash
|
||||
result := keccak256(memPtr, 66)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma experimental "ABIEncoderV2";
|
||||
|
||||
import "./LibEIP712Domain.sol";
|
||||
|
||||
|
||||
contract LibZeroExTransaction is
|
||||
LibEIP712Domain
|
||||
{
|
||||
// Hash for the EIP712 0x transaction schema
|
||||
// keccak256(abi.encodePacked(
|
||||
// "ZeroExTransaction(",
|
||||
// "uint256 salt,",
|
||||
// "address signerAddress,",
|
||||
// "bytes data",
|
||||
// ")"
|
||||
// ));
|
||||
bytes32 constant internal EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = 0x213c6f636f3ea94e701c0adf9b2624aa45a6c694f9a292c094f9a81c24b5df4c;
|
||||
|
||||
struct ZeroExTransaction {
|
||||
uint256 salt; // Arbitrary number to ensure uniqueness of transaction hash.
|
||||
address signerAddress; // Address of transaction signer.
|
||||
bytes data; // AbiV2 encoded calldata.
|
||||
}
|
||||
|
||||
/// @dev Calculates the EIP712 hash of a 0x transaction using the domain separator of the Exchange contract.
|
||||
/// @param transaction 0x transaction containing salt, signerAddress, and data.
|
||||
/// @return EIP712 hash of the transaction with the domain separator of this contract.
|
||||
function getTransactionHash(ZeroExTransaction memory transaction)
|
||||
public
|
||||
view
|
||||
returns (bytes32 transactionHash)
|
||||
{
|
||||
// Hash the transaction with the domain separator of the Exchange contract.
|
||||
transactionHash = hashEIP712ExchangeMessage(hashZeroExTransaction(transaction));
|
||||
return transactionHash;
|
||||
}
|
||||
|
||||
/// @dev Calculates EIP712 hash of the 0x transaction with no domain separator.
|
||||
/// @param transaction 0x transaction containing salt, signerAddress, and data.
|
||||
/// @return EIP712 hash of the transaction with no domain separator.
|
||||
function hashZeroExTransaction(ZeroExTransaction memory transaction)
|
||||
internal
|
||||
pure
|
||||
returns (bytes32 result)
|
||||
{
|
||||
bytes32 schemaHash = EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH;
|
||||
bytes memory data = transaction.data;
|
||||
uint256 salt = transaction.salt;
|
||||
address signerAddress = transaction.signerAddress;
|
||||
|
||||
// Assembly for more efficiently computing:
|
||||
// keccak256(abi.encodePacked(
|
||||
// EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH,
|
||||
// transaction.salt,
|
||||
// uint256(transaction.signerAddress),
|
||||
// keccak256(transaction.data)
|
||||
// ));
|
||||
|
||||
assembly {
|
||||
// Compute hash of data
|
||||
let dataHash := keccak256(add(data, 32), mload(data))
|
||||
|
||||
// Load free memory pointer
|
||||
let memPtr := mload(64)
|
||||
|
||||
mstore(memPtr, schemaHash) // hash of schema
|
||||
mstore(add(memPtr, 32), salt) // salt
|
||||
mstore(add(memPtr, 64), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff)) // signerAddress
|
||||
mstore(add(memPtr, 96), dataHash) // hash of data
|
||||
|
||||
// Compute hash
|
||||
result := keccak256(memPtr, 128)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma experimental "ABIEncoderV2";
|
||||
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "../interfaces/ICoordinatorApprovalVerifier.sol";
|
||||
|
||||
|
||||
contract MCoordinatorApprovalVerifier is
|
||||
ICoordinatorApprovalVerifier
|
||||
{
|
||||
/// @dev Validates that the feeRecipients of a batch of order have approved a 0x transaction.
|
||||
/// @param transaction 0x transaction containing salt, signerAddress, and data.
|
||||
/// @param orders Array of order structs containing order specifications.
|
||||
/// @param txOrigin Required signer of Ethereum transaction calling this function.
|
||||
/// @param transactionSignature Proof that the transaction has been signed by the signer.
|
||||
/// @param approvalExpirationTimeSeconds Array of expiration times in seconds for which each corresponding approval signature expires.
|
||||
/// @param approvalSignatures Array of signatures that correspond to the feeRecipients of each order.
|
||||
function assertValidTransactionOrdersApproval(
|
||||
LibZeroExTransaction.ZeroExTransaction memory transaction,
|
||||
LibOrder.Order[] memory orders,
|
||||
address txOrigin,
|
||||
bytes memory transactionSignature,
|
||||
uint256[] memory approvalExpirationTimeSeconds,
|
||||
bytes[] memory approvalSignatures
|
||||
)
|
||||
internal
|
||||
view;
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
import "../interfaces/ISignatureValidator.sol";
|
||||
|
||||
|
||||
contract MSignatureValidator is
|
||||
ISignatureValidator
|
||||
{
|
||||
// Allowed signature types.
|
||||
enum SignatureType {
|
||||
Illegal, // 0x00, default value
|
||||
Invalid, // 0x01
|
||||
EIP712, // 0x02
|
||||
EthSign, // 0x03
|
||||
NSignatureTypes // 0x04, number of signature types. Always leave at end.
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
import "./MixinCoordinatorRegistryCore.sol";
|
||||
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
contract CoordinatorRegistry is
|
||||
MixinCoordinatorRegistryCore
|
||||
{
|
||||
constructor ()
|
||||
public
|
||||
MixinCoordinatorRegistryCore()
|
||||
{}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
import "./interfaces/ICoordinatorRegistryCore.sol";
|
||||
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
contract MixinCoordinatorRegistryCore is
|
||||
ICoordinatorRegistryCore
|
||||
{
|
||||
// mapping from `coordinatorOperator` -> `coordinatorEndpoint`
|
||||
mapping (address => string) internal coordinatorEndpoints;
|
||||
|
||||
/// @dev Called by a Coordinator operator to set the endpoint of their Coordinator.
|
||||
/// @param coordinatorEndpoint endpoint of the Coordinator.
|
||||
function setCoordinatorEndpoint(string calldata coordinatorEndpoint) external {
|
||||
address coordinatorOperator = msg.sender;
|
||||
coordinatorEndpoints[coordinatorOperator] = coordinatorEndpoint;
|
||||
emit CoordinatorEndpointSet(coordinatorOperator, coordinatorEndpoint);
|
||||
}
|
||||
|
||||
/// @dev Gets the endpoint for a Coordinator.
|
||||
/// @param coordinatorOperator operator of the Coordinator endpoint.
|
||||
function getCoordinatorEndpoint(address coordinatorOperator)
|
||||
external
|
||||
view
|
||||
returns (string memory coordinatorEndpoint)
|
||||
{
|
||||
return coordinatorEndpoints[coordinatorOperator];
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
contract ICoordinatorRegistryCore
|
||||
{
|
||||
/// @dev Emitted when a Coordinator endpoint is set.
|
||||
event CoordinatorEndpointSet(
|
||||
address coordinatorOperator,
|
||||
string coordinatorEndpoint
|
||||
);
|
||||
|
||||
/// @dev Called by a Coordinator operator to set the endpoint of their Coordinator.
|
||||
/// @param coordinatorEndpoint endpoint of the Coordinator.
|
||||
function setCoordinatorEndpoint(string calldata coordinatorEndpoint) external;
|
||||
|
||||
/// @dev Gets the endpoint for a Coordinator.
|
||||
/// @param coordinatorOperator operator of the Coordinator endpoint.
|
||||
function getCoordinatorEndpoint(address coordinatorOperator)
|
||||
external
|
||||
view
|
||||
returns (string memory coordinatorEndpoint);
|
||||
}
|
89
contracts/coordinator/package.json
Normal file
89
contracts/coordinator/package.json
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"name": "@0x/contracts-coordinator",
|
||||
"version": "2.0.6",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
"description": "Smart contract extensions of 0x protocol",
|
||||
"main": "lib/src/index.js",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yarn pre_build && tsc -b",
|
||||
"build:ci": "yarn build",
|
||||
"pre_build": "run-s compile generate_contract_wrappers",
|
||||
"test": "yarn run_mocha",
|
||||
"rebuild_and_test": "run-s build test",
|
||||
"test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov",
|
||||
"test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html",
|
||||
"test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha",
|
||||
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit",
|
||||
"compile": "sol-compiler",
|
||||
"watch": "sol-compiler -w",
|
||||
"clean": "shx rm -rf lib generated-artifacts generated-wrappers",
|
||||
"generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../../node_modules/@0x/abi-gen-templates/contract.handlebars --partials '../../node_modules/@0x/abi-gen-templates/partials/**/*.handlebars' --output generated-wrappers --backend ethers",
|
||||
"lint": "tslint --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts",
|
||||
"fix": "tslint --fix --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts",
|
||||
"coverage:report:text": "istanbul report text",
|
||||
"coverage:report:html": "istanbul report html && open coverage/index.html",
|
||||
"profiler:report:html": "istanbul report html && open coverage/index.html",
|
||||
"coverage:report:lcov": "istanbul report lcov",
|
||||
"test:circleci": "yarn test",
|
||||
"contracts:gen": "contracts-gen",
|
||||
"lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
|
||||
},
|
||||
"config": {
|
||||
"abis": "./generated-artifacts/@(Coordinator|CoordinatorRegistry).json",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/0xProject/0x-monorepo.git"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/0xProject/0x-monorepo/issues"
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/extensions/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^2.1.1",
|
||||
"@0x/contracts-gen": "^1.0.10",
|
||||
"@0x/contracts-test-utils": "^3.1.9",
|
||||
"@0x/dev-utils": "^2.2.4",
|
||||
"@0x/sol-compiler": "^3.1.9",
|
||||
"@0x/tslint-config": "^3.0.1",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/node": "*",
|
||||
"chai": "^4.0.1",
|
||||
"chai-as-promised": "^7.1.0",
|
||||
"chai-bignumber": "^3.0.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"make-promises-safe": "^1.1.0",
|
||||
"mocha": "^4.1.0",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"shx": "^0.2.2",
|
||||
"solhint": "^1.4.1",
|
||||
"tslint": "5.11.0",
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^5.1.1",
|
||||
"@0x/contracts-asset-proxy": "^2.2.1",
|
||||
"@0x/contracts-erc20": "^2.2.7",
|
||||
"@0x/contracts-exchange": "^2.1.7",
|
||||
"@0x/contracts-exchange-libs": "^3.0.1",
|
||||
"@0x/contracts-utils": "^3.1.8",
|
||||
"@0x/order-utils": "^8.2.1",
|
||||
"@0x/types": "^2.4.0",
|
||||
"@0x/typescript-typings": "^4.2.3",
|
||||
"@0x/utils": "^4.4.0",
|
||||
"@0x/web3-wrapper": "^6.0.7",
|
||||
"ethereum-types": "^2.1.3",
|
||||
"ethereumjs-util": "^5.1.1",
|
||||
"lodash": "^4.17.11"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
13
contracts/coordinator/src/artifacts.ts
Normal file
13
contracts/coordinator/src/artifacts.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as Coordinator from '../generated-artifacts/Coordinator.json';
|
||||
import * as CoordinatorRegistry from '../generated-artifacts/CoordinatorRegistry.json';
|
||||
export const artifacts = {
|
||||
Coordinator: Coordinator as ContractArtifact,
|
||||
CoordinatorRegistry: CoordinatorRegistry as ContractArtifact,
|
||||
};
|
3
contracts/coordinator/src/index.ts
Normal file
3
contracts/coordinator/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './artifacts';
|
||||
export * from './wrappers';
|
||||
export * from '../test/utils';
|
7
contracts/coordinator/src/wrappers.ts
Normal file
7
contracts/coordinator/src/wrappers.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../generated-wrappers/coordinator';
|
||||
export * from '../generated-wrappers/coordinator_registry';
|
521
contracts/coordinator/test/coordinator.ts
Normal file
521
contracts/coordinator/test/coordinator.ts
Normal file
@@ -0,0 +1,521 @@
|
||||
import { ERC20ProxyContract, ERC20Wrapper } from '@0x/contracts-asset-proxy';
|
||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import {
|
||||
artifacts as exchangeArtifacts,
|
||||
ExchangeCancelEventArgs,
|
||||
ExchangeCancelUpToEventArgs,
|
||||
ExchangeContract,
|
||||
ExchangeFillEventArgs,
|
||||
} from '@0x/contracts-exchange';
|
||||
import {
|
||||
chaiSetup,
|
||||
constants as devConstants,
|
||||
expectTransactionFailedAsync,
|
||||
getLatestBlockTimestampAsync,
|
||||
OrderFactory,
|
||||
provider,
|
||||
TransactionFactory,
|
||||
txDefaults,
|
||||
web3Wrapper,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { BlockchainLifecycle } from '@0x/dev-utils';
|
||||
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
|
||||
import { RevertReason, SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as chai from 'chai';
|
||||
import { LogWithDecodedArgs } from 'ethereum-types';
|
||||
|
||||
import { ApprovalFactory, artifacts, constants, CoordinatorContract, exchangeDataEncoder } from '../src';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
web3Wrapper.abiDecoder.addABI(exchangeArtifacts.Exchange.compilerOutput.abi);
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
describe('Coordinator tests', () => {
|
||||
let makerAddress: string;
|
||||
let owner: string;
|
||||
let takerAddress: string;
|
||||
let feeRecipientAddress: string;
|
||||
|
||||
let erc20Proxy: ERC20ProxyContract;
|
||||
let erc20TokenA: DummyERC20TokenContract;
|
||||
let erc20TokenB: DummyERC20TokenContract;
|
||||
let zrxToken: DummyERC20TokenContract;
|
||||
let coordinatorContract: CoordinatorContract;
|
||||
let exchange: ExchangeContract;
|
||||
|
||||
let erc20Wrapper: ERC20Wrapper;
|
||||
let orderFactory: OrderFactory;
|
||||
let takerTransactionFactory: TransactionFactory;
|
||||
let makerTransactionFactory: TransactionFactory;
|
||||
let approvalFactory: ApprovalFactory;
|
||||
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
after(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
before(async () => {
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = accounts.slice(0, 4));
|
||||
|
||||
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
|
||||
erc20Proxy = await erc20Wrapper.deployProxyAsync();
|
||||
const numDummyErc20ToDeploy = 3;
|
||||
[erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(
|
||||
numDummyErc20ToDeploy,
|
||||
devConstants.DUMMY_TOKEN_DECIMALS,
|
||||
);
|
||||
await erc20Wrapper.setBalancesAndAllowancesAsync();
|
||||
|
||||
exchange = await ExchangeContract.deployFrom0xArtifactAsync(
|
||||
exchangeArtifacts.Exchange,
|
||||
provider,
|
||||
txDefaults,
|
||||
assetDataUtils.encodeERC20AssetData(zrxToken.address),
|
||||
);
|
||||
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: owner }),
|
||||
devConstants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
|
||||
await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await exchange.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }),
|
||||
devConstants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
|
||||
coordinatorContract = await CoordinatorContract.deployFrom0xArtifactAsync(
|
||||
artifacts.Coordinator,
|
||||
provider,
|
||||
txDefaults,
|
||||
exchange.address,
|
||||
);
|
||||
|
||||
// Configure order defaults
|
||||
const defaultOrderParams = {
|
||||
...devConstants.STATIC_ORDER_PARAMS,
|
||||
exchangeAddress: exchange.address,
|
||||
senderAddress: coordinatorContract.address,
|
||||
makerAddress,
|
||||
feeRecipientAddress,
|
||||
makerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenA.address),
|
||||
takerAssetData: assetDataUtils.encodeERC20AssetData(erc20TokenB.address),
|
||||
};
|
||||
const makerPrivateKey = devConstants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
|
||||
const takerPrivateKey = devConstants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(takerAddress)];
|
||||
const feeRecipientPrivateKey = devConstants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(feeRecipientAddress)];
|
||||
orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams);
|
||||
makerTransactionFactory = new TransactionFactory(makerPrivateKey, exchange.address);
|
||||
takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchange.address);
|
||||
approvalFactory = new ApprovalFactory(feeRecipientPrivateKey, coordinatorContract.address);
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
|
||||
describe('single order fills', () => {
|
||||
for (const fnName of constants.SINGLE_FILL_FN_NAMES) {
|
||||
it(`${fnName} should fill the order with a signed approval`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = takerTransactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory.newSignedApproval(
|
||||
transaction,
|
||||
takerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const transactionReceipt = await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await coordinatorContract.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: takerAddress },
|
||||
),
|
||||
devConstants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
const fillLogs = transactionReceipt.logs.filter(
|
||||
log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
|
||||
);
|
||||
expect(fillLogs.length).to.eq(1);
|
||||
const fillLogArgs = (fillLogs[0] as LogWithDecodedArgs<ExchangeFillEventArgs>).args;
|
||||
expect(fillLogArgs.makerAddress).to.eq(makerAddress);
|
||||
expect(fillLogArgs.takerAddress).to.eq(takerAddress);
|
||||
expect(fillLogArgs.senderAddress).to.eq(coordinatorContract.address);
|
||||
expect(fillLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
|
||||
expect(fillLogArgs.makerAssetData).to.eq(orders[0].makerAssetData);
|
||||
expect(fillLogArgs.takerAssetData).to.eq(orders[0].takerAssetData);
|
||||
expect(fillLogArgs.makerAssetFilledAmount).to.bignumber.eq(orders[0].makerAssetAmount);
|
||||
expect(fillLogArgs.takerAssetFilledAmount).to.bignumber.eq(orders[0].takerAssetAmount);
|
||||
expect(fillLogArgs.makerFeePaid).to.bignumber.eq(orders[0].makerFee);
|
||||
expect(fillLogArgs.takerFeePaid).to.bignumber.eq(orders[0].takerFee);
|
||||
expect(fillLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(orders[0]));
|
||||
});
|
||||
it(`${fnName} should fill the order if called by approver`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = takerTransactionFactory.newSignedTransaction(data);
|
||||
const transactionReceipt = await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await coordinatorContract.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
feeRecipientAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{ from: feeRecipientAddress },
|
||||
),
|
||||
devConstants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
const fillLogs = transactionReceipt.logs.filter(
|
||||
log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
|
||||
);
|
||||
expect(fillLogs.length).to.eq(1);
|
||||
const fillLogArgs = (fillLogs[0] as LogWithDecodedArgs<ExchangeFillEventArgs>).args;
|
||||
expect(fillLogArgs.makerAddress).to.eq(makerAddress);
|
||||
expect(fillLogArgs.takerAddress).to.eq(takerAddress);
|
||||
expect(fillLogArgs.senderAddress).to.eq(coordinatorContract.address);
|
||||
expect(fillLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
|
||||
expect(fillLogArgs.makerAssetData).to.eq(orders[0].makerAssetData);
|
||||
expect(fillLogArgs.takerAssetData).to.eq(orders[0].takerAssetData);
|
||||
expect(fillLogArgs.makerAssetFilledAmount).to.bignumber.eq(orders[0].makerAssetAmount);
|
||||
expect(fillLogArgs.takerAssetFilledAmount).to.bignumber.eq(orders[0].takerAssetAmount);
|
||||
expect(fillLogArgs.makerFeePaid).to.bignumber.eq(orders[0].makerFee);
|
||||
expect(fillLogArgs.takerFeePaid).to.bignumber.eq(orders[0].takerFee);
|
||||
expect(fillLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(orders[0]));
|
||||
});
|
||||
it(`${fnName} should revert with no approval signature`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = takerTransactionFactory.newSignedTransaction(data);
|
||||
await expectTransactionFailedAsync(
|
||||
coordinatorContract.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{
|
||||
from: takerAddress,
|
||||
gas: devConstants.MAX_EXECUTE_TRANSACTION_GAS,
|
||||
},
|
||||
),
|
||||
RevertReason.InvalidApprovalSignature,
|
||||
);
|
||||
});
|
||||
it(`${fnName} should revert with an invalid approval signature`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = takerTransactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory.newSignedApproval(
|
||||
transaction,
|
||||
takerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const signature = `${approval.signature.slice(0, 4)}FFFFFFFF${approval.signature.slice(12)}`;
|
||||
await expectTransactionFailedAsync(
|
||||
coordinatorContract.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[signature],
|
||||
{ from: takerAddress },
|
||||
),
|
||||
RevertReason.InvalidApprovalSignature,
|
||||
);
|
||||
});
|
||||
it(`${fnName} should revert with an expired approval`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = takerTransactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).minus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory.newSignedApproval(
|
||||
transaction,
|
||||
takerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
await expectTransactionFailedAsync(
|
||||
coordinatorContract.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: takerAddress },
|
||||
),
|
||||
RevertReason.ApprovalExpired,
|
||||
);
|
||||
});
|
||||
it(`${fnName} should revert if not called by tx signer or approver`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = takerTransactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory.newSignedApproval(
|
||||
transaction,
|
||||
takerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
await expectTransactionFailedAsync(
|
||||
coordinatorContract.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: owner },
|
||||
),
|
||||
RevertReason.InvalidOrigin,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('batch order fills', () => {
|
||||
for (const fnName of [...constants.MARKET_FILL_FN_NAMES, ...constants.BATCH_FILL_FN_NAMES]) {
|
||||
it(`${fnName} should fill the orders with a signed approval`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = takerTransactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory.newSignedApproval(
|
||||
transaction,
|
||||
takerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const transactionReceipt = await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await coordinatorContract.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: takerAddress, gas: devConstants.MAX_EXECUTE_TRANSACTION_GAS },
|
||||
),
|
||||
devConstants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
const fillLogs = transactionReceipt.logs.filter(
|
||||
log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
|
||||
);
|
||||
expect(fillLogs.length).to.eq(orders.length);
|
||||
orders.forEach((order, index) => {
|
||||
const fillLogArgs = (fillLogs[index] as LogWithDecodedArgs<ExchangeFillEventArgs>).args;
|
||||
expect(fillLogArgs.makerAddress).to.eq(makerAddress);
|
||||
expect(fillLogArgs.takerAddress).to.eq(takerAddress);
|
||||
expect(fillLogArgs.senderAddress).to.eq(coordinatorContract.address);
|
||||
expect(fillLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
|
||||
expect(fillLogArgs.makerAssetData).to.eq(order.makerAssetData);
|
||||
expect(fillLogArgs.takerAssetData).to.eq(order.takerAssetData);
|
||||
expect(fillLogArgs.makerAssetFilledAmount).to.bignumber.eq(order.makerAssetAmount);
|
||||
expect(fillLogArgs.takerAssetFilledAmount).to.bignumber.eq(order.takerAssetAmount);
|
||||
expect(fillLogArgs.makerFeePaid).to.bignumber.eq(order.makerFee);
|
||||
expect(fillLogArgs.takerFeePaid).to.bignumber.eq(order.takerFee);
|
||||
expect(fillLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order));
|
||||
});
|
||||
});
|
||||
it(`${fnName} should fill the orders if called by approver`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = takerTransactionFactory.newSignedTransaction(data);
|
||||
const transactionReceipt = await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await coordinatorContract.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
feeRecipientAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{ from: feeRecipientAddress, gas: devConstants.MAX_EXECUTE_TRANSACTION_GAS },
|
||||
),
|
||||
devConstants.AWAIT_TRANSACTION_MINED_MS,
|
||||
);
|
||||
const fillLogs = transactionReceipt.logs.filter(
|
||||
log => (log as LogWithDecodedArgs<ExchangeFillEventArgs>).event === 'Fill',
|
||||
);
|
||||
expect(fillLogs.length).to.eq(orders.length);
|
||||
orders.forEach((order, index) => {
|
||||
const fillLogArgs = (fillLogs[index] as LogWithDecodedArgs<ExchangeFillEventArgs>).args;
|
||||
expect(fillLogArgs.makerAddress).to.eq(makerAddress);
|
||||
expect(fillLogArgs.takerAddress).to.eq(takerAddress);
|
||||
expect(fillLogArgs.senderAddress).to.eq(coordinatorContract.address);
|
||||
expect(fillLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
|
||||
expect(fillLogArgs.makerAssetData).to.eq(order.makerAssetData);
|
||||
expect(fillLogArgs.takerAssetData).to.eq(order.takerAssetData);
|
||||
expect(fillLogArgs.makerAssetFilledAmount).to.bignumber.eq(order.makerAssetAmount);
|
||||
expect(fillLogArgs.takerAssetFilledAmount).to.bignumber.eq(order.takerAssetAmount);
|
||||
expect(fillLogArgs.makerFeePaid).to.bignumber.eq(order.makerFee);
|
||||
expect(fillLogArgs.takerFeePaid).to.bignumber.eq(order.takerFee);
|
||||
expect(fillLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order));
|
||||
});
|
||||
});
|
||||
it(`${fnName} should revert with an invalid approval signature`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = takerTransactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory.newSignedApproval(
|
||||
transaction,
|
||||
takerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const signature = `${approval.signature.slice(0, 4)}FFFFFFFF${approval.signature.slice(12)}`;
|
||||
await expectTransactionFailedAsync(
|
||||
coordinatorContract.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[signature],
|
||||
{ from: takerAddress },
|
||||
),
|
||||
RevertReason.InvalidApprovalSignature,
|
||||
);
|
||||
});
|
||||
it(`${fnName} should revert with an expired approval`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = takerTransactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).minus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory.newSignedApproval(
|
||||
transaction,
|
||||
takerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
await expectTransactionFailedAsync(
|
||||
coordinatorContract.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: takerAddress },
|
||||
),
|
||||
RevertReason.ApprovalExpired,
|
||||
);
|
||||
});
|
||||
it(`${fnName} should revert if not called by tx signer or approver`, async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = takerTransactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory.newSignedApproval(
|
||||
transaction,
|
||||
takerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
await expectTransactionFailedAsync(
|
||||
coordinatorContract.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
takerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: owner },
|
||||
),
|
||||
RevertReason.InvalidOrigin,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('cancels', () => {
|
||||
it('cancelOrder call should be successful without an approval', async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(constants.CANCEL_ORDER, orders);
|
||||
const transaction = makerTransactionFactory.newSignedTransaction(data);
|
||||
const transactionReceipt = await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await coordinatorContract.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
makerAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{
|
||||
from: makerAddress,
|
||||
},
|
||||
),
|
||||
);
|
||||
const cancelLogs = transactionReceipt.logs.filter(
|
||||
log => (log as LogWithDecodedArgs<ExchangeCancelEventArgs>).event === 'Cancel',
|
||||
);
|
||||
expect(cancelLogs.length).to.eq(1);
|
||||
const cancelLogArgs = (cancelLogs[0] as LogWithDecodedArgs<ExchangeCancelEventArgs>).args;
|
||||
expect(cancelLogArgs.makerAddress).to.eq(makerAddress);
|
||||
expect(cancelLogArgs.senderAddress).to.eq(coordinatorContract.address);
|
||||
expect(cancelLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
|
||||
expect(cancelLogArgs.makerAssetData).to.eq(orders[0].makerAssetData);
|
||||
expect(cancelLogArgs.takerAssetData).to.eq(orders[0].takerAssetData);
|
||||
expect(cancelLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(orders[0]));
|
||||
});
|
||||
it('batchCancelOrders call should be successful without an approval', async () => {
|
||||
const orders = [await orderFactory.newSignedOrderAsync(), await orderFactory.newSignedOrderAsync()];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(constants.BATCH_CANCEL_ORDERS, orders);
|
||||
const transaction = makerTransactionFactory.newSignedTransaction(data);
|
||||
const transactionReceipt = await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await coordinatorContract.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
makerAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{
|
||||
from: makerAddress,
|
||||
},
|
||||
),
|
||||
);
|
||||
const cancelLogs = transactionReceipt.logs.filter(
|
||||
log => (log as LogWithDecodedArgs<ExchangeCancelEventArgs>).event === 'Cancel',
|
||||
);
|
||||
expect(cancelLogs.length).to.eq(orders.length);
|
||||
orders.forEach((order, index) => {
|
||||
const cancelLogArgs = (cancelLogs[index] as LogWithDecodedArgs<ExchangeCancelEventArgs>).args;
|
||||
expect(cancelLogArgs.makerAddress).to.eq(makerAddress);
|
||||
expect(cancelLogArgs.senderAddress).to.eq(coordinatorContract.address);
|
||||
expect(cancelLogArgs.feeRecipientAddress).to.eq(feeRecipientAddress);
|
||||
expect(cancelLogArgs.makerAssetData).to.eq(order.makerAssetData);
|
||||
expect(cancelLogArgs.takerAssetData).to.eq(order.takerAssetData);
|
||||
expect(cancelLogArgs.orderHash).to.eq(orderHashUtils.getOrderHashHex(order));
|
||||
});
|
||||
});
|
||||
it('cancelOrdersUpTo call should be successful without an approval', async () => {
|
||||
const orders: SignedOrder[] = [];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(constants.CANCEL_ORDERS_UP_TO, orders);
|
||||
const transaction = makerTransactionFactory.newSignedTransaction(data);
|
||||
const transactionReceipt = await web3Wrapper.awaitTransactionSuccessAsync(
|
||||
await coordinatorContract.executeTransaction.sendTransactionAsync(
|
||||
transaction,
|
||||
makerAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{
|
||||
from: makerAddress,
|
||||
},
|
||||
),
|
||||
);
|
||||
const cancelLogs = transactionReceipt.logs.filter(
|
||||
log => (log as LogWithDecodedArgs<ExchangeCancelUpToEventArgs>).event === 'CancelUpTo',
|
||||
);
|
||||
expect(cancelLogs.length).to.eq(1);
|
||||
const cancelLogArgs = (cancelLogs[0] as LogWithDecodedArgs<ExchangeCancelUpToEventArgs>).args;
|
||||
expect(cancelLogArgs.makerAddress).to.eq(makerAddress);
|
||||
expect(cancelLogArgs.senderAddress).to.eq(coordinatorContract.address);
|
||||
expect(cancelLogArgs.orderEpoch).to.bignumber.eq(new BigNumber(1));
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:disable:max-file-line-count
|
81
contracts/coordinator/test/coordinator_registry.ts
Normal file
81
contracts/coordinator/test/coordinator_registry.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { artifacts as exchangeArtifacts } from '@0x/contracts-exchange';
|
||||
import { chaiSetup, provider, web3Wrapper } from '@0x/contracts-test-utils';
|
||||
import { BlockchainLifecycle } from '@0x/dev-utils';
|
||||
import * as chai from 'chai';
|
||||
import { LogWithDecodedArgs } from 'ethereum-types';
|
||||
|
||||
import { CoordinatorRegistryCoordinatorEndpointSetEventArgs } from '../src';
|
||||
|
||||
import { CoordinatorRegistryWrapper } from './utils/coordinator_registry_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
web3Wrapper.abiDecoder.addABI(exchangeArtifacts.Exchange.compilerOutput.abi);
|
||||
// tslint:disable:no-unnecessary-type-assertion
|
||||
describe('Coordinator Registry tests', () => {
|
||||
let coordinatorOperator: string;
|
||||
const coordinatorEndpoint = 'http://sometec.0x.org';
|
||||
const nilCoordinatorEndpoint = '';
|
||||
let coordinatorRegistryWrapper: CoordinatorRegistryWrapper;
|
||||
// tests
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
after(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
before(async () => {
|
||||
// setup accounts (skip owner)
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
[, coordinatorOperator] = accounts;
|
||||
// deploy coordinator registry
|
||||
coordinatorRegistryWrapper = new CoordinatorRegistryWrapper(provider);
|
||||
await coordinatorRegistryWrapper.deployCoordinatorRegistryAsync();
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('core', () => {
|
||||
it('Should successfully set a Coordinator endpoint', async () => {
|
||||
await coordinatorRegistryWrapper.setCoordinatorEndpointAsync(coordinatorOperator, coordinatorEndpoint);
|
||||
const recordedCoordinatorEndpoint = await coordinatorRegistryWrapper.getCoordinatorEndpointAsync(
|
||||
coordinatorOperator,
|
||||
);
|
||||
expect(recordedCoordinatorEndpoint).to.be.equal(coordinatorEndpoint);
|
||||
});
|
||||
it('Should successfully unset a Coordinator endpoint', async () => {
|
||||
// set Coordinator endpoint
|
||||
await coordinatorRegistryWrapper.setCoordinatorEndpointAsync(coordinatorOperator, coordinatorEndpoint);
|
||||
let recordedCoordinatorEndpoint = await coordinatorRegistryWrapper.getCoordinatorEndpointAsync(
|
||||
coordinatorOperator,
|
||||
);
|
||||
expect(recordedCoordinatorEndpoint).to.be.equal(coordinatorEndpoint);
|
||||
// unset Coordinator endpoint
|
||||
await coordinatorRegistryWrapper.setCoordinatorEndpointAsync(coordinatorOperator, nilCoordinatorEndpoint);
|
||||
recordedCoordinatorEndpoint = await coordinatorRegistryWrapper.getCoordinatorEndpointAsync(
|
||||
coordinatorOperator,
|
||||
);
|
||||
expect(recordedCoordinatorEndpoint).to.be.equal(nilCoordinatorEndpoint);
|
||||
});
|
||||
it('Should emit an event when setting Coordinator endpoint', async () => {
|
||||
// set Coordinator endpoint
|
||||
const txReceipt = await coordinatorRegistryWrapper.setCoordinatorEndpointAsync(
|
||||
coordinatorOperator,
|
||||
coordinatorEndpoint,
|
||||
);
|
||||
const recordedCoordinatorEndpoint = await coordinatorRegistryWrapper.getCoordinatorEndpointAsync(
|
||||
coordinatorOperator,
|
||||
);
|
||||
expect(recordedCoordinatorEndpoint).to.be.equal(coordinatorEndpoint);
|
||||
// validate event
|
||||
expect(txReceipt.logs.length).to.be.equal(1);
|
||||
const log = txReceipt.logs[0] as LogWithDecodedArgs<CoordinatorRegistryCoordinatorEndpointSetEventArgs>;
|
||||
expect(log.args.coordinatorOperator).to.be.equal(coordinatorOperator);
|
||||
expect(log.args.coordinatorEndpoint).to.be.equal(coordinatorEndpoint);
|
||||
});
|
||||
});
|
||||
});
|
19
contracts/coordinator/test/global_hooks.ts
Normal file
19
contracts/coordinator/test/global_hooks.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { env, EnvVars } from '@0x/dev-utils';
|
||||
|
||||
import { coverage, profiler, provider } from '@0x/contracts-test-utils';
|
||||
import { providerUtils } from '@0x/utils';
|
||||
|
||||
before('start web3 provider', () => {
|
||||
providerUtils.startProviderEngine(provider);
|
||||
});
|
||||
after('generate coverage report', async () => {
|
||||
if (env.parseBoolean(EnvVars.SolidityCoverage)) {
|
||||
const coverageSubprovider = coverage.getCoverageSubproviderSingleton();
|
||||
await coverageSubprovider.writeCoverageAsync();
|
||||
}
|
||||
if (env.parseBoolean(EnvVars.SolidityProfiler)) {
|
||||
const profilerSubprovider = profiler.getProfilerSubproviderSingleton();
|
||||
await profilerSubprovider.writeProfilerOutputAsync();
|
||||
}
|
||||
provider.stop();
|
||||
});
|
79
contracts/coordinator/test/libs.ts
Normal file
79
contracts/coordinator/test/libs.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { addressUtils, chaiSetup, constants, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils';
|
||||
import { BlockchainLifecycle } from '@0x/dev-utils';
|
||||
import { transactionHashUtils } from '@0x/order-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as chai from 'chai';
|
||||
|
||||
import { artifacts, CoordinatorContract, hashUtils } from '../src';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
describe('Libs tests', () => {
|
||||
let coordinatorContract: CoordinatorContract;
|
||||
const exchangeAddress = addressUtils.generatePseudoRandomAddress();
|
||||
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
after(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
before(async () => {
|
||||
coordinatorContract = await CoordinatorContract.deployFrom0xArtifactAsync(
|
||||
artifacts.Coordinator,
|
||||
provider,
|
||||
txDefaults,
|
||||
exchangeAddress,
|
||||
);
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
|
||||
describe('getTransactionHash', () => {
|
||||
it('should return the correct transaction hash', async () => {
|
||||
const tx = {
|
||||
verifyingContractAddress: exchangeAddress,
|
||||
salt: new BigNumber(0),
|
||||
signerAddress: constants.NULL_ADDRESS,
|
||||
data: '0x1234',
|
||||
};
|
||||
const expectedTxHash = transactionHashUtils.getTransactionHashHex(tx);
|
||||
const txHash = await coordinatorContract.getTransactionHash.callAsync(tx);
|
||||
expect(expectedTxHash).to.eq(txHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getApprovalHash', () => {
|
||||
it('should return the correct approval hash', async () => {
|
||||
const signedTx = {
|
||||
verifyingContractAddress: exchangeAddress,
|
||||
salt: new BigNumber(0),
|
||||
signerAddress: constants.NULL_ADDRESS,
|
||||
data: '0x1234',
|
||||
signature: '0x5678',
|
||||
};
|
||||
const approvalExpirationTimeSeconds = new BigNumber(0);
|
||||
const txOrigin = constants.NULL_ADDRESS;
|
||||
const approval = {
|
||||
txOrigin,
|
||||
transactionHash: transactionHashUtils.getTransactionHashHex(signedTx),
|
||||
transactionSignature: signedTx.signature,
|
||||
approvalExpirationTimeSeconds,
|
||||
};
|
||||
const expectedApprovalHash = hashUtils.getApprovalHashHex(
|
||||
signedTx,
|
||||
coordinatorContract.address,
|
||||
txOrigin,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const approvalHash = await coordinatorContract.getCoordinatorApprovalHash.callAsync(approval);
|
||||
expect(expectedApprovalHash).to.eq(approvalHash);
|
||||
});
|
||||
});
|
||||
});
|
728
contracts/coordinator/test/mixins.ts
Normal file
728
contracts/coordinator/test/mixins.ts
Normal file
@@ -0,0 +1,728 @@
|
||||
import {
|
||||
addressUtils,
|
||||
chaiSetup,
|
||||
constants as devConstants,
|
||||
expectContractCallFailedAsync,
|
||||
getLatestBlockTimestampAsync,
|
||||
provider,
|
||||
TransactionFactory,
|
||||
txDefaults,
|
||||
web3Wrapper,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { BlockchainLifecycle } from '@0x/dev-utils';
|
||||
import { transactionHashUtils } from '@0x/order-utils';
|
||||
import { RevertReason, SignatureType, SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as chai from 'chai';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
|
||||
import { ApprovalFactory, artifacts, constants, CoordinatorContract, exchangeDataEncoder } from '../src';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
describe('Mixins tests', () => {
|
||||
let transactionSignerAddress: string;
|
||||
let approvalSignerAddress1: string;
|
||||
let approvalSignerAddress2: string;
|
||||
let mixins: CoordinatorContract;
|
||||
let transactionFactory: TransactionFactory;
|
||||
let approvalFactory1: ApprovalFactory;
|
||||
let approvalFactory2: ApprovalFactory;
|
||||
let defaultOrder: SignedOrder;
|
||||
const exchangeAddress = addressUtils.generatePseudoRandomAddress();
|
||||
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
after(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
before(async () => {
|
||||
mixins = await CoordinatorContract.deployFrom0xArtifactAsync(
|
||||
artifacts.Coordinator,
|
||||
provider,
|
||||
txDefaults,
|
||||
exchangeAddress,
|
||||
);
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
[transactionSignerAddress, approvalSignerAddress1, approvalSignerAddress2] = accounts.slice(0, 3);
|
||||
defaultOrder = {
|
||||
exchangeAddress: devConstants.NULL_ADDRESS,
|
||||
makerAddress: devConstants.NULL_ADDRESS,
|
||||
takerAddress: devConstants.NULL_ADDRESS,
|
||||
senderAddress: mixins.address,
|
||||
feeRecipientAddress: approvalSignerAddress1,
|
||||
makerAssetData: devConstants.NULL_BYTES,
|
||||
takerAssetData: devConstants.NULL_BYTES,
|
||||
makerAssetAmount: devConstants.ZERO_AMOUNT,
|
||||
takerAssetAmount: devConstants.ZERO_AMOUNT,
|
||||
makerFee: devConstants.ZERO_AMOUNT,
|
||||
takerFee: devConstants.ZERO_AMOUNT,
|
||||
expirationTimeSeconds: devConstants.ZERO_AMOUNT,
|
||||
salt: devConstants.ZERO_AMOUNT,
|
||||
signature: devConstants.NULL_BYTES,
|
||||
};
|
||||
const transactionSignerPrivateKey =
|
||||
devConstants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(transactionSignerAddress)];
|
||||
const approvalSignerPrivateKey1 = devConstants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(approvalSignerAddress1)];
|
||||
const approvalSignerPrivateKey2 = devConstants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(approvalSignerAddress2)];
|
||||
transactionFactory = new TransactionFactory(transactionSignerPrivateKey, exchangeAddress);
|
||||
approvalFactory1 = new ApprovalFactory(approvalSignerPrivateKey1, mixins.address);
|
||||
approvalFactory2 = new ApprovalFactory(approvalSignerPrivateKey2, mixins.address);
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
|
||||
describe('getSignerAddress', () => {
|
||||
it('should return the correct address using the EthSign signature type', async () => {
|
||||
const data = devConstants.NULL_BYTES;
|
||||
const transaction = transactionFactory.newSignedTransaction(data, SignatureType.EthSign);
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const signerAddress = await mixins.getSignerAddress.callAsync(transactionHash, transaction.signature);
|
||||
expect(transaction.signerAddress).to.eq(signerAddress);
|
||||
});
|
||||
it('should return the correct address using the EIP712 signature type', async () => {
|
||||
const data = devConstants.NULL_BYTES;
|
||||
const transaction = transactionFactory.newSignedTransaction(data, SignatureType.EIP712);
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
const signerAddress = await mixins.getSignerAddress.callAsync(transactionHash, transaction.signature);
|
||||
expect(transaction.signerAddress).to.eq(signerAddress);
|
||||
});
|
||||
it('should revert with with the Illegal signature type', async () => {
|
||||
const data = devConstants.NULL_BYTES;
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const illegalSignatureByte = ethUtil.toBuffer(SignatureType.Illegal).toString('hex');
|
||||
transaction.signature = `${transaction.signature.slice(
|
||||
0,
|
||||
transaction.signature.length - 2,
|
||||
)}${illegalSignatureByte}`;
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
expectContractCallFailedAsync(
|
||||
mixins.getSignerAddress.callAsync(transactionHash, transaction.signature),
|
||||
RevertReason.SignatureIllegal,
|
||||
);
|
||||
});
|
||||
it('should revert with with the Invalid signature type', async () => {
|
||||
const data = devConstants.NULL_BYTES;
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const invalidSignatureByte = ethUtil.toBuffer(SignatureType.Invalid).toString('hex');
|
||||
transaction.signature = `0x${invalidSignatureByte}`;
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
expectContractCallFailedAsync(
|
||||
mixins.getSignerAddress.callAsync(transactionHash, transaction.signature),
|
||||
RevertReason.SignatureInvalid,
|
||||
);
|
||||
});
|
||||
it("should revert with with a signature type that doesn't exist", async () => {
|
||||
const data = devConstants.NULL_BYTES;
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const invalidSignatureByte = '04';
|
||||
transaction.signature = `${transaction.signature.slice(
|
||||
0,
|
||||
transaction.signature.length - 2,
|
||||
)}${invalidSignatureByte}`;
|
||||
const transactionHash = transactionHashUtils.getTransactionHashHex(transaction);
|
||||
expectContractCallFailedAsync(
|
||||
mixins.getSignerAddress.callAsync(transactionHash, transaction.signature),
|
||||
RevertReason.SignatureUnsupported,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('decodeOrdersFromFillData', () => {
|
||||
for (const fnName of constants.SINGLE_FILL_FN_NAMES) {
|
||||
it(`should correctly decode the orders for ${fnName} data`, async () => {
|
||||
const orders = [defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const decodedOrders = await mixins.decodeOrdersFromFillData.callAsync(data);
|
||||
const decodedSignedOrders = decodedOrders.map(order => ({
|
||||
...order,
|
||||
exchangeAddress: devConstants.NULL_ADDRESS,
|
||||
signature: devConstants.NULL_BYTES,
|
||||
}));
|
||||
expect(orders).to.deep.eq(decodedSignedOrders);
|
||||
});
|
||||
}
|
||||
for (const fnName of constants.BATCH_FILL_FN_NAMES) {
|
||||
it(`should correctly decode the orders for ${fnName} data`, async () => {
|
||||
const orders = [defaultOrder, defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const decodedOrders = await mixins.decodeOrdersFromFillData.callAsync(data);
|
||||
const decodedSignedOrders = decodedOrders.map(order => ({
|
||||
...order,
|
||||
exchangeAddress: devConstants.NULL_ADDRESS,
|
||||
signature: devConstants.NULL_BYTES,
|
||||
}));
|
||||
expect(orders).to.deep.eq(decodedSignedOrders);
|
||||
});
|
||||
}
|
||||
for (const fnName of constants.MARKET_FILL_FN_NAMES) {
|
||||
it(`should correctly decode the orders for ${fnName} data`, async () => {
|
||||
const orders = [defaultOrder, defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const decodedOrders = await mixins.decodeOrdersFromFillData.callAsync(data);
|
||||
const decodedSignedOrders = decodedOrders.map(order => ({
|
||||
...order,
|
||||
exchangeAddress: devConstants.NULL_ADDRESS,
|
||||
signature: devConstants.NULL_BYTES,
|
||||
}));
|
||||
expect(orders).to.deep.eq(decodedSignedOrders);
|
||||
});
|
||||
}
|
||||
for (const fnName of [constants.CANCEL_ORDER, constants.BATCH_CANCEL_ORDERS, constants.CANCEL_ORDERS_UP_TO]) {
|
||||
it(`should correctly decode the orders for ${fnName} data`, async () => {
|
||||
const orders = [defaultOrder, defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const decodedOrders = await mixins.decodeOrdersFromFillData.callAsync(data);
|
||||
const emptyArray: any[] = [];
|
||||
expect(emptyArray).to.deep.eq(decodedOrders);
|
||||
});
|
||||
}
|
||||
it('should decode an empty array for invalid data', async () => {
|
||||
const data = '0x0123456789';
|
||||
const decodedOrders = await mixins.decodeOrdersFromFillData.callAsync(data);
|
||||
const emptyArray: any[] = [];
|
||||
expect(emptyArray).to.deep.eq(decodedOrders);
|
||||
});
|
||||
it('should revert if data is less than 4 bytes long', async () => {
|
||||
const data = '0x010203';
|
||||
await expectContractCallFailedAsync(
|
||||
mixins.decodeOrdersFromFillData.callAsync(data),
|
||||
RevertReason.LibBytesGreaterOrEqualTo4LengthRequired,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Single order approvals', () => {
|
||||
for (const fnName of constants.SINGLE_FILL_FN_NAMES) {
|
||||
it(`Should be successful: function=${fnName}, caller=tx_signer, senderAddress=[verifier], approval_sig=[approver1], expiration=[valid]`, async () => {
|
||||
const orders = [defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory1.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
await mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: transactionSignerAddress },
|
||||
);
|
||||
});
|
||||
it(`Should be successful: function=${fnName}, caller=tx_signer, senderAddress=[null], approval_sig=[approver1], expiration=[valid]`, async () => {
|
||||
const order = {
|
||||
...defaultOrder,
|
||||
senderAddress: devConstants.NULL_ADDRESS,
|
||||
};
|
||||
const orders = [order];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory1.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
await mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: transactionSignerAddress },
|
||||
);
|
||||
});
|
||||
it(`Should be successful: function=${fnName}, caller=approver1, senderAddress=[verifier], approval_sig=[], expiration=[]`, async () => {
|
||||
const orders = [defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
await mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
approvalSignerAddress1,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{
|
||||
from: approvalSignerAddress1,
|
||||
},
|
||||
);
|
||||
});
|
||||
it(`Should be successful: function=${fnName}, caller=approver1, senderAddress=[verifier], approval_sig=[approver1], expiration=[invalid]`, async () => {
|
||||
const orders = [defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory1.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
await mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
approvalSignerAddress1,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: approvalSignerAddress1 },
|
||||
);
|
||||
});
|
||||
it(`Should be successful: function=${fnName}, caller=approver1, senderAddress=[verifier], approval_sig=[], expiration=[]`, async () => {
|
||||
const orders = [defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
await mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
approvalSignerAddress1,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{
|
||||
from: approvalSignerAddress1,
|
||||
},
|
||||
);
|
||||
});
|
||||
it(`Should revert: function=${fnName}, caller=tx_signer, senderAddress=[verifier], approval_sig=[invalid], expiration=[valid]`, async () => {
|
||||
const orders = [defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory1.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const signature = `${approval.signature.slice(0, 4)}FFFFFFFF${approval.signature.slice(12)}`;
|
||||
expectContractCallFailedAsync(
|
||||
mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[signature],
|
||||
{ from: transactionSignerAddress },
|
||||
),
|
||||
RevertReason.InvalidApprovalSignature,
|
||||
);
|
||||
});
|
||||
it(`Should revert: function=${fnName}, caller=tx_signer, senderAddress=[verifier], approval_sig=[approver1], expiration=[invalid]`, async () => {
|
||||
const orders = [defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).minus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory1.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
expectContractCallFailedAsync(
|
||||
mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: transactionSignerAddress },
|
||||
),
|
||||
RevertReason.ApprovalExpired,
|
||||
);
|
||||
});
|
||||
it(`Should revert: function=${fnName}, caller=approver2, senderAddress=[verifier], approval_sig=[approver1], expiration=[valid]`, async () => {
|
||||
const orders = [defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory1.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
expectContractCallFailedAsync(
|
||||
mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: approvalSignerAddress2 },
|
||||
),
|
||||
RevertReason.InvalidOrigin,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('Batch order approvals', () => {
|
||||
for (const fnName of [
|
||||
...constants.BATCH_FILL_FN_NAMES,
|
||||
...constants.MARKET_FILL_FN_NAMES,
|
||||
constants.MATCH_ORDERS,
|
||||
]) {
|
||||
it(`Should be successful: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1,approver1], approval_sig=[approver1], expiration=[valid]`, async () => {
|
||||
const orders = [defaultOrder, defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory1.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
await mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: transactionSignerAddress },
|
||||
);
|
||||
});
|
||||
it(`Should be successful: function=${fnName} caller=tx_signer, senderAddress=[null,null], feeRecipient=[approver1,approver1], approval_sig=[approver1], expiration=[valid]`, async () => {
|
||||
const orders = [defaultOrder, defaultOrder].map(order => ({
|
||||
...order,
|
||||
senderAddress: devConstants.NULL_ADDRESS,
|
||||
}));
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory1.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
await mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: transactionSignerAddress },
|
||||
);
|
||||
});
|
||||
it(`Should be successful: function=${fnName} caller=tx_signer, senderAddress=[null,null], feeRecipient=[approver1,approver1], approval_sig=[], expiration=[]`, async () => {
|
||||
const orders = [defaultOrder, defaultOrder].map(order => ({
|
||||
...order,
|
||||
senderAddress: devConstants.NULL_ADDRESS,
|
||||
}));
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
await mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{ from: transactionSignerAddress },
|
||||
);
|
||||
});
|
||||
it(`Should be successful: function=${fnName} caller=tx_signer, senderAddress=[verifier,null], feeRecipient=[approver1,approver1], approval_sig=[approver1], expiration=[valid]`, async () => {
|
||||
const orders = [defaultOrder, { ...defaultOrder, senderAddress: devConstants.NULL_ADDRESS }];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory1.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
await mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval.signature],
|
||||
{ from: transactionSignerAddress },
|
||||
);
|
||||
});
|
||||
it(`Should be successful: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1,approver2], approval_sig=[approver1,approver2], expiration=[valid,valid]`, async () => {
|
||||
const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval1 = approvalFactory1.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const approval2 = approvalFactory2.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
await mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds, approvalExpirationTimeSeconds],
|
||||
[approval1.signature, approval2.signature],
|
||||
{ from: transactionSignerAddress },
|
||||
);
|
||||
});
|
||||
it(`Should be successful: function=${fnName} caller=approver1, senderAddress=[verifier,verifier], feeRecipient=[approver1,approver1], approval_sig=[], expiration=[]`, async () => {
|
||||
const orders = [defaultOrder, defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
await mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
approvalSignerAddress1,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{ from: approvalSignerAddress1 },
|
||||
);
|
||||
});
|
||||
it(`Should revert: function=${fnName} caller=approver1, senderAddress=[verifier,verifier], feeRecipient=[approver1,approver2], approval_sig=[approver2], expiration=[valid]`, async () => {
|
||||
const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval2 = approvalFactory2.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
expectContractCallFailedAsync(
|
||||
mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval2.signature],
|
||||
{ from: approvalSignerAddress1 },
|
||||
),
|
||||
RevertReason.InvalidOrigin,
|
||||
);
|
||||
});
|
||||
it(`Should revert: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver1], approval_sig=[], expiration=[]`, async () => {
|
||||
const orders = [defaultOrder, defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
expectContractCallFailedAsync(
|
||||
mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{ from: transactionSignerAddress },
|
||||
),
|
||||
RevertReason.InvalidApprovalSignature,
|
||||
);
|
||||
});
|
||||
it(`Should revert: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver1], approval_sig=[invalid], expiration=[valid]`, async () => {
|
||||
const orders = [defaultOrder, defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval = approvalFactory1.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const signature = `${approval.signature.slice(0, 4)}FFFFFFFF${approval.signature.slice(12)}`;
|
||||
expectContractCallFailedAsync(
|
||||
mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[signature],
|
||||
{ from: transactionSignerAddress },
|
||||
),
|
||||
RevertReason.InvalidApprovalSignature,
|
||||
);
|
||||
});
|
||||
it(`Should revert: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver2], approval_sig=[valid,invalid], expiration=[valid,valid]`, async () => {
|
||||
const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval1 = approvalFactory1.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const approval2 = approvalFactory2.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const approvalSignature2 = `${approval2.signature.slice(0, 4)}FFFFFFFF${approval2.signature.slice(12)}`;
|
||||
expectContractCallFailedAsync(
|
||||
mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds, approvalExpirationTimeSeconds],
|
||||
[approval1.signature, approvalSignature2],
|
||||
{ from: transactionSignerAddress },
|
||||
),
|
||||
RevertReason.InvalidApprovalSignature,
|
||||
);
|
||||
});
|
||||
it(`Should revert: function=${fnName} caller=approver1, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver2], approval_sig=[invalid], expiration=[valid]`, async () => {
|
||||
const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval2 = approvalFactory2.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const approvalSignature2 = `${approval2.signature.slice(0, 4)}FFFFFFFF${approval2.signature.slice(12)}`;
|
||||
expectContractCallFailedAsync(
|
||||
mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
approvalSignerAddress1,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approvalSignature2],
|
||||
{ from: approvalSignerAddress1 },
|
||||
),
|
||||
RevertReason.InvalidApprovalSignature,
|
||||
);
|
||||
});
|
||||
it(`Should revert: function=${fnName} caller=tx_signer, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver2], approval_sig=[valid,valid], expiration=[valid,invalid]`, async () => {
|
||||
const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds1 = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approvalExpirationTimeSeconds2 = new BigNumber(currentTimestamp).minus(constants.TIME_BUFFER);
|
||||
const approval1 = approvalFactory1.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds1,
|
||||
);
|
||||
const approval2 = approvalFactory2.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds2,
|
||||
);
|
||||
expectContractCallFailedAsync(
|
||||
mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds1, approvalExpirationTimeSeconds2],
|
||||
[approval1.signature, approval2.signature],
|
||||
{ from: transactionSignerAddress },
|
||||
),
|
||||
RevertReason.ApprovalExpired,
|
||||
);
|
||||
});
|
||||
it(`Should revert: function=${fnName} caller=approver1, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver2], approval_sig=[valid], expiration=[invalid]`, async () => {
|
||||
const orders = [defaultOrder, { ...defaultOrder, feeRecipientAddress: approvalSignerAddress2 }];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).minus(constants.TIME_BUFFER);
|
||||
const approval2 = approvalFactory2.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
expectContractCallFailedAsync(
|
||||
mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
approvalSignerAddress1,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval2.signature],
|
||||
{ from: approvalSignerAddress1 },
|
||||
),
|
||||
RevertReason.ApprovalExpired,
|
||||
);
|
||||
});
|
||||
it(`Should revert: function=${fnName} caller=approver2, senderAddress=[verifier,verifier], feeRecipient=[approver1, approver1], approval_sig=[valid], expiration=[valid]`, async () => {
|
||||
const orders = [defaultOrder, defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(fnName, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
const currentTimestamp = await getLatestBlockTimestampAsync();
|
||||
const approvalExpirationTimeSeconds = new BigNumber(currentTimestamp).plus(constants.TIME_BUFFER);
|
||||
const approval1 = approvalFactory1.newSignedApproval(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
expectContractCallFailedAsync(
|
||||
mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[approvalExpirationTimeSeconds],
|
||||
[approval1.signature],
|
||||
{ from: approvalSignerAddress2 },
|
||||
),
|
||||
RevertReason.InvalidOrigin,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
describe('cancels', () => {
|
||||
it('should allow the tx signer to call `cancelOrder` without approval', async () => {
|
||||
const orders = [defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(constants.CANCEL_ORDER, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
await mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{ from: transactionSignerAddress },
|
||||
);
|
||||
});
|
||||
it('should allow the tx signer to call `batchCancelOrders` without approval', async () => {
|
||||
const orders = [defaultOrder, defaultOrder];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(constants.BATCH_CANCEL_ORDERS, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
await mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{ from: transactionSignerAddress },
|
||||
);
|
||||
});
|
||||
it('should allow the tx signer to call `cancelOrdersUpTo` without approval', async () => {
|
||||
const orders: SignedOrder[] = [];
|
||||
const data = exchangeDataEncoder.encodeOrdersToExchangeData(constants.CANCEL_ORDERS_UP_TO, orders);
|
||||
const transaction = transactionFactory.newSignedTransaction(data);
|
||||
await mixins.assertValidCoordinatorApprovals.callAsync(
|
||||
transaction,
|
||||
transactionSignerAddress,
|
||||
transaction.signature,
|
||||
[],
|
||||
[],
|
||||
{ from: transactionSignerAddress },
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:disable:max-file-line-count
|
38
contracts/coordinator/test/utils/approval_factory.ts
Normal file
38
contracts/coordinator/test/utils/approval_factory.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { signingUtils } from '@0x/contracts-test-utils';
|
||||
import { SignatureType, SignedZeroExTransaction } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
|
||||
import { hashUtils, SignedCoordinatorApproval } from './index';
|
||||
|
||||
export class ApprovalFactory {
|
||||
private readonly _privateKey: Buffer;
|
||||
private readonly _verifyingContractAddress: string;
|
||||
|
||||
constructor(privateKey: Buffer, verifyingContractAddress: string) {
|
||||
this._privateKey = privateKey;
|
||||
this._verifyingContractAddress = verifyingContractAddress;
|
||||
}
|
||||
|
||||
public newSignedApproval(
|
||||
transaction: SignedZeroExTransaction,
|
||||
txOrigin: string,
|
||||
approvalExpirationTimeSeconds: BigNumber,
|
||||
signatureType: SignatureType = SignatureType.EthSign,
|
||||
): SignedCoordinatorApproval {
|
||||
const approvalHashBuff = hashUtils.getApprovalHashBuffer(
|
||||
transaction,
|
||||
this._verifyingContractAddress,
|
||||
txOrigin,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const signatureBuff = signingUtils.signMessage(approvalHashBuff, this._privateKey, signatureType);
|
||||
const signedApproval = {
|
||||
txOrigin,
|
||||
transaction,
|
||||
approvalExpirationTimeSeconds,
|
||||
signature: ethUtil.addHexPrefix(signatureBuff.toString('hex')),
|
||||
};
|
||||
return signedApproval;
|
||||
}
|
||||
}
|
12
contracts/coordinator/test/utils/constants.ts
Normal file
12
contracts/coordinator/test/utils/constants.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
export const constants = {
|
||||
SINGLE_FILL_FN_NAMES: ['fillOrder', 'fillOrKillOrder', 'fillOrderNoThrow'],
|
||||
BATCH_FILL_FN_NAMES: ['batchFillOrders', 'batchFillOrKillOrders', 'batchFillOrdersNoThrow'],
|
||||
MARKET_FILL_FN_NAMES: ['marketBuyOrders', 'marketBuyOrdersNoThrow', 'marketSellOrders', 'marketSellOrdersNoThrow'],
|
||||
MATCH_ORDERS: 'matchOrders',
|
||||
CANCEL_ORDER: 'cancelOrder',
|
||||
BATCH_CANCEL_ORDERS: 'batchCancelOrders',
|
||||
CANCEL_ORDERS_UP_TO: 'cancelOrdersUpTo',
|
||||
TIME_BUFFER: new BigNumber(1000),
|
||||
};
|
@@ -0,0 +1,64 @@
|
||||
import { LogDecoder, txDefaults } from '@0x/contracts-test-utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { TransactionReceiptWithDecodedLogs, ZeroExProvider } from 'ethereum-types';
|
||||
|
||||
import { artifacts, CoordinatorRegistryContract } from '../../src';
|
||||
|
||||
export class CoordinatorRegistryWrapper {
|
||||
private readonly _web3Wrapper: Web3Wrapper;
|
||||
private readonly _provider: ZeroExProvider;
|
||||
private readonly _logDecoder: LogDecoder;
|
||||
private _coordinatorRegistryContract?: CoordinatorRegistryContract;
|
||||
/**
|
||||
* Instanitates an CoordinatorRegistryWrapper
|
||||
* @param provider Web3 provider to use for all JSON RPC requests
|
||||
* Instance of CoordinatorRegistryWrapper
|
||||
*/
|
||||
constructor(provider: ZeroExProvider) {
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
this._provider = provider;
|
||||
this._logDecoder = new LogDecoder(this._web3Wrapper, artifacts);
|
||||
}
|
||||
public async deployCoordinatorRegistryAsync(): Promise<CoordinatorRegistryContract> {
|
||||
this._coordinatorRegistryContract = await CoordinatorRegistryContract.deployFrom0xArtifactAsync(
|
||||
artifacts.CoordinatorRegistry,
|
||||
this._provider,
|
||||
txDefaults,
|
||||
);
|
||||
if (this._coordinatorRegistryContract === undefined) {
|
||||
throw new Error(`Failed to deploy Coordinator Registry contract.`);
|
||||
}
|
||||
return this._coordinatorRegistryContract;
|
||||
}
|
||||
public async setCoordinatorEndpointAsync(
|
||||
coordinatorOperator: string,
|
||||
coordinatorEndpoint: string,
|
||||
): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
this._assertCoordinatorRegistryDeployed();
|
||||
const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(
|
||||
await (this
|
||||
._coordinatorRegistryContract as CoordinatorRegistryContract).setCoordinatorEndpoint.sendTransactionAsync(
|
||||
coordinatorEndpoint,
|
||||
{
|
||||
from: coordinatorOperator,
|
||||
},
|
||||
),
|
||||
);
|
||||
return txReceipt;
|
||||
}
|
||||
public async getCoordinatorEndpointAsync(coordinatorOperator: string): Promise<string> {
|
||||
this._assertCoordinatorRegistryDeployed();
|
||||
const coordinatorEndpoint = await (this
|
||||
._coordinatorRegistryContract as CoordinatorRegistryContract).getCoordinatorEndpoint.callAsync(
|
||||
coordinatorOperator,
|
||||
);
|
||||
return coordinatorEndpoint;
|
||||
}
|
||||
private _assertCoordinatorRegistryDeployed(): void {
|
||||
if (this._coordinatorRegistryContract === undefined) {
|
||||
throw new Error(
|
||||
'The Coordinator Registry contract was not deployed through the CoordinatorRegistryWrapper. Call `deployCoordinatorRegistryAsync` to deploy.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
47
contracts/coordinator/test/utils/exchange_data_encoder.ts
Normal file
47
contracts/coordinator/test/utils/exchange_data_encoder.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { IExchangeContract } from '@0x/contracts-exchange';
|
||||
import { constants as devConstants, provider } from '@0x/contracts-test-utils';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
|
||||
import { constants } from './index';
|
||||
|
||||
export const exchangeDataEncoder = {
|
||||
encodeOrdersToExchangeData(fnName: string, orders: SignedOrder[]): string {
|
||||
const exchangeInstance = new IExchangeContract(devConstants.NULL_ADDRESS, provider);
|
||||
let data;
|
||||
if (constants.SINGLE_FILL_FN_NAMES.indexOf(fnName) !== -1) {
|
||||
data = (exchangeInstance as any)[fnName].getABIEncodedTransactionData(
|
||||
orders[0],
|
||||
orders[0].takerAssetAmount,
|
||||
orders[0].signature,
|
||||
);
|
||||
} else if (constants.BATCH_FILL_FN_NAMES.indexOf(fnName) !== -1) {
|
||||
data = (exchangeInstance as any)[fnName].getABIEncodedTransactionData(
|
||||
orders,
|
||||
orders.map(order => order.takerAssetAmount),
|
||||
orders.map(order => order.signature),
|
||||
);
|
||||
} else if (constants.MARKET_FILL_FN_NAMES.indexOf(fnName) !== -1) {
|
||||
data = (exchangeInstance as any)[fnName].getABIEncodedTransactionData(
|
||||
orders,
|
||||
orders.map(order => order.takerAssetAmount).reduce((prev, curr) => prev.plus(curr)),
|
||||
orders.map(order => order.signature),
|
||||
);
|
||||
} else if (fnName === constants.MATCH_ORDERS) {
|
||||
data = exchangeInstance.matchOrders.getABIEncodedTransactionData(
|
||||
orders[0],
|
||||
orders[1],
|
||||
orders[0].signature,
|
||||
orders[1].signature,
|
||||
);
|
||||
} else if (fnName === constants.CANCEL_ORDER) {
|
||||
data = exchangeInstance.cancelOrder.getABIEncodedTransactionData(orders[0]);
|
||||
} else if (fnName === constants.BATCH_CANCEL_ORDERS) {
|
||||
data = exchangeInstance.batchCancelOrders.getABIEncodedTransactionData(orders);
|
||||
} else if (fnName === constants.CANCEL_ORDERS_UP_TO) {
|
||||
data = exchangeInstance.cancelOrdersUpTo.getABIEncodedTransactionData(devConstants.ZERO_AMOUNT);
|
||||
} else {
|
||||
throw new Error(`Error: ${fnName} not a supported function`);
|
||||
}
|
||||
return data;
|
||||
},
|
||||
};
|
33
contracts/coordinator/test/utils/hash_utils.ts
Normal file
33
contracts/coordinator/test/utils/hash_utils.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { eip712Utils } from '@0x/order-utils';
|
||||
import { SignedZeroExTransaction } from '@0x/types';
|
||||
import { BigNumber, signTypedDataUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
export const hashUtils = {
|
||||
getApprovalHashBuffer(
|
||||
transaction: SignedZeroExTransaction,
|
||||
verifyingContractAddress: string,
|
||||
txOrigin: string,
|
||||
approvalExpirationTimeSeconds: BigNumber,
|
||||
): Buffer {
|
||||
const typedData = eip712Utils.createCoordinatorApprovalTypedData(
|
||||
transaction,
|
||||
verifyingContractAddress,
|
||||
txOrigin,
|
||||
approvalExpirationTimeSeconds,
|
||||
);
|
||||
const hashBuffer = signTypedDataUtils.generateTypedDataHash(typedData);
|
||||
return hashBuffer;
|
||||
},
|
||||
getApprovalHashHex(
|
||||
transaction: SignedZeroExTransaction,
|
||||
verifyingContractAddress: string,
|
||||
txOrigin: string,
|
||||
approvalExpirationTimeSeconds: BigNumber,
|
||||
): string {
|
||||
const hashHex = `0x${hashUtils
|
||||
.getApprovalHashBuffer(transaction, verifyingContractAddress, txOrigin, approvalExpirationTimeSeconds)
|
||||
.toString('hex')}`;
|
||||
return hashHex;
|
||||
},
|
||||
};
|
5
contracts/coordinator/test/utils/index.ts
Normal file
5
contracts/coordinator/test/utils/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export { hashUtils } from './hash_utils';
|
||||
export { ApprovalFactory } from './approval_factory';
|
||||
export { constants } from './constants';
|
||||
export { exchangeDataEncoder } from './exchange_data_encoder';
|
||||
export * from './types';
|
12
contracts/coordinator/test/utils/types.ts
Normal file
12
contracts/coordinator/test/utils/types.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { SignedZeroExTransaction } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
|
||||
export interface CoordinatorApproval {
|
||||
transaction: SignedZeroExTransaction;
|
||||
txOrigin: string;
|
||||
approvalExpirationTimeSeconds: BigNumber;
|
||||
}
|
||||
|
||||
export interface SignedCoordinatorApproval extends CoordinatorApproval {
|
||||
signature: string;
|
||||
}
|
7
contracts/coordinator/tsconfig.json
Normal file
7
contracts/coordinator/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
|
||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
||||
"files": ["generated-artifacts/Coordinator.json", "generated-artifacts/CoordinatorRegistry.json"],
|
||||
"exclude": ["./deploy/solc/solc_bin"]
|
||||
}
|
6
contracts/coordinator/tslint.json
Normal file
6
contracts/coordinator/tslint.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": ["@0x/tslint-config"],
|
||||
"rules": {
|
||||
"custom-no-magic-numbers": false
|
||||
}
|
||||
}
|
49
contracts/dev-utils/CHANGELOG.json
Normal file
49
contracts/dev-utils/CHANGELOG.json
Normal file
@@ -0,0 +1,49 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1563047529,
|
||||
"version": "0.0.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1563006338,
|
||||
"version": "0.0.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Create dev-utils package",
|
||||
"pr": 1848
|
||||
},
|
||||
{
|
||||
"note": "Add `LibAssetData` and `LibTransactionDecoder` contracts",
|
||||
"pr": 1848
|
||||
},
|
||||
{
|
||||
"note": "Refactor `LibAssetData` to only check 0x-specific allowances",
|
||||
"pr": 1848
|
||||
},
|
||||
{
|
||||
"note": "Refactor `LibAssetData` balance/allowance checks to never revert",
|
||||
"pr": 1848
|
||||
},
|
||||
{
|
||||
"note": "Refactor `OrderValidationUtils` to calculate `fillableTakerAssetAmount`",
|
||||
"pr": 1848
|
||||
},
|
||||
{
|
||||
"note": "Add support for StaticCallProxy",
|
||||
"pr": 1863
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
23
contracts/dev-utils/CHANGELOG.md
Normal file
23
contracts/dev-utils/CHANGELOG.md
Normal file
@@ -0,0 +1,23 @@
|
||||
<!--
|
||||
changelogUtils.file is auto-generated using the monorepo-scripts package. Don't edit directly.
|
||||
Edit the package's CHANGELOG.json file only.
|
||||
-->
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v0.0.3 - _July 13, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v0.0.2 - _July 13, 2019_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v0.0.1 - _Invalid date_
|
||||
|
||||
* Create dev-utils package (#1848)
|
||||
* Add `LibAssetData` and `LibTransactionDecoder` contracts (#1848)
|
||||
* Refactor `LibAssetData` to only check 0x-specific allowances (#1848)
|
||||
* Refactor `LibAssetData` balance/allowance checks to never revert (#1848)
|
||||
* Refactor `OrderValidationUtils` to calculate `fillableTakerAssetAmount` (#1848)
|
||||
* Add support for StaticCallProxy (#1863)
|
1
contracts/dev-utils/DEPLOYS.json
Normal file
1
contracts/dev-utils/DEPLOYS.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
73
contracts/dev-utils/README.md
Normal file
73
contracts/dev-utils/README.md
Normal file
@@ -0,0 +1,73 @@
|
||||
## Dev-Utils
|
||||
|
||||
This package implements various utilities for developers. For example, the `DevUtils` contract can query batches of balances or allowances given some `assetData`, can validate batches of orders, and can decode 0x-specific calldata. Addresses of the deployed contracts can be found in the 0x [wiki](https://0xproject.com/wiki#Deployed-Addresses) or the [DEPLOYS](./DEPLOYS.json) file within this package.
|
||||
|
||||
## Installation
|
||||
|
||||
**Install**
|
||||
|
||||
```bash
|
||||
npm install @0x/contracts-dev-utils --save
|
||||
```
|
||||
|
||||
## Bug bounty
|
||||
|
||||
A bug bounty for the 2.0.0 contracts is ongoing! Instructions can be found [here](https://0xproject.com/wiki#Bug-Bounty).
|
||||
|
||||
## 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.
|
||||
|
||||
For proposals regarding the 0x protocol's smart contract architecture, message format, or additional functionality, go to the [0x Improvement Proposals (ZEIPs)](https://github.com/0xProject/ZEIPs) repository and follow the contribution guidelines provided therein.
|
||||
|
||||
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
|
||||
|
||||
To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory:
|
||||
|
||||
```bash
|
||||
PKG=@0x/contracts-extensions yarn build
|
||||
```
|
||||
|
||||
Or continuously rebuild on change:
|
||||
|
||||
```bash
|
||||
PKG=@0x/contracts-extensions yarn watch
|
||||
```
|
||||
|
||||
### Clean
|
||||
|
||||
```bash
|
||||
yarn clean
|
||||
```
|
||||
|
||||
### Lint
|
||||
|
||||
```bash
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
|
||||
#### Testing options
|
||||
|
||||
Contracts testing options like coverage, profiling, revert traces or backing node choosing - are described [here](../TESTING.md).
|
32
contracts/dev-utils/compiler.json
Normal file
32
contracts/dev-utils/compiler.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"artifactsDir": "./generated-artifacts",
|
||||
"contractsDir": "./contracts",
|
||||
"useDockerisedSolc": false,
|
||||
"isOfflineMode": false,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "constantinople",
|
||||
"optimizer": {
|
||||
"enabled": true,
|
||||
"runs": 1000000,
|
||||
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
|
||||
},
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
"abi",
|
||||
"devdoc",
|
||||
"evm.bytecode.object",
|
||||
"evm.bytecode.sourceMap",
|
||||
"evm.deployedBytecode.object",
|
||||
"evm.deployedBytecode.sourceMap"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"contracts": [
|
||||
"src/DevUtils.sol",
|
||||
"src/LibAssetData.sol",
|
||||
"src/LibTransactionDecoder.sol",
|
||||
"src/EthBalanceChecker.sol"
|
||||
]
|
||||
}
|
37
contracts/dev-utils/contracts/src/DevUtils.sol
Normal file
37
contracts/dev-utils/contracts/src/DevUtils.sol
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "./OrderValidationUtils.sol";
|
||||
import "./LibTransactionDecoder.sol";
|
||||
import "./EthBalanceChecker.sol";
|
||||
|
||||
|
||||
// solhint-disable no-empty-blocks
|
||||
contract DevUtils is
|
||||
OrderValidationUtils,
|
||||
LibTransactionDecoder,
|
||||
EthBalanceChecker
|
||||
{
|
||||
constructor (address _exchange, bytes memory _zrxAssetData)
|
||||
public
|
||||
OrderValidationUtils(_exchange, _zrxAssetData)
|
||||
{}
|
||||
}
|
39
contracts/dev-utils/contracts/src/EthBalanceChecker.sol
Normal file
39
contracts/dev-utils/contracts/src/EthBalanceChecker.sol
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
|
||||
|
||||
contract EthBalanceChecker {
|
||||
|
||||
/// @dev Batch fetches ETH balances
|
||||
/// @param addresses Array of addresses.
|
||||
/// @return Array of ETH balances.
|
||||
function getEthBalances(address[] memory addresses)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory)
|
||||
{
|
||||
uint256[] memory balances = new uint256[](addresses.length);
|
||||
for (uint256 i = 0; i != addresses.length; i++) {
|
||||
balances[i] = addresses[i].balance;
|
||||
}
|
||||
return balances;
|
||||
}
|
||||
|
||||
}
|
541
contracts/dev-utils/contracts/src/LibAssetData.sol
Normal file
541
contracts/dev-utils/contracts/src/LibAssetData.sol
Normal file
@@ -0,0 +1,541 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "@0x/contracts-asset-proxy/contracts/src/libs/LibAssetProxyIds.sol";
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
|
||||
|
||||
contract LibAssetData is
|
||||
LibAssetProxyIds
|
||||
{
|
||||
// 2^256 - 1
|
||||
uint256 constant internal _MAX_UINT256 = uint256(-1);
|
||||
|
||||
// ERC20 selectors
|
||||
bytes4 constant internal _ERC20_BALANCE_OF_SELECTOR = 0x70a08231;
|
||||
bytes4 constant internal _ERC20_ALLOWANCE_SELECTOR = 0xdd62ed3e;
|
||||
|
||||
// ERC721 selectors
|
||||
bytes4 constant internal _ERC721_OWNER_OF_SELECTOR = 0x6352211e;
|
||||
bytes4 constant internal _ERC721_IS_APPROVED_FOR_ALL_SELECTOR = 0xe985e9c5;
|
||||
bytes4 constant internal _ERC721_GET_APPROVED_SELECTOR = 0x081812fc;
|
||||
|
||||
// ERC1155 selectors
|
||||
bytes4 constant internal _ERC1155_BALANCE_OF_SELECTOR = 0x00fdd58e;
|
||||
bytes4 constant internal _ERC1155_IS_APPROVED_FOR_ALL_SELECTOR = 0xe985e9c5;
|
||||
|
||||
// `transferFrom` selector for all AssetProxy contracts
|
||||
bytes4 constant internal _ASSET_PROXY_TRANSFER_FROM_SELECTOR = 0xa85e59e4;
|
||||
|
||||
using LibBytes for bytes;
|
||||
|
||||
// solhint-disable var-name-mixedcase
|
||||
IExchange internal _EXCHANGE;
|
||||
address internal _ERC20_PROXY_ADDRESS;
|
||||
address internal _ERC721_PROXY_ADDRESS;
|
||||
address internal _ERC1155_PROXY_ADDRESS;
|
||||
address internal _STATIC_CALL_PROXY_ADDRESS;
|
||||
// solhint-enable var-name-mixedcase
|
||||
|
||||
constructor (address _exchange)
|
||||
public
|
||||
{
|
||||
_EXCHANGE = IExchange(_exchange);
|
||||
_ERC20_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(ERC20_PROXY_ID);
|
||||
_ERC721_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(ERC721_PROXY_ID);
|
||||
_ERC1155_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(ERC1155_PROXY_ID);
|
||||
_STATIC_CALL_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(STATIC_CALL_PROXY_ID);
|
||||
}
|
||||
|
||||
/// @dev Returns the owner's balance of the assets(s) specified in
|
||||
/// assetData. When the asset data contains multiple assets (eg in
|
||||
/// ERC1155 or Multi-Asset), the return value indicates how many
|
||||
/// complete "baskets" of those assets are owned by owner.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Details of asset, encoded per the AssetProxy contract specification.
|
||||
/// @return Number of assets (or asset baskets) held by owner.
|
||||
function getBalance(address ownerAddress, bytes memory assetData)
|
||||
public
|
||||
view
|
||||
returns (uint256 balance)
|
||||
{
|
||||
// Get id of AssetProxy contract
|
||||
bytes4 assetProxyId = assetData.readBytes4(0);
|
||||
|
||||
if (assetProxyId == ERC20_PROXY_ID) {
|
||||
// Get ERC20 token address
|
||||
address tokenAddress = assetData.readAddress(16);
|
||||
|
||||
// Encode data for `balanceOf(ownerAddress)`
|
||||
bytes memory balanceOfData = abi.encodeWithSelector(_ERC20_BALANCE_OF_SELECTOR, ownerAddress);
|
||||
|
||||
// Query balance
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(balanceOfData);
|
||||
balance = success && returnData.length == 32 ? returnData.readUint256(0) : 0;
|
||||
} else if (assetProxyId == ERC721_PROXY_ID) {
|
||||
// Get ERC721 token address and id
|
||||
(, address tokenAddress, uint256 tokenId) = decodeERC721AssetData(assetData);
|
||||
|
||||
// Check if id is owned by ownerAddress
|
||||
balance = getERC721TokenOwner(tokenAddress, tokenId) == ownerAddress ? 1 : 0;
|
||||
} else if (assetProxyId == ERC1155_PROXY_ID) {
|
||||
// Get ERC1155 token address, array of ids, and array of values
|
||||
(, address tokenAddress, uint256[] memory tokenIds, uint256[] memory tokenValues,) = decodeERC1155AssetData(assetData);
|
||||
|
||||
uint256 length = tokenIds.length;
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
// Encode data for `balanceOf(ownerAddress, tokenIds[i])
|
||||
bytes memory balanceOfData = abi.encodeWithSelector(
|
||||
_ERC1155_BALANCE_OF_SELECTOR,
|
||||
ownerAddress,
|
||||
tokenIds[i]
|
||||
);
|
||||
|
||||
// Query balance
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(balanceOfData);
|
||||
uint256 totalBalance = success && returnData.length == 32 ? returnData.readUint256(0) : 0;
|
||||
|
||||
// Scale total balance down by corresponding value in assetData
|
||||
uint256 scaledBalance = totalBalance / tokenValues[i];
|
||||
if (scaledBalance < balance || balance == 0) {
|
||||
balance = scaledBalance;
|
||||
}
|
||||
}
|
||||
} else if (assetProxyId == STATIC_CALL_PROXY_ID) {
|
||||
// Encode data for `staticCallProxy.transferFrom(assetData,...)`
|
||||
bytes memory transferFromData = abi.encodeWithSelector(
|
||||
_ASSET_PROXY_TRANSFER_FROM_SELECTOR,
|
||||
assetData,
|
||||
address(0), // `from` address is not used
|
||||
address(0), // `to` address is not used
|
||||
0 // `amount` is not used
|
||||
);
|
||||
|
||||
// Check if staticcall would be successful
|
||||
(bool success,) = _STATIC_CALL_PROXY_ADDRESS.staticcall(transferFromData);
|
||||
|
||||
// Success means that the staticcall can be made an unlimited amount of times
|
||||
balance = success ? _MAX_UINT256 : 0;
|
||||
} else if (assetProxyId == MULTI_ASSET_PROXY_ID) {
|
||||
// Get array of values and array of assetDatas
|
||||
(, uint256[] memory assetAmounts, bytes[] memory nestedAssetData) = decodeMultiAssetData(assetData);
|
||||
|
||||
uint256 length = nestedAssetData.length;
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
// Query balance of individual assetData
|
||||
uint256 totalBalance = getBalance(ownerAddress, nestedAssetData[i]);
|
||||
|
||||
// Scale total balance down by corresponding value in assetData
|
||||
uint256 scaledBalance = totalBalance / assetAmounts[i];
|
||||
if (scaledBalance < balance || balance == 0) {
|
||||
balance = scaledBalance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Balance will be 0 if assetProxyId is unknown
|
||||
return balance;
|
||||
}
|
||||
|
||||
/// @dev Calls getBalance() for each element of assetData.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Array of asset details, each encoded per the AssetProxy contract specification.
|
||||
/// @return Array of asset balances from getBalance(), with each element
|
||||
/// corresponding to the same-indexed element in the assetData input.
|
||||
function getBatchBalances(address ownerAddress, bytes[] memory assetData)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory balances)
|
||||
{
|
||||
uint256 length = assetData.length;
|
||||
balances = new uint256[](length);
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
balances[i] = getBalance(ownerAddress, assetData[i]);
|
||||
}
|
||||
return balances;
|
||||
}
|
||||
|
||||
/// @dev Returns the number of asset(s) (described by assetData) that
|
||||
/// the corresponding AssetProxy contract is authorized to spend. When the asset data contains
|
||||
/// multiple assets (eg for Multi-Asset), the return value indicates
|
||||
/// how many complete "baskets" of those assets may be spent by all of the corresponding
|
||||
/// AssetProxy contracts.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Details of asset, encoded per the AssetProxy contract specification.
|
||||
/// @return Number of assets (or asset baskets) that the corresponding AssetProxy is authorized to spend.
|
||||
function getAssetProxyAllowance(address ownerAddress, bytes memory assetData)
|
||||
public
|
||||
view
|
||||
returns (uint256 allowance)
|
||||
{
|
||||
// Get id of AssetProxy contract
|
||||
bytes4 assetProxyId = assetData.readBytes4(0);
|
||||
|
||||
if (assetProxyId == MULTI_ASSET_PROXY_ID) {
|
||||
// Get array of values and array of assetDatas
|
||||
(, uint256[] memory amounts, bytes[] memory nestedAssetData) = decodeMultiAssetData(assetData);
|
||||
|
||||
uint256 length = nestedAssetData.length;
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
// Query allowance of individual assetData
|
||||
uint256 totalAllowance = getAssetProxyAllowance(ownerAddress, nestedAssetData[i]);
|
||||
|
||||
// Scale total allowance down by corresponding value in assetData
|
||||
uint256 scaledAllowance = totalAllowance / amounts[i];
|
||||
if (scaledAllowance < allowance || allowance == 0) {
|
||||
allowance = scaledAllowance;
|
||||
}
|
||||
}
|
||||
return allowance;
|
||||
}
|
||||
|
||||
if (assetProxyId == ERC20_PROXY_ID) {
|
||||
// Get ERC20 token address
|
||||
address tokenAddress = assetData.readAddress(16);
|
||||
|
||||
// Encode data for `allowance(ownerAddress, _ERC20_PROXY_ADDRESS)`
|
||||
bytes memory allowanceData = abi.encodeWithSelector(
|
||||
_ERC20_ALLOWANCE_SELECTOR,
|
||||
ownerAddress,
|
||||
_ERC20_PROXY_ADDRESS
|
||||
);
|
||||
|
||||
// Query allowance
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(allowanceData);
|
||||
allowance = success && returnData.length == 32 ? returnData.readUint256(0) : 0;
|
||||
} else if (assetProxyId == ERC721_PROXY_ID) {
|
||||
// Get ERC721 token address and id
|
||||
(, address tokenAddress, uint256 tokenId) = decodeERC721AssetData(assetData);
|
||||
|
||||
// Encode data for `isApprovedForAll(ownerAddress, _ERC721_PROXY_ADDRESS)`
|
||||
bytes memory isApprovedForAllData = abi.encodeWithSelector(
|
||||
_ERC721_IS_APPROVED_FOR_ALL_SELECTOR,
|
||||
ownerAddress,
|
||||
_ERC721_PROXY_ADDRESS
|
||||
);
|
||||
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(isApprovedForAllData);
|
||||
|
||||
// If not approved for all, call `getApproved(tokenId)`
|
||||
if (!success || returnData.length != 32 || returnData.readUint256(0) != 1) {
|
||||
// Encode data for `getApproved(tokenId)`
|
||||
bytes memory getApprovedData = abi.encodeWithSelector(_ERC721_GET_APPROVED_SELECTOR, tokenId);
|
||||
(success, returnData) = tokenAddress.staticcall(getApprovedData);
|
||||
|
||||
// Allowance is 1 if successful and the approved address is the ERC721Proxy
|
||||
allowance = success && returnData.length == 32 && returnData.readAddress(12) == _ERC721_PROXY_ADDRESS ? 1 : 0;
|
||||
} else {
|
||||
// Allowance is 2^256 - 1 if `isApprovedForAll` returned true
|
||||
allowance = _MAX_UINT256;
|
||||
}
|
||||
} else if (assetProxyId == ERC1155_PROXY_ID) {
|
||||
// Get ERC1155 token address
|
||||
(, address tokenAddress, , , ) = decodeERC1155AssetData(assetData);
|
||||
|
||||
// Encode data for `isApprovedForAll(ownerAddress, _ERC1155_PROXY_ADDRESS)`
|
||||
bytes memory isApprovedForAllData = abi.encodeWithSelector(
|
||||
_ERC1155_IS_APPROVED_FOR_ALL_SELECTOR,
|
||||
ownerAddress,
|
||||
_ERC1155_PROXY_ADDRESS
|
||||
);
|
||||
|
||||
// Query allowance
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(isApprovedForAllData);
|
||||
allowance = success && returnData.length == 32 && returnData.readUint256(0) == 1 ? _MAX_UINT256 : 0;
|
||||
} else if (assetProxyId == STATIC_CALL_PROXY_ID) {
|
||||
// The StaticCallProxy does not require any approvals
|
||||
allowance = _MAX_UINT256;
|
||||
}
|
||||
|
||||
// Allowance will be 0 if the assetProxyId is unknown
|
||||
return allowance;
|
||||
}
|
||||
|
||||
/// @dev Calls getAssetProxyAllowance() for each element of assetData.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Array of asset details, each encoded per the AssetProxy contract specification.
|
||||
/// @return An array of asset allowances from getAllowance(), with each
|
||||
/// element corresponding to the same-indexed element in the assetData input.
|
||||
function getBatchAssetProxyAllowances(address ownerAddress, bytes[] memory assetData)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory allowances)
|
||||
{
|
||||
uint256 length = assetData.length;
|
||||
allowances = new uint256[](length);
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
allowances[i] = getAssetProxyAllowance(ownerAddress, assetData[i]);
|
||||
}
|
||||
return allowances;
|
||||
}
|
||||
|
||||
/// @dev Calls getBalance() and getAllowance() for assetData.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Details of asset, encoded per the AssetProxy contract specification.
|
||||
/// @return Number of assets (or asset baskets) held by owner, and number
|
||||
/// of assets (or asset baskets) that the corresponding AssetProxy is authorized to spend.
|
||||
function getBalanceAndAssetProxyAllowance(address ownerAddress, bytes memory assetData)
|
||||
public
|
||||
view
|
||||
returns (uint256 balance, uint256 allowance)
|
||||
{
|
||||
balance = getBalance(ownerAddress, assetData);
|
||||
allowance = getAssetProxyAllowance(ownerAddress, assetData);
|
||||
return (balance, allowance);
|
||||
}
|
||||
|
||||
/// @dev Calls getBatchBalances() and getBatchAllowances() for each element of assetData.
|
||||
/// @param ownerAddress Owner of the assets specified by assetData.
|
||||
/// @param assetData Array of asset details, each encoded per the AssetProxy contract specification.
|
||||
/// @return An array of asset balances from getBalance(), and an array of
|
||||
/// asset allowances from getAllowance(), with each element
|
||||
/// corresponding to the same-indexed element in the assetData input.
|
||||
function getBatchBalancesAndAssetProxyAllowances(address ownerAddress, bytes[] memory assetData)
|
||||
public
|
||||
view
|
||||
returns (uint256[] memory balances, uint256[] memory allowances)
|
||||
{
|
||||
balances = getBatchBalances(ownerAddress, assetData);
|
||||
allowances = getBatchAssetProxyAllowances(ownerAddress, assetData);
|
||||
return (balances, allowances);
|
||||
}
|
||||
|
||||
/// @dev Encode ERC-20 asset data into the format described in the AssetProxy contract specification.
|
||||
/// @param tokenAddress The address of the ERC-20 contract hosting the asset to be traded.
|
||||
/// @return AssetProxy-compliant data describing the asset.
|
||||
function encodeERC20AssetData(address tokenAddress)
|
||||
public
|
||||
pure
|
||||
returns (bytes memory assetData)
|
||||
{
|
||||
assetData = abi.encodeWithSelector(ERC20_PROXY_ID, tokenAddress);
|
||||
return assetData;
|
||||
}
|
||||
|
||||
/// @dev Decode ERC-20 asset data from the format described in the AssetProxy contract specification.
|
||||
/// @param assetData AssetProxy-compliant asset data describing an ERC-20 asset.
|
||||
/// @return The ERC-20 AssetProxy identifier, and the address of the ERC-20
|
||||
/// contract hosting this asset.
|
||||
function decodeERC20AssetData(bytes memory assetData)
|
||||
public
|
||||
pure
|
||||
returns (
|
||||
bytes4 assetProxyId,
|
||||
address tokenAddress
|
||||
)
|
||||
{
|
||||
assetProxyId = assetData.readBytes4(0);
|
||||
|
||||
require(
|
||||
assetProxyId == ERC20_PROXY_ID,
|
||||
"WRONG_PROXY_ID"
|
||||
);
|
||||
|
||||
tokenAddress = assetData.readAddress(16);
|
||||
return (assetProxyId, tokenAddress);
|
||||
}
|
||||
|
||||
/// @dev Encode ERC-721 asset data into the format described in the AssetProxy specification.
|
||||
/// @param tokenAddress The address of the ERC-721 contract hosting the asset to be traded.
|
||||
/// @param tokenId The identifier of the specific asset to be traded.
|
||||
/// @return AssetProxy-compliant asset data describing the asset.
|
||||
function encodeERC721AssetData(address tokenAddress, uint256 tokenId)
|
||||
public
|
||||
pure
|
||||
returns (bytes memory assetData)
|
||||
{
|
||||
assetData = abi.encodeWithSelector(
|
||||
ERC721_PROXY_ID,
|
||||
tokenAddress,
|
||||
tokenId
|
||||
);
|
||||
return assetData;
|
||||
}
|
||||
|
||||
/// @dev Decode ERC-721 asset data from the format described in the AssetProxy contract specification.
|
||||
/// @param assetData AssetProxy-compliant asset data describing an ERC-721 asset.
|
||||
/// @return The ERC-721 AssetProxy identifier, the address of the ERC-721
|
||||
/// contract hosting this asset, and the identifier of the specific
|
||||
/// asset to be traded.
|
||||
function decodeERC721AssetData(bytes memory assetData)
|
||||
public
|
||||
pure
|
||||
returns (
|
||||
bytes4 assetProxyId,
|
||||
address tokenAddress,
|
||||
uint256 tokenId
|
||||
)
|
||||
{
|
||||
assetProxyId = assetData.readBytes4(0);
|
||||
|
||||
require(
|
||||
assetProxyId == ERC721_PROXY_ID,
|
||||
"WRONG_PROXY_ID"
|
||||
);
|
||||
|
||||
tokenAddress = assetData.readAddress(16);
|
||||
tokenId = assetData.readUint256(36);
|
||||
return (assetProxyId, tokenAddress, tokenId);
|
||||
}
|
||||
|
||||
/// @dev Encode ERC-1155 asset data into the format described in the AssetProxy contract specification.
|
||||
/// @param tokenAddress The address of the ERC-1155 contract hosting the asset(s) to be traded.
|
||||
/// @param tokenIds The identifiers of the specific assets to be traded.
|
||||
/// @param tokenValues The amounts of each asset to be traded.
|
||||
/// @param callbackData Data to be passed to receiving contracts when a transfer is performed.
|
||||
/// @return AssetProxy-compliant asset data describing the set of assets.
|
||||
function encodeERC1155AssetData(
|
||||
address tokenAddress,
|
||||
uint256[] memory tokenIds,
|
||||
uint256[] memory tokenValues,
|
||||
bytes memory callbackData
|
||||
)
|
||||
public
|
||||
pure
|
||||
returns (bytes memory assetData)
|
||||
{
|
||||
assetData = abi.encodeWithSelector(
|
||||
ERC1155_PROXY_ID,
|
||||
tokenAddress,
|
||||
tokenIds,
|
||||
tokenValues,
|
||||
callbackData
|
||||
);
|
||||
return assetData;
|
||||
}
|
||||
|
||||
/// @dev Decode ERC-1155 asset data from the format described in the AssetProxy contract specification.
|
||||
/// @param assetData AssetProxy-compliant asset data describing an ERC-1155 set of assets.
|
||||
/// @return The ERC-1155 AssetProxy identifier, the address of the ERC-1155
|
||||
/// contract hosting the assets, an array of the identifiers of the
|
||||
/// assets to be traded, an array of asset amounts to be traded, and
|
||||
/// callback data. Each element of the arrays corresponds to the
|
||||
/// same-indexed element of the other array. Return values specified as
|
||||
/// `memory` are returned as pointers to locations within the memory of
|
||||
/// the input parameter `assetData`.
|
||||
function decodeERC1155AssetData(bytes memory assetData)
|
||||
public
|
||||
pure
|
||||
returns (
|
||||
bytes4 assetProxyId,
|
||||
address tokenAddress,
|
||||
uint256[] memory tokenIds,
|
||||
uint256[] memory tokenValues,
|
||||
bytes memory callbackData
|
||||
)
|
||||
{
|
||||
assetProxyId = assetData.readBytes4(0);
|
||||
|
||||
require(
|
||||
assetProxyId == ERC1155_PROXY_ID,
|
||||
"WRONG_PROXY_ID"
|
||||
);
|
||||
|
||||
assembly {
|
||||
// Skip selector and length to get to the first parameter:
|
||||
assetData := add(assetData, 36)
|
||||
// Read the value of the first parameter:
|
||||
tokenAddress := mload(assetData)
|
||||
// Point to the next parameter's data:
|
||||
tokenIds := add(assetData, mload(add(assetData, 32)))
|
||||
// Point to the next parameter's data:
|
||||
tokenValues := add(assetData, mload(add(assetData, 64)))
|
||||
// Point to the next parameter's data:
|
||||
callbackData := add(assetData, mload(add(assetData, 96)))
|
||||
}
|
||||
|
||||
return (
|
||||
assetProxyId,
|
||||
tokenAddress,
|
||||
tokenIds,
|
||||
tokenValues,
|
||||
callbackData
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Encode data for multiple assets, per the AssetProxy contract specification.
|
||||
/// @param amounts The amounts of each asset to be traded.
|
||||
/// @param nestedAssetData AssetProxy-compliant data describing each asset to be traded.
|
||||
/// @return AssetProxy-compliant data describing the set of assets.
|
||||
function encodeMultiAssetData(uint256[] memory amounts, bytes[] memory nestedAssetData)
|
||||
public
|
||||
pure
|
||||
returns (bytes memory assetData)
|
||||
{
|
||||
assetData = abi.encodeWithSelector(
|
||||
MULTI_ASSET_PROXY_ID,
|
||||
amounts,
|
||||
nestedAssetData
|
||||
);
|
||||
return assetData;
|
||||
}
|
||||
|
||||
/// @dev Decode multi-asset data from the format described in the AssetProxy contract specification.
|
||||
/// @param assetData AssetProxy-compliant data describing a multi-asset basket.
|
||||
/// @return The Multi-Asset AssetProxy identifier, an array of the amounts
|
||||
/// of the assets to be traded, and an array of the
|
||||
/// AssetProxy-compliant data describing each asset to be traded. Each
|
||||
/// element of the arrays corresponds to the same-indexed element of the other array.
|
||||
function decodeMultiAssetData(bytes memory assetData)
|
||||
public
|
||||
pure
|
||||
returns (
|
||||
bytes4 assetProxyId,
|
||||
uint256[] memory amounts,
|
||||
bytes[] memory nestedAssetData
|
||||
)
|
||||
{
|
||||
assetProxyId = assetData.readBytes4(0);
|
||||
|
||||
require(
|
||||
assetProxyId == MULTI_ASSET_PROXY_ID,
|
||||
"WRONG_PROXY_ID"
|
||||
);
|
||||
|
||||
// solhint-disable indent
|
||||
(amounts, nestedAssetData) = abi.decode(
|
||||
assetData.slice(4, assetData.length),
|
||||
(uint256[], bytes[])
|
||||
);
|
||||
// solhint-enable indent
|
||||
}
|
||||
|
||||
/// @dev Calls `asset.ownerOf(tokenId)`, but returns a null owner instead of reverting on an unowned asset.
|
||||
/// @param tokenAddress Address of ERC721 asset.
|
||||
/// @param tokenId The identifier for the specific NFT.
|
||||
/// @return Owner of tokenId or null address if unowned.
|
||||
function getERC721TokenOwner(address tokenAddress, uint256 tokenId)
|
||||
public
|
||||
view
|
||||
returns (address ownerAddress)
|
||||
{
|
||||
bytes memory ownerOfCalldata = abi.encodeWithSelector(
|
||||
_ERC721_OWNER_OF_SELECTOR,
|
||||
tokenId
|
||||
);
|
||||
|
||||
(bool success, bytes memory returnData) = tokenAddress.staticcall(ownerOfCalldata);
|
||||
|
||||
ownerAddress = (success && returnData.length == 32) ? returnData.readAddress(12) : address(0);
|
||||
return ownerAddress;
|
||||
}
|
||||
}
|
193
contracts/dev-utils/contracts/src/LibTransactionDecoder.sol
Normal file
193
contracts/dev-utils/contracts/src/LibTransactionDecoder.sol
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
|
||||
Copyright 2019 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibExchangeSelectors.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
|
||||
|
||||
contract LibTransactionDecoder is
|
||||
LibExchangeSelectors
|
||||
{
|
||||
using LibBytes for bytes;
|
||||
|
||||
/// @dev Decodes the call data for an Exchange contract method call.
|
||||
/// @param transactionData ABI-encoded calldata for an Exchange
|
||||
/// contract method call.
|
||||
/// @return The name of the function called, and the parameters it was
|
||||
/// given. For single-order fills and cancels, the arrays will have
|
||||
/// just one element.
|
||||
function decodeZeroExTransactionData(bytes memory transactionData)
|
||||
public
|
||||
pure
|
||||
returns(
|
||||
string memory functionName,
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256[] memory takerAssetFillAmounts,
|
||||
bytes[] memory signatures
|
||||
)
|
||||
{
|
||||
bytes4 functionSelector = transactionData.readBytes4(0);
|
||||
|
||||
if (functionSelector == BATCH_CANCEL_ORDERS_SELECTOR) {
|
||||
functionName = "batchCancelOrders";
|
||||
} else if (functionSelector == BATCH_FILL_ORDERS_SELECTOR) {
|
||||
functionName = "batchFillOrders";
|
||||
} else if (functionSelector == BATCH_FILL_ORDERS_NO_THROW_SELECTOR) {
|
||||
functionName = "batchFillOrdersNoThrow";
|
||||
} else if (functionSelector == BATCH_FILL_OR_KILL_ORDERS_SELECTOR) {
|
||||
functionName = "batchFillOrKillOrders";
|
||||
} else if (functionSelector == CANCEL_ORDER_SELECTOR) {
|
||||
functionName = "cancelOrder";
|
||||
} else if (functionSelector == FILL_ORDER_SELECTOR) {
|
||||
functionName = "fillOrder";
|
||||
} else if (functionSelector == FILL_ORDER_NO_THROW_SELECTOR) {
|
||||
functionName = "fillOrderNoThrow";
|
||||
} else if (functionSelector == FILL_OR_KILL_ORDER_SELECTOR) {
|
||||
functionName = "fillOrKillOrder";
|
||||
} else if (functionSelector == MARKET_BUY_ORDERS_SELECTOR) {
|
||||
functionName = "marketBuyOrders";
|
||||
} else if (functionSelector == MARKET_BUY_ORDERS_NO_THROW_SELECTOR) {
|
||||
functionName = "marketBuyOrdersNoThrow";
|
||||
} else if (functionSelector == MARKET_SELL_ORDERS_SELECTOR) {
|
||||
functionName = "marketSellOrders";
|
||||
} else if (functionSelector == MARKET_SELL_ORDERS_NO_THROW_SELECTOR) {
|
||||
functionName = "marketSellOrdersNoThrow";
|
||||
} else if (functionSelector == MATCH_ORDERS_SELECTOR) {
|
||||
functionName = "matchOrders";
|
||||
} else if (
|
||||
functionSelector == CANCEL_ORDERS_UP_TO_SELECTOR ||
|
||||
functionSelector == EXECUTE_TRANSACTION_SELECTOR
|
||||
// TODO: add new noThrow cancel functions when https://github.com/0xProject/ZEIPs/issues/35 is merged.
|
||||
) {
|
||||
revert("UNIMPLEMENTED");
|
||||
} else {
|
||||
revert("UNKNOWN_FUNCTION_SELECTOR");
|
||||
}
|
||||
|
||||
if (functionSelector == BATCH_CANCEL_ORDERS_SELECTOR) {
|
||||
// solhint-disable-next-line indent
|
||||
orders = abi.decode(transactionData.slice(4, transactionData.length), (LibOrder.Order[]));
|
||||
takerAssetFillAmounts = new uint256[](0);
|
||||
signatures = new bytes[](0);
|
||||
} else if (
|
||||
functionSelector == BATCH_FILL_OR_KILL_ORDERS_SELECTOR ||
|
||||
functionSelector == BATCH_FILL_ORDERS_NO_THROW_SELECTOR ||
|
||||
functionSelector == BATCH_FILL_ORDERS_SELECTOR
|
||||
) {
|
||||
(orders, takerAssetFillAmounts, signatures) = _makeReturnValuesForBatchFill(transactionData);
|
||||
} else if (functionSelector == CANCEL_ORDER_SELECTOR) {
|
||||
orders = new LibOrder.Order[](1);
|
||||
orders[0] = abi.decode(transactionData.slice(4, transactionData.length), (LibOrder.Order));
|
||||
takerAssetFillAmounts = new uint256[](0);
|
||||
signatures = new bytes[](0);
|
||||
} else if (
|
||||
functionSelector == FILL_OR_KILL_ORDER_SELECTOR ||
|
||||
functionSelector == FILL_ORDER_SELECTOR ||
|
||||
functionSelector == FILL_ORDER_NO_THROW_SELECTOR
|
||||
) {
|
||||
(orders, takerAssetFillAmounts, signatures) = _makeReturnValuesForSingleOrderFill(transactionData);
|
||||
} else if (
|
||||
functionSelector == MARKET_BUY_ORDERS_SELECTOR ||
|
||||
functionSelector == MARKET_BUY_ORDERS_NO_THROW_SELECTOR ||
|
||||
functionSelector == MARKET_SELL_ORDERS_SELECTOR ||
|
||||
functionSelector == MARKET_SELL_ORDERS_NO_THROW_SELECTOR
|
||||
) {
|
||||
(orders, takerAssetFillAmounts, signatures) = _makeReturnValuesForMarketFill(transactionData);
|
||||
} else if (functionSelector == MATCH_ORDERS_SELECTOR) {
|
||||
(
|
||||
LibOrder.Order memory leftOrder,
|
||||
LibOrder.Order memory rightOrder,
|
||||
bytes memory leftSignature,
|
||||
bytes memory rightSignature
|
||||
) = abi.decode(
|
||||
transactionData.slice(4, transactionData.length),
|
||||
(LibOrder.Order, LibOrder.Order, bytes, bytes)
|
||||
);
|
||||
|
||||
orders = new LibOrder.Order[](2);
|
||||
orders[0] = leftOrder;
|
||||
orders[1] = rightOrder;
|
||||
|
||||
takerAssetFillAmounts = new uint256[](2);
|
||||
takerAssetFillAmounts[0] = leftOrder.takerAssetAmount;
|
||||
takerAssetFillAmounts[1] = rightOrder.takerAssetAmount;
|
||||
|
||||
signatures = new bytes[](2);
|
||||
signatures[0] = leftSignature;
|
||||
signatures[1] = rightSignature;
|
||||
}
|
||||
}
|
||||
|
||||
function _makeReturnValuesForSingleOrderFill(bytes memory transactionData)
|
||||
private
|
||||
pure
|
||||
returns(
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256[] memory takerAssetFillAmounts,
|
||||
bytes[] memory signatures
|
||||
)
|
||||
{
|
||||
orders = new LibOrder.Order[](1);
|
||||
takerAssetFillAmounts = new uint256[](1);
|
||||
signatures = new bytes[](1);
|
||||
// solhint-disable-next-line indent
|
||||
(orders[0], takerAssetFillAmounts[0], signatures[0]) = abi.decode(
|
||||
transactionData.slice(4, transactionData.length),
|
||||
(LibOrder.Order, uint256, bytes)
|
||||
);
|
||||
}
|
||||
|
||||
function _makeReturnValuesForBatchFill(bytes memory transactionData)
|
||||
private
|
||||
pure
|
||||
returns(
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256[] memory takerAssetFillAmounts,
|
||||
bytes[] memory signatures
|
||||
)
|
||||
{
|
||||
// solhint-disable-next-line indent
|
||||
(orders, takerAssetFillAmounts, signatures) = abi.decode(
|
||||
transactionData.slice(4, transactionData.length),
|
||||
// solhint-disable-next-line indent
|
||||
(LibOrder.Order[], uint256[], bytes[])
|
||||
);
|
||||
}
|
||||
|
||||
function _makeReturnValuesForMarketFill(bytes memory transactionData)
|
||||
private
|
||||
pure
|
||||
returns(
|
||||
LibOrder.Order[] memory orders,
|
||||
uint256[] memory takerAssetFillAmounts,
|
||||
bytes[] memory signatures
|
||||
)
|
||||
{
|
||||
takerAssetFillAmounts = new uint256[](1);
|
||||
// solhint-disable-next-line indent
|
||||
(orders, takerAssetFillAmounts[0], signatures) = abi.decode(
|
||||
transactionData.slice(4, transactionData.length),
|
||||
// solhint-disable-next-line indent
|
||||
(LibOrder.Order[], uint256, bytes[])
|
||||
);
|
||||
}
|
||||
}
|
184
contracts/dev-utils/contracts/src/OrderValidationUtils.sol
Normal file
184
contracts/dev-utils/contracts/src/OrderValidationUtils.sol
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
|
||||
Copyright 2018 ZeroEx Intl.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.5;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
|
||||
import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol";
|
||||
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
|
||||
import "./LibAssetData.sol";
|
||||
|
||||
|
||||
contract OrderValidationUtils is
|
||||
LibAssetData,
|
||||
LibMath
|
||||
{
|
||||
using LibBytes for bytes;
|
||||
|
||||
// solhint-disable var-name-mixedcase
|
||||
bytes internal _ZRX_ASSET_DATA;
|
||||
// solhint-enable var-name-mixedcase
|
||||
|
||||
constructor (address _exchange, bytes memory _zrxAssetData)
|
||||
public
|
||||
LibAssetData(_exchange)
|
||||
{
|
||||
_ZRX_ASSET_DATA = _zrxAssetData;
|
||||
}
|
||||
|
||||
/// @dev Fetches all order-relevant information needed to validate if the supplied order is fillable.
|
||||
/// @param order The order structure.
|
||||
/// @param signature Signature provided by maker that proves the order's authenticity.
|
||||
/// `0x01` can always be provided if the signature does not need to be validated.
|
||||
/// @return The orderInfo (hash, status, and `takerAssetAmount` already filled for the given order),
|
||||
/// fillableTakerAssetAmount (amount of the order's `takerAssetAmount` that is fillable given all on-chain state),
|
||||
/// and isValidSignature (validity of the provided signature).
|
||||
/// NOTE: If the `takerAssetData` encodes data for multiple assets, `fillableTakerAssetAmount` will represent a "scaled"
|
||||
/// amount, meaning it must be multiplied by all the individual asset amounts within the `takerAssetData` to get the final
|
||||
/// amount of each asset that can be filled.
|
||||
function getOrderRelevantState(LibOrder.Order memory order, bytes memory signature)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
LibOrder.OrderInfo memory orderInfo,
|
||||
uint256 fillableTakerAssetAmount,
|
||||
bool isValidSignature
|
||||
)
|
||||
{
|
||||
// Get info specific to order
|
||||
orderInfo = _EXCHANGE.getOrderInfo(order);
|
||||
|
||||
// Validate the maker's signature
|
||||
address makerAddress = order.makerAddress;
|
||||
isValidSignature = _EXCHANGE.isValidSignature(
|
||||
orderInfo.orderHash,
|
||||
makerAddress,
|
||||
signature
|
||||
);
|
||||
|
||||
// Get the transferable amount of the `makerAsset`
|
||||
uint256 transferableMakerAssetAmount = getTransferableAssetAmount(makerAddress, order.makerAssetData);
|
||||
|
||||
// Assign to stack variables to reduce redundant mloads/sloads
|
||||
uint256 takerAssetAmount = order.takerAssetAmount;
|
||||
uint256 makerFee = order.makerFee;
|
||||
bytes memory zrxAssetData = _ZRX_ASSET_DATA;
|
||||
|
||||
// Get the amount of `takerAsset` that is transferable to maker given the transferability of `makerAsset`, `makerFeeAsset`,
|
||||
// and the total amounts specified in the order
|
||||
uint256 transferableTakerAssetAmount;
|
||||
if (order.makerAssetData.equals(zrxAssetData)) {
|
||||
// If `makerAsset` equals `makerFeeAsset`, the % that can be filled is
|
||||
// transferableMakerAssetAmount / (makerAssetAmount + makerFee)
|
||||
transferableTakerAssetAmount = getPartialAmountFloor(
|
||||
transferableMakerAssetAmount,
|
||||
safeAdd(order.makerAssetAmount, makerFee),
|
||||
takerAssetAmount
|
||||
);
|
||||
} else {
|
||||
// Get the transferable amount of the `makerFeeAsset`
|
||||
uint256 transferableMakerFeeAssetAmount = getTransferableAssetAmount(makerAddress, zrxAssetData);
|
||||
|
||||
// If `makerFee` is 0, the % that can be filled is (transferableMakerAssetAmount / makerAssetAmount)
|
||||
if (makerFee == 0) {
|
||||
transferableTakerAssetAmount = getPartialAmountFloor(
|
||||
transferableMakerAssetAmount,
|
||||
order.makerAssetAmount,
|
||||
takerAssetAmount
|
||||
);
|
||||
|
||||
// If `makerAsset` does not equal `makerFeeAsset`, the % that can be filled is the lower of
|
||||
// (transferableMakerAssetAmount / makerAssetAmount) and (transferableMakerAssetFeeAmount / makerFee)
|
||||
} else {
|
||||
uint256 transferableMakerToTakerAmount = getPartialAmountFloor(
|
||||
transferableMakerAssetAmount,
|
||||
order.makerAssetAmount,
|
||||
takerAssetAmount
|
||||
);
|
||||
uint256 transferableMakerFeeToTakerAmount = getPartialAmountFloor(
|
||||
transferableMakerFeeAssetAmount,
|
||||
makerFee,
|
||||
takerAssetAmount
|
||||
);
|
||||
transferableTakerAssetAmount = min256(transferableMakerToTakerAmount, transferableMakerFeeToTakerAmount);
|
||||
}
|
||||
}
|
||||
|
||||
// `fillableTakerAssetAmount` is the lower of the order's remaining `takerAssetAmount` and the `transferableTakerAssetAmount`
|
||||
fillableTakerAssetAmount = min256(
|
||||
safeSub(takerAssetAmount, orderInfo.orderTakerAssetFilledAmount),
|
||||
transferableTakerAssetAmount
|
||||
);
|
||||
|
||||
return (orderInfo, fillableTakerAssetAmount, isValidSignature);
|
||||
}
|
||||
|
||||
/// @dev Fetches all order-relevant information needed to validate if the supplied orders are fillable.
|
||||
/// @param orders Array of order structures.
|
||||
/// @param signatures Array of signatures provided by makers that prove the authenticity of the orders.
|
||||
/// `0x01` can always be provided if a signature does not need to be validated.
|
||||
/// @return The ordersInfo (array of the hash, status, and `takerAssetAmount` already filled for each order),
|
||||
/// fillableTakerAssetAmounts (array of amounts for each order's `takerAssetAmount` that is fillable given all on-chain state),
|
||||
/// and isValidSignature (array containing the validity of each provided signature).
|
||||
/// NOTE: If the `takerAssetData` encodes data for multiple assets, each element of `fillableTakerAssetAmounts`
|
||||
/// will represent a "scaled" amount, meaning it must be multiplied by all the individual asset amounts within
|
||||
/// the `takerAssetData` to get the final amount of each asset that can be filled.
|
||||
function getOrderRelevantStates(LibOrder.Order[] memory orders, bytes[] memory signatures)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
LibOrder.OrderInfo[] memory ordersInfo,
|
||||
uint256[] memory fillableTakerAssetAmounts,
|
||||
bool[] memory isValidSignature
|
||||
)
|
||||
{
|
||||
uint256 length = orders.length;
|
||||
ordersInfo = new LibOrder.OrderInfo[](length);
|
||||
fillableTakerAssetAmounts = new uint256[](length);
|
||||
isValidSignature = new bool[](length);
|
||||
|
||||
for (uint256 i = 0; i != length; i++) {
|
||||
(ordersInfo[i], fillableTakerAssetAmounts[i], isValidSignature[i]) = getOrderRelevantState(
|
||||
orders[i],
|
||||
signatures[i]
|
||||
);
|
||||
}
|
||||
|
||||
return (ordersInfo, fillableTakerAssetAmounts, isValidSignature);
|
||||
}
|
||||
|
||||
/// @dev Gets the amount of an asset transferable by the owner.
|
||||
/// @param ownerAddress Address of the owner of the asset.
|
||||
/// @param assetData Description of tokens, per the AssetProxy contract specification.
|
||||
/// @return The amount of the asset tranferable by the owner.
|
||||
/// NOTE: If the `assetData` encodes data for multiple assets, the `transferableAssetAmount`
|
||||
/// will represent the amount of times the entire `assetData` can be transferred. To calculate
|
||||
/// the total individual transferable amounts, this scaled `transferableAmount` must be multiplied by
|
||||
/// the individual asset amounts located within the `assetData`.
|
||||
function getTransferableAssetAmount(address ownerAddress, bytes memory assetData)
|
||||
public
|
||||
view
|
||||
returns (uint256 transferableAssetAmount)
|
||||
{
|
||||
(uint256 balance, uint256 allowance) = getBalanceAndAssetProxyAllowance(ownerAddress, assetData);
|
||||
transferableAssetAmount = min256(balance, allowance);
|
||||
return transferableAssetAmount;
|
||||
}
|
||||
}
|
91
contracts/dev-utils/package.json
Normal file
91
contracts/dev-utils/package.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"name": "@0x/contracts-dev-utils",
|
||||
"version": "0.0.3",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
"description": "0x protocol specific utility contracts",
|
||||
"main": "lib/src/index.js",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yarn pre_build && tsc -b",
|
||||
"build:ci": "yarn build",
|
||||
"pre_build": "run-s compile generate_contract_wrappers",
|
||||
"test": "yarn run_mocha",
|
||||
"rebuild_and_test": "run-s build test",
|
||||
"test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov",
|
||||
"test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html",
|
||||
"test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha",
|
||||
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit",
|
||||
"compile": "sol-compiler",
|
||||
"watch": "sol-compiler -w",
|
||||
"clean": "shx rm -rf lib generated-artifacts generated-wrappers",
|
||||
"generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../../node_modules/@0x/abi-gen-templates/contract.handlebars --partials '../../node_modules/@0x/abi-gen-templates/partials/**/*.handlebars' --output generated-wrappers --backend ethers",
|
||||
"lint": "tslint --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts",
|
||||
"fix": "tslint --fix --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts",
|
||||
"coverage:report:text": "istanbul report text",
|
||||
"coverage:report:html": "istanbul report html && open coverage/index.html",
|
||||
"profiler:report:html": "istanbul report html && open coverage/index.html",
|
||||
"coverage:report:lcov": "istanbul report lcov",
|
||||
"test:circleci": "yarn test",
|
||||
"contracts:gen": "contracts-gen",
|
||||
"lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol"
|
||||
},
|
||||
"config": {
|
||||
"abis": "./generated-artifacts/@(DevUtils|LibAssetData|LibTransactionDecoder|EthBalanceChecker).json",
|
||||
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually."
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/0xProject/0x-monorepo.git"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/0xProject/0x-monorepo/issues"
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/contracts/dev-utils/README.md",
|
||||
"devDependencies": {
|
||||
"@0x/abi-gen": "^2.1.1",
|
||||
"@0x/contract-wrappers": "^9.1.6",
|
||||
"@0x/contracts-gen": "^1.0.10",
|
||||
"@0x/contracts-test-utils": "^3.1.9",
|
||||
"@0x/dev-utils": "^2.2.4",
|
||||
"@0x/sol-compiler": "^3.1.9",
|
||||
"@0x/tslint-config": "^3.0.1",
|
||||
"@types/lodash": "4.14.104",
|
||||
"@types/node": "*",
|
||||
"chai": "^4.0.1",
|
||||
"chai-as-promised": "^7.1.0",
|
||||
"chai-bignumber": "^3.0.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"make-promises-safe": "^1.1.0",
|
||||
"mocha": "^4.1.0",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"shx": "^0.2.2",
|
||||
"solhint": "^1.4.1",
|
||||
"tslint": "5.11.0",
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/base-contract": "^5.1.1",
|
||||
"@0x/contracts-asset-proxy": "^2.2.1",
|
||||
"@0x/contracts-erc1155": "^1.1.8",
|
||||
"@0x/contracts-erc20": "^2.2.7",
|
||||
"@0x/contracts-erc721": "^2.1.8",
|
||||
"@0x/contracts-exchange": "^2.1.7",
|
||||
"@0x/contracts-exchange-libs": "^3.0.1",
|
||||
"@0x/contracts-utils": "^3.1.8",
|
||||
"@0x/order-utils": "^8.2.1",
|
||||
"@0x/types": "^2.4.0",
|
||||
"@0x/typescript-typings": "^4.2.3",
|
||||
"@0x/utils": "^4.4.0",
|
||||
"@0x/web3-wrapper": "^6.0.7",
|
||||
"ethereum-types": "^2.1.3",
|
||||
"ethereumjs-util": "^5.1.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
15
contracts/dev-utils/src/artifacts.ts
Normal file
15
contracts/dev-utils/src/artifacts.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
|
||||
import * as DevUtils from '../generated-artifacts/DevUtils.json';
|
||||
import * as LibAssetData from '../generated-artifacts/LibAssetData.json';
|
||||
import * as LibTransactionDecoder from '../generated-artifacts/LibTransactionDecoder.json';
|
||||
export const artifacts = {
|
||||
DevUtils: DevUtils as ContractArtifact,
|
||||
LibTransactionDecoder: LibTransactionDecoder as ContractArtifact,
|
||||
LibAssetData: LibAssetData as ContractArtifact,
|
||||
};
|
2
contracts/dev-utils/src/index.ts
Normal file
2
contracts/dev-utils/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './artifacts';
|
||||
export * from './wrappers';
|
8
contracts/dev-utils/src/wrappers.ts
Normal file
8
contracts/dev-utils/src/wrappers.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* -----------------------------------------------------------------------------
|
||||
* Warning: This file is auto-generated by contracts-gen. Don't edit manually.
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
export * from '../generated-wrappers/dev_utils';
|
||||
export * from '../generated-wrappers/lib_asset_data';
|
||||
export * from '../generated-wrappers/lib_transaction_decoder';
|
19
contracts/dev-utils/test/global_hooks.ts
Normal file
19
contracts/dev-utils/test/global_hooks.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { env, EnvVars } from '@0x/dev-utils';
|
||||
|
||||
import { coverage, profiler, provider } from '@0x/contracts-test-utils';
|
||||
import { providerUtils } from '@0x/utils';
|
||||
|
||||
before('start web3 provider', () => {
|
||||
providerUtils.startProviderEngine(provider);
|
||||
});
|
||||
after('generate coverage report', async () => {
|
||||
if (env.parseBoolean(EnvVars.SolidityCoverage)) {
|
||||
const coverageSubprovider = coverage.getCoverageSubproviderSingleton();
|
||||
await coverageSubprovider.writeCoverageAsync();
|
||||
}
|
||||
if (env.parseBoolean(EnvVars.SolidityProfiler)) {
|
||||
const profilerSubprovider = profiler.getProfilerSubproviderSingleton();
|
||||
await profilerSubprovider.writeProfilerOutputAsync();
|
||||
}
|
||||
provider.stop();
|
||||
});
|
527
contracts/dev-utils/test/lib_asset_data.ts
Normal file
527
contracts/dev-utils/test/lib_asset_data.ts
Normal file
@@ -0,0 +1,527 @@
|
||||
import * as chai from 'chai';
|
||||
import { LogWithDecodedArgs } from 'ethereum-types';
|
||||
|
||||
import {
|
||||
artifacts as proxyArtifacts,
|
||||
ERC1155ProxyContract,
|
||||
ERC20ProxyContract,
|
||||
ERC721ProxyContract,
|
||||
MultiAssetProxyContract,
|
||||
StaticCallProxyContract,
|
||||
TestStaticCallTargetContract,
|
||||
} from '@0x/contracts-asset-proxy';
|
||||
import {
|
||||
artifacts as erc1155Artifacts,
|
||||
ERC1155MintableContract,
|
||||
ERC1155TransferSingleEventArgs,
|
||||
} from '@0x/contracts-erc1155';
|
||||
import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { artifacts as erc721Artifacts, DummyERC721TokenContract } from '@0x/contracts-erc721';
|
||||
import { artifacts as exchangeArtifacts, ExchangeContract } from '@0x/contracts-exchange';
|
||||
import { chaiSetup, constants, LogDecoder, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils';
|
||||
import { BlockchainLifecycle } from '@0x/dev-utils';
|
||||
import { assetDataUtils } from '@0x/order-utils';
|
||||
import { AssetProxyId } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
|
||||
import { artifacts, LibAssetDataContract } from '../src';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
const KNOWN_ERC20_ENCODING = {
|
||||
address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48',
|
||||
assetData: '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48',
|
||||
};
|
||||
const KNOWN_ERC721_ENCODING = {
|
||||
address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48',
|
||||
tokenId: new BigNumber(1),
|
||||
assetData:
|
||||
'0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001',
|
||||
};
|
||||
const KNOWN_ERC1155_ENCODING = {
|
||||
tokenAddress: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48',
|
||||
tokenIds: [new BigNumber(100), new BigNumber(1001), new BigNumber(10001)],
|
||||
tokenValues: [new BigNumber(200), new BigNumber(2001), new BigNumber(20001)],
|
||||
callbackData:
|
||||
'0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001',
|
||||
assetData:
|
||||
'0xa7cb5fb70000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000002711000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000007d10000000000000000000000000000000000000000000000000000000000004e210000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000',
|
||||
};
|
||||
const KNOWN_MULTI_ASSET_ENCODING = {
|
||||
amounts: [new BigNumber(70), new BigNumber(1), new BigNumber(18)],
|
||||
nestedAssetData: [
|
||||
KNOWN_ERC20_ENCODING.assetData,
|
||||
KNOWN_ERC721_ENCODING.assetData,
|
||||
KNOWN_ERC1155_ENCODING.assetData,
|
||||
],
|
||||
assetData:
|
||||
'0x94cfcdd7000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000204a7cb5fb70000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000002711000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000007d10000000000000000000000000000000000000000000000000000000000004e210000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c4800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
|
||||
};
|
||||
|
||||
describe('LibAssetData', () => {
|
||||
let exchange: ExchangeContract;
|
||||
let erc20Proxy: ERC20ProxyContract;
|
||||
let erc721Proxy: ERC721ProxyContract;
|
||||
let erc1155Proxy: ERC1155ProxyContract;
|
||||
let multiAssetProxy: MultiAssetProxyContract;
|
||||
let staticCallProxy: StaticCallProxyContract;
|
||||
let staticCallTarget: TestStaticCallTargetContract;
|
||||
let libAssetData: LibAssetDataContract;
|
||||
|
||||
let tokenOwnerAddress: string;
|
||||
|
||||
let erc20Token: DummyERC20TokenContract;
|
||||
let erc721Token: DummyERC721TokenContract;
|
||||
let erc1155Token: ERC1155MintableContract;
|
||||
|
||||
const erc20TokenTotalSupply = new BigNumber(1);
|
||||
|
||||
const firstERC721TokenId = new BigNumber(1);
|
||||
const numberOfERC721Tokens = 10;
|
||||
|
||||
let erc1155TokenId: BigNumber;
|
||||
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
|
||||
exchange = await ExchangeContract.deployFrom0xArtifactAsync(
|
||||
exchangeArtifacts.Exchange,
|
||||
provider,
|
||||
txDefaults,
|
||||
constants.NULL_BYTES,
|
||||
);
|
||||
|
||||
erc20Proxy = await ERC20ProxyContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.ERC20Proxy,
|
||||
provider,
|
||||
txDefaults,
|
||||
);
|
||||
erc721Proxy = await ERC721ProxyContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.ERC721Proxy,
|
||||
provider,
|
||||
txDefaults,
|
||||
);
|
||||
erc1155Proxy = await ERC1155ProxyContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.ERC1155Proxy,
|
||||
provider,
|
||||
txDefaults,
|
||||
);
|
||||
multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.MultiAssetProxy,
|
||||
provider,
|
||||
txDefaults,
|
||||
);
|
||||
staticCallProxy = await StaticCallProxyContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.StaticCallProxy,
|
||||
provider,
|
||||
txDefaults,
|
||||
);
|
||||
|
||||
await exchange.registerAssetProxy.awaitTransactionSuccessAsync(erc20Proxy.address);
|
||||
await exchange.registerAssetProxy.awaitTransactionSuccessAsync(erc721Proxy.address);
|
||||
await exchange.registerAssetProxy.awaitTransactionSuccessAsync(erc1155Proxy.address);
|
||||
await exchange.registerAssetProxy.awaitTransactionSuccessAsync(multiAssetProxy.address);
|
||||
await exchange.registerAssetProxy.awaitTransactionSuccessAsync(staticCallProxy.address);
|
||||
|
||||
libAssetData = await LibAssetDataContract.deployFrom0xArtifactAsync(
|
||||
artifacts.LibAssetData,
|
||||
provider,
|
||||
txDefaults,
|
||||
exchange.address,
|
||||
);
|
||||
|
||||
staticCallTarget = await TestStaticCallTargetContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.TestStaticCallTarget,
|
||||
provider,
|
||||
txDefaults,
|
||||
);
|
||||
|
||||
[tokenOwnerAddress] = await web3Wrapper.getAvailableAddressesAsync();
|
||||
|
||||
erc20Token = await DummyERC20TokenContract.deployFrom0xArtifactAsync(
|
||||
erc20Artifacts.DummyERC20Token,
|
||||
provider,
|
||||
txDefaults,
|
||||
'Dummy',
|
||||
'DUM',
|
||||
new BigNumber(1),
|
||||
erc20TokenTotalSupply,
|
||||
);
|
||||
|
||||
erc721Token = await DummyERC721TokenContract.deployFrom0xArtifactAsync(
|
||||
erc721Artifacts.DummyERC721Token,
|
||||
provider,
|
||||
txDefaults,
|
||||
'Dummy',
|
||||
'DUM',
|
||||
);
|
||||
// mint `numberOfERC721Tokens` tokens
|
||||
const transactionMinedPromises = [];
|
||||
for (let i = 0; i < numberOfERC721Tokens; i++) {
|
||||
transactionMinedPromises.push(
|
||||
erc721Token.mint.awaitTransactionSuccessAsync(tokenOwnerAddress, firstERC721TokenId.plus(i - 1)),
|
||||
);
|
||||
}
|
||||
await Promise.all(transactionMinedPromises);
|
||||
|
||||
erc1155Token = await ERC1155MintableContract.deployFrom0xArtifactAsync(
|
||||
erc1155Artifacts.ERC1155Mintable,
|
||||
provider,
|
||||
txDefaults,
|
||||
);
|
||||
|
||||
const logDecoder = new LogDecoder(web3Wrapper, erc1155Artifacts);
|
||||
const transactionReceipt = await logDecoder.getTxWithDecodedLogsAsync(
|
||||
await erc1155Token.create.sendTransactionAsync('uri:Dummy', /*isNonFungible:*/ false),
|
||||
);
|
||||
|
||||
// tslint:disable-next-line no-unnecessary-type-assertion
|
||||
erc1155TokenId = (transactionReceipt.logs[0] as LogWithDecodedArgs<ERC1155TransferSingleEventArgs>).args.id;
|
||||
await erc1155Token.mintFungible.awaitTransactionSuccessAsync(
|
||||
erc1155TokenId,
|
||||
[tokenOwnerAddress],
|
||||
[new BigNumber(1)],
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
|
||||
it('should have a deployed-to address', () => {
|
||||
expect(libAssetData.address.slice(0, 2)).to.equal('0x');
|
||||
});
|
||||
|
||||
describe('encoding and decoding', () => {
|
||||
it('should encode ERC20 asset data', async () => {
|
||||
expect(await libAssetData.encodeERC20AssetData.callAsync(KNOWN_ERC20_ENCODING.address)).to.equal(
|
||||
KNOWN_ERC20_ENCODING.assetData,
|
||||
);
|
||||
});
|
||||
|
||||
it('should decode ERC20 asset data', async () => {
|
||||
expect(await libAssetData.decodeERC20AssetData.callAsync(KNOWN_ERC20_ENCODING.assetData)).to.deep.equal([
|
||||
AssetProxyId.ERC20,
|
||||
KNOWN_ERC20_ENCODING.address,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should encode ERC721 asset data', async () => {
|
||||
expect(
|
||||
await libAssetData.encodeERC721AssetData.callAsync(
|
||||
KNOWN_ERC721_ENCODING.address,
|
||||
KNOWN_ERC721_ENCODING.tokenId,
|
||||
),
|
||||
).to.equal(KNOWN_ERC721_ENCODING.assetData);
|
||||
});
|
||||
|
||||
it('should decode ERC721 asset data', async () => {
|
||||
expect(await libAssetData.decodeERC721AssetData.callAsync(KNOWN_ERC721_ENCODING.assetData)).to.deep.equal([
|
||||
AssetProxyId.ERC721,
|
||||
KNOWN_ERC721_ENCODING.address,
|
||||
KNOWN_ERC721_ENCODING.tokenId,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should encode ERC1155 asset data', async () => {
|
||||
expect(
|
||||
await libAssetData.encodeERC1155AssetData.callAsync(
|
||||
KNOWN_ERC1155_ENCODING.tokenAddress,
|
||||
KNOWN_ERC1155_ENCODING.tokenIds,
|
||||
KNOWN_ERC1155_ENCODING.tokenValues,
|
||||
KNOWN_ERC1155_ENCODING.callbackData,
|
||||
),
|
||||
).to.equal(KNOWN_ERC1155_ENCODING.assetData);
|
||||
});
|
||||
|
||||
it('should decode ERC1155 asset data', async () => {
|
||||
expect(await libAssetData.decodeERC1155AssetData.callAsync(KNOWN_ERC1155_ENCODING.assetData)).to.deep.equal(
|
||||
[
|
||||
AssetProxyId.ERC1155,
|
||||
KNOWN_ERC1155_ENCODING.tokenAddress,
|
||||
KNOWN_ERC1155_ENCODING.tokenIds,
|
||||
KNOWN_ERC1155_ENCODING.tokenValues,
|
||||
KNOWN_ERC1155_ENCODING.callbackData,
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
it('should encode multiasset data', async () => {
|
||||
expect(
|
||||
await libAssetData.encodeMultiAssetData.callAsync(
|
||||
KNOWN_MULTI_ASSET_ENCODING.amounts,
|
||||
KNOWN_MULTI_ASSET_ENCODING.nestedAssetData,
|
||||
),
|
||||
).to.equal(KNOWN_MULTI_ASSET_ENCODING.assetData);
|
||||
});
|
||||
|
||||
it('should decode multiasset data', async () => {
|
||||
expect(
|
||||
await libAssetData.decodeMultiAssetData.callAsync(KNOWN_MULTI_ASSET_ENCODING.assetData),
|
||||
).to.deep.equal([
|
||||
AssetProxyId.MultiAsset,
|
||||
KNOWN_MULTI_ASSET_ENCODING.amounts,
|
||||
KNOWN_MULTI_ASSET_ENCODING.nestedAssetData,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBalance', () => {
|
||||
it('should query ERC20 balance by asset data', async () => {
|
||||
const assetData = assetDataUtils.encodeERC20AssetData(erc20Token.address);
|
||||
expect(await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData)).to.bignumber.equal(
|
||||
erc20TokenTotalSupply,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return 0 if ERC20 token does not exist', async () => {
|
||||
const assetData = assetDataUtils.encodeERC20AssetData(constants.NULL_ADDRESS);
|
||||
const balance = await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData);
|
||||
expect(balance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
|
||||
it('should query ERC721 balance by asset data', async () => {
|
||||
const assetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, firstERC721TokenId);
|
||||
expect(await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData)).to.bignumber.equal(1);
|
||||
});
|
||||
|
||||
it('should return 0 if ERC721 token does not exist', async () => {
|
||||
const assetData = assetDataUtils.encodeERC721AssetData(constants.NULL_ADDRESS, firstERC721TokenId);
|
||||
const balance = await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData);
|
||||
expect(balance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
|
||||
it('should query ERC1155 balances by asset data', async () => {
|
||||
const assetData = assetDataUtils.encodeERC1155AssetData(
|
||||
erc1155Token.address,
|
||||
[erc1155TokenId],
|
||||
[new BigNumber(1)],
|
||||
constants.NULL_BYTES,
|
||||
);
|
||||
expect(await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData)).to.bignumber.equal(1);
|
||||
});
|
||||
|
||||
it('should return 0 if ERC1155 token does not exist', async () => {
|
||||
const assetData = assetDataUtils.encodeERC1155AssetData(
|
||||
constants.NULL_ADDRESS,
|
||||
[erc1155TokenId],
|
||||
[new BigNumber(1)],
|
||||
constants.NULL_BYTES,
|
||||
);
|
||||
const balance = await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData);
|
||||
expect(balance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
|
||||
it('should query multi-asset batch balance by asset data', async () => {
|
||||
const assetData = assetDataUtils.encodeMultiAssetData(
|
||||
[new BigNumber(1), new BigNumber(1)],
|
||||
[
|
||||
assetDataUtils.encodeERC20AssetData(erc20Token.address),
|
||||
assetDataUtils.encodeERC721AssetData(erc721Token.address, firstERC721TokenId),
|
||||
],
|
||||
);
|
||||
expect(await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData)).to.bignumber.equal(
|
||||
Math.min(erc20TokenTotalSupply.toNumber(), numberOfERC721Tokens),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return a balance of 0 if the assetData does not correspond to an AssetProxy contract', async () => {
|
||||
const fakeAssetData = '0x01020304';
|
||||
const balance = await libAssetData.getBalance.callAsync(tokenOwnerAddress, fakeAssetData);
|
||||
expect(balance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
|
||||
it('should return a balance of MAX_UINT256 if the the StaticCallProxy assetData contains data for a successful staticcall', async () => {
|
||||
const staticCallData = staticCallTarget.isOddNumber.getABIEncodedTransactionData(new BigNumber(1));
|
||||
const trueAsBuffer = ethUtil.toBuffer('0x0000000000000000000000000000000000000000000000000000000000000001');
|
||||
const expectedResultHash = ethUtil.bufferToHex(ethUtil.sha3(trueAsBuffer));
|
||||
const assetData = assetDataUtils.encodeStaticCallAssetData(
|
||||
staticCallTarget.address,
|
||||
staticCallData,
|
||||
expectedResultHash,
|
||||
);
|
||||
const balance = await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData);
|
||||
expect(balance).to.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
|
||||
it('should return a balance of 0 if the the StaticCallProxy assetData contains data for an unsuccessful staticcall', async () => {
|
||||
const staticCallData = staticCallTarget.isOddNumber.getABIEncodedTransactionData(new BigNumber(0));
|
||||
const trueAsBuffer = ethUtil.toBuffer('0x0000000000000000000000000000000000000000000000000000000000000001');
|
||||
const expectedResultHash = ethUtil.bufferToHex(ethUtil.sha3(trueAsBuffer));
|
||||
const assetData = assetDataUtils.encodeStaticCallAssetData(
|
||||
staticCallTarget.address,
|
||||
staticCallData,
|
||||
expectedResultHash,
|
||||
);
|
||||
const balance = await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData);
|
||||
expect(balance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAssetProxyAllowance', () => {
|
||||
it('should query ERC20 allowances by asset data', async () => {
|
||||
const allowance = new BigNumber(1);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, allowance, {
|
||||
from: tokenOwnerAddress,
|
||||
});
|
||||
const assetData = assetDataUtils.encodeERC20AssetData(erc20Token.address);
|
||||
expect(
|
||||
await libAssetData.getAssetProxyAllowance.callAsync(tokenOwnerAddress, assetData),
|
||||
).to.bignumber.equal(allowance);
|
||||
});
|
||||
|
||||
it('should query ERC721 approval by asset data', async () => {
|
||||
await erc721Token.approve.awaitTransactionSuccessAsync(erc721Proxy.address, firstERC721TokenId, {
|
||||
from: tokenOwnerAddress,
|
||||
});
|
||||
const assetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, firstERC721TokenId);
|
||||
expect(
|
||||
await libAssetData.getAssetProxyAllowance.callAsync(tokenOwnerAddress, assetData),
|
||||
).to.bignumber.equal(1);
|
||||
});
|
||||
|
||||
it('should query ERC721 approvalForAll by assetData', async () => {
|
||||
await erc721Token.setApprovalForAll.awaitTransactionSuccessAsync(erc721Proxy.address, true, {
|
||||
from: tokenOwnerAddress,
|
||||
});
|
||||
const assetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, firstERC721TokenId);
|
||||
expect(
|
||||
await libAssetData.getAssetProxyAllowance.callAsync(tokenOwnerAddress, assetData),
|
||||
).to.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
|
||||
it('should query ERC1155 allowances by asset data', async () => {
|
||||
await erc1155Token.setApprovalForAll.awaitTransactionSuccessAsync(erc1155Proxy.address, true, {
|
||||
from: tokenOwnerAddress,
|
||||
});
|
||||
const assetData = assetDataUtils.encodeERC1155AssetData(
|
||||
erc1155Token.address,
|
||||
[erc1155TokenId],
|
||||
[new BigNumber(1)],
|
||||
constants.NULL_BYTES,
|
||||
);
|
||||
expect(
|
||||
await libAssetData.getAssetProxyAllowance.callAsync(tokenOwnerAddress, assetData),
|
||||
).to.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
|
||||
it('should query multi-asset allowances by asset data', async () => {
|
||||
const allowance = new BigNumber(1);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, allowance, {
|
||||
from: tokenOwnerAddress,
|
||||
});
|
||||
await erc721Token.approve.awaitTransactionSuccessAsync(erc721Proxy.address, firstERC721TokenId, {
|
||||
from: tokenOwnerAddress,
|
||||
});
|
||||
const assetData = assetDataUtils.encodeMultiAssetData(
|
||||
[new BigNumber(1), new BigNumber(1)],
|
||||
[
|
||||
assetDataUtils.encodeERC20AssetData(erc20Token.address),
|
||||
assetDataUtils.encodeERC721AssetData(erc721Token.address, firstERC721TokenId),
|
||||
],
|
||||
);
|
||||
expect(
|
||||
await libAssetData.getAssetProxyAllowance.callAsync(tokenOwnerAddress, assetData),
|
||||
).to.bignumber.equal(1);
|
||||
return;
|
||||
});
|
||||
|
||||
it('should return an allowance of 0 if the assetData does not correspond to an AssetProxy contract', async () => {
|
||||
const fakeAssetData = '0x01020304';
|
||||
const allowance = await libAssetData.getAssetProxyAllowance.callAsync(tokenOwnerAddress, fakeAssetData);
|
||||
expect(allowance).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
|
||||
it('should return an allowance of MAX_UINT256 for any staticCallAssetData', async () => {
|
||||
const staticCallData = AssetProxyId.StaticCall;
|
||||
const assetData = assetDataUtils.encodeStaticCallAssetData(
|
||||
staticCallTarget.address,
|
||||
staticCallData,
|
||||
constants.KECCAK256_NULL,
|
||||
);
|
||||
const allowance = await libAssetData.getAssetProxyAllowance.callAsync(tokenOwnerAddress, assetData);
|
||||
expect(allowance).to.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBatchBalances', () => {
|
||||
it('should query balances for a batch of asset data strings', async () => {
|
||||
const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20Token.address);
|
||||
const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, firstERC721TokenId);
|
||||
expect(
|
||||
await libAssetData.getBatchBalances.callAsync(tokenOwnerAddress, [erc20AssetData, erc721AssetData]),
|
||||
).to.deep.equal([new BigNumber(erc20TokenTotalSupply), new BigNumber(1)]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getERC721TokenOwner', async () => {
|
||||
it('should return the null address when tokenId is not owned', async () => {
|
||||
const nonexistentTokenId = new BigNumber(1234567890);
|
||||
expect(
|
||||
await libAssetData.getERC721TokenOwner.callAsync(erc721Token.address, nonexistentTokenId),
|
||||
).to.be.equal(constants.NULL_ADDRESS);
|
||||
});
|
||||
it('should return the owner address when tokenId is owned', async () => {
|
||||
expect(
|
||||
await libAssetData.getERC721TokenOwner.callAsync(erc721Token.address, firstERC721TokenId),
|
||||
).to.be.equal(tokenOwnerAddress);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBalanceAndAllowance', () => {
|
||||
it('should query balance and allowance together, from asset data', async () => {
|
||||
const allowance = new BigNumber(1);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, allowance, {
|
||||
from: tokenOwnerAddress,
|
||||
});
|
||||
const assetData = assetDataUtils.encodeERC20AssetData(erc20Token.address);
|
||||
expect(
|
||||
await libAssetData.getBalanceAndAssetProxyAllowance.callAsync(tokenOwnerAddress, assetData),
|
||||
).to.deep.equal([new BigNumber(erc20TokenTotalSupply), allowance]);
|
||||
});
|
||||
});
|
||||
describe('getBatchBalancesAndAllowances', () => {
|
||||
it('should query balances and allowances together, from an asset data array', async () => {
|
||||
const allowance = new BigNumber(1);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, allowance, {
|
||||
from: tokenOwnerAddress,
|
||||
});
|
||||
const assetData = assetDataUtils.encodeERC20AssetData(erc20Token.address);
|
||||
expect(
|
||||
await libAssetData.getBatchBalancesAndAssetProxyAllowances.callAsync(tokenOwnerAddress, [assetData]),
|
||||
).to.deep.equal([[new BigNumber(erc20TokenTotalSupply)], [allowance]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBatchAssetProxyAllowances', () => {
|
||||
it('should query allowances for a batch of asset data strings', async () => {
|
||||
const allowance = new BigNumber(1);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, allowance, {
|
||||
from: tokenOwnerAddress,
|
||||
});
|
||||
await erc721Token.approve.awaitTransactionSuccessAsync(erc721Proxy.address, firstERC721TokenId, {
|
||||
from: tokenOwnerAddress,
|
||||
});
|
||||
const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20Token.address);
|
||||
const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, firstERC721TokenId);
|
||||
expect(
|
||||
await libAssetData.getBatchAssetProxyAllowances.callAsync(tokenOwnerAddress, [
|
||||
erc20AssetData,
|
||||
erc721AssetData,
|
||||
]),
|
||||
).to.deep.equal([new BigNumber(1), new BigNumber(1)]);
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:disable:max-file-line-count
|
139
contracts/dev-utils/test/lib_transaction_decoder.ts
Normal file
139
contracts/dev-utils/test/lib_transaction_decoder.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { IExchangeContract } from '@0x/contracts-exchange';
|
||||
import { chaiSetup, constants, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils';
|
||||
import { BlockchainLifecycle } from '@0x/dev-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as chai from 'chai';
|
||||
|
||||
import { artifacts, LibTransactionDecoderContract } from '../src';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
const order = {
|
||||
makerAddress: '0xe36ea790bc9d7ab70c55260c66d52b1eca985f84',
|
||||
takerAddress: '0x0000000000000000000000000000000000000000',
|
||||
feeRecipientAddress: '0x78dc5d2d739606d31509c31d654056a45185ecb6',
|
||||
senderAddress: '0x6ecbe1db9ef729cbe972c83fb886247691fb6beb',
|
||||
makerAssetAmount: new BigNumber('100000000000000000000'),
|
||||
takerAssetAmount: new BigNumber('200000000000000000000'),
|
||||
makerFee: new BigNumber('1000000000000000000'),
|
||||
takerFee: new BigNumber('1000000000000000000'),
|
||||
expirationTimeSeconds: new BigNumber('1552396423'),
|
||||
salt: new BigNumber('66097384406870180066678463045003379626790660770396923976862707230261946348951'),
|
||||
makerAssetData: '0xf47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064',
|
||||
takerAssetData: '0xf47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e3',
|
||||
};
|
||||
const takerAssetFillAmount = new BigNumber('100000000000000000000');
|
||||
const signature =
|
||||
'0x1ce8e3c600d933423172b5021158a6be2e818613ff8e762d70ef490c752fd98a626a215f09f169668990414de75a53da221c294a3002f796d004827258b641876e03';
|
||||
|
||||
describe('LibTransactionDecoder', () => {
|
||||
let libTxDecoder: LibTransactionDecoderContract;
|
||||
const exchangeInterface = new IExchangeContract(constants.NULL_ADDRESS, provider, txDefaults);
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
libTxDecoder = await LibTransactionDecoderContract.deployFrom0xArtifactAsync(
|
||||
artifacts.LibTransactionDecoder,
|
||||
provider,
|
||||
txDefaults,
|
||||
);
|
||||
});
|
||||
after(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
|
||||
it('should decode an Exchange.batchCancelOrders() transaction', async () => {
|
||||
const input = exchangeInterface.batchCancelOrders.getABIEncodedTransactionData([order, order]);
|
||||
expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([
|
||||
'batchCancelOrders',
|
||||
[order, order],
|
||||
[],
|
||||
[],
|
||||
]);
|
||||
});
|
||||
|
||||
for (const func of ['batchFillOrders', 'batchFillOrdersNoThrow', 'batchFillOrKillOrders']) {
|
||||
const input = (exchangeInterface as any)[func].getABIEncodedTransactionData(
|
||||
[order, order],
|
||||
[takerAssetFillAmount, takerAssetFillAmount],
|
||||
[signature, signature],
|
||||
);
|
||||
it(`should decode an Exchange.${func}() transaction`, async () => {
|
||||
expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([
|
||||
func,
|
||||
[order, order],
|
||||
[takerAssetFillAmount, takerAssetFillAmount],
|
||||
[signature, signature],
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
it('should decode an Exchange.cancelOrder() transaction', async () => {
|
||||
const input = exchangeInterface.cancelOrder.getABIEncodedTransactionData(order);
|
||||
expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([
|
||||
'cancelOrder',
|
||||
[order],
|
||||
[],
|
||||
[],
|
||||
]);
|
||||
});
|
||||
|
||||
for (const func of ['fillOrder', 'fillOrderNoThrow', 'fillOrKillOrder']) {
|
||||
const input = (exchangeInterface as any)[func].getABIEncodedTransactionData(
|
||||
order,
|
||||
takerAssetFillAmount,
|
||||
signature,
|
||||
);
|
||||
it(`should decode an Exchange.${func}() transaction`, async () => {
|
||||
expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([
|
||||
func,
|
||||
[order],
|
||||
[takerAssetFillAmount],
|
||||
[signature],
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
for (const func of ['marketBuyOrders', 'marketBuyOrdersNoThrow', 'marketSellOrders', 'marketSellOrdersNoThrow']) {
|
||||
const input = (exchangeInterface as any)[func].getABIEncodedTransactionData(
|
||||
[order, order],
|
||||
takerAssetFillAmount,
|
||||
[signature, signature],
|
||||
);
|
||||
it(`should decode an Exchange.${func}() transaction`, async () => {
|
||||
expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([
|
||||
func,
|
||||
[order, order],
|
||||
[takerAssetFillAmount],
|
||||
[signature, signature],
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
it('should decode an Exchange.matchOrders() transaction', async () => {
|
||||
const complementaryOrder = {
|
||||
...order,
|
||||
makerAddress: order.takerAddress,
|
||||
takerAddress: order.makerAddress,
|
||||
makerAssetData: order.takerAssetData,
|
||||
takerAssetData: order.makerAssetData,
|
||||
makerAssetAmount: order.takerAssetAmount,
|
||||
takerAssetAmount: order.makerAssetAmount,
|
||||
makerFee: order.takerFee,
|
||||
takerFee: order.makerFee,
|
||||
};
|
||||
const input = exchangeInterface.matchOrders.getABIEncodedTransactionData(
|
||||
order,
|
||||
complementaryOrder,
|
||||
signature,
|
||||
signature,
|
||||
);
|
||||
expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([
|
||||
'matchOrders',
|
||||
[order, complementaryOrder],
|
||||
[order.takerAssetAmount, complementaryOrder.takerAssetAmount],
|
||||
[signature, signature],
|
||||
]);
|
||||
});
|
||||
});
|
442
contracts/dev-utils/test/order_validation_utils.ts
Normal file
442
contracts/dev-utils/test/order_validation_utils.ts
Normal file
@@ -0,0 +1,442 @@
|
||||
import {
|
||||
artifacts as proxyArtifacts,
|
||||
ERC20ProxyContract,
|
||||
ERC20Wrapper,
|
||||
ERC721ProxyContract,
|
||||
ERC721Wrapper,
|
||||
MultiAssetProxyContract,
|
||||
} from '@0x/contracts-asset-proxy';
|
||||
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
|
||||
import { DummyERC721TokenContract } from '@0x/contracts-erc721';
|
||||
import { artifacts as exchangeArtifacts, ExchangeContract, ExchangeWrapper } from '@0x/contracts-exchange';
|
||||
import {
|
||||
chaiSetup,
|
||||
constants,
|
||||
OrderFactory,
|
||||
OrderStatus,
|
||||
provider,
|
||||
txDefaults,
|
||||
web3Wrapper,
|
||||
} from '@0x/contracts-test-utils';
|
||||
import { BlockchainLifecycle } from '@0x/dev-utils';
|
||||
import { assetDataUtils, orderHashUtils } from '@0x/order-utils';
|
||||
import { SignedOrder } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as chai from 'chai';
|
||||
|
||||
import { artifacts, DevUtilsContract } from '../src';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
describe('OrderValidationUtils', () => {
|
||||
let makerAddress: string;
|
||||
let takerAddress: string;
|
||||
let owner: string;
|
||||
let erc20AssetData: string;
|
||||
let erc20AssetData2: string;
|
||||
let erc721AssetData: string;
|
||||
let zrxAssetData: string;
|
||||
|
||||
let erc20Token: DummyERC20TokenContract;
|
||||
let erc20Token2: DummyERC20TokenContract;
|
||||
let zrxToken: DummyERC20TokenContract;
|
||||
let erc721Token: DummyERC721TokenContract;
|
||||
let exchange: ExchangeContract;
|
||||
let devUtils: DevUtilsContract;
|
||||
let erc20Proxy: ERC20ProxyContract;
|
||||
let erc721Proxy: ERC721ProxyContract;
|
||||
let multiAssetProxy: MultiAssetProxyContract;
|
||||
|
||||
let signedOrder: SignedOrder;
|
||||
let orderFactory: OrderFactory;
|
||||
|
||||
const tokenId = new BigNumber(123456789);
|
||||
|
||||
before(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
after(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
const accounts = await web3Wrapper.getAvailableAddressesAsync();
|
||||
const usedAddresses = ([owner, makerAddress, takerAddress] = accounts.slice(0, 3));
|
||||
|
||||
const erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
|
||||
const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner);
|
||||
|
||||
const numDummyErc20ToDeploy = 3;
|
||||
[erc20Token, zrxToken, erc20Token2] = await erc20Wrapper.deployDummyTokensAsync(
|
||||
numDummyErc20ToDeploy,
|
||||
constants.DUMMY_TOKEN_DECIMALS,
|
||||
);
|
||||
erc20Proxy = await erc20Wrapper.deployProxyAsync();
|
||||
|
||||
[erc721Token] = await erc721Wrapper.deployDummyTokensAsync();
|
||||
erc721Proxy = await erc721Wrapper.deployProxyAsync();
|
||||
|
||||
zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address);
|
||||
exchange = await ExchangeContract.deployFrom0xArtifactAsync(
|
||||
exchangeArtifacts.Exchange,
|
||||
provider,
|
||||
txDefaults,
|
||||
zrxAssetData,
|
||||
);
|
||||
|
||||
multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync(
|
||||
proxyArtifacts.MultiAssetProxy,
|
||||
provider,
|
||||
txDefaults,
|
||||
);
|
||||
const exchangeWrapper = new ExchangeWrapper(exchange, provider);
|
||||
await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner);
|
||||
await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner);
|
||||
await exchangeWrapper.registerAssetProxyAsync(multiAssetProxy.address, owner);
|
||||
await erc20Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(exchange.address, { from: owner });
|
||||
await erc721Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(exchange.address, { from: owner });
|
||||
|
||||
devUtils = await DevUtilsContract.deployFrom0xArtifactAsync(
|
||||
artifacts.DevUtils,
|
||||
provider,
|
||||
txDefaults,
|
||||
exchange.address,
|
||||
zrxAssetData,
|
||||
);
|
||||
|
||||
erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20Token.address);
|
||||
erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20Token2.address);
|
||||
erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId);
|
||||
const defaultOrderParams = {
|
||||
...constants.STATIC_ORDER_PARAMS,
|
||||
exchangeAddress: exchange.address,
|
||||
makerAddress,
|
||||
feeRecipientAddress: constants.NULL_ADDRESS,
|
||||
makerAssetData: erc20AssetData,
|
||||
takerAssetData: erc20AssetData2,
|
||||
};
|
||||
const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)];
|
||||
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
|
||||
describe('getTransferableAssetAmount', () => {
|
||||
it('should return the balance when balance < allowance', async () => {
|
||||
const balance = new BigNumber(123);
|
||||
const allowance = new BigNumber(456);
|
||||
await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, balance);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, allowance, {
|
||||
from: makerAddress,
|
||||
});
|
||||
const transferableAmount = await devUtils.getTransferableAssetAmount.callAsync(
|
||||
makerAddress,
|
||||
erc20AssetData,
|
||||
);
|
||||
expect(transferableAmount).to.bignumber.equal(balance);
|
||||
});
|
||||
it('should return the allowance when allowance < balance', async () => {
|
||||
const balance = new BigNumber(456);
|
||||
const allowance = new BigNumber(123);
|
||||
await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, balance);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, allowance, {
|
||||
from: makerAddress,
|
||||
});
|
||||
const transferableAmount = await devUtils.getTransferableAssetAmount.callAsync(
|
||||
makerAddress,
|
||||
erc20AssetData,
|
||||
);
|
||||
expect(transferableAmount).to.bignumber.equal(allowance);
|
||||
});
|
||||
it('should return the correct transferable amount for multiAssetData', async () => {
|
||||
const multiAssetData = assetDataUtils.encodeMultiAssetData(
|
||||
[new BigNumber(1), new BigNumber(1)],
|
||||
[erc20AssetData, erc20AssetData2],
|
||||
);
|
||||
const transferableAmount1 = new BigNumber(10);
|
||||
const transferableAmount2 = new BigNumber(5);
|
||||
await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, transferableAmount1);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, transferableAmount1, {
|
||||
from: makerAddress,
|
||||
});
|
||||
await erc20Token2.setBalance.awaitTransactionSuccessAsync(makerAddress, transferableAmount2);
|
||||
await erc20Token2.approve.awaitTransactionSuccessAsync(erc20Proxy.address, transferableAmount2, {
|
||||
from: makerAddress,
|
||||
});
|
||||
const transferableAmount = await devUtils.getTransferableAssetAmount.callAsync(
|
||||
makerAddress,
|
||||
multiAssetData,
|
||||
);
|
||||
expect(transferableAmount).to.bignumber.equal(transferableAmount2);
|
||||
});
|
||||
});
|
||||
describe('getOrderRelevantState', () => {
|
||||
beforeEach(async () => {
|
||||
signedOrder = await orderFactory.newSignedOrderAsync();
|
||||
});
|
||||
it('should return the correct orderInfo when the order is valid', async () => {
|
||||
const [orderInfo] = await devUtils.getOrderRelevantState.callAsync(signedOrder, signedOrder.signature);
|
||||
expect(orderInfo.orderHash).to.equal(orderHashUtils.getOrderHashHex(signedOrder));
|
||||
expect(orderInfo.orderStatus).to.equal(OrderStatus.Fillable);
|
||||
expect(orderInfo.orderTakerAssetFilledAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
it('should return isValidSignature=true when the signature is valid', async () => {
|
||||
const [, , isValidSignature] = await devUtils.getOrderRelevantState.callAsync(
|
||||
signedOrder,
|
||||
signedOrder.signature,
|
||||
);
|
||||
expect(isValidSignature).to.equal(true);
|
||||
});
|
||||
it('should return isValidSignature=false when the signature is invalid', async () => {
|
||||
const invalidSignature = '0x01';
|
||||
const [, , isValidSignature] = await devUtils.getOrderRelevantState.callAsync(
|
||||
signedOrder,
|
||||
invalidSignature,
|
||||
);
|
||||
expect(isValidSignature).to.equal(false);
|
||||
});
|
||||
it('should return a fillableTakerAssetAmount of 0 when balances or allowances are insufficient', async () => {
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync(
|
||||
signedOrder,
|
||||
signedOrder.signature,
|
||||
);
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
it('should return a fillableTakerAssetAmount of 0 when fee balances/allowances are insufficient', async () => {
|
||||
await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, {
|
||||
from: makerAddress,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync(
|
||||
signedOrder,
|
||||
signedOrder.signature,
|
||||
);
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
it('should return a fillableTakerAssetAmount of 0 when balances/allowances of one asset within a multiAssetData are insufficient', async () => {
|
||||
const multiAssetData = assetDataUtils.encodeMultiAssetData(
|
||||
[new BigNumber(1), new BigNumber(1)],
|
||||
[erc20AssetData, erc20AssetData2],
|
||||
);
|
||||
signedOrder = await orderFactory.newSignedOrderAsync({ makerAssetData: multiAssetData });
|
||||
await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, {
|
||||
from: makerAddress,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync(
|
||||
signedOrder,
|
||||
signedOrder.signature,
|
||||
);
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
it('should return the correct fillableTakerAssetAmount when fee balances/allowances are partially sufficient', async () => {
|
||||
await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, {
|
||||
from: makerAddress,
|
||||
});
|
||||
const divisor = 4;
|
||||
await zrxToken.setBalance.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
signedOrder.makerFee.dividedToIntegerBy(divisor),
|
||||
);
|
||||
await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerFee, {
|
||||
from: makerAddress,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync(
|
||||
signedOrder,
|
||||
signedOrder.signature,
|
||||
);
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(
|
||||
signedOrder.takerAssetAmount.dividedToIntegerBy(divisor),
|
||||
);
|
||||
});
|
||||
it('should return the correct fillableTakerAssetAmount when non-fee balances/allowances are partially sufficient', async () => {
|
||||
const divisor = 4;
|
||||
await erc20Token.setBalance.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
signedOrder.makerAssetAmount.dividedToIntegerBy(divisor),
|
||||
);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, {
|
||||
from: makerAddress,
|
||||
});
|
||||
await zrxToken.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerFee);
|
||||
await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerFee, {
|
||||
from: makerAddress,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync(
|
||||
signedOrder,
|
||||
signedOrder.signature,
|
||||
);
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(
|
||||
signedOrder.takerAssetAmount.dividedToIntegerBy(divisor),
|
||||
);
|
||||
});
|
||||
it('should return the correct fillableTakerAssetAmount when balances/allowances of one asset within a multiAssetData are partially sufficient', async () => {
|
||||
const multiAssetData = assetDataUtils.encodeMultiAssetData(
|
||||
[new BigNumber(1), new BigNumber(1)],
|
||||
[erc20AssetData, erc20AssetData2],
|
||||
);
|
||||
signedOrder = await orderFactory.newSignedOrderAsync({ makerAssetData: multiAssetData });
|
||||
await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, {
|
||||
from: makerAddress,
|
||||
});
|
||||
await zrxToken.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerFee);
|
||||
await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerFee, {
|
||||
from: makerAddress,
|
||||
});
|
||||
const divisor = 4;
|
||||
await erc20Token2.setBalance.awaitTransactionSuccessAsync(
|
||||
makerAddress,
|
||||
signedOrder.makerAssetAmount.dividedToIntegerBy(divisor),
|
||||
);
|
||||
await erc20Token2.approve.awaitTransactionSuccessAsync(
|
||||
erc20Proxy.address,
|
||||
signedOrder.makerAssetAmount.dividedToIntegerBy(divisor),
|
||||
{
|
||||
from: makerAddress,
|
||||
},
|
||||
);
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync(
|
||||
signedOrder,
|
||||
signedOrder.signature,
|
||||
);
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(
|
||||
signedOrder.takerAssetAmount.dividedToIntegerBy(divisor),
|
||||
);
|
||||
});
|
||||
it('should return a fillableTakerAssetAmount of 0 when non-fee balances/allowances are insufficient', async () => {
|
||||
await zrxToken.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerFee);
|
||||
await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerFee, {
|
||||
from: makerAddress,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync(
|
||||
signedOrder,
|
||||
signedOrder.signature,
|
||||
);
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
});
|
||||
it('should return a fillableTakerAssetAmount equal to the takerAssetAmount when the order is unfilled and balances/allowances are sufficient', async () => {
|
||||
await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, {
|
||||
from: makerAddress,
|
||||
});
|
||||
await zrxToken.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerFee);
|
||||
await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerFee, {
|
||||
from: makerAddress,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync(
|
||||
signedOrder,
|
||||
signedOrder.signature,
|
||||
);
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(signedOrder.takerAssetAmount);
|
||||
});
|
||||
it('should return the correct fillableTakerAssetAmount when balances/allowances are partially sufficient and makerAsset=makerFeeAsset', async () => {
|
||||
signedOrder = await orderFactory.newSignedOrderAsync({
|
||||
makerAssetData: zrxAssetData,
|
||||
makerAssetAmount: new BigNumber(10),
|
||||
takerAssetAmount: new BigNumber(20),
|
||||
makerFee: new BigNumber(40),
|
||||
});
|
||||
const transferableMakerAssetAmount = new BigNumber(10);
|
||||
await zrxToken.setBalance.awaitTransactionSuccessAsync(makerAddress, transferableMakerAssetAmount);
|
||||
await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, transferableMakerAssetAmount, {
|
||||
from: makerAddress,
|
||||
});
|
||||
const expectedFillableTakerAssetAmount = transferableMakerAssetAmount
|
||||
.times(signedOrder.takerAssetAmount)
|
||||
.dividedToIntegerBy(signedOrder.makerAssetAmount.plus(signedOrder.makerFee));
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync(
|
||||
signedOrder,
|
||||
signedOrder.signature,
|
||||
);
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(expectedFillableTakerAssetAmount);
|
||||
});
|
||||
it('should return the correct fillabeTakerassetAmount when makerAsset balances/allowances are sufficient and there are no maker fees', async () => {
|
||||
signedOrder = await orderFactory.newSignedOrderAsync({ makerFee: constants.ZERO_AMOUNT });
|
||||
await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, {
|
||||
from: makerAddress,
|
||||
});
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync(
|
||||
signedOrder,
|
||||
signedOrder.signature,
|
||||
);
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(signedOrder.takerAssetAmount);
|
||||
});
|
||||
it('should return a fillableTakerAssetAmount when the remaining takerAssetAmount is less than the transferable amount', async () => {
|
||||
await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, {
|
||||
from: makerAddress,
|
||||
});
|
||||
await zrxToken.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerFee);
|
||||
await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerFee, {
|
||||
from: makerAddress,
|
||||
});
|
||||
await erc20Token2.setBalance.awaitTransactionSuccessAsync(takerAddress, signedOrder.takerAssetAmount);
|
||||
await erc20Token2.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.takerAssetAmount, {
|
||||
from: takerAddress,
|
||||
});
|
||||
await zrxToken.setBalance.awaitTransactionSuccessAsync(takerAddress, signedOrder.takerFee);
|
||||
|
||||
await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.takerFee, {
|
||||
from: takerAddress,
|
||||
});
|
||||
const takerAssetFillAmount = signedOrder.takerAssetAmount.dividedToIntegerBy(4);
|
||||
await exchange.fillOrder.awaitTransactionSuccessAsync(
|
||||
signedOrder,
|
||||
takerAssetFillAmount,
|
||||
signedOrder.signature,
|
||||
{ from: takerAddress },
|
||||
);
|
||||
const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync(
|
||||
signedOrder,
|
||||
signedOrder.signature,
|
||||
);
|
||||
expect(fillableTakerAssetAmount).to.bignumber.equal(
|
||||
signedOrder.takerAssetAmount.minus(takerAssetFillAmount),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('getOrderRelevantStates', async () => {
|
||||
it('should return the correct information for multiple orders', async () => {
|
||||
await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount);
|
||||
await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, {
|
||||
from: makerAddress,
|
||||
});
|
||||
await zrxToken.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerFee);
|
||||
await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerFee, {
|
||||
from: makerAddress,
|
||||
});
|
||||
const signedOrder2 = await orderFactory.newSignedOrderAsync({ makerAssetData: erc721AssetData });
|
||||
const invalidSignature = '0x01';
|
||||
await exchange.cancelOrder.awaitTransactionSuccessAsync(signedOrder2, { from: makerAddress });
|
||||
const [
|
||||
ordersInfo,
|
||||
fillableTakerAssetAmounts,
|
||||
isValidSignature,
|
||||
] = await devUtils.getOrderRelevantStates.callAsync(
|
||||
[signedOrder, signedOrder2],
|
||||
[signedOrder.signature, invalidSignature],
|
||||
);
|
||||
expect(ordersInfo[0].orderHash).to.equal(orderHashUtils.getOrderHashHex(signedOrder));
|
||||
expect(ordersInfo[1].orderHash).to.equal(orderHashUtils.getOrderHashHex(signedOrder2));
|
||||
expect(ordersInfo[0].orderStatus).to.equal(OrderStatus.Fillable);
|
||||
expect(ordersInfo[1].orderStatus).to.equal(OrderStatus.Cancelled);
|
||||
expect(ordersInfo[0].orderTakerAssetFilledAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
expect(ordersInfo[1].orderTakerAssetFilledAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
expect(fillableTakerAssetAmounts[0]).to.bignumber.equal(signedOrder.takerAssetAmount);
|
||||
expect(fillableTakerAssetAmounts[1]).to.bignumber.equal(constants.ZERO_AMOUNT);
|
||||
expect(isValidSignature[0]).to.equal(true);
|
||||
expect(isValidSignature[1]).to.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
// tslint:disable:max-file-line-count
|
12
contracts/dev-utils/tsconfig.json
Normal file
12
contracts/dev-utils/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true },
|
||||
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
|
||||
"files": [
|
||||
"generated-artifacts/DevUtils.json",
|
||||
"generated-artifacts/LibAssetData.json",
|
||||
"generated-artifacts/LibTransactionDecoder.json",
|
||||
"generated-artifacts/EthBalanceChecker.json"
|
||||
],
|
||||
"exclude": ["./deploy/solc/solc_bin"]
|
||||
}
|
6
contracts/dev-utils/tslint.json
Normal file
6
contracts/dev-utils/tslint.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": ["@0x/tslint-config"],
|
||||
"rules": {
|
||||
"custom-no-magic-numbers": false
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user