From 4e20fe1602868e41a63f22b4ba0dbaf63ce674db Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Sun, 17 Mar 2019 19:46:45 -0700 Subject: [PATCH 01/24] Update ganache-core to 2.5.3 --- packages/subproviders/package.json | 2 +- packages/web3-wrapper/package.json | 2 +- yarn.lock | 133 +++++++++++++++++++++++------ 3 files changed, 107 insertions(+), 30 deletions(-) diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index 925fa66b52..49dadb2ed9 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -45,7 +45,7 @@ "ethereum-types": "^2.1.0", "ethereumjs-tx": "^1.3.5", "ethereumjs-util": "^5.1.1", - "ganache-core": "^2.3.3", + "ganache-core": "^2.5.3", "hdkey": "^0.7.1", "json-rpc-error": "2.0.0", "lodash": "^4.17.11", diff --git a/packages/web3-wrapper/package.json b/packages/web3-wrapper/package.json index 21ed0f28f2..d93e1d1dc0 100644 --- a/packages/web3-wrapper/package.json +++ b/packages/web3-wrapper/package.json @@ -42,7 +42,7 @@ "chai-as-promised": "^7.1.0", "chai-bignumber": "^3.0.0", "dirty-chai": "^2.0.1", - "ganache-core": "^2.3.3", + "ganache-core": "^2.5.3", "make-promises-safe": "^1.1.0", "mocha": "^4.1.0", "npm-run-all": "^4.1.2", diff --git a/yarn.lock b/yarn.lock index 4f342df7da..ed2369fc1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6495,6 +6495,17 @@ ethereumjs-block@^1.2.2, ethereumjs-block@^1.4.1, ethereumjs-block@^1.6.0, ether ethereumjs-util "^5.0.0" merkle-patricia-tree "^2.1.2" +ethereumjs-block@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-2.2.0.tgz#8c6c3ab4a5eff0a16d9785fbeedbe643f4dbcbef" + integrity sha512-Ye+uG/L2wrp364Zihdlr/GfC3ft+zG8PdHcRtsBFNNH1CkOhxOwdB8friBU85n89uRZ9eIMAywCq0F4CwT1wAw== + dependencies: + async "^2.0.1" + ethereumjs-common "^1.1.0" + ethereumjs-tx "^1.2.2" + ethereumjs-util "^5.0.0" + merkle-patricia-tree "^2.1.2" + ethereumjs-blockstream@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/ethereumjs-blockstream/-/ethereumjs-blockstream-6.0.0.tgz#79d726d1f358935eb65195e91d40344c31e87eff" @@ -6507,9 +6518,10 @@ ethereumjs-common@^0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-0.6.1.tgz#ec98edf315a7f107afb6acc48e937a8266979fae" -ethereumjs-common@~0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-0.4.1.tgz#27690a24a817b058cc3a2aedef9392e8d7d63984" +ethereumjs-common@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-1.1.0.tgz#5ec9086c314d619d8f05e79a0525829fcb0e93cb" + integrity sha512-LUmYkKV/HcZbWRyu3OU9YOevsH3VJDXtI6kEd8VZweQec+JjDGKCmAVKUyzhYUHqxRJu7JNALZ3A/b3NXOP6tA== ethereumjs-tx@1.3.7: version "1.3.7" @@ -6559,21 +6571,18 @@ ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereum safe-buffer "^5.1.1" secp256k1 "^3.0.1" -ethereumjs-vm@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-2.4.0.tgz#244f1e35f2755e537a13546111d1a4c159d34b13" +ethereumjs-util@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz#e9c51e5549e8ebd757a339cc00f5380507e799c8" + integrity sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q== dependencies: - async "^2.1.2" - async-eventemitter "^0.2.2" - ethereumjs-account "^2.0.3" - ethereumjs-block "~1.7.0" - ethereumjs-common "~0.4.0" - ethereumjs-util "^5.2.0" - fake-merkle-patricia-tree "^1.0.1" - functional-red-black-tree "^1.0.1" - merkle-patricia-tree "^2.1.2" - rustbn.js "~0.2.0" + bn.js "^4.11.0" + create-hash "^1.1.2" + ethjs-util "0.1.6" + keccak "^1.0.2" + rlp "^2.0.0" safe-buffer "^5.1.1" + secp256k1 "^3.0.1" ethereumjs-vm@^2.0.2, ethereumjs-vm@^2.1.0, ethereumjs-vm@^2.3.4: version "2.3.4" @@ -6591,6 +6600,23 @@ ethereumjs-vm@^2.0.2, ethereumjs-vm@^2.1.0, ethereumjs-vm@^2.3.4: rustbn.js "~0.1.1" safe-buffer "^5.1.1" +ethereumjs-vm@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz#76243ed8de031b408793ac33907fb3407fe400c6" + integrity sha512-r/XIUik/ynGbxS3y+mvGnbOKnuLo40V5Mj1J25+HEO63aWYREIqvWeRO/hnROlMBE5WoniQmPmhiaN0ctiHaXw== + dependencies: + async "^2.1.2" + async-eventemitter "^0.2.2" + ethereumjs-account "^2.0.3" + ethereumjs-block "~2.2.0" + ethereumjs-common "^1.1.0" + ethereumjs-util "^6.0.0" + fake-merkle-patricia-tree "^1.0.1" + functional-red-black-tree "^1.0.1" + merkle-patricia-tree "^2.3.2" + rustbn.js "~0.2.0" + safe-buffer "^5.1.1" + ethereumjs-wallet@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-0.6.2.tgz#67244b6af3e8113b53d709124b25477b64aeccda" @@ -6642,6 +6668,14 @@ ethjs-unit@0.1.6: bn.js "4.11.6" number-to-bn "1.7.0" +ethjs-util@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" + integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w== + dependencies: + is-hex-prefixed "1.0.0" + strip-hex-prefix "1.0.0" + ethjs-util@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.4.tgz#1c8b6879257444ef4d3f3fbbac2ded12cd997d93" @@ -7506,9 +7540,10 @@ ganache-cli@6.4.1: source-map-support "0.5.9" yargs "11.1.0" -ganache-core@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/ganache-core/-/ganache-core-2.3.3.tgz#e35c76d405f0ffba5c48621596fdcc38b0a03136" +ganache-core@^2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/ganache-core/-/ganache-core-2.5.3.tgz#8c6f21d820a694826082dfbb2dc59f834a6874fc" + integrity sha512-lTqPSxgWS5p4zJ8yNbhhsBXMPfcuV+jjnnYtsbJTph9L6InA6UkNpO0hhHuDFWF3GpblP3LjvWPpqW+X6pdZGg== dependencies: abstract-leveldown "3.0.0" async "2.6.1" @@ -7524,11 +7559,11 @@ ganache-core@^2.3.3: ethereumjs-block "2.1.0" ethereumjs-tx "1.3.7" ethereumjs-util "5.2.0" - ethereumjs-vm "2.4.0" + ethereumjs-vm "^2.6.0" heap "0.2.6" level-sublevel "6.6.4" levelup "3.1.1" - lodash "4.17.10" + lodash "4.17.11" merkle-patricia-tree "2.3.1" rlp "2.1.0" seedrandom "2.4.4" @@ -10627,9 +10662,9 @@ lodash.words@^3.0.0: dependencies: lodash._root "^3.0.0" -lodash@4.17.10, lodash@^4.17.10: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" +lodash@4.17.11, lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.3: + version "4.17.11" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" lodash@=4.17.4: version "4.17.4" @@ -10639,14 +10674,14 @@ lodash@^3.3.1, lodash@^3.6.0, lodash@^3.7.0: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.3: - version "4.17.11" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" - lodash@^4.14.0, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1: version "4.17.5" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" +lodash@^4.17.10: + version "4.17.10" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" + lodash@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" @@ -11046,6 +11081,20 @@ merkle-patricia-tree@2.3.1, merkle-patricia-tree@^2.1.2: rlp "^2.0.0" semaphore ">=1.0.1" +merkle-patricia-tree@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/merkle-patricia-tree/-/merkle-patricia-tree-2.3.2.tgz#982ca1b5a0fde00eed2f6aeed1f9152860b8208a" + integrity sha512-81PW5m8oz/pz3GvsAwbauj7Y00rqm81Tzad77tHBwU7pIAtN+TJnMSOJhxBKflSVYhptMMb9RskhqHqrSm1V+g== + dependencies: + async "^1.4.2" + ethereumjs-util "^5.0.0" + level-ws "0.0.0" + levelup "^1.2.1" + memdown "^1.0.0" + readable-stream "^2.0.0" + rlp "^2.0.0" + semaphore ">=1.0.1" + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -13506,6 +13555,16 @@ react-dom@^16.3.2: object-assign "^4.1.1" prop-types "^15.6.0" +react-dom@^16.4.2: + version "16.8.4" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.4.tgz#1061a8e01a2b3b0c8160037441c3bf00a0e3bc48" + integrity sha512-Ob2wK7XG2tUDt7ps7LtLzGYYB6DXMCLj0G5fO6WeEICtT4/HdpOi7W/xLzZnR6RCG1tYza60nMdqtxzA8FaPJQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.13.4" + react-dom@^16.5.2: version "16.5.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.5.2.tgz#b69ee47aa20bab5327b2b9d7c1fe2a30f2cfa9d7" @@ -13809,6 +13868,16 @@ react@^16.3.2: object-assign "^4.1.1" prop-types "^15.6.0" +react@^16.4.2: + version "16.8.4" + resolved "https://registry.yarnpkg.com/react/-/react-16.8.4.tgz#fdf7bd9ae53f03a9c4cd1a371432c206be1c4768" + integrity sha512-0GQ6gFXfUH7aZcjGVymlPOASTuSjlQL4ZtVC5YKH+3JL6bBLCVO21DknzmaPlI90LN253ojj02nsapy+j7wIjg== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.13.4" + react@^16.5.2: version "16.5.2" resolved "https://registry.yarnpkg.com/react/-/react-16.5.2.tgz#19f6b444ed139baa45609eee6dc3d318b3895d42" @@ -14680,6 +14749,14 @@ schedule@^0.5.0: dependencies: object-assign "^4.1.1" +scheduler@^0.13.4: + version "0.13.4" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.4.tgz#8fef05e7a3580c76c0364d2df5e550e4c9140298" + integrity sha512-cvSOlRPxOHs5dAhP9yiS/6IDmVAVxmk33f0CtTJRkmUWcb1Us+t7b1wqdzoC0REw2muC9V5f1L/w5R5uKGaepA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + schema-utils@^0.4.4: version "0.4.7" resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" From aea278c0226d7317256f206e51e9656a4459e756 Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Sun, 17 Mar 2019 19:48:02 -0700 Subject: [PATCH 02/24] Update evmVersion to petersburg --- contracts/asset-proxy/compiler.json | 2 +- contracts/coordinator/compiler.json | 2 +- contracts/erc1155/compiler.json | 2 +- contracts/erc20/compiler.json | 2 +- contracts/erc721/compiler.json | 2 +- contracts/exchange-forwarder/compiler.json | 2 +- contracts/exchange-libs/compiler.json | 2 +- contracts/exchange/compiler.json | 2 +- contracts/extensions/compiler.json | 2 +- contracts/multisig/compiler.json | 2 +- contracts/utils/compiler.json | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/asset-proxy/compiler.json b/contracts/asset-proxy/compiler.json index 28f73402ca..e3b9ed00ec 100644 --- a/contracts/asset-proxy/compiler.json +++ b/contracts/asset-proxy/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "byzantium", + "evmVersion": "petersburg", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/coordinator/compiler.json b/contracts/coordinator/compiler.json index 40b486d1a7..28e07105b9 100644 --- a/contracts/coordinator/compiler.json +++ b/contracts/coordinator/compiler.json @@ -3,7 +3,7 @@ "contractsDir": "./contracts", "useDockerisedSolc": true, "compilerSettings": { - "evmVersion": "byzantium", + "evmVersion": "petersburg", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/erc1155/compiler.json b/contracts/erc1155/compiler.json index e545f46b08..46d62f1178 100644 --- a/contracts/erc1155/compiler.json +++ b/contracts/erc1155/compiler.json @@ -3,7 +3,7 @@ "contractsDir": "contracts", "useDockerisedSolc": true, "compilerSettings": { - "evmVersion": "byzantium", + "evmVersion": "petersburg", "optimizer": { "enabled": true, "runs": 1000000 }, "outputSelection": { "*": { diff --git a/contracts/erc20/compiler.json b/contracts/erc20/compiler.json index 714ee44df9..910c18689d 100644 --- a/contracts/erc20/compiler.json +++ b/contracts/erc20/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "byzantium", + "evmVersion": "petersburg", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/erc721/compiler.json b/contracts/erc721/compiler.json index d7294e62ae..bfbcbb878e 100644 --- a/contracts/erc721/compiler.json +++ b/contracts/erc721/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "byzantium", + "evmVersion": "petersburg", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/exchange-forwarder/compiler.json b/contracts/exchange-forwarder/compiler.json index deb76ddfa8..4be147db96 100644 --- a/contracts/exchange-forwarder/compiler.json +++ b/contracts/exchange-forwarder/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "byzantium", + "evmVersion": "petersburg", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/exchange-libs/compiler.json b/contracts/exchange-libs/compiler.json index 8523e1155e..4abdfde931 100644 --- a/contracts/exchange-libs/compiler.json +++ b/contracts/exchange-libs/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "byzantium", + "evmVersion": "petersburg", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/exchange/compiler.json b/contracts/exchange/compiler.json index dfbf86a0bc..95bbd3bee4 100644 --- a/contracts/exchange/compiler.json +++ b/contracts/exchange/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "byzantium", + "evmVersion": "petersburg", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/extensions/compiler.json b/contracts/extensions/compiler.json index 9e8471e32b..534f4a93fd 100644 --- a/contracts/extensions/compiler.json +++ b/contracts/extensions/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "byzantium", + "evmVersion": "petersburg", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/multisig/compiler.json b/contracts/multisig/compiler.json index 2395d7c8ff..8b024af976 100644 --- a/contracts/multisig/compiler.json +++ b/contracts/multisig/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "byzantium", + "evmVersion": "petersburg", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/utils/compiler.json b/contracts/utils/compiler.json index 7dd36ba4d9..ce996091e8 100644 --- a/contracts/utils/compiler.json +++ b/contracts/utils/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "byzantium", + "evmVersion": "petersburg", "optimizer": { "enabled": true, "runs": 1000000, From caf286b8cbc4e31253a0b055f2203bbbcd48e3ee Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Sun, 17 Mar 2019 20:01:48 -0700 Subject: [PATCH 03/24] Update CHANGELOGs --- contracts/asset-proxy/CHANGELOG.json | 4 ---- contracts/coordinator/CHANGELOG.json | 14 +++++++++++++- contracts/erc20/CHANGELOG.json | 4 ---- contracts/erc721/CHANGELOG.json | 4 ---- contracts/exchange-forwarder/CHANGELOG.json | 4 ---- contracts/exchange-libs/CHANGELOG.json | 4 ---- contracts/exchange/CHANGELOG.json | 4 ---- contracts/extensions/CHANGELOG.json | 4 ---- contracts/multisig/CHANGELOG.json | 4 ---- contracts/utils/CHANGELOG.json | 4 ---- packages/subproviders/CHANGELOG.json | 9 +++++++++ packages/web3-wrapper/CHANGELOG.json | 9 +++++++++ 12 files changed, 31 insertions(+), 37 deletions(-) diff --git a/contracts/asset-proxy/CHANGELOG.json b/contracts/asset-proxy/CHANGELOG.json index d4a8083933..fc665e19a1 100644 --- a/contracts/asset-proxy/CHANGELOG.json +++ b/contracts/asset-proxy/CHANGELOG.json @@ -2,10 +2,6 @@ { "version": "2.0.0", "changes": [ - { - "note": "Set evmVersion to byzantium", - "pr": 1678 - }, { "note": "Do not reexport external dependencies", "pr": 1682 diff --git a/contracts/coordinator/CHANGELOG.json b/contracts/coordinator/CHANGELOG.json index 9aae87f350..eb06267e8d 100644 --- a/contracts/coordinator/CHANGELOG.json +++ b/contracts/coordinator/CHANGELOG.json @@ -1,6 +1,6 @@ [ { - "version": "0.0.1", + "version": "1.0.0", "changes": [ { "note": "Created Coordinator package" @@ -12,6 +12,18 @@ { "note": "Add `SignatureType.Invalid`", "pr": 1705 + }, + { +<<<<<<< HEAD + "note": "Add `SignatureType.Invalid`", + "pr": 1705 + }, + { + "note": "Set `evmVersion` to `petersburg`", +======= + "note": "Set `evmVersion` to `constantinople`", +>>>>>>> 4a3e9e8f4... fgrtre + "pr": 1707 } ] } diff --git a/contracts/erc20/CHANGELOG.json b/contracts/erc20/CHANGELOG.json index 7d2d6d8218..87a442ea37 100644 --- a/contracts/erc20/CHANGELOG.json +++ b/contracts/erc20/CHANGELOG.json @@ -2,10 +2,6 @@ { "version": "2.0.0", "changes": [ - { - "note": "Set evmVersion to byzantium", - "pr": 1678 - }, { "note": "Upgrade contracts to Solidity 0.5.5", "pr": 1682 diff --git a/contracts/erc721/CHANGELOG.json b/contracts/erc721/CHANGELOG.json index f7cfc8b6b5..91d3674e16 100644 --- a/contracts/erc721/CHANGELOG.json +++ b/contracts/erc721/CHANGELOG.json @@ -2,10 +2,6 @@ { "version": "2.0.0", "changes": [ - { - "note": "Set evmVersion to byzantium", - "pr": 1678 - }, { "note": "Upgrade contracts to Solidity 0.5.5", "pr": 1682 diff --git a/contracts/exchange-forwarder/CHANGELOG.json b/contracts/exchange-forwarder/CHANGELOG.json index 50d4375ca1..1bebb14105 100644 --- a/contracts/exchange-forwarder/CHANGELOG.json +++ b/contracts/exchange-forwarder/CHANGELOG.json @@ -2,10 +2,6 @@ { "version": "2.0.0", "changes": [ - { - "note": "Set evmVersion to byzantium", - "pr": 1678 - }, { "note": "Do not reexport external dependencies", "pr": 1682 diff --git a/contracts/exchange-libs/CHANGELOG.json b/contracts/exchange-libs/CHANGELOG.json index 23d12e531d..19f4222093 100644 --- a/contracts/exchange-libs/CHANGELOG.json +++ b/contracts/exchange-libs/CHANGELOG.json @@ -2,10 +2,6 @@ { "version": "2.0.0", "changes": [ - { - "note": "Set evmVersion to byzantium", - "pr": 1678 - }, { "note": "Upgrade contracts to Solidity 0.5.5", "pr": 1682 diff --git a/contracts/exchange/CHANGELOG.json b/contracts/exchange/CHANGELOG.json index 76bad01b67..f5ec62d609 100644 --- a/contracts/exchange/CHANGELOG.json +++ b/contracts/exchange/CHANGELOG.json @@ -2,10 +2,6 @@ { "version": "2.0.0", "changes": [ - { - "note": "Set evmVersion to byzantium", - "pr": 1678 - }, { "note": "Do not reexport external dependencies", "pr": 1682 diff --git a/contracts/extensions/CHANGELOG.json b/contracts/extensions/CHANGELOG.json index 88f6fc4628..337addf6af 100644 --- a/contracts/extensions/CHANGELOG.json +++ b/contracts/extensions/CHANGELOG.json @@ -2,10 +2,6 @@ { "version": "3.0.0", "changes": [ - { - "note": "Set evmVersion to byzantium", - "pr": 1678 - }, { "note": "Do not reexport external dependencies", "pr": 1682 diff --git a/contracts/multisig/CHANGELOG.json b/contracts/multisig/CHANGELOG.json index de02c953b0..9c224349a3 100644 --- a/contracts/multisig/CHANGELOG.json +++ b/contracts/multisig/CHANGELOG.json @@ -2,10 +2,6 @@ { "version": "3.0.0", "changes": [ - { - "note": "Set evmVersion to byzantium", - "pr": 1678 - }, { "note": "Do not reexport external dependencies", "pr": 1682 diff --git a/contracts/utils/CHANGELOG.json b/contracts/utils/CHANGELOG.json index a3787b8dbc..ecec7db642 100644 --- a/contracts/utils/CHANGELOG.json +++ b/contracts/utils/CHANGELOG.json @@ -2,10 +2,6 @@ { "version": "3.0.0", "changes": [ - { - "note": "Set evmVersion to byzantium", - "pr": 1678 - }, { "note": "Optimize loops in LibAddressArray", "pr": 1668 diff --git a/packages/subproviders/CHANGELOG.json b/packages/subproviders/CHANGELOG.json index cda828df83..7851c865f0 100644 --- a/packages/subproviders/CHANGELOG.json +++ b/packages/subproviders/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "4.0.3", + "changes": [ + { + "note": "Update ganache-core to 2.5.3", + "pr": 1707 + } + ] + }, { "timestamp": 1551479279, "version": "4.0.2", diff --git a/packages/web3-wrapper/CHANGELOG.json b/packages/web3-wrapper/CHANGELOG.json index 6f92903073..216e058d70 100644 --- a/packages/web3-wrapper/CHANGELOG.json +++ b/packages/web3-wrapper/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "6.0.3", + "changes": [ + { + "note": "Update ganache-core to 2.5.3", + "pr": 1707 + } + ] + }, { "timestamp": 1551479279, "version": "6.0.2", From b008fabdacb4171f25fa36871903f6de071cd72d Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Sun, 17 Mar 2019 20:32:17 -0700 Subject: [PATCH 04/24] Change petersburg to constantinople --- contracts/asset-proxy/compiler.json | 2 +- contracts/coordinator/CHANGELOG.json | 8 -------- contracts/coordinator/compiler.json | 2 +- contracts/erc1155/compiler.json | 2 +- contracts/erc20/compiler.json | 2 +- contracts/erc721/compiler.json | 2 +- contracts/exchange-forwarder/compiler.json | 2 +- contracts/exchange-libs/compiler.json | 2 +- contracts/exchange/compiler.json | 2 +- contracts/extensions/compiler.json | 2 +- contracts/multisig/compiler.json | 2 +- contracts/utils/compiler.json | 2 +- 12 files changed, 11 insertions(+), 19 deletions(-) diff --git a/contracts/asset-proxy/compiler.json b/contracts/asset-proxy/compiler.json index e3b9ed00ec..6bf9c16c56 100644 --- a/contracts/asset-proxy/compiler.json +++ b/contracts/asset-proxy/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "petersburg", + "evmVersion": "constantinople", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/coordinator/CHANGELOG.json b/contracts/coordinator/CHANGELOG.json index eb06267e8d..fc1a3f7f0d 100644 --- a/contracts/coordinator/CHANGELOG.json +++ b/contracts/coordinator/CHANGELOG.json @@ -14,15 +14,7 @@ "pr": 1705 }, { -<<<<<<< HEAD - "note": "Add `SignatureType.Invalid`", - "pr": 1705 - }, - { - "note": "Set `evmVersion` to `petersburg`", -======= "note": "Set `evmVersion` to `constantinople`", ->>>>>>> 4a3e9e8f4... fgrtre "pr": 1707 } ] diff --git a/contracts/coordinator/compiler.json b/contracts/coordinator/compiler.json index 28e07105b9..8df8f3cd33 100644 --- a/contracts/coordinator/compiler.json +++ b/contracts/coordinator/compiler.json @@ -3,7 +3,7 @@ "contractsDir": "./contracts", "useDockerisedSolc": true, "compilerSettings": { - "evmVersion": "petersburg", + "evmVersion": "constantinople", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/erc1155/compiler.json b/contracts/erc1155/compiler.json index 46d62f1178..2c3318284a 100644 --- a/contracts/erc1155/compiler.json +++ b/contracts/erc1155/compiler.json @@ -3,7 +3,7 @@ "contractsDir": "contracts", "useDockerisedSolc": true, "compilerSettings": { - "evmVersion": "petersburg", + "evmVersion": "constantinople", "optimizer": { "enabled": true, "runs": 1000000 }, "outputSelection": { "*": { diff --git a/contracts/erc20/compiler.json b/contracts/erc20/compiler.json index 910c18689d..c84caf47f1 100644 --- a/contracts/erc20/compiler.json +++ b/contracts/erc20/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "petersburg", + "evmVersion": "constantinople", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/erc721/compiler.json b/contracts/erc721/compiler.json index bfbcbb878e..a3b8f417d5 100644 --- a/contracts/erc721/compiler.json +++ b/contracts/erc721/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "petersburg", + "evmVersion": "constantinople", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/exchange-forwarder/compiler.json b/contracts/exchange-forwarder/compiler.json index 4be147db96..d49cd34b7d 100644 --- a/contracts/exchange-forwarder/compiler.json +++ b/contracts/exchange-forwarder/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "petersburg", + "evmVersion": "constantinople", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/exchange-libs/compiler.json b/contracts/exchange-libs/compiler.json index 4abdfde931..fed143589c 100644 --- a/contracts/exchange-libs/compiler.json +++ b/contracts/exchange-libs/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "petersburg", + "evmVersion": "constantinople", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/exchange/compiler.json b/contracts/exchange/compiler.json index 95bbd3bee4..c1f8663d37 100644 --- a/contracts/exchange/compiler.json +++ b/contracts/exchange/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "petersburg", + "evmVersion": "constantinople", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/extensions/compiler.json b/contracts/extensions/compiler.json index 534f4a93fd..bf3756c15b 100644 --- a/contracts/extensions/compiler.json +++ b/contracts/extensions/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "petersburg", + "evmVersion": "constantinople", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/multisig/compiler.json b/contracts/multisig/compiler.json index 8b024af976..942df6bb25 100644 --- a/contracts/multisig/compiler.json +++ b/contracts/multisig/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "petersburg", + "evmVersion": "constantinople", "optimizer": { "enabled": true, "runs": 1000000, diff --git a/contracts/utils/compiler.json b/contracts/utils/compiler.json index ce996091e8..4632bf57b7 100644 --- a/contracts/utils/compiler.json +++ b/contracts/utils/compiler.json @@ -4,7 +4,7 @@ "useDockerisedSolc": true, "isOfflineMode": false, "compilerSettings": { - "evmVersion": "petersburg", + "evmVersion": "constantinople", "optimizer": { "enabled": true, "runs": 1000000, From a401b8f475b20d20fed516b7c02dbd9a6b97eb36 Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Mon, 18 Mar 2019 13:07:19 -0700 Subject: [PATCH 05/24] Fix web3wrapper node version test --- packages/web3-wrapper/test/web3_wrapper_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web3-wrapper/test/web3_wrapper_test.ts b/packages/web3-wrapper/test/web3_wrapper_test.ts index c4ee91d3dc..54f77d75b6 100644 --- a/packages/web3-wrapper/test/web3_wrapper_test.ts +++ b/packages/web3-wrapper/test/web3_wrapper_test.ts @@ -35,7 +35,7 @@ describe('Web3Wrapper tests', () => { describe('#getNodeVersionAsync', () => { it('gets the node version', async () => { const nodeVersion = await web3Wrapper.getNodeVersionAsync(); - const NODE_VERSION = 'EthereumJS TestRPC/v2.3.3/ethereum-js'; + const NODE_VERSION = 'EthereumJS TestRPC/v2.5.3/ethereum-js'; expect(nodeVersion).to.be.equal(NODE_VERSION); }); }); From dc57e7a5b36930f0209ee7d74ad30ecf1c42bfb3 Mon Sep 17 00:00:00 2001 From: Rahul Singireddy Date: Mon, 18 Mar 2019 14:19:27 -0700 Subject: [PATCH 06/24] adds daniel pyrahon to website --- packages/website/public/images/team/danielp.png | Bin 0 -> 87307 bytes packages/website/ts/pages/about/team.tsx | 6 ++++++ 2 files changed, 6 insertions(+) create mode 100644 packages/website/public/images/team/danielp.png diff --git a/packages/website/public/images/team/danielp.png b/packages/website/public/images/team/danielp.png new file mode 100644 index 0000000000000000000000000000000000000000..d388e0df3750c5da22087afa2ae84df0a150c491 GIT binary patch literal 87307 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 Date: Mon, 18 Mar 2019 14:51:51 -0700 Subject: [PATCH 07/24] Run linter --- packages/website/ts/pages/about/team.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/website/ts/pages/about/team.tsx b/packages/website/ts/pages/about/team.tsx index 369dfa9ddf..d93bdf9bc4 100644 --- a/packages/website/ts/pages/about/team.tsx +++ b/packages/website/ts/pages/about/team.tsx @@ -163,7 +163,6 @@ const team: TeamMember[] = [ name: 'Daniel Pyrathon', title: 'engineer', }, - ]; const advisors: TeamMember[] = [ From 5063c17e6bfbef396a121eb3b1a2eecee0fb488f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 5 Mar 2019 08:52:33 -0800 Subject: [PATCH 08/24] ERC1155 Asset Data tests + types --- packages/order-utils/src/asset_data_utils.ts | 3 +++ packages/order-utils/test/asset_data_utils_test.ts | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/packages/order-utils/src/asset_data_utils.ts b/packages/order-utils/src/asset_data_utils.ts index 2acf57d5f6..1dc6922365 100644 --- a/packages/order-utils/src/asset_data_utils.ts +++ b/packages/order-utils/src/asset_data_utils.ts @@ -5,6 +5,9 @@ import { ERC1155AssetDataNoProxyId, ERC20AssetData, ERC721AssetData, + ERC1155AssetData, + ERC1155AssetDataAbi, + ERC1155AssetDataNoProxyId, MultiAssetData, MultiAssetDataWithRecursiveDecoding, SingleAssetData, diff --git a/packages/order-utils/test/asset_data_utils_test.ts b/packages/order-utils/test/asset_data_utils_test.ts index 2634e7f595..d0206b1aa0 100644 --- a/packages/order-utils/test/asset_data_utils_test.ts +++ b/packages/order-utils/test/asset_data_utils_test.ts @@ -64,7 +64,11 @@ describe('assetDataUtils', () => { KNOWN_ERC1155_ENCODING.tokenAddress, KNOWN_ERC1155_ENCODING.tokenIds, KNOWN_ERC1155_ENCODING.tokenValues, +<<<<<<< HEAD KNOWN_ERC1155_ENCODING.callbackData, +======= + KNOWN_ERC1155_ENCODING.callbackData +>>>>>>> 5bddc5795... ERC1155 Asset Data tests + types ); expect(assetData).to.equal(KNOWN_ERC1155_ENCODING.assetData); }); From 82b6dad1bad503a44d913e14c6240343f54fbdd0 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 4 Mar 2019 18:12:45 -0800 Subject: [PATCH 09/24] Exchange Integration Tests --- contracts/exchange/test/core.ts | 399 ++++++++++++++++++++++++++++++++ 1 file changed, 399 insertions(+) diff --git a/contracts/exchange/test/core.ts b/contracts/exchange/test/core.ts index 7acb4e2aba..44961576a2 100644 --- a/contracts/exchange/test/core.ts +++ b/contracts/exchange/test/core.ts @@ -5,6 +5,7 @@ import { ERC721ProxyContract, ERC721Wrapper, MultiAssetProxyContract, + ERC1155ProxyWrapper, } from '@0x/contracts-asset-proxy'; import { artifacts as erc20Artifacts, @@ -26,6 +27,7 @@ import { txDefaults, web3Wrapper, } from '@0x/contracts-test-utils'; +import { ERC1155MintableContract, DummyERC1155ReceiverBatchTokenReceivedEventArgs } from '@0x/contracts-erc1155'; import { BlockchainLifecycle } from '@0x/dev-utils'; import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; import { RevertReason, SignatureType, SignedOrder } from '@0x/types'; @@ -44,6 +46,8 @@ import { ReentrantERC20TokenContract, TestStaticCallReceiverContract, } from '../src'; +import { Erc1155Wrapper, ERC1155Contract } from '../../erc1155/lib/src'; +import { Exception } from 'handlebars'; chaiSetup.configure(); const expect = chai.expect; @@ -64,19 +68,26 @@ describe('Exchange core', () => { let exchange: ExchangeContract; let erc20Proxy: ERC20ProxyContract; let erc721Proxy: ERC721ProxyContract; + let erc1155Proxy: ERC721ProxyContract; let multiAssetProxy: MultiAssetProxyContract; let maliciousWallet: TestStaticCallReceiverContract; let maliciousValidator: TestStaticCallReceiverContract; + let erc1155Contract: ERC1155MintableContract; let signedOrder: SignedOrder; let erc20Balances: ERC20BalancesByOwner; let exchangeWrapper: ExchangeWrapper; let erc20Wrapper: ERC20Wrapper; let erc721Wrapper: ERC721Wrapper; + let erc1155Wrapper: Erc1155Wrapper; + let erc1155ProxyWrapper: ERC1155ProxyWrapper; let orderFactory: OrderFactory; let erc721MakerAssetIds: BigNumber[]; let erc721TakerAssetIds: BigNumber[]; + let erc1155FungibleTokens: BigNumber[]; + let erc1155NonFungibleTokensOwnedByMaker: BigNumber[]; + let erc1155NonFungibleTokensOwnedByTaker: BigNumber[]; let defaultMakerAssetAddress: string; let defaultTakerAssetAddress: string; @@ -93,6 +104,7 @@ describe('Exchange core', () => { erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); + erc1155ProxyWrapper = new ERC1155ProxyWrapper(provider, usedAddresses, owner); // Deploy AssetProxies, Exchange, tokens, and malicious contracts erc20Proxy = await erc20Wrapper.deployProxyAsync(); @@ -108,6 +120,9 @@ describe('Exchange core', () => { constants.DUMMY_TOKEN_DECIMALS, ); [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); + erc1155Proxy = await erc1155ProxyWrapper.deployProxyAsync(); + [erc1155Wrapper] = await erc1155ProxyWrapper.deployDummyTokensAsync(); + erc1155Contract = erc1155Wrapper.getContract(); exchange = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, provider, @@ -154,6 +169,20 @@ describe('Exchange core', () => { constants.AWAIT_TRANSACTION_MINED_MS, ); + // Configure ERC1155Proxy + await web3Wrapper.awaitTransactionSuccessAsync( + await erc1155Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc1155Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Configure MultiAssetProxy await web3Wrapper.awaitTransactionSuccessAsync( await multiAssetProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { @@ -178,6 +207,7 @@ describe('Exchange core', () => { exchangeWrapper = new ExchangeWrapper(exchange, provider); await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); + await exchangeWrapper.registerAssetProxyAsync(erc1155Proxy.address, owner); await exchangeWrapper.registerAssetProxyAsync(multiAssetProxy.address, owner); // Configure ERC20 tokens @@ -189,6 +219,21 @@ describe('Exchange core', () => { erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address]; erc721TakerAssetIds = erc721Balances[takerAddress][erc721Token.address]; + // Configure ERC1155 tokens + await erc1155ProxyWrapper.setBalancesAndAllowancesAsync(); + erc1155FungibleTokens = erc1155ProxyWrapper.getFungibleTokenIds(); + const nonFungibleTokens = erc1155ProxyWrapper.getNonFungibleTokenIds(); + const tokenBalances = await erc1155ProxyWrapper.getBalancesAsync(); + erc1155NonFungibleTokensOwnedByMaker = []; + erc1155NonFungibleTokensOwnedByTaker = []; + _.each(nonFungibleTokens, (nonFungibleToken: BigNumber) => { + const nonFungibleTokenAsString = nonFungibleToken.toString(); + const nonFungibleTokenHeldByMaker = tokenBalances.nonFungible[makerAddress][erc1155Contract.address][nonFungibleTokenAsString][0]; + erc1155NonFungibleTokensOwnedByMaker.push(nonFungibleTokenHeldByMaker); + const nonFungibleTokenHeldByTaker = tokenBalances.nonFungible[takerAddress][erc1155Contract.address][nonFungibleTokenAsString][0]; + erc1155NonFungibleTokensOwnedByTaker.push(nonFungibleTokenHeldByTaker); + }); + // Configure order defaults defaultMakerAssetAddress = erc20TokenA.address; defaultTakerAssetAddress = erc20TokenB.address; @@ -1041,6 +1086,360 @@ describe('Exchange core', () => { }); }); + describe.only('Testing exchange of erc1155 assets', () => { + it('should allow a single fungible erc1155 asset to be exchanged for another', async () => { + // setup test parameters + const tokenHolders = [makerAddress, takerAddress]; + const makerAssetsToTransfer = erc1155FungibleTokens.slice(0, 1); + const takerAssetsToTransfer = erc1155FungibleTokens.slice(1, 2); + const makerValuesToTransfer = [new BigNumber(500)]; + const takerValuesToTransfer = [new BigNumber(200)]; + const tokensToTransfer = makerAssetsToTransfer.concat(takerAssetsToTransfer); + const makerAssetAmount = new BigNumber(1); + const takerAssetAmount = new BigNumber(1); + const totalMakerValuesTransferred = _.map(makerValuesToTransfer, (value: BigNumber) => {return value.times(makerAssetAmount)}); + const totalTakerValuesTransferred = _.map(takerValuesToTransfer, (value: BigNumber) => {return value.times(takerAssetAmount)}); + const receiverCallbackData = '0x'; + const makerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, makerAssetsToTransfer, makerValuesToTransfer, receiverCallbackData); + const takerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, takerAssetsToTransfer, takerValuesToTransfer, receiverCallbackData); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + const takerAssetFillAmount = new BigNumber(1); + // check balances before transfer + const expectedInitialBalances = [ + // makerAddress / makerToken + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // makerAddress / takerToken + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // takerAddress / makerToken + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // takerAddress / takerToken + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + ]; + await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedInitialBalances); + // execute transfer + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount, + }); + // check balances after transfer + const expectedFinalBalances = [ + // makerAddress / makerToken + expectedInitialBalances[0].minus(totalMakerValuesTransferred[0]), + // makerAddress / takerToken + expectedInitialBalances[1].plus(totalTakerValuesTransferred[0]), + // takerAddress / makerToken + expectedInitialBalances[2].plus(totalMakerValuesTransferred[0]), + // takerAddress / takerToken + expectedInitialBalances[3].minus(totalTakerValuesTransferred[0]), + ]; + await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedFinalBalances); + }); + it('should allow a single non-fungible erc1155 asset to be exchanged for another', async () => { + // setup test parameters + const tokenHolders = [makerAddress, takerAddress]; + const makerAssetsToTransfer = erc1155NonFungibleTokensOwnedByMaker.slice(0, 1); + const takerAssetsToTransfer = erc1155NonFungibleTokensOwnedByTaker.slice(0, 1); + const makerValuesToTransfer = [new BigNumber(1)]; + const takerValuesToTransfer = [new BigNumber(1)]; + const tokensToTransfer = makerAssetsToTransfer.concat(takerAssetsToTransfer); + const makerAssetAmount = new BigNumber(1); + const takerAssetAmount = new BigNumber(1); + const totalMakerValuesTransferred = _.map(makerValuesToTransfer, (value: BigNumber) => {return value.times(makerAssetAmount)}); + const totalTakerValuesTransferred = _.map(takerValuesToTransfer, (value: BigNumber) => {return value.times(takerAssetAmount)}); + const receiverCallbackData = '0x'; + const makerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, makerAssetsToTransfer, makerValuesToTransfer, receiverCallbackData); + const takerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, takerAssetsToTransfer, takerValuesToTransfer, receiverCallbackData); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + const takerAssetFillAmount = new BigNumber(1); + // check balances before transfer + const nftOwnerBalance = new BigNumber(1); + const nftNotOwnerBalance = new BigNumber(0); + const expectedInitialBalances = [ + // makerAddress / makerToken + nftOwnerBalance, + // makerAddress / takerToken + nftNotOwnerBalance, + // takerAddress / makerToken + nftNotOwnerBalance, + // takerAddress / takerToken + nftOwnerBalance, + ]; + await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedInitialBalances); + // execute transfer + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount, + }); + // check balances after transfer + const expectedFinalBalances = [ + // makerAddress / makerToken + expectedInitialBalances[0].minus(totalMakerValuesTransferred[0]), + // makerAddress / takerToken + expectedInitialBalances[1].plus(totalTakerValuesTransferred[0]), + // takerAddress / makerToken + expectedInitialBalances[2].plus(totalMakerValuesTransferred[0]), + // takerAddress / takerToken + expectedInitialBalances[3].minus(totalTakerValuesTransferred[0]), + ]; + await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedFinalBalances); + }); + it('should allow multiple erc1155 assets to be exchanged for a single asset', async () => { + // setup test parameters + const tokenHolders = [makerAddress, takerAddress]; + const makerAssetsToTransfer = erc1155FungibleTokens.slice(0, 3); + const takerAssetsToTransfer = erc1155NonFungibleTokensOwnedByTaker.slice(0, 1); + const makerValuesToTransfer = [new BigNumber(500), new BigNumber(700), new BigNumber(900)]; + const takerValuesToTransfer = [new BigNumber(1)]; + const tokensToTransfer = makerAssetsToTransfer.concat(takerAssetsToTransfer); + const makerAssetAmount = new BigNumber(1); + const takerAssetAmount = new BigNumber(1); + const totalMakerValuesTransferred = _.map(makerValuesToTransfer, (value: BigNumber) => {return value.times(makerAssetAmount)}); + const totalTakerValuesTransferred = _.map(takerValuesToTransfer, (value: BigNumber) => {return value.times(takerAssetAmount)}); + const receiverCallbackData = '0x'; + const makerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, makerAssetsToTransfer, makerValuesToTransfer, receiverCallbackData); + const takerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, takerAssetsToTransfer, takerValuesToTransfer, receiverCallbackData); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + const takerAssetFillAmount = new BigNumber(1); + // check balances before transfer + const nftOwnerBalance = new BigNumber(1); + const nftNotOwnerBalance = new BigNumber(0); + const expectedInitialBalances = [ + // makerAddress / makerToken[0] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // makerAddress / makerToken[1] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // makerAddress / makerToken[2] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // makerAddress / takerToken + nftNotOwnerBalance, + // takerAddress / makerToken[0] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // takerAddress / makerToken[1] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // takerAddress / makerToken[2] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // takerAddress / takerToken + nftOwnerBalance, + ]; + await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedInitialBalances); + // execute transfer + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount, + }); + // check balances after transfer + const expectedFinalBalances = [ + // makerAddress / makerToken[0] + expectedInitialBalances[0].minus(totalMakerValuesTransferred[0]), + // makerAddress / makerToken[1] + expectedInitialBalances[1].minus(totalMakerValuesTransferred[1]), + // makerAddress / makerToken[2] + expectedInitialBalances[2].minus(totalMakerValuesTransferred[2]), + // makerAddress / takerToken + expectedInitialBalances[3].plus(totalTakerValuesTransferred[0]), + // takerAddress / makerToken[0] + expectedInitialBalances[4].plus(totalMakerValuesTransferred[0]), + // takerAddress / makerToken[1] + expectedInitialBalances[5].plus(totalMakerValuesTransferred[1]), + // takerAddress / makerToken[2] + expectedInitialBalances[6].plus(totalMakerValuesTransferred[2]), + // takerAddress / takerToken + expectedInitialBalances[7].minus(totalTakerValuesTransferred[0]), + ]; + await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedFinalBalances); + }); + it('should allow multiple erc1155 assets to be exchanged for multiple erc1155 assets, mixed fungible/non-fungible', async () => { + // setup test parameters + // the maker is trading two fungibles & one non-fungible + // the taker is trading one fungible & two non-fungibles + const tokenHolders = [makerAddress, takerAddress]; + const makerFungibleAssetsToTransfer = erc1155FungibleTokens.slice(0, 2); + const makerNonFungibleAssetsToTransfer = erc1155NonFungibleTokensOwnedByMaker.slice(0, 1); + const makerAssetsToTransfer = makerFungibleAssetsToTransfer.concat(makerNonFungibleAssetsToTransfer); + const takerFungibleAssetsToTransfer = erc1155FungibleTokens.slice(2, 3); + const takerNonFungibleAssetsToTransfer = erc1155NonFungibleTokensOwnedByTaker.slice(0, 2); + const takerAssetsToTransfer = takerFungibleAssetsToTransfer.concat(takerNonFungibleAssetsToTransfer); + const makerValuesToTransfer = [new BigNumber(500), new BigNumber(700), new BigNumber(1)]; + const takerValuesToTransfer = [new BigNumber(900), new BigNumber(1), new BigNumber(1)]; + const tokensToTransfer = makerAssetsToTransfer.concat(takerAssetsToTransfer); + const makerAssetAmount = new BigNumber(1); + const takerAssetAmount = new BigNumber(1); + const totalMakerValuesTransferred = _.map(makerValuesToTransfer, (value: BigNumber) => {return value.times(makerAssetAmount)}); + const totalTakerValuesTransferred = _.map(takerValuesToTransfer, (value: BigNumber) => {return value.times(takerAssetAmount)}); + const receiverCallbackData = '0x'; + const makerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, makerAssetsToTransfer, makerValuesToTransfer, receiverCallbackData); + const takerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, takerAssetsToTransfer, takerValuesToTransfer, receiverCallbackData); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + const takerAssetFillAmount = new BigNumber(1); + // check balances before transfer + const nftOwnerBalance = new BigNumber(1); + const nftNotOwnerBalance = new BigNumber(0); + const expectedInitialBalances = [ + // makerAddress / makerToken[0] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // makerAddress / makerToken[1] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // makerAddress / makerToken[2] + nftOwnerBalance, + // makerAddress / takerToken[0] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // makerAddress / takerToken[1] + nftNotOwnerBalance, + // makerAddress / takerToken[2] + nftNotOwnerBalance, + // takerAddress / makerToken[0] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // takerAddress / makerToken[1] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // takerAddress / makerToken[2] + nftNotOwnerBalance, + // takerAddress / takerToken[0] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // takerAddress / takerToken[1] + nftOwnerBalance, + // takerAddress / takerToken[2] + nftOwnerBalance, + ]; + await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedInitialBalances); + // execute transfer + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount, + }); + // check balances after transfer + const expectedFinalBalances = [ + // makerAddress / makerToken[0] + expectedInitialBalances[0].minus(totalMakerValuesTransferred[0]), + // makerAddress / makerToken[1] + expectedInitialBalances[1].minus(totalMakerValuesTransferred[1]), + // makerAddress / makerToken[2] + expectedInitialBalances[2].minus(totalMakerValuesTransferred[2]), + // makerAddress / takerToken[0] + expectedInitialBalances[3].plus(totalTakerValuesTransferred[0]), + // makerAddress / takerToken[1] + expectedInitialBalances[4].plus(totalTakerValuesTransferred[1]), + // makerAddress / takerToken[2] + expectedInitialBalances[5].plus(totalTakerValuesTransferred[2]), + // takerAddress / makerToken[0] + expectedInitialBalances[6].plus(totalMakerValuesTransferred[0]), + // takerAddress / makerToken[1] + expectedInitialBalances[7].plus(totalMakerValuesTransferred[1]), + // takerAddress / makerToken[2] + expectedInitialBalances[8].plus(totalMakerValuesTransferred[2]), + // takerAddress / takerToken[0] + expectedInitialBalances[9].minus(totalTakerValuesTransferred[0]), + // takerAddress / takerToken[1] + expectedInitialBalances[10].minus(totalTakerValuesTransferred[1]), + // takerAddress / takerToken[2] + expectedInitialBalances[11].minus(totalTakerValuesTransferred[2]), + ]; + await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedFinalBalances); + }); + it('should allow an order exchanging erc1155 assets to be partially filled', async () => { + // NOTICE: + // As-per the eip1155 standard, there is no way to distinguish between a fungible or non-fungible erc1155 assets. + // Hence we cannot force partial fills to fail if there is a non-fungible asset (which should be fill or kill). + // We considered encoding whether an asset is fungible/non-fungible in erc1155 assetData, but + // this is no more robust than a simple check by the client. Enforcing this at the smart contract level + // is something that could be done with the upcoming static call proxy. + // + // setup test parameters + // the maker is trading two fungibles and the taker is trading one fungible + // note that this will result in a partial fill because the `takerAssetAmount` + // less than the `takerAssetAmount` of the order. + const takerAssetFillAmount = new BigNumber(6); + const tokenHolders = [makerAddress, takerAddress]; + const makerAssetsToTransfer = erc1155FungibleTokens.slice(0, 2); + const takerAssetsToTransfer = erc1155FungibleTokens.slice(2, 3); + const makerValuesToTransfer = [new BigNumber(500), new BigNumber(700)]; + const takerValuesToTransfer = [new BigNumber(900)]; + const tokensToTransfer = makerAssetsToTransfer.concat(takerAssetsToTransfer); + const makerAssetAmount = new BigNumber(10); + const takerAssetAmount = new BigNumber(20); + const totalMakerValuesTransferred = _.map(makerValuesToTransfer, (value: BigNumber) => {return value.times(makerAssetAmount).times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount)}); + const totalTakerValuesTransferred = _.map(takerValuesToTransfer, (value: BigNumber) => {return value.times(takerAssetAmount).times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount)}); + const receiverCallbackData = '0x'; + const makerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, makerAssetsToTransfer, makerValuesToTransfer, receiverCallbackData); + const takerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, takerAssetsToTransfer, takerValuesToTransfer, receiverCallbackData); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + // check balances before transfer + const expectedInitialBalances = [ + // makerAddress / makerToken[0] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // makerAddress / makerToken[1] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // makerAddress / takerToken[0] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // takerAddress / makerToken[0] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // takerAddress / makerToken[1] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + // takerAddress / takerToken[0] + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + ]; + await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedInitialBalances); + // execute transfer + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount, + }); + // check balances after transfer + const expectedFinalBalances = [ + // makerAddress / makerToken[0] + expectedInitialBalances[0].minus(totalMakerValuesTransferred[0]), + // makerAddress / makerToken[1] + expectedInitialBalances[1].minus(totalMakerValuesTransferred[1]), + // makerAddress / takerToken[0] + expectedInitialBalances[2].plus(totalTakerValuesTransferred[0]), + // takerAddress / makerToken[0] + expectedInitialBalances[3].plus(totalMakerValuesTransferred[0]), + // takerAddress / makerToken[1] + expectedInitialBalances[4].plus(totalMakerValuesTransferred[1]), + // takerAddress / takerToken[0] + expectedInitialBalances[5].minus(totalTakerValuesTransferred[0]), + ]; + await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedFinalBalances); + // check that the order is partially filled + const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = takerAssetFillAmount; + const expectedOrderStatus = OrderStatus.Fillable; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + }); + describe('getOrderInfo', () => { beforeEach(async () => { signedOrder = await orderFactory.newSignedOrderAsync(); From 31dbca7efcd0746961a1d9fe97bcebb2b41a42d1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 7 Mar 2019 15:47:54 -0800 Subject: [PATCH 10/24] More integration tests for ERC1155 <> Multi Asset Proxy encoding / decoding --- contracts/exchange/test/core.ts | 144 ++++++++++++++---- .../order-utils/test/asset_data_utils_test.ts | 79 +++++++--- 2 files changed, 170 insertions(+), 53 deletions(-) diff --git a/contracts/exchange/test/core.ts b/contracts/exchange/test/core.ts index 44961576a2..53d6b1d028 100644 --- a/contracts/exchange/test/core.ts +++ b/contracts/exchange/test/core.ts @@ -169,8 +169,8 @@ describe('Exchange core', () => { constants.AWAIT_TRANSACTION_MINED_MS, ); - // Configure ERC1155Proxy - await web3Wrapper.awaitTransactionSuccessAsync( + // Configure ERC1155Proxy + await web3Wrapper.awaitTransactionSuccessAsync( await erc1155Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: owner, }), @@ -228,9 +228,11 @@ describe('Exchange core', () => { erc1155NonFungibleTokensOwnedByTaker = []; _.each(nonFungibleTokens, (nonFungibleToken: BigNumber) => { const nonFungibleTokenAsString = nonFungibleToken.toString(); - const nonFungibleTokenHeldByMaker = tokenBalances.nonFungible[makerAddress][erc1155Contract.address][nonFungibleTokenAsString][0]; + const nonFungibleTokenHeldByMaker = + tokenBalances.nonFungible[makerAddress][erc1155Contract.address][nonFungibleTokenAsString][0]; erc1155NonFungibleTokensOwnedByMaker.push(nonFungibleTokenHeldByMaker); - const nonFungibleTokenHeldByTaker = tokenBalances.nonFungible[takerAddress][erc1155Contract.address][nonFungibleTokenAsString][0]; + const nonFungibleTokenHeldByTaker = + tokenBalances.nonFungible[takerAddress][erc1155Contract.address][nonFungibleTokenAsString][0]; erc1155NonFungibleTokensOwnedByTaker.push(nonFungibleTokenHeldByTaker); }); @@ -1097,11 +1099,25 @@ describe('Exchange core', () => { const tokensToTransfer = makerAssetsToTransfer.concat(takerAssetsToTransfer); const makerAssetAmount = new BigNumber(1); const takerAssetAmount = new BigNumber(1); - const totalMakerValuesTransferred = _.map(makerValuesToTransfer, (value: BigNumber) => {return value.times(makerAssetAmount)}); - const totalTakerValuesTransferred = _.map(takerValuesToTransfer, (value: BigNumber) => {return value.times(takerAssetAmount)}); + const totalMakerValuesTransferred = _.map(makerValuesToTransfer, (value: BigNumber) => { + return value.times(makerAssetAmount); + }); + const totalTakerValuesTransferred = _.map(takerValuesToTransfer, (value: BigNumber) => { + return value.times(takerAssetAmount); + }); const receiverCallbackData = '0x'; - const makerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, makerAssetsToTransfer, makerValuesToTransfer, receiverCallbackData); - const takerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, takerAssetsToTransfer, takerValuesToTransfer, receiverCallbackData); + const makerAssetData = assetDataUtils.encodeERC1155AssetData( + erc1155Contract.address, + makerAssetsToTransfer, + makerValuesToTransfer, + receiverCallbackData, + ); + const takerAssetData = assetDataUtils.encodeERC1155AssetData( + erc1155Contract.address, + takerAssetsToTransfer, + takerValuesToTransfer, + receiverCallbackData, + ); signedOrder = await orderFactory.newSignedOrderAsync({ makerAssetData, takerAssetData, @@ -1114,9 +1130,9 @@ describe('Exchange core', () => { // check balances before transfer const expectedInitialBalances = [ // makerAddress / makerToken - constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, // makerAddress / takerToken - constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, // takerAddress / makerToken constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, // takerAddress / takerToken @@ -1150,11 +1166,25 @@ describe('Exchange core', () => { const tokensToTransfer = makerAssetsToTransfer.concat(takerAssetsToTransfer); const makerAssetAmount = new BigNumber(1); const takerAssetAmount = new BigNumber(1); - const totalMakerValuesTransferred = _.map(makerValuesToTransfer, (value: BigNumber) => {return value.times(makerAssetAmount)}); - const totalTakerValuesTransferred = _.map(takerValuesToTransfer, (value: BigNumber) => {return value.times(takerAssetAmount)}); + const totalMakerValuesTransferred = _.map(makerValuesToTransfer, (value: BigNumber) => { + return value.times(makerAssetAmount); + }); + const totalTakerValuesTransferred = _.map(takerValuesToTransfer, (value: BigNumber) => { + return value.times(takerAssetAmount); + }); const receiverCallbackData = '0x'; - const makerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, makerAssetsToTransfer, makerValuesToTransfer, receiverCallbackData); - const takerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, takerAssetsToTransfer, takerValuesToTransfer, receiverCallbackData); + const makerAssetData = assetDataUtils.encodeERC1155AssetData( + erc1155Contract.address, + makerAssetsToTransfer, + makerValuesToTransfer, + receiverCallbackData, + ); + const takerAssetData = assetDataUtils.encodeERC1155AssetData( + erc1155Contract.address, + takerAssetsToTransfer, + takerValuesToTransfer, + receiverCallbackData, + ); signedOrder = await orderFactory.newSignedOrderAsync({ makerAssetData, takerAssetData, @@ -1171,7 +1201,7 @@ describe('Exchange core', () => { // makerAddress / makerToken nftOwnerBalance, // makerAddress / takerToken - nftNotOwnerBalance, + nftNotOwnerBalance, // takerAddress / makerToken nftNotOwnerBalance, // takerAddress / takerToken @@ -1205,11 +1235,25 @@ describe('Exchange core', () => { const tokensToTransfer = makerAssetsToTransfer.concat(takerAssetsToTransfer); const makerAssetAmount = new BigNumber(1); const takerAssetAmount = new BigNumber(1); - const totalMakerValuesTransferred = _.map(makerValuesToTransfer, (value: BigNumber) => {return value.times(makerAssetAmount)}); - const totalTakerValuesTransferred = _.map(takerValuesToTransfer, (value: BigNumber) => {return value.times(takerAssetAmount)}); + const totalMakerValuesTransferred = _.map(makerValuesToTransfer, (value: BigNumber) => { + return value.times(makerAssetAmount); + }); + const totalTakerValuesTransferred = _.map(takerValuesToTransfer, (value: BigNumber) => { + return value.times(takerAssetAmount); + }); const receiverCallbackData = '0x'; - const makerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, makerAssetsToTransfer, makerValuesToTransfer, receiverCallbackData); - const takerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, takerAssetsToTransfer, takerValuesToTransfer, receiverCallbackData); + const makerAssetData = assetDataUtils.encodeERC1155AssetData( + erc1155Contract.address, + makerAssetsToTransfer, + makerValuesToTransfer, + receiverCallbackData, + ); + const takerAssetData = assetDataUtils.encodeERC1155AssetData( + erc1155Contract.address, + takerAssetsToTransfer, + takerValuesToTransfer, + receiverCallbackData, + ); signedOrder = await orderFactory.newSignedOrderAsync({ makerAssetData, takerAssetData, @@ -1224,11 +1268,11 @@ describe('Exchange core', () => { const nftNotOwnerBalance = new BigNumber(0); const expectedInitialBalances = [ // makerAddress / makerToken[0] - constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, // makerAddress / makerToken[1] - constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, // makerAddress / makerToken[2] - constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, // makerAddress / takerToken nftNotOwnerBalance, // takerAddress / makerToken[0] @@ -1282,11 +1326,25 @@ describe('Exchange core', () => { const tokensToTransfer = makerAssetsToTransfer.concat(takerAssetsToTransfer); const makerAssetAmount = new BigNumber(1); const takerAssetAmount = new BigNumber(1); - const totalMakerValuesTransferred = _.map(makerValuesToTransfer, (value: BigNumber) => {return value.times(makerAssetAmount)}); - const totalTakerValuesTransferred = _.map(takerValuesToTransfer, (value: BigNumber) => {return value.times(takerAssetAmount)}); + const totalMakerValuesTransferred = _.map(makerValuesToTransfer, (value: BigNumber) => { + return value.times(makerAssetAmount); + }); + const totalTakerValuesTransferred = _.map(takerValuesToTransfer, (value: BigNumber) => { + return value.times(takerAssetAmount); + }); const receiverCallbackData = '0x'; - const makerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, makerAssetsToTransfer, makerValuesToTransfer, receiverCallbackData); - const takerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, takerAssetsToTransfer, takerValuesToTransfer, receiverCallbackData); + const makerAssetData = assetDataUtils.encodeERC1155AssetData( + erc1155Contract.address, + makerAssetsToTransfer, + makerValuesToTransfer, + receiverCallbackData, + ); + const takerAssetData = assetDataUtils.encodeERC1155AssetData( + erc1155Contract.address, + takerAssetsToTransfer, + takerValuesToTransfer, + receiverCallbackData, + ); signedOrder = await orderFactory.newSignedOrderAsync({ makerAssetData, takerAssetData, @@ -1301,11 +1359,11 @@ describe('Exchange core', () => { const nftNotOwnerBalance = new BigNumber(0); const expectedInitialBalances = [ // makerAddress / makerToken[0] - constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, // makerAddress / makerToken[1] constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, // makerAddress / makerToken[2] - nftOwnerBalance, + nftOwnerBalance, // makerAddress / takerToken[0] constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, // makerAddress / takerToken[1] @@ -1380,11 +1438,31 @@ describe('Exchange core', () => { const tokensToTransfer = makerAssetsToTransfer.concat(takerAssetsToTransfer); const makerAssetAmount = new BigNumber(10); const takerAssetAmount = new BigNumber(20); - const totalMakerValuesTransferred = _.map(makerValuesToTransfer, (value: BigNumber) => {return value.times(makerAssetAmount).times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount)}); - const totalTakerValuesTransferred = _.map(takerValuesToTransfer, (value: BigNumber) => {return value.times(takerAssetAmount).times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount)}); + const totalMakerValuesTransferred = _.map(makerValuesToTransfer, (value: BigNumber) => { + return value + .times(makerAssetAmount) + .times(takerAssetFillAmount) + .dividedToIntegerBy(takerAssetAmount); + }); + const totalTakerValuesTransferred = _.map(takerValuesToTransfer, (value: BigNumber) => { + return value + .times(takerAssetAmount) + .times(takerAssetFillAmount) + .dividedToIntegerBy(takerAssetAmount); + }); const receiverCallbackData = '0x'; - const makerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, makerAssetsToTransfer, makerValuesToTransfer, receiverCallbackData); - const takerAssetData = assetDataUtils.encodeERC1155AssetData(erc1155Contract.address, takerAssetsToTransfer, takerValuesToTransfer, receiverCallbackData); + const makerAssetData = assetDataUtils.encodeERC1155AssetData( + erc1155Contract.address, + makerAssetsToTransfer, + makerValuesToTransfer, + receiverCallbackData, + ); + const takerAssetData = assetDataUtils.encodeERC1155AssetData( + erc1155Contract.address, + takerAssetsToTransfer, + takerValuesToTransfer, + receiverCallbackData, + ); signedOrder = await orderFactory.newSignedOrderAsync({ makerAssetData, takerAssetData, @@ -1396,7 +1474,7 @@ describe('Exchange core', () => { // check balances before transfer const expectedInitialBalances = [ // makerAddress / makerToken[0] - constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, + constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, // makerAddress / makerToken[1] constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, // makerAddress / takerToken[0] diff --git a/packages/order-utils/test/asset_data_utils_test.ts b/packages/order-utils/test/asset_data_utils_test.ts index d0206b1aa0..a9cdcf44f2 100644 --- a/packages/order-utils/test/asset_data_utils_test.ts +++ b/packages/order-utils/test/asset_data_utils_test.ts @@ -1,6 +1,6 @@ import * as chai from 'chai'; -import { AssetProxyId, ERC721AssetData } from '@0x/types'; +import { AssetProxyId, ERC1155AssetData, ERC721AssetData } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { assetDataUtils } from '../src/asset_data_utils'; @@ -30,10 +30,14 @@ const KNOWN_ERC1155_ENCODING = { '0x9645780d0000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000002711000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000007d10000000000000000000000000000000000000000000000000000000000004e210000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000', }; const KNOWN_MULTI_ASSET_ENCODING = { - amounts: [new BigNumber(1), new BigNumber(1)], - nestedAssetData: [KNOWN_ERC20_ENCODING.assetData, KNOWN_ERC721_ENCODING.assetData], + amounts: [new BigNumber(70), new BigNumber(1), new BigNumber(18)], + nestedAssetData: [ + KNOWN_ERC20_ENCODING.assetData, + KNOWN_ERC721_ENCODING.assetData, + KNOWN_ERC1155_ENCODING.assetData, + ], assetData: - '0x94cfcdd7000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000', + '0x94cfcdd7000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c4800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002049645780d0000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000002711000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000007d10000000000000000000000000000000000000000000000000000000000004e210000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c4800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', }; describe('assetDataUtils', () => { @@ -80,14 +84,14 @@ describe('assetDataUtils', () => { expect(decodedAssetData.tokenIds).to.be.deep.equal(KNOWN_ERC1155_ENCODING.tokenIds); expect(decodedAssetData.callbackData).to.be.equal(KNOWN_ERC1155_ENCODING.callbackData); }); - it('should encode ERC20 and ERC721 multiAssetData', () => { + it('should encode ERC20, ERC721 and ERC1155 multiAssetData', () => { const assetData = assetDataUtils.encodeMultiAssetData( KNOWN_MULTI_ASSET_ENCODING.amounts, KNOWN_MULTI_ASSET_ENCODING.nestedAssetData, ); expect(assetData).to.equal(KNOWN_MULTI_ASSET_ENCODING.assetData); }); - it('should decode ERC20 and ERC721 multiAssetData', () => { + it('should decode ERC20, ERC721 and ERC1155 multiAssetData', () => { const decodedAssetData = assetDataUtils.decodeMultiAssetData(KNOWN_MULTI_ASSET_ENCODING.assetData); expect(decodedAssetData.assetProxyId).to.equal(AssetProxyId.MultiAsset); expect(decodedAssetData.amounts).to.deep.equal(KNOWN_MULTI_ASSET_ENCODING.amounts); @@ -97,44 +101,79 @@ describe('assetDataUtils', () => { const decodedAssetData = assetDataUtils.decodeMultiAssetDataRecursively(KNOWN_MULTI_ASSET_ENCODING.assetData); expect(decodedAssetData.assetProxyId).to.equal(AssetProxyId.MultiAsset); expect(decodedAssetData.amounts).to.deep.equal(KNOWN_MULTI_ASSET_ENCODING.amounts); + expect(decodedAssetData.nestedAssetData.length).to.equal(3); const decodedErc20AssetData = decodedAssetData.nestedAssetData[0]; - // tslint:disable-next-line:no-unnecessary-type-assertion - const decodedErc721AssetData = decodedAssetData.nestedAssetData[1] as ERC721AssetData; expect(decodedErc20AssetData.tokenAddress).to.equal(KNOWN_ERC20_ENCODING.address); expect(decodedErc20AssetData.assetProxyId).to.equal(AssetProxyId.ERC20); + // tslint:disable-next-line:no-unnecessary-type-assertion + const decodedErc721AssetData = decodedAssetData.nestedAssetData[1] as ERC721AssetData; expect(decodedErc721AssetData.tokenAddress).to.equal(KNOWN_ERC721_ENCODING.address); expect(decodedErc721AssetData.assetProxyId).to.equal(AssetProxyId.ERC721); expect(decodedErc721AssetData.tokenId).to.be.bignumber.equal(KNOWN_ERC721_ENCODING.tokenId); + // tslint:disable-next-line:no-unnecessary-type-assertion + const decodedErc1155AssetData = decodedAssetData.nestedAssetData[2] as ERC1155AssetData; + expect(decodedErc1155AssetData.tokenAddress).to.be.equal(KNOWN_ERC1155_ENCODING.tokenAddress); + expect(decodedErc1155AssetData.tokenValues).to.be.deep.equal(KNOWN_ERC1155_ENCODING.tokenValues); + expect(decodedErc1155AssetData.tokenIds).to.be.deep.equal(KNOWN_ERC1155_ENCODING.tokenIds); + expect(decodedErc1155AssetData.callbackData).to.be.equal(KNOWN_ERC1155_ENCODING.callbackData); }); it('should recursively decode nested assetData within multiAssetData', () => { - const amounts = [new BigNumber(1), new BigNumber(1), new BigNumber(2)]; + // setup test parameters + const erc20Amount = new BigNumber(1); + const erc721Amount = new BigNumber(1); + const erc1155Amount = new BigNumber(15); + const nestedAssetsAmount = new BigNumber(2); + const amounts = [erc20Amount, erc721Amount, erc1155Amount, nestedAssetsAmount]; const nestedAssetData = [ KNOWN_ERC20_ENCODING.assetData, KNOWN_ERC721_ENCODING.assetData, + KNOWN_ERC1155_ENCODING.assetData, KNOWN_MULTI_ASSET_ENCODING.assetData, ]; const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); + // execute test const decodedAssetData = assetDataUtils.decodeMultiAssetDataRecursively(assetData); + // validate asset data expect(decodedAssetData.assetProxyId).to.equal(AssetProxyId.MultiAsset); - const expectedAmounts = [new BigNumber(1), new BigNumber(1), new BigNumber(2), new BigNumber(2)]; + const expectedAmounts = [ + erc20Amount, + erc721Amount, + erc1155Amount, + KNOWN_MULTI_ASSET_ENCODING.amounts[0].times(nestedAssetsAmount), + KNOWN_MULTI_ASSET_ENCODING.amounts[1].times(nestedAssetsAmount), + KNOWN_MULTI_ASSET_ENCODING.amounts[2].times(nestedAssetsAmount), + ]; expect(decodedAssetData.amounts).to.deep.equal(expectedAmounts); - const expectedLength = 4; - expect(decodedAssetData.nestedAssetData.length).to.be.equal(expectedLength); - const decodedErc20AssetData1 = decodedAssetData.nestedAssetData[0]; - // tslint:disable-next-line:no-unnecessary-type-assertion - const decodedErc721AssetData1 = decodedAssetData.nestedAssetData[1] as ERC721AssetData; - const decodedErc20AssetData2 = decodedAssetData.nestedAssetData[2]; - // tslint:disable-next-line:no-unnecessary-type-assertion - const decodedErc721AssetData2 = decodedAssetData.nestedAssetData[3] as ERC721AssetData; + const expectedNestedAssetDataLength = 6; + expect(decodedAssetData.nestedAssetData.length).to.be.equal(expectedNestedAssetDataLength); + // validate nested asset data (outer) + let nestedAssetDataIndex = 0; + const decodedErc20AssetData1 = decodedAssetData.nestedAssetData[nestedAssetDataIndex++]; expect(decodedErc20AssetData1.tokenAddress).to.equal(KNOWN_ERC20_ENCODING.address); expect(decodedErc20AssetData1.assetProxyId).to.equal(AssetProxyId.ERC20); + // tslint:disable-next-line:no-unnecessary-type-assertion + const decodedErc721AssetData1 = decodedAssetData.nestedAssetData[nestedAssetDataIndex++] as ERC721AssetData; expect(decodedErc721AssetData1.tokenAddress).to.equal(KNOWN_ERC721_ENCODING.address); expect(decodedErc721AssetData1.assetProxyId).to.equal(AssetProxyId.ERC721); - expect(decodedErc721AssetData1.tokenId).to.be.bignumber.equal(KNOWN_ERC721_ENCODING.tokenId); + // tslint:disable-next-line:no-unnecessary-type-assertion + const decodedErc1155AssetData1 = decodedAssetData.nestedAssetData[nestedAssetDataIndex++] as ERC1155AssetData; + expect(decodedErc1155AssetData1.tokenAddress).to.be.equal(KNOWN_ERC1155_ENCODING.tokenAddress); + expect(decodedErc1155AssetData1.tokenValues).to.be.deep.equal(KNOWN_ERC1155_ENCODING.tokenValues); + expect(decodedErc1155AssetData1.tokenIds).to.be.deep.equal(KNOWN_ERC1155_ENCODING.tokenIds); + expect(decodedErc1155AssetData1.callbackData).to.be.equal(KNOWN_ERC1155_ENCODING.callbackData); + // validate nested asset data (inner) + const decodedErc20AssetData2 = decodedAssetData.nestedAssetData[nestedAssetDataIndex++]; expect(decodedErc20AssetData2.tokenAddress).to.equal(KNOWN_ERC20_ENCODING.address); expect(decodedErc20AssetData2.assetProxyId).to.equal(AssetProxyId.ERC20); + // tslint:disable-next-line:no-unnecessary-type-assertion + const decodedErc721AssetData2 = decodedAssetData.nestedAssetData[nestedAssetDataIndex++] as ERC721AssetData; expect(decodedErc721AssetData2.tokenAddress).to.equal(KNOWN_ERC721_ENCODING.address); expect(decodedErc721AssetData2.assetProxyId).to.equal(AssetProxyId.ERC721); - expect(decodedErc721AssetData2.tokenId).to.be.bignumber.equal(KNOWN_ERC721_ENCODING.tokenId); + // tslint:disable-next-line:no-unnecessary-type-assertion + const decodedErc1155AssetData2 = decodedAssetData.nestedAssetData[nestedAssetDataIndex++] as ERC1155AssetData; + expect(decodedErc1155AssetData2.tokenAddress).to.be.equal(KNOWN_ERC1155_ENCODING.tokenAddress); + expect(decodedErc1155AssetData2.tokenValues).to.be.deep.equal(KNOWN_ERC1155_ENCODING.tokenValues); + expect(decodedErc1155AssetData2.tokenIds).to.be.deep.equal(KNOWN_ERC1155_ENCODING.tokenIds); + expect(decodedErc1155AssetData2.callbackData).to.be.equal(KNOWN_ERC1155_ENCODING.callbackData); }); }); From 6957e6e8f72b3a0518569c737cd8ed13eb7bce7d Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 7 Mar 2019 16:50:09 -0800 Subject: [PATCH 11/24] ERC1155 integration tests with MultiAssetProxy --- contracts/asset-proxy/test/proxies.ts | 348 ++++++++++++++++++++++++++ contracts/exchange/test/core.ts | 2 +- contracts/test-utils/src/constants.ts | 2 +- 3 files changed, 350 insertions(+), 2 deletions(-) diff --git a/contracts/asset-proxy/test/proxies.ts b/contracts/asset-proxy/test/proxies.ts index 7977871358..638b550e4f 100644 --- a/contracts/asset-proxy/test/proxies.ts +++ b/contracts/asset-proxy/test/proxies.ts @@ -28,6 +28,7 @@ import * as chai from 'chai'; import { LogWithDecodedArgs } from 'ethereum-types'; import * as _ from 'lodash'; +import { Erc1155Wrapper } from '../../erc1155'; import { artifacts, ERC20ProxyContract, @@ -77,6 +78,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(); }); @@ -127,6 +137,22 @@ describe('Asset Transfer Proxies', () => { constants.AWAIT_TRANSACTION_MINED_MS, ); + // Configure ERC115Proxy + erc1155ProxyWrapper = new ERC1155ProxyWrapper(provider, usedAddresses, owner); + erc1155Proxy = await erc1155ProxyWrapper.deployProxyAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc1155Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc1155Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Configure MultiAssetProxy await web3Wrapper.awaitTransactionSuccessAsync( await multiAssetProxy.addAuthorizedAddress.sendTransactionAsync(authorized, { @@ -146,6 +172,12 @@ describe('Asset Transfer Proxies', () => { }), constants.AWAIT_TRANSACTION_MINED_MS, ); + await web3Wrapper.awaitTransactionSuccessAsync( + await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc1155Proxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); // Deploy and configure ERC20 tokens const numDummyErc20ToDeploy = 2; @@ -213,6 +245,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(); @@ -944,6 +992,306 @@ 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); diff --git a/contracts/exchange/test/core.ts b/contracts/exchange/test/core.ts index 53d6b1d028..21d5482de6 100644 --- a/contracts/exchange/test/core.ts +++ b/contracts/exchange/test/core.ts @@ -121,7 +121,7 @@ describe('Exchange core', () => { ); [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); erc1155Proxy = await erc1155ProxyWrapper.deployProxyAsync(); - [erc1155Wrapper] = await erc1155ProxyWrapper.deployDummyTokensAsync(); + [erc1155Wrapper] = await erc1155ProxyWrapper.deployDummyContractsAsync(); erc1155Contract = erc1155Wrapper.getContract(); exchange = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, diff --git a/contracts/test-utils/src/constants.ts b/contracts/test-utils/src/constants.ts index 2e58d764d0..199042edc0 100644 --- a/contracts/test-utils/src/constants.ts +++ b/contracts/test-utils/src/constants.ts @@ -38,7 +38,7 @@ export const constants = { NUM_DUMMY_ERC20_TO_DEPLOY: 3, NUM_DUMMY_ERC721_TO_DEPLOY: 2, NUM_ERC721_TOKENS_TO_MINT: 2, - NUM_DUMMY_ERC1155_CONTRACTS_TO_DEPLOY: 1, + NUM_DUMMY_ERC1155_CONTRACTS_TO_DEPLOY: 2, NUM_ERC1155_FUNGIBLE_TOKENS_MINT: 3, NUM_ERC1155_NONFUNGIBLE_TOKENS_MINT: 3, NULL_ADDRESS: '0x0000000000000000000000000000000000000000', From 6992bff0e0fbdd50326e190b1cd9fe5cfcfaa651 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 7 Mar 2019 17:30:16 -0800 Subject: [PATCH 12/24] Ran prettier & linter --- contracts/asset-proxy/test/proxies.ts | 46 ++++++++++++++++----------- contracts/exchange/package.json | 1 + contracts/exchange/test/core.ts | 10 +++--- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/contracts/asset-proxy/test/proxies.ts b/contracts/asset-proxy/test/proxies.ts index 638b550e4f..b4e202a9db 100644 --- a/contracts/asset-proxy/test/proxies.ts +++ b/contracts/asset-proxy/test/proxies.ts @@ -137,21 +137,21 @@ describe('Asset Transfer Proxies', () => { constants.AWAIT_TRANSACTION_MINED_MS, ); - // Configure ERC115Proxy - erc1155ProxyWrapper = new ERC1155ProxyWrapper(provider, usedAddresses, owner); - erc1155Proxy = await erc1155ProxyWrapper.deployProxyAsync(); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc1155Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc1155Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); + // Configure ERC115Proxy + erc1155ProxyWrapper = new ERC1155ProxyWrapper(provider, usedAddresses, owner); + erc1155Proxy = await erc1155ProxyWrapper.deployProxyAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc1155Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc1155Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); // Configure MultiAssetProxy await web3Wrapper.awaitTransactionSuccessAsync( @@ -259,7 +259,7 @@ describe('Asset Transfer Proxies', () => { const nonFungibleTokenAsString = nonFungibleToken.toString(); const nonFungibleTokenHeldBySpender = tokenBalances.nonFungible[fromAddress][erc1155Contract.address][nonFungibleTokenAsString][0]; - erc1155NonFungibleTokensOwnedBySpender.push(nonFungibleTokenHeldBySpender); + erc1155NonFungibleTokensOwnedBySpender.push(nonFungibleTokenHeldBySpender); }); }); beforeEach(async () => { @@ -1240,7 +1240,7 @@ describe('Asset Transfer Proxies', () => { const erc1155ValuesToTransfer = [new BigNumber(25)]; const erc1155Amount = new BigNumber(23); const erc1155ReceiverCallbackData = '0x0102030405'; - const erc1155AssetData = assetDataUtils.encodeERC1155AssetData( + const erc1155AssetData = assetDataUtils.encodeERC1155AssetData( erc1155Contract.address, erc1155TokensToTransfer, erc1155ValuesToTransfer, @@ -1263,7 +1263,11 @@ describe('Asset Transfer Proxies', () => { constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, constants.INITIAL_ERC1155_FUNGIBLE_BALANCE, ]; - await erc1155Wrapper.assertBalancesAsync(erc1155TokenHolders, erc1155TokensToTransfer, erc1155ExpectedInitialBalances); + await erc1155Wrapper.assertBalancesAsync( + erc1155TokenHolders, + erc1155TokensToTransfer, + erc1155ExpectedInitialBalances, + ); // execute transfer await web3Wrapper.awaitTransactionSuccessAsync( await web3Wrapper.sendTransactionAsync({ @@ -1290,7 +1294,11 @@ describe('Asset Transfer Proxies', () => { erc1155ExpectedInitialBalances[0].minus(erc1155TotalValueTransferred), erc1155ExpectedInitialBalances[1].plus(erc1155TotalValueTransferred), ]; - await erc1155Wrapper.assertBalancesAsync(erc1155TokenHolders, erc1155TokensToTransfer, expectedFinalBalances); + await erc1155Wrapper.assertBalancesAsync( + erc1155TokenHolders, + erc1155TokensToTransfer, + expectedFinalBalances, + ); }); it('should successfully transfer a combination of ERC20 and ERC721 tokens', async () => { const inputAmount = new BigNumber(1); diff --git a/contracts/exchange/package.json b/contracts/exchange/package.json index 69ba190525..5f45dc25c4 100644 --- a/contracts/exchange/package.json +++ b/contracts/exchange/package.json @@ -71,6 +71,7 @@ "@0x/contracts-asset-proxy": "^1.0.9", "@0x/contracts-erc20": "^1.0.9", "@0x/contracts-erc721": "^1.0.9", + "@0x/contracts-erc1155": "^0.0.1", "@0x/contracts-exchange-libs": "^1.1.3", "@0x/contracts-utils": "^2.0.8", "@0x/order-utils": "^7.0.2", diff --git a/contracts/exchange/test/core.ts b/contracts/exchange/test/core.ts index 21d5482de6..ff6874c0ea 100644 --- a/contracts/exchange/test/core.ts +++ b/contracts/exchange/test/core.ts @@ -1,12 +1,13 @@ import { artifacts as proxyArtifacts, + ERC1155ProxyWrapper, ERC20ProxyContract, ERC20Wrapper, ERC721ProxyContract, ERC721Wrapper, MultiAssetProxyContract, - ERC1155ProxyWrapper, } from '@0x/contracts-asset-proxy'; +import { ERC1155MintableContract } from '@0x/contracts-erc1155'; import { artifacts as erc20Artifacts, DummyERC20TokenContract, @@ -27,7 +28,6 @@ import { txDefaults, web3Wrapper, } from '@0x/contracts-test-utils'; -import { ERC1155MintableContract, DummyERC1155ReceiverBatchTokenReceivedEventArgs } from '@0x/contracts-erc1155'; import { BlockchainLifecycle } from '@0x/dev-utils'; import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; import { RevertReason, SignatureType, SignedOrder } from '@0x/types'; @@ -38,6 +38,7 @@ import { LogWithDecodedArgs } from 'ethereum-types'; import ethUtil = require('ethereumjs-util'); import * as _ from 'lodash'; +import { Erc1155Wrapper } from '../../erc1155/lib/src'; import { artifacts, ExchangeCancelEventArgs, @@ -46,8 +47,6 @@ import { ReentrantERC20TokenContract, TestStaticCallReceiverContract, } from '../src'; -import { Erc1155Wrapper, ERC1155Contract } from '../../erc1155/lib/src'; -import { Exception } from 'handlebars'; chaiSetup.configure(); const expect = chai.expect; @@ -1087,8 +1086,7 @@ describe('Exchange core', () => { ); }); }); - - describe.only('Testing exchange of erc1155 assets', () => { + describe('Testing exchange of erc1155 assets', () => { it('should allow a single fungible erc1155 asset to be exchanged for another', async () => { // setup test parameters const tokenHolders = [makerAddress, takerAddress]; From c0260bc44acfc961475dbe60b32393b87a80ac61 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 11 Mar 2019 18:24:19 -0700 Subject: [PATCH 13/24] rebased against development --- contracts/asset-proxy/test/proxies.ts | 3 ++- contracts/exchange/package.json | 2 +- packages/order-utils/src/asset_data_utils.ts | 3 --- packages/order-utils/test/asset_data_utils_test.ts | 4 ---- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/contracts/asset-proxy/test/proxies.ts b/contracts/asset-proxy/test/proxies.ts index b4e202a9db..81d1475d82 100644 --- a/contracts/asset-proxy/test/proxies.ts +++ b/contracts/asset-proxy/test/proxies.ts @@ -1,3 +1,4 @@ +import { ERC1155MintableContract, Erc1155Wrapper } from '@0x/contracts-erc1155'; import { artifacts as erc20Artifacts, DummyERC20TokenContract, @@ -28,9 +29,9 @@ import * as chai from 'chai'; import { LogWithDecodedArgs } from 'ethereum-types'; import * as _ from 'lodash'; -import { Erc1155Wrapper } from '../../erc1155'; import { artifacts, + ERC1155ProxyWrapper, ERC20ProxyContract, ERC20Wrapper, ERC721ProxyContract, diff --git a/contracts/exchange/package.json b/contracts/exchange/package.json index 5f45dc25c4..c32eb31e5e 100644 --- a/contracts/exchange/package.json +++ b/contracts/exchange/package.json @@ -71,7 +71,7 @@ "@0x/contracts-asset-proxy": "^1.0.9", "@0x/contracts-erc20": "^1.0.9", "@0x/contracts-erc721": "^1.0.9", - "@0x/contracts-erc1155": "^0.0.1", + "@0x/contracts-erc1155": "^1.0.0", "@0x/contracts-exchange-libs": "^1.1.3", "@0x/contracts-utils": "^2.0.8", "@0x/order-utils": "^7.0.2", diff --git a/packages/order-utils/src/asset_data_utils.ts b/packages/order-utils/src/asset_data_utils.ts index 1dc6922365..2acf57d5f6 100644 --- a/packages/order-utils/src/asset_data_utils.ts +++ b/packages/order-utils/src/asset_data_utils.ts @@ -5,9 +5,6 @@ import { ERC1155AssetDataNoProxyId, ERC20AssetData, ERC721AssetData, - ERC1155AssetData, - ERC1155AssetDataAbi, - ERC1155AssetDataNoProxyId, MultiAssetData, MultiAssetDataWithRecursiveDecoding, SingleAssetData, diff --git a/packages/order-utils/test/asset_data_utils_test.ts b/packages/order-utils/test/asset_data_utils_test.ts index a9cdcf44f2..203956948e 100644 --- a/packages/order-utils/test/asset_data_utils_test.ts +++ b/packages/order-utils/test/asset_data_utils_test.ts @@ -68,11 +68,7 @@ describe('assetDataUtils', () => { KNOWN_ERC1155_ENCODING.tokenAddress, KNOWN_ERC1155_ENCODING.tokenIds, KNOWN_ERC1155_ENCODING.tokenValues, -<<<<<<< HEAD KNOWN_ERC1155_ENCODING.callbackData, -======= - KNOWN_ERC1155_ENCODING.callbackData ->>>>>>> 5bddc5795... ERC1155 Asset Data tests + types ); expect(assetData).to.equal(KNOWN_ERC1155_ENCODING.assetData); }); From 29ebed951499c763d0ef7755ada325b85edee3c0 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 14 Mar 2019 22:13:53 -0700 Subject: [PATCH 14/24] Added ERC1155 artifacts to log decoder for exchange wrapper --- contracts/exchange/test/utils/exchange_wrapper.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contracts/exchange/test/utils/exchange_wrapper.ts b/contracts/exchange/test/utils/exchange_wrapper.ts index fef8b55db3..a06e35a462 100644 --- a/contracts/exchange/test/utils/exchange_wrapper.ts +++ b/contracts/exchange/test/utils/exchange_wrapper.ts @@ -1,3 +1,4 @@ +import { artifacts as erc1155Artifacts } from '@0x/contracts-erc1155'; import { artifacts as erc20Artifacts } from '@0x/contracts-erc20'; import { artifacts as erc721Artifacts } from '@0x/contracts-erc721'; import { @@ -25,7 +26,12 @@ export class ExchangeWrapper { constructor(exchangeContract: ExchangeContract, provider: Web3ProviderEngine | ZeroExProvider) { this._exchange = exchangeContract; this._web3Wrapper = new Web3Wrapper(provider); - this._logDecoder = new LogDecoder(this._web3Wrapper, { ...artifacts, ...erc20Artifacts, ...erc721Artifacts }); + this._logDecoder = new LogDecoder(this._web3Wrapper, { + ...artifacts, + ...erc20Artifacts, + ...erc721Artifacts, + ...erc1155Artifacts, + }); } public async fillOrderAsync( signedOrder: SignedOrder, From 7024f29865561f3f94f555bf56b50bc89a8f4883 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 14 Mar 2019 23:08:52 -0700 Subject: [PATCH 15/24] updated changelogs --- contracts/asset-proxy/CHANGELOG.json | 6 +++++- contracts/exchange/CHANGELOG.json | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/contracts/asset-proxy/CHANGELOG.json b/contracts/asset-proxy/CHANGELOG.json index fc665e19a1..2871872a8a 100644 --- a/contracts/asset-proxy/CHANGELOG.json +++ b/contracts/asset-proxy/CHANGELOG.json @@ -13,12 +13,16 @@ { "note": "Bumped solidity version to ^0.5.5", "pr": 1701 + }, + { + "note": "Integration testing for ERC1155Proxy", + "pr": 1673 } ] }, { "timestamp": 1551479279, - "version": "1.0.9", + "version": "1.å.9", "changes": [ { "note": "Dependencies updated" diff --git a/contracts/exchange/CHANGELOG.json b/contracts/exchange/CHANGELOG.json index f5ec62d609..6f9c055614 100644 --- a/contracts/exchange/CHANGELOG.json +++ b/contracts/exchange/CHANGELOG.json @@ -9,6 +9,10 @@ { "note": "Upgrade contracts to Solidity 0.5.5", "pr": 1682 + }, + { + "note": "Integration testing for ERC1155Proxy", + "pr": 1673 } ] }, From 30d0bdec07d9a376efe0cc77d5a21f7e4ff7b0a6 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 18 Mar 2019 17:31:47 -0700 Subject: [PATCH 16/24] Updated changelog and yarn.lock --- contracts/asset-proxy/CHANGELOG.json | 2 +- yarn.lock | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/contracts/asset-proxy/CHANGELOG.json b/contracts/asset-proxy/CHANGELOG.json index 2871872a8a..4bdbc98084 100644 --- a/contracts/asset-proxy/CHANGELOG.json +++ b/contracts/asset-proxy/CHANGELOG.json @@ -22,7 +22,7 @@ }, { "timestamp": 1551479279, - "version": "1.å.9", + "version": "1.0.9", "changes": [ { "note": "Dependencies updated" diff --git a/yarn.lock b/yarn.lock index ed2369fc1a..19f79e4574 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6498,7 +6498,6 @@ ethereumjs-block@^1.2.2, ethereumjs-block@^1.4.1, ethereumjs-block@^1.6.0, ether ethereumjs-block@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-2.2.0.tgz#8c6c3ab4a5eff0a16d9785fbeedbe643f4dbcbef" - integrity sha512-Ye+uG/L2wrp364Zihdlr/GfC3ft+zG8PdHcRtsBFNNH1CkOhxOwdB8friBU85n89uRZ9eIMAywCq0F4CwT1wAw== dependencies: async "^2.0.1" ethereumjs-common "^1.1.0" @@ -6521,7 +6520,6 @@ ethereumjs-common@^0.6.0: ethereumjs-common@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-1.1.0.tgz#5ec9086c314d619d8f05e79a0525829fcb0e93cb" - integrity sha512-LUmYkKV/HcZbWRyu3OU9YOevsH3VJDXtI6kEd8VZweQec+JjDGKCmAVKUyzhYUHqxRJu7JNALZ3A/b3NXOP6tA== ethereumjs-tx@1.3.7: version "1.3.7" @@ -6574,7 +6572,6 @@ ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereum ethereumjs-util@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz#e9c51e5549e8ebd757a339cc00f5380507e799c8" - integrity sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q== dependencies: bn.js "^4.11.0" create-hash "^1.1.2" @@ -6603,7 +6600,6 @@ ethereumjs-vm@^2.0.2, ethereumjs-vm@^2.1.0, ethereumjs-vm@^2.3.4: ethereumjs-vm@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz#76243ed8de031b408793ac33907fb3407fe400c6" - integrity sha512-r/XIUik/ynGbxS3y+mvGnbOKnuLo40V5Mj1J25+HEO63aWYREIqvWeRO/hnROlMBE5WoniQmPmhiaN0ctiHaXw== dependencies: async "^2.1.2" async-eventemitter "^0.2.2" @@ -6671,7 +6667,6 @@ ethjs-unit@0.1.6: ethjs-util@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" - integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w== dependencies: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" @@ -7543,7 +7538,6 @@ ganache-cli@6.4.1: ganache-core@^2.5.3: version "2.5.3" resolved "https://registry.yarnpkg.com/ganache-core/-/ganache-core-2.5.3.tgz#8c6f21d820a694826082dfbb2dc59f834a6874fc" - integrity sha512-lTqPSxgWS5p4zJ8yNbhhsBXMPfcuV+jjnnYtsbJTph9L6InA6UkNpO0hhHuDFWF3GpblP3LjvWPpqW+X6pdZGg== dependencies: abstract-leveldown "3.0.0" async "2.6.1" @@ -11084,7 +11078,6 @@ merkle-patricia-tree@2.3.1, merkle-patricia-tree@^2.1.2: merkle-patricia-tree@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/merkle-patricia-tree/-/merkle-patricia-tree-2.3.2.tgz#982ca1b5a0fde00eed2f6aeed1f9152860b8208a" - integrity sha512-81PW5m8oz/pz3GvsAwbauj7Y00rqm81Tzad77tHBwU7pIAtN+TJnMSOJhxBKflSVYhptMMb9RskhqHqrSm1V+g== dependencies: async "^1.4.2" ethereumjs-util "^5.0.0" @@ -13558,7 +13551,6 @@ react-dom@^16.3.2: react-dom@^16.4.2: version "16.8.4" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.4.tgz#1061a8e01a2b3b0c8160037441c3bf00a0e3bc48" - integrity sha512-Ob2wK7XG2tUDt7ps7LtLzGYYB6DXMCLj0G5fO6WeEICtT4/HdpOi7W/xLzZnR6RCG1tYza60nMdqtxzA8FaPJQ== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -13871,7 +13863,6 @@ react@^16.3.2: react@^16.4.2: version "16.8.4" resolved "https://registry.yarnpkg.com/react/-/react-16.8.4.tgz#fdf7bd9ae53f03a9c4cd1a371432c206be1c4768" - integrity sha512-0GQ6gFXfUH7aZcjGVymlPOASTuSjlQL4ZtVC5YKH+3JL6bBLCVO21DknzmaPlI90LN253ojj02nsapy+j7wIjg== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -14752,7 +14743,6 @@ schedule@^0.5.0: scheduler@^0.13.4: version "0.13.4" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.4.tgz#8fef05e7a3580c76c0364d2df5e550e4c9140298" - integrity sha512-cvSOlRPxOHs5dAhP9yiS/6IDmVAVxmk33f0CtTJRkmUWcb1Us+t7b1wqdzoC0REw2muC9V5f1L/w5R5uKGaepA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" From 98c6fa10e65d5fa0255c7db92e19c22904f37dfd Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 18 Mar 2019 17:42:06 -0700 Subject: [PATCH 17/24] Added 1explicit ERC1555Poxy testsfor cwhen receiver allback data is NULLL (0x) --- contracts/asset-proxy/test/erc1155_proxy.ts | 44 +++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/contracts/asset-proxy/test/erc1155_proxy.ts b/contracts/asset-proxy/test/erc1155_proxy.ts index 7931fdfda0..f55e11024f 100644 --- a/contracts/asset-proxy/test/erc1155_proxy.ts +++ b/contracts/asset-proxy/test/erc1155_proxy.ts @@ -433,6 +433,50 @@ describe('ERC1155Proxy', () => { ]; await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedFinalBalances); }); + it('should successfully transfer value to a smart contract and trigger its callback, when callback `data` is NULL', async () => { + // setup test parameters + const tokenHolders = [spender, receiverContract]; + const tokensToTransfer = fungibleTokens.slice(0, 1); + const valuesToTransfer = [fungibleValueToTransferLarge]; + const valueMultiplier = valueMultiplierSmall; + const totalValuesTransferred = _.map(valuesToTransfer, (value: BigNumber) => { + return value.times(valueMultiplier); + }); + // check balances before transfer + const expectedInitialBalances = [spenderInitialFungibleBalance, receiverContractInitialFungibleBalance]; + await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedInitialBalances); + // execute transfer + const nullReceiverCallbackData = '0x'; + const txReceipt = await erc1155ProxyWrapper.transferFromWithLogsAsync( + spender, + receiverContract, + erc1155Contract.address, + tokensToTransfer, + valuesToTransfer, + valueMultiplier, + nullReceiverCallbackData, + authorized, + ); + // check receiver log ignored extra asset data + expect(txReceipt.logs.length).to.be.equal(2); + const receiverLog = txReceipt.logs[1] as LogWithDecodedArgs< + DummyERC1155ReceiverBatchTokenReceivedEventArgs + >; + expect(receiverLog.args.operator).to.be.equal(erc1155Proxy.address); + expect(receiverLog.args.from).to.be.equal(spender); + expect(receiverLog.args.tokenIds.length).to.be.deep.equal(1); + expect(receiverLog.args.tokenIds[0]).to.be.bignumber.equal(tokensToTransfer[0]); + expect(receiverLog.args.tokenValues.length).to.be.deep.equal(1); + expect(receiverLog.args.tokenValues[0]).to.be.bignumber.equal(totalValuesTransferred[0]); + // note - if the `extraData` is ignored then the receiver log should ignore it as well. + expect(receiverLog.args.data).to.be.deep.equal(nullReceiverCallbackData); + // check balances after transfer + const expectedFinalBalances = [ + expectedInitialBalances[0].minus(totalValuesTransferred[0]), + expectedInitialBalances[1].plus(totalValuesTransferred[0]), + ]; + await erc1155Wrapper.assertBalancesAsync(tokenHolders, tokensToTransfer, expectedFinalBalances); + }); it('should successfully transfer value and ignore extra assetData', async () => { // setup test parameters const tokenHolders = [spender, receiverContract]; From 384114d3c7d3b8999efb510102bfe7700be65054 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 19 Mar 2019 15:01:24 -0700 Subject: [PATCH 18/24] Added Dutch Auction address on mainnet to contract-addresses package --- packages/contract-addresses/src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/contract-addresses/src/index.ts b/packages/contract-addresses/src/index.ts index 4f580e8256..d9541c4e46 100644 --- a/packages/contract-addresses/src/index.ts +++ b/packages/contract-addresses/src/index.ts @@ -32,8 +32,7 @@ const networkToAddresses: { [networkId: number]: ContractAddresses } = { assetProxyOwner: '0x17992e4ffb22730138e4b62aaa6367fa9d3699a6', forwarder: '0x5468a1dc173652ee28d249c271fa9933144746b1', orderValidator: '0x9463e518dea6810309563c81d5266c1b1d149138', - // @todo hysz/dekz: Add mainnet address once deployed. - dutchAuction: NULL_ADDRESS, + dutchAuction: '0x07b32a653754945666cfca91168bb207323dfe67', }, 3: { erc20Proxy: '0xb1408f4c245a23c31b98d2c626777d4c0d766caa', From 8c3abf347323017372271ed00a5ea1ee2574da6a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 19 Mar 2019 15:03:07 -0700 Subject: [PATCH 19/24] Updated changelog --- packages/contract-addresses/CHANGELOG.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/contract-addresses/CHANGELOG.json b/packages/contract-addresses/CHANGELOG.json index ec04c440da..b3e9a79a63 100644 --- a/packages/contract-addresses/CHANGELOG.json +++ b/packages/contract-addresses/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "2.2.3", + "changes": [ + { + "note": "Added Dutch Auction mainnet address", + "pr": 1715 + } + ] + }, { "version": "2.2.2", "changes": [ From 05424c9f33e8ad7475ae40ef4b46bd7b9b384b8e Mon Sep 17 00:00:00 2001 From: Ognyan Chikov Date: Wed, 20 Mar 2019 11:37:38 +0200 Subject: [PATCH 20/24] Fix documentation typos --- packages/website/md/docs/sol_coverage/usage.md | 4 ++-- packages/website/md/docs/sol_profiler/usage.md | 4 ++-- packages/website/md/docs/sol_trace/usage.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/website/md/docs/sol_coverage/usage.md b/packages/website/md/docs/sol_coverage/usage.md index 89cee45122..202bb1aad8 100644 --- a/packages/website/md/docs/sol_coverage/usage.md +++ b/packages/website/md/docs/sol_coverage/usage.md @@ -8,10 +8,10 @@ In order to use `CoverageSubprovider` with your favorite framework you need to p ### Sol-compiler -If you are generating your artifacts with [@0x/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactsAdapter` we've implemented for you. +If you are generating your artifacts with [@0x/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactAdapter` we've implemented for you. ```typescript -import { SolCompilerArtifactsAdapter } from '@0x/sol-coverage'; +import { SolCompilerArtifactAdapter } from '@0x/sol-coverage'; // Both artifactsDir and contractsDir are optional and will be fetched from compiler.json if not passed in const artifactAdapter = new SolCompilerArtifactAdapter(artifactsDir, contractsDir); ``` diff --git a/packages/website/md/docs/sol_profiler/usage.md b/packages/website/md/docs/sol_profiler/usage.md index 0fbc31bc10..51e0b7bb73 100644 --- a/packages/website/md/docs/sol_profiler/usage.md +++ b/packages/website/md/docs/sol_profiler/usage.md @@ -8,10 +8,10 @@ In order to use `ProfilerSubprovider` with your favorite framework you need to p ### Sol-compiler -If you are generating your artifacts with [@0x/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactsAdapter` we've implemented for you. +If you are generating your artifacts with [@0x/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactAdapter` we've implemented for you. ```typescript -import { SolCompilerArtifactsAdapter } from '@0x/sol-profiler'; +import { SolCompilerArtifactAdapter } from '@0x/sol-profiler'; // Both artifactsDir and contractsDir are optional and will be fetched from compiler.json if not passed in const artifactAdapter = new SolCompilerArtifactAdapter(artifactsDir, contractsDir); ``` diff --git a/packages/website/md/docs/sol_trace/usage.md b/packages/website/md/docs/sol_trace/usage.md index d117a0faf1..0afe8c653d 100644 --- a/packages/website/md/docs/sol_trace/usage.md +++ b/packages/website/md/docs/sol_trace/usage.md @@ -8,10 +8,10 @@ In order to use `RevertTraceSubprovider` with your favorite framework you need t ### Sol-compiler -If you are generating your artifacts with [@0x/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactsAdapter` we've implemented for you. +If you are generating your artifacts with [@0x/sol-compiler](https://0x.org/docs/sol-compiler) you can use the `SolCompilerArtifactAdapter` we've implemented for you. ```typescript -import { SolCompilerArtifactsAdapter } from '@0x/sol-trace'; +import { SolCompilerArtifactAdapter } from '@0x/sol-trace'; // Both artifactsDir and contractsDir are optional and will be fetched from compiler.json if not passed in const artifactAdapter = new SolCompilerArtifactAdapter(artifactsDir, contractsDir); ``` From 54dbef2b4f281cf3ab41784ee26ee5e3b4b9a930 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 20 Mar 2019 13:31:35 +0100 Subject: [PATCH 21/24] Add semaphore around event callbacks that all modify shared state --- packages/order-watcher/package.json | 1 + .../src/order_watcher/order_watcher.ts | 21 ++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/order-watcher/package.json b/packages/order-watcher/package.json index bdb7d2a84c..7558a01052 100644 --- a/packages/order-watcher/package.json +++ b/packages/order-watcher/package.json @@ -81,6 +81,7 @@ "ethereumjs-blockstream": "6.0.0", "ethers": "~4.0.4", "lodash": "^4.17.11", + "semaphore-async-await": "^1.5.1", "websocket": "^1.0.26" }, "publishConfig": { diff --git a/packages/order-watcher/src/order_watcher/order_watcher.ts b/packages/order-watcher/src/order_watcher/order_watcher.ts index d1cab2b10d..6128a70922 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher.ts @@ -42,6 +42,7 @@ import { ZeroExProvider, } from 'ethereum-types'; import * as _ from 'lodash'; +import { Lock } from 'semaphore-async-await'; import { orderWatcherPartialConfigSchema } from '../schemas/order_watcher_partial_config_schema'; import { OnOrderStateChangeCallback, OrderWatcherConfig, OrderWatcherError } from '../types'; @@ -84,6 +85,7 @@ export class OrderWatcher { private readonly _dependentOrderHashesTracker: DependentOrderHashesTracker; private readonly _orderStateByOrderHashCache: OrderStateByOrderHash = {}; private readonly _orderByOrderHash: OrderByOrderHash = {}; + private readonly _orderByOrderHashLock = new Lock(); private readonly _eventWatcher: EventWatcher; private readonly _provider: ZeroExProvider; private readonly _collisionResistantAbiDecoder: CollisionResistanceAbiDecoder; @@ -196,10 +198,12 @@ export class OrderWatcher { throw new Error(OrderWatcherError.SubscriptionAlreadyPresent); } this._callbackIfExists = callback; - this._eventWatcher.subscribe(this._onEventWatcherCallbackAsync.bind(this)); - this._expirationWatcher.subscribe(this._onOrderExpired.bind(this)); + this._eventWatcher.subscribe( + this._addLockToCallbackAsync.bind(this, this._onEventWatcherCallbackAsync.bind(this)), + ); + this._expirationWatcher.subscribe(this._addLockToCallbackAsync.bind(this, this._onOrderExpired.bind(this))); this._cleanupJobIntervalIdIfExists = intervalUtils.setAsyncExcludingInterval( - this._cleanupAsync.bind(this), + this._addLockToCallbackAsync.bind(this, this._cleanupAsync.bind(this)), this._cleanupJobInterval, (err: Error) => { this.unsubscribe(); @@ -229,6 +233,17 @@ export class OrderWatcher { orderCount: _.size(this._orderByOrderHash), }; } + private async _addLockToCallbackAsync(cbAsync: any, ...params: any[]): Promise { + await this._orderByOrderHashLock.acquire(); + try { + await cbAsync(...params); + await this._orderByOrderHashLock.release(); + } catch (err) { + // Make sure to releasee the lock if an error is thrown + await this._orderByOrderHashLock.release(); + throw err; + } + } private async _cleanupAsync(): Promise { for (const orderHash of _.keys(this._orderByOrderHash)) { this._cleanupOrderRelatedState(orderHash); From 7cd27cd9c83b46b97a44e1135d16de655f39b12d Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 20 Mar 2019 13:31:55 +0100 Subject: [PATCH 22/24] Make sure we reset blockchain state --- packages/order-watcher/test/order_watcher_test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/order-watcher/test/order_watcher_test.ts b/packages/order-watcher/test/order_watcher_test.ts index d272c7fa45..051c3121a9 100644 --- a/packages/order-watcher/test/order_watcher_test.ts +++ b/packages/order-watcher/test/order_watcher_test.ts @@ -175,10 +175,14 @@ describe('OrderWatcher', () => { }); }); describe('tests with cleanup', async () => { + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); afterEach(async () => { orderWatcher.unsubscribe(); const orderHash = orderHashUtils.getOrderHashHex(signedOrder); orderWatcher.removeOrder(orderHash); + await blockchainLifecycle.revertAsync(); }); it('should emit orderStateInvalid when makerAddress allowance set to 0 for watched order', (done: DoneCallback) => { (async () => { From 941877a05ac5c94de7d709c69c38649ef983e029 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 20 Mar 2019 13:48:59 +0100 Subject: [PATCH 23/24] Fix linter --- .../order-watcher/src/order_watcher/order_watcher.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/order-watcher/src/order_watcher/order_watcher.ts b/packages/order-watcher/src/order_watcher/order_watcher.ts index 6128a70922..5ee01c42c0 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher.ts @@ -85,7 +85,7 @@ export class OrderWatcher { private readonly _dependentOrderHashesTracker: DependentOrderHashesTracker; private readonly _orderStateByOrderHashCache: OrderStateByOrderHash = {}; private readonly _orderByOrderHash: OrderByOrderHash = {}; - private readonly _orderByOrderHashLock = new Lock(); + private readonly _lock = new Lock(); private readonly _eventWatcher: EventWatcher; private readonly _provider: ZeroExProvider; private readonly _collisionResistantAbiDecoder: CollisionResistanceAbiDecoder; @@ -234,13 +234,13 @@ export class OrderWatcher { }; } private async _addLockToCallbackAsync(cbAsync: any, ...params: any[]): Promise { - await this._orderByOrderHashLock.acquire(); + await this._lock.acquire(); try { await cbAsync(...params); - await this._orderByOrderHashLock.release(); + await this._lock.release(); } catch (err) { // Make sure to releasee the lock if an error is thrown - await this._orderByOrderHashLock.release(); + await this._lock.release(); throw err; } } @@ -508,4 +508,4 @@ export class OrderWatcher { this._callbackIfExists(null, orderState); } } -} +} // tslint:disable:max-file-line-count From 31faef70301a39d44d388506f2ed501fba28fafe Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 20 Mar 2019 14:19:25 +0100 Subject: [PATCH 24/24] Add changelog entry --- packages/order-watcher/CHANGELOG.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/order-watcher/CHANGELOG.json b/packages/order-watcher/CHANGELOG.json index 8d4e22944a..4e0015bce4 100644 --- a/packages/order-watcher/CHANGELOG.json +++ b/packages/order-watcher/CHANGELOG.json @@ -9,6 +9,10 @@ { "note": "Fix issue where ERC721 Approval events could cause a lookup on undefined object", "pr": 1692 + }, + { + "note": "Fix race-condition bugs due to async event callbacks modifying shared state", + "pr": 1718 } ] },