From 4a4c26a2e3a64f52f602c998095cf5d164ec0276 Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Sun, 20 Jan 2019 21:21:44 -0800 Subject: Split protocol package into exchange, asset-proxy, and multisig --- contracts/asset-proxy/CHANGELOG.json | 65 + contracts/asset-proxy/CHANGELOG.md | 28 + contracts/asset-proxy/DEPLOYS.json | 92 ++ contracts/asset-proxy/README.md | 74 + contracts/asset-proxy/compiler.json | 22 + .../contracts/asset-proxy/ERC20Proxy.sol | 184 +++ .../contracts/asset-proxy/ERC721Proxy.sol | 171 +++ .../asset-proxy/MixinAssetProxyDispatcher.sol | 174 +++ .../contracts/asset-proxy/MixinAuthorizable.sol | 117 ++ .../contracts/asset-proxy/MultiAssetProxy.sol | 306 ++++ .../asset-proxy/mixins/MAssetProxyDispatcher.sol | 45 + .../contracts/asset-proxy/mixins/MAuthorizable.sol | 41 + .../generated-artifacts/ERC20Proxy.json | 252 ++++ .../generated-artifacts/ERC721Proxy.json | 252 ++++ .../generated-artifacts/MixinAuthorizable.json | 229 +++ .../generated-artifacts/MultiAssetProxy.json | 337 +++++ contracts/asset-proxy/package.json | 90 ++ contracts/asset-proxy/src/artifacts/index.ts | 13 + contracts/asset-proxy/src/index.ts | 3 + contracts/asset-proxy/src/wrappers/index.ts | 4 + contracts/asset-proxy/test/authorizable.ts | 211 +++ contracts/asset-proxy/test/global_hooks.ts | 17 + contracts/asset-proxy/test/proxies.ts | 1286 +++++++++++++++++ contracts/asset-proxy/test/utils/erc20_wrapper.ts | 179 +++ contracts/asset-proxy/test/utils/erc721_wrapper.ts | 236 ++++ contracts/asset-proxy/test/utils/index.ts | 2 + contracts/asset-proxy/tsconfig.json | 16 + contracts/asset-proxy/tslint.json | 6 + contracts/exchange/CHANGELOG.json | 65 + contracts/exchange/CHANGELOG.md | 28 + contracts/exchange/DEPLOYS.json | 92 ++ contracts/exchange/README.md | 74 + contracts/exchange/compiler.json | 28 + contracts/exchange/contracts/exchange/Exchange.sol | 53 + .../exchange/MixinAssetProxyDispatcher.sol | 174 +++ .../contracts/exchange/MixinExchangeCore.sol | 529 +++++++ .../contracts/exchange/MixinMatchOrders.sol | 335 +++++ .../contracts/exchange/MixinSignatureValidator.sol | 324 +++++ .../contracts/exchange/MixinTransactions.sol | 152 ++ .../contracts/exchange/MixinWrapperFunctions.sol | 426 ++++++ .../exchange/mixins/MAssetProxyDispatcher.sol | 45 + .../contracts/exchange/mixins/MExchangeCore.sol | 157 +++ .../contracts/exchange/mixins/MMatchOrders.sol | 58 + .../exchange/mixins/MSignatureValidator.sol | 75 + .../contracts/exchange/mixins/MTransactions.sol | 58 + .../exchange/mixins/MWrapperFunctions.sol | 41 + .../contracts/test/TestAssetProxyDispatcher.sol | 37 + .../contracts/test/TestExchangeInternals.sol | 191 +++ .../contracts/test/TestSignatureValidator.sol | 45 + .../contracts/test/TestStaticCallReceiver.sol | 81 ++ contracts/exchange/package.json | 94 ++ contracts/exchange/src/artifacts/index.ts | 15 + contracts/exchange/src/index.ts | 3 + contracts/exchange/src/wrappers/index.ts | 5 + contracts/exchange/test/core.ts | 1161 ++++++++++++++++ contracts/exchange/test/dispatcher.ts | 284 ++++ contracts/exchange/test/fill_order.ts | 314 +++++ contracts/exchange/test/global_hooks.ts | 17 + contracts/exchange/test/internal.ts | 472 +++++++ contracts/exchange/test/match_orders.ts | 1284 +++++++++++++++++ contracts/exchange/test/signature_validator.ts | 526 +++++++ contracts/exchange/test/transactions.ts | 467 +++++++ contracts/exchange/test/utils/asset_wrapper.ts | 222 +++ contracts/exchange/test/utils/erc20_wrapper.ts | 177 +++ contracts/exchange/test/utils/erc721_wrapper.ts | 234 ++++ contracts/exchange/test/utils/exchange_wrapper.ts | 292 ++++ .../test/utils/fill_order_combinatorial_utils.ts | 928 +++++++++++++ contracts/exchange/test/utils/index.ts | 3 + .../exchange/test/utils/match_order_tester.ts | 564 ++++++++ .../test/utils/order_factory_from_scenario.ts | 294 ++++ ...le_asset_balance_and_proxy_allowance_fetcher.ts | 19 + .../utils/simple_order_filled_cancelled_fetcher.ts | 31 + contracts/exchange/test/utils/types.ts | 8 + contracts/exchange/test/wrapper.ts | 1461 ++++++++++++++++++++ contracts/exchange/tsconfig.json | 17 + contracts/exchange/tslint.json | 6 + contracts/multisig/compiler.json | 8 +- .../contracts/multisig/AssetProxyOwner.sol | 108 ++ .../contracts/test/TestAssetProxyOwner.sol | 58 + .../multisig/contracts/test/TestRejectEther.sol | 23 + .../test/TestRejectEther/TestRejectEther.sol | 23 - contracts/multisig/package.json | 4 +- contracts/multisig/src/artifacts/index.ts | 6 +- contracts/multisig/test/asset_proxy_owner.ts | 507 +++++++ .../test/utils/asset_proxy_owner_wrapper.ts | 71 + contracts/multisig/tsconfig.json | 2 + contracts/protocol/CHANGELOG.json | 65 - contracts/protocol/CHANGELOG.md | 28 - contracts/protocol/DEPLOYS.json | 92 -- contracts/protocol/README.md | 74 - contracts/protocol/compiler.json | 34 - .../contracts/protocol/AssetProxy/ERC20Proxy.sol | 184 --- .../contracts/protocol/AssetProxy/ERC721Proxy.sol | 171 --- .../protocol/AssetProxy/MixinAuthorizable.sol | 117 -- .../protocol/AssetProxy/MultiAssetProxy.sol | 306 ---- .../protocol/AssetProxy/mixins/MAuthorizable.sol | 41 - .../protocol/AssetProxyOwner/AssetProxyOwner.sol | 108 -- .../contracts/protocol/Exchange/Exchange.sol | 53 - .../Exchange/MixinAssetProxyDispatcher.sol | 174 --- .../protocol/Exchange/MixinExchangeCore.sol | 529 ------- .../protocol/Exchange/MixinMatchOrders.sol | 335 ----- .../protocol/Exchange/MixinSignatureValidator.sol | 324 ----- .../protocol/Exchange/MixinTransactions.sol | 152 -- .../protocol/Exchange/MixinWrapperFunctions.sol | 426 ------ .../Exchange/mixins/MAssetProxyDispatcher.sol | 45 - .../protocol/Exchange/mixins/MExchangeCore.sol | 157 --- .../protocol/Exchange/mixins/MMatchOrders.sol | 58 - .../Exchange/mixins/MSignatureValidator.sol | 75 - .../protocol/Exchange/mixins/MTransactions.sol | 58 - .../protocol/Exchange/mixins/MWrapperFunctions.sol | 41 - .../TestAssetProxyDispatcher.sol | 37 - .../TestAssetProxyOwner/TestAssetProxyOwner.sol | 58 - .../TestExchangeInternals.sol | 191 --- .../TestSignatureValidator.sol | 45 - .../TestStaticCallReceiver.sol | 81 -- contracts/protocol/package.json | 93 -- contracts/protocol/src/artifacts/index.ts | 27 - contracts/protocol/src/index.ts | 3 - contracts/protocol/src/wrappers/index.ts | 10 - .../protocol/test/asset_proxy/authorizable.ts | 211 --- contracts/protocol/test/asset_proxy/proxies.ts | 1285 ----------------- contracts/protocol/test/exchange/core.ts | 1157 ---------------- contracts/protocol/test/exchange/dispatcher.ts | 284 ---- contracts/protocol/test/exchange/fill_order.ts | 314 ----- contracts/protocol/test/exchange/internal.ts | 472 ------- contracts/protocol/test/exchange/match_orders.ts | 1284 ----------------- .../protocol/test/exchange/signature_validator.ts | 526 ------- contracts/protocol/test/exchange/transactions.ts | 466 ------- contracts/protocol/test/exchange/wrapper.ts | 1461 -------------------- contracts/protocol/test/global_hooks.ts | 17 - .../protocol/test/multisig/asset_proxy_owner.ts | 506 ------- .../test/utils/asset_proxy_owner_wrapper.ts | 70 - contracts/protocol/test/utils/asset_wrapper.ts | 222 --- contracts/protocol/test/utils/erc20_wrapper.ts | 179 --- contracts/protocol/test/utils/erc721_wrapper.ts | 236 ---- contracts/protocol/test/utils/exchange_wrapper.ts | 292 ---- .../test/utils/fill_order_combinatorial_utils.ts | 928 ------------- contracts/protocol/test/utils/index.ts | 3 - .../protocol/test/utils/match_order_tester.ts | 564 -------- .../test/utils/order_factory_from_scenario.ts | 294 ---- ...le_asset_balance_and_proxy_allowance_fetcher.ts | 19 - .../utils/simple_order_filled_cancelled_fetcher.ts | 31 - contracts/protocol/test/utils/types.ts | 8 - contracts/protocol/tsconfig.json | 23 - contracts/protocol/tslint.json | 6 - 145 files changed, 17202 insertions(+), 15076 deletions(-) create mode 100644 contracts/asset-proxy/CHANGELOG.json create mode 100644 contracts/asset-proxy/CHANGELOG.md create mode 100644 contracts/asset-proxy/DEPLOYS.json create mode 100644 contracts/asset-proxy/README.md create mode 100644 contracts/asset-proxy/compiler.json create mode 100644 contracts/asset-proxy/contracts/asset-proxy/ERC20Proxy.sol create mode 100644 contracts/asset-proxy/contracts/asset-proxy/ERC721Proxy.sol create mode 100644 contracts/asset-proxy/contracts/asset-proxy/MixinAssetProxyDispatcher.sol create mode 100644 contracts/asset-proxy/contracts/asset-proxy/MixinAuthorizable.sol create mode 100644 contracts/asset-proxy/contracts/asset-proxy/MultiAssetProxy.sol create mode 100644 contracts/asset-proxy/contracts/asset-proxy/mixins/MAssetProxyDispatcher.sol create mode 100644 contracts/asset-proxy/contracts/asset-proxy/mixins/MAuthorizable.sol create mode 100644 contracts/asset-proxy/generated-artifacts/ERC20Proxy.json create mode 100644 contracts/asset-proxy/generated-artifacts/ERC721Proxy.json create mode 100644 contracts/asset-proxy/generated-artifacts/MixinAuthorizable.json create mode 100644 contracts/asset-proxy/generated-artifacts/MultiAssetProxy.json create mode 100644 contracts/asset-proxy/package.json create mode 100644 contracts/asset-proxy/src/artifacts/index.ts create mode 100644 contracts/asset-proxy/src/index.ts create mode 100644 contracts/asset-proxy/src/wrappers/index.ts create mode 100644 contracts/asset-proxy/test/authorizable.ts create mode 100644 contracts/asset-proxy/test/global_hooks.ts create mode 100644 contracts/asset-proxy/test/proxies.ts create mode 100644 contracts/asset-proxy/test/utils/erc20_wrapper.ts create mode 100644 contracts/asset-proxy/test/utils/erc721_wrapper.ts create mode 100644 contracts/asset-proxy/test/utils/index.ts create mode 100644 contracts/asset-proxy/tsconfig.json create mode 100644 contracts/asset-proxy/tslint.json create mode 100644 contracts/exchange/CHANGELOG.json create mode 100644 contracts/exchange/CHANGELOG.md create mode 100644 contracts/exchange/DEPLOYS.json create mode 100644 contracts/exchange/README.md create mode 100644 contracts/exchange/compiler.json create mode 100644 contracts/exchange/contracts/exchange/Exchange.sol create mode 100644 contracts/exchange/contracts/exchange/MixinAssetProxyDispatcher.sol create mode 100644 contracts/exchange/contracts/exchange/MixinExchangeCore.sol create mode 100644 contracts/exchange/contracts/exchange/MixinMatchOrders.sol create mode 100644 contracts/exchange/contracts/exchange/MixinSignatureValidator.sol create mode 100644 contracts/exchange/contracts/exchange/MixinTransactions.sol create mode 100644 contracts/exchange/contracts/exchange/MixinWrapperFunctions.sol create mode 100644 contracts/exchange/contracts/exchange/mixins/MAssetProxyDispatcher.sol create mode 100644 contracts/exchange/contracts/exchange/mixins/MExchangeCore.sol create mode 100644 contracts/exchange/contracts/exchange/mixins/MMatchOrders.sol create mode 100644 contracts/exchange/contracts/exchange/mixins/MSignatureValidator.sol create mode 100644 contracts/exchange/contracts/exchange/mixins/MTransactions.sol create mode 100644 contracts/exchange/contracts/exchange/mixins/MWrapperFunctions.sol create mode 100644 contracts/exchange/contracts/test/TestAssetProxyDispatcher.sol create mode 100644 contracts/exchange/contracts/test/TestExchangeInternals.sol create mode 100644 contracts/exchange/contracts/test/TestSignatureValidator.sol create mode 100644 contracts/exchange/contracts/test/TestStaticCallReceiver.sol create mode 100644 contracts/exchange/package.json create mode 100644 contracts/exchange/src/artifacts/index.ts create mode 100644 contracts/exchange/src/index.ts create mode 100644 contracts/exchange/src/wrappers/index.ts create mode 100644 contracts/exchange/test/core.ts create mode 100644 contracts/exchange/test/dispatcher.ts create mode 100644 contracts/exchange/test/fill_order.ts create mode 100644 contracts/exchange/test/global_hooks.ts create mode 100644 contracts/exchange/test/internal.ts create mode 100644 contracts/exchange/test/match_orders.ts create mode 100644 contracts/exchange/test/signature_validator.ts create mode 100644 contracts/exchange/test/transactions.ts create mode 100644 contracts/exchange/test/utils/asset_wrapper.ts create mode 100644 contracts/exchange/test/utils/erc20_wrapper.ts create mode 100644 contracts/exchange/test/utils/erc721_wrapper.ts create mode 100644 contracts/exchange/test/utils/exchange_wrapper.ts create mode 100644 contracts/exchange/test/utils/fill_order_combinatorial_utils.ts create mode 100644 contracts/exchange/test/utils/index.ts create mode 100644 contracts/exchange/test/utils/match_order_tester.ts create mode 100644 contracts/exchange/test/utils/order_factory_from_scenario.ts create mode 100644 contracts/exchange/test/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts create mode 100644 contracts/exchange/test/utils/simple_order_filled_cancelled_fetcher.ts create mode 100644 contracts/exchange/test/utils/types.ts create mode 100644 contracts/exchange/test/wrapper.ts create mode 100644 contracts/exchange/tsconfig.json create mode 100644 contracts/exchange/tslint.json create mode 100644 contracts/multisig/contracts/multisig/AssetProxyOwner.sol create mode 100644 contracts/multisig/contracts/test/TestAssetProxyOwner.sol create mode 100644 contracts/multisig/contracts/test/TestRejectEther.sol delete mode 100644 contracts/multisig/contracts/test/TestRejectEther/TestRejectEther.sol create mode 100644 contracts/multisig/test/asset_proxy_owner.ts create mode 100644 contracts/multisig/test/utils/asset_proxy_owner_wrapper.ts delete mode 100644 contracts/protocol/CHANGELOG.json delete mode 100644 contracts/protocol/CHANGELOG.md delete mode 100644 contracts/protocol/DEPLOYS.json delete mode 100644 contracts/protocol/README.md delete mode 100644 contracts/protocol/compiler.json delete mode 100644 contracts/protocol/contracts/protocol/AssetProxy/ERC20Proxy.sol delete mode 100644 contracts/protocol/contracts/protocol/AssetProxy/ERC721Proxy.sol delete mode 100644 contracts/protocol/contracts/protocol/AssetProxy/MixinAuthorizable.sol delete mode 100644 contracts/protocol/contracts/protocol/AssetProxy/MultiAssetProxy.sol delete mode 100644 contracts/protocol/contracts/protocol/AssetProxy/mixins/MAuthorizable.sol delete mode 100644 contracts/protocol/contracts/protocol/AssetProxyOwner/AssetProxyOwner.sol delete mode 100644 contracts/protocol/contracts/protocol/Exchange/Exchange.sol delete mode 100644 contracts/protocol/contracts/protocol/Exchange/MixinAssetProxyDispatcher.sol delete mode 100644 contracts/protocol/contracts/protocol/Exchange/MixinExchangeCore.sol delete mode 100644 contracts/protocol/contracts/protocol/Exchange/MixinMatchOrders.sol delete mode 100644 contracts/protocol/contracts/protocol/Exchange/MixinSignatureValidator.sol delete mode 100644 contracts/protocol/contracts/protocol/Exchange/MixinTransactions.sol delete mode 100644 contracts/protocol/contracts/protocol/Exchange/MixinWrapperFunctions.sol delete mode 100644 contracts/protocol/contracts/protocol/Exchange/mixins/MAssetProxyDispatcher.sol delete mode 100644 contracts/protocol/contracts/protocol/Exchange/mixins/MExchangeCore.sol delete mode 100644 contracts/protocol/contracts/protocol/Exchange/mixins/MMatchOrders.sol delete mode 100644 contracts/protocol/contracts/protocol/Exchange/mixins/MSignatureValidator.sol delete mode 100644 contracts/protocol/contracts/protocol/Exchange/mixins/MTransactions.sol delete mode 100644 contracts/protocol/contracts/protocol/Exchange/mixins/MWrapperFunctions.sol delete mode 100644 contracts/protocol/contracts/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol delete mode 100644 contracts/protocol/contracts/test/TestAssetProxyOwner/TestAssetProxyOwner.sol delete mode 100644 contracts/protocol/contracts/test/TestExchangeInternals/TestExchangeInternals.sol delete mode 100644 contracts/protocol/contracts/test/TestSignatureValidator/TestSignatureValidator.sol delete mode 100644 contracts/protocol/contracts/test/TestStaticCallReceiver/TestStaticCallReceiver.sol delete mode 100644 contracts/protocol/package.json delete mode 100644 contracts/protocol/src/artifacts/index.ts delete mode 100644 contracts/protocol/src/index.ts delete mode 100644 contracts/protocol/src/wrappers/index.ts delete mode 100644 contracts/protocol/test/asset_proxy/authorizable.ts delete mode 100644 contracts/protocol/test/asset_proxy/proxies.ts delete mode 100644 contracts/protocol/test/exchange/core.ts delete mode 100644 contracts/protocol/test/exchange/dispatcher.ts delete mode 100644 contracts/protocol/test/exchange/fill_order.ts delete mode 100644 contracts/protocol/test/exchange/internal.ts delete mode 100644 contracts/protocol/test/exchange/match_orders.ts delete mode 100644 contracts/protocol/test/exchange/signature_validator.ts delete mode 100644 contracts/protocol/test/exchange/transactions.ts delete mode 100644 contracts/protocol/test/exchange/wrapper.ts delete mode 100644 contracts/protocol/test/global_hooks.ts delete mode 100644 contracts/protocol/test/multisig/asset_proxy_owner.ts delete mode 100644 contracts/protocol/test/utils/asset_proxy_owner_wrapper.ts delete mode 100644 contracts/protocol/test/utils/asset_wrapper.ts delete mode 100644 contracts/protocol/test/utils/erc20_wrapper.ts delete mode 100644 contracts/protocol/test/utils/erc721_wrapper.ts delete mode 100644 contracts/protocol/test/utils/exchange_wrapper.ts delete mode 100644 contracts/protocol/test/utils/fill_order_combinatorial_utils.ts delete mode 100644 contracts/protocol/test/utils/index.ts delete mode 100644 contracts/protocol/test/utils/match_order_tester.ts delete mode 100644 contracts/protocol/test/utils/order_factory_from_scenario.ts delete mode 100644 contracts/protocol/test/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts delete mode 100644 contracts/protocol/test/utils/simple_order_filled_cancelled_fetcher.ts delete mode 100644 contracts/protocol/test/utils/types.ts delete mode 100644 contracts/protocol/tsconfig.json delete mode 100644 contracts/protocol/tslint.json diff --git a/contracts/asset-proxy/CHANGELOG.json b/contracts/asset-proxy/CHANGELOG.json new file mode 100644 index 000000000..0d513cc17 --- /dev/null +++ b/contracts/asset-proxy/CHANGELOG.json @@ -0,0 +1,65 @@ +[ + { + "version": "3.0.0", + "changes": [ + { + "note": "Upgrade the bignumber.js to v8.0.2", + "pr": 1517 + } + ] + }, + { + "timestamp": 1547747677, + "version": "2.2.3", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1547561734, + "version": "2.2.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1547225310, + "version": "2.2.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "2.2.0", + "changes": [ + { + "note": "Added LibAddressArray", + "pr": 1383 + }, + { + "note": "Add validation and comments to MultiAssetProxy", + "pr": 1455 + }, + { + "note": "Move OrderValidator to extensions", + "pr": 1464 + } + ], + "timestamp": 1547040760 + }, + { + "timestamp": 1544741676, + "version": "2.1.59", + "changes": [ + { + "note": "Dependencies updated" + } + ] + } +] diff --git a/contracts/asset-proxy/CHANGELOG.md b/contracts/asset-proxy/CHANGELOG.md new file mode 100644 index 000000000..779eb68a7 --- /dev/null +++ b/contracts/asset-proxy/CHANGELOG.md @@ -0,0 +1,28 @@ + + +CHANGELOG + +## v2.2.3 - _January 17, 2019_ + + * Dependencies updated + +## v2.2.2 - _January 15, 2019_ + + * Dependencies updated + +## v2.2.1 - _January 11, 2019_ + + * Dependencies updated + +## v2.2.0 - _January 9, 2019_ + + * Added LibAddressArray (#1383) + * Add validation and comments to MultiAssetProxy (#1455) + * Move OrderValidator to extensions (#1464) + +## v2.1.59 - _December 13, 2018_ + + * Dependencies updated diff --git a/contracts/asset-proxy/DEPLOYS.json b/contracts/asset-proxy/DEPLOYS.json new file mode 100644 index 000000000..5c24ae59c --- /dev/null +++ b/contracts/asset-proxy/DEPLOYS.json @@ -0,0 +1,92 @@ +[ + { + "name": "MultiAssetProxy", + "version": "1.0.0", + "changes": [ + { + "note": "Add MultiAssetProxy implementation", + "pr": 1224 + } + ] + }, + { + "name": "OrderValidator", + "version": "1.0.0", + "changes": [ + { + "note": "remove `getApproved` check from ERC721 approval query", + "pr": 1149 + } + ] + }, + { + "name": "OrderValidator", + "version": "1.0.0", + "changes": [ + { + "note": "protocol v2 deploy", + "networks": { + "1": "0x9463e518dea6810309563c81d5266c1b1d149138", + "3": "0x90431a90516ab49af23a0530e04e8c7836e7122f", + "42": "0xb389da3d204b412df2f75c6afb3d0a7ce0bc283d" + } + } + ] + }, + { + "name": "Exchange", + "version": "2.0.0", + "changes": [ + { + "note": "protocol v2 deploy", + "networks": { + "1": "0x4f833a24e1f95d70f028921e27040ca56e09ab0b", + "3": "0x4530c0483a1633c7a1c97d2c53721caff2caaaaf", + "42": "0x35dd2932454449b14cee11a94d3674a936d5d7b2" + } + } + ] + }, + { + "name": "ERC20Proxy", + "version": "1.0.0", + "changes": [ + { + "note": "protocol v2 deploy", + "networks": { + "1": "0x2240dab907db71e64d3e0dba4800c83b5c502d4e", + "3": "0xb1408f4c245a23c31b98d2c626777d4c0d766caa", + "42": "0xf1ec01d6236d3cd881a0bf0130ea25fe4234003e" + } + } + ] + }, + { + "name": "ERC721Proxy", + "version": "1.0.0", + "changes": [ + { + "note": "protocol v2 deploy", + "networks": { + "1": "0x208e41fb445f1bb1b6780d58356e81405f3e6127", + "3": "0xe654aac058bfbf9f83fcaee7793311dd82f6ddb4", + "42": "0x2a9127c745688a165106c11cd4d647d2220af821" + } + } + ] + }, + { + "name": "AssetProxyOwner", + "version": "1.0.0", + "changes": [ + { + "note": "protocol v2 deploy", + "networks": { + "1": "0x17992e4ffb22730138e4b62aaa6367fa9d3699a6", + "3": "0xf5fa5b5fed2727a0e44ac67f6772e97977aa358b", + "42": "0x2c824d2882baa668e0d5202b1e7f2922278703f8" + } + } + ] + } +] diff --git a/contracts/asset-proxy/README.md b/contracts/asset-proxy/README.md new file mode 100644 index 000000000..dc1f32c98 --- /dev/null +++ b/contracts/asset-proxy/README.md @@ -0,0 +1,74 @@ +## Contracts + +Smart contracts that implement the 0x protocol. Addresses of the deployed contracts can be found in the 0x [wiki](https://0xproject.com/wiki#Deployed-Addresses) or the [CHANGELOG](./CHANGELOG.json) of this package. + +## Usage + +Contracts that make up and interact with version 2.0.0 of the protocol can be found in the [contracts](./contracts) directory. The contents of this directory are broken down into the following subdirectories: + +- [protocol](./contracts/protocol) + - This directory contains the contracts that make up version 2.0.0. A full specification can be found [here](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md). +- [test](./contracts/test) + - This directory contains mocks and other contracts that are used solely for testing contracts within the other directories. + +## Bug bounty + +A bug bounty for the 2.0.0 contracts is ongoing! Instructions can be found [here](https://0xproject.com/wiki#Bug-Bounty). + +## Contributing + +We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. + +For proposals regarding the 0x protocol's smart contract architecture, message format, or additional functionality, go to the [0x Improvement Proposals (ZEIPs)](https://github.com/0xProject/ZEIPs) repository and follow the contribution guidelines provided therein. + +Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. + +### Install Dependencies + +If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them: + +```bash +yarn config set workspaces-experimental true +``` + +Then install dependencies + +```bash +yarn install +``` + +### Build + +To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: + +```bash +PKG=@0x/contracts-protocol yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0x/contracts-protocol yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` + +#### Testing options + +Contracts testing options like coverage, profiling, revert traces or backing node choosing - are described [here](../TESTING.md). diff --git a/contracts/asset-proxy/compiler.json b/contracts/asset-proxy/compiler.json new file mode 100644 index 000000000..bc3dead2c --- /dev/null +++ b/contracts/asset-proxy/compiler.json @@ -0,0 +1,22 @@ +{ + "artifactsDir": "./generated-artifacts", + "contractsDir": "./contracts", + "compilerSettings": { + "optimizer": { + "enabled": true, + "runs": 1000000 + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + } + }, + "contracts": ["ERC20Proxy", "ERC721Proxy", "MixinAuthorizable", "MultiAssetProxy"] +} diff --git a/contracts/asset-proxy/contracts/asset-proxy/ERC20Proxy.sol b/contracts/asset-proxy/contracts/asset-proxy/ERC20Proxy.sol new file mode 100644 index 000000000..258443bca --- /dev/null +++ b/contracts/asset-proxy/contracts/asset-proxy/ERC20Proxy.sol @@ -0,0 +1,184 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "./MixinAuthorizable.sol"; + + +contract ERC20Proxy is + MixinAuthorizable +{ + // Id of this proxy. + bytes4 constant internal PROXY_ID = bytes4(keccak256("ERC20Token(address)")); + + // solhint-disable-next-line payable-fallback + function () + external + { + assembly { + // The first 4 bytes of calldata holds the function selector + let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000) + + // `transferFrom` will be called with the following parameters: + // assetData Encoded byte array. + // from Address to transfer asset from. + // to Address to transfer asset to. + // amount Amount of asset to transfer. + // bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4 + if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) { + + // To lookup a value in a mapping, we load from the storage location keccak256(k, p), + // where k is the key left padded to 32 bytes and p is the storage slot + let start := mload(64) + mstore(start, and(caller, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(start, 32), authorized_slot) + + // Revert if authorized[msg.sender] == false + if iszero(sload(keccak256(start, 64))) { + // Revert with `Error("SENDER_NOT_AUTHORIZED")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000001553454e4445525f4e4f545f415554484f52495a454400000000000000) + mstore(96, 0) + revert(0, 100) + } + + // `transferFrom`. + // The function is marked `external`, so no abi decodeding is done for + // us. Instead, we expect the `calldata` memory to contain the + // following: + // + // | Area | Offset | Length | Contents | + // |----------|--------|---------|-------------------------------------| + // | Header | 0 | 4 | function selector | + // | Params | | 4 * 32 | function parameters: | + // | | 4 | | 1. offset to assetData (*) | + // | | 36 | | 2. from | + // | | 68 | | 3. to | + // | | 100 | | 4. amount | + // | Data | | | assetData: | + // | | 132 | 32 | assetData Length | + // | | 164 | ** | assetData Contents | + // + // (*): offset is computed from start of function parameters, so offset + // by an additional 4 bytes in the calldata. + // + // (**): see table below to compute length of assetData Contents + // + // WARNING: The ABIv2 specification allows additional padding between + // the Params and Data section. This will result in a larger + // offset to assetData. + + // Asset data itself is encoded as follows: + // + // | Area | Offset | Length | Contents | + // |----------|--------|---------|-------------------------------------| + // | Header | 0 | 4 | function selector | + // | Params | | 1 * 32 | function parameters: | + // | | 4 | 12 + 20 | 1. token address | + + // We construct calldata for the `token.transferFrom` ABI. + // The layout of this calldata is in the table below. + // + // | Area | Offset | Length | Contents | + // |----------|--------|---------|-------------------------------------| + // | Header | 0 | 4 | function selector | + // | Params | | 3 * 32 | function parameters: | + // | | 4 | | 1. from | + // | | 36 | | 2. to | + // | | 68 | | 3. amount | + + /////// Read token address from calldata /////// + // * The token address is stored in `assetData`. + // + // * The "offset to assetData" is stored at offset 4 in the calldata (table 1). + // [assetDataOffsetFromParams = calldataload(4)] + // + // * Notes that the "offset to assetData" is relative to the "Params" area of calldata; + // add 4 bytes to account for the length of the "Header" area (table 1). + // [assetDataOffsetFromHeader = assetDataOffsetFromParams + 4] + // + // * The "token address" is offset 32+4=36 bytes into "assetData" (tables 1 & 2). + // [tokenOffset = assetDataOffsetFromHeader + 36 = calldataload(4) + 4 + 36] + let token := calldataload(add(calldataload(4), 40)) + + /////// Setup Header Area /////// + // This area holds the 4-byte `transferFrom` selector. + // Any trailing data in transferFromSelector will be + // overwritten in the next `mstore` call. + mstore(0, 0x23b872dd00000000000000000000000000000000000000000000000000000000) + + /////// Setup Params Area /////// + // We copy the fields `from`, `to` and `amount` in bulk + // from our own calldata to the new calldata. + calldatacopy(4, 36, 96) + + /////// Call `token.transferFrom` using the calldata /////// + let success := call( + gas, // forward all gas + token, // call address of token contract + 0, // don't send any ETH + 0, // pointer to start of input + 100, // length of input + 0, // write output over input + 32 // output size should be 32 bytes + ) + + /////// Check return data. /////// + // If there is no return data, we assume the token incorrectly + // does not return a bool. In this case we expect it to revert + // on failure, which was handled above. + // If the token does return data, we require that it is a single + // nonzero 32 bytes value. + // So the transfer succeeded if the call succeeded and either + // returned nothing, or returned a non-zero 32 byte value. + success := and(success, or( + iszero(returndatasize), + and( + eq(returndatasize, 32), + gt(mload(0), 0) + ) + )) + if success { + return(0, 0) + } + + // Revert with `Error("TRANSFER_FAILED")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000000f5452414e534645525f4641494c454400000000000000000000000000) + mstore(96, 0) + revert(0, 100) + } + + // Revert if undefined function is called + revert(0, 0) + } + } + + /// @dev Gets the proxy id associated with the proxy address. + /// @return Proxy id. + function getProxyId() + external + pure + returns (bytes4) + { + return PROXY_ID; + } +} diff --git a/contracts/asset-proxy/contracts/asset-proxy/ERC721Proxy.sol b/contracts/asset-proxy/contracts/asset-proxy/ERC721Proxy.sol new file mode 100644 index 000000000..65b664b8b --- /dev/null +++ b/contracts/asset-proxy/contracts/asset-proxy/ERC721Proxy.sol @@ -0,0 +1,171 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "./MixinAuthorizable.sol"; + + +contract ERC721Proxy is + MixinAuthorizable +{ + // Id of this proxy. + bytes4 constant internal PROXY_ID = bytes4(keccak256("ERC721Token(address,uint256)")); + + // solhint-disable-next-line payable-fallback + function () + external + { + assembly { + // The first 4 bytes of calldata holds the function selector + let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000) + + // `transferFrom` will be called with the following parameters: + // assetData Encoded byte array. + // from Address to transfer asset from. + // to Address to transfer asset to. + // amount Amount of asset to transfer. + // bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4 + if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) { + + // To lookup a value in a mapping, we load from the storage location keccak256(k, p), + // where k is the key left padded to 32 bytes and p is the storage slot + let start := mload(64) + mstore(start, and(caller, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(start, 32), authorized_slot) + + // Revert if authorized[msg.sender] == false + if iszero(sload(keccak256(start, 64))) { + // Revert with `Error("SENDER_NOT_AUTHORIZED")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000001553454e4445525f4e4f545f415554484f52495a454400000000000000) + mstore(96, 0) + revert(0, 100) + } + + // `transferFrom`. + // The function is marked `external`, so no abi decodeding is done for + // us. Instead, we expect the `calldata` memory to contain the + // following: + // + // | Area | Offset | Length | Contents | + // |----------|--------|---------|-------------------------------------| + // | Header | 0 | 4 | function selector | + // | Params | | 4 * 32 | function parameters: | + // | | 4 | | 1. offset to assetData (*) | + // | | 36 | | 2. from | + // | | 68 | | 3. to | + // | | 100 | | 4. amount | + // | Data | | | assetData: | + // | | 132 | 32 | assetData Length | + // | | 164 | ** | assetData Contents | + // + // (*): offset is computed from start of function parameters, so offset + // by an additional 4 bytes in the calldata. + // + // (**): see table below to compute length of assetData Contents + // + // WARNING: The ABIv2 specification allows additional padding between + // the Params and Data section. This will result in a larger + // offset to assetData. + + // Asset data itself is encoded as follows: + // + // | Area | Offset | Length | Contents | + // |----------|--------|---------|-------------------------------------| + // | Header | 0 | 4 | function selector | + // | Params | | 2 * 32 | function parameters: | + // | | 4 | 12 + 20 | 1. token address | + // | | 36 | | 2. tokenId | + + // We construct calldata for the `token.transferFrom` ABI. + // The layout of this calldata is in the table below. + // + // | Area | Offset | Length | Contents | + // |----------|--------|---------|-------------------------------------| + // | Header | 0 | 4 | function selector | + // | Params | | 3 * 32 | function parameters: | + // | | 4 | | 1. from | + // | | 36 | | 2. to | + // | | 68 | | 3. tokenId | + + // There exists only 1 of each token. + // require(amount == 1, "INVALID_AMOUNT") + if sub(calldataload(100), 1) { + // Revert with `Error("INVALID_AMOUNT")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000000e494e56414c49445f414d4f554e540000000000000000000000000000) + mstore(96, 0) + revert(0, 100) + } + + /////// Setup Header Area /////// + // This area holds the 4-byte `transferFrom` selector. + // Any trailing data in transferFromSelector will be + // overwritten in the next `mstore` call. + mstore(0, 0x23b872dd00000000000000000000000000000000000000000000000000000000) + + /////// Setup Params Area /////// + // We copy the fields `from` and `to` in bulk + // from our own calldata to the new calldata. + calldatacopy(4, 36, 64) + + // Copy `tokenId` field from our own calldata to the new calldata. + let assetDataOffset := calldataload(4) + calldatacopy(68, add(assetDataOffset, 72), 32) + + /////// Call `token.transferFrom` using the calldata /////// + let token := calldataload(add(assetDataOffset, 40)) + let success := call( + gas, // forward all gas + token, // call address of token contract + 0, // don't send any ETH + 0, // pointer to start of input + 100, // length of input + 0, // write output to null + 0 // output size is 0 bytes + ) + if success { + return(0, 0) + } + + // Revert with `Error("TRANSFER_FAILED")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000000f5452414e534645525f4641494c454400000000000000000000000000) + mstore(96, 0) + revert(0, 100) + } + + // Revert if undefined function is called + revert(0, 0) + } + } + + /// @dev Gets the proxy id associated with the proxy address. + /// @return Proxy id. + function getProxyId() + external + pure + returns (bytes4) + { + return PROXY_ID; + } +} diff --git a/contracts/asset-proxy/contracts/asset-proxy/MixinAssetProxyDispatcher.sol b/contracts/asset-proxy/contracts/asset-proxy/MixinAssetProxyDispatcher.sol new file mode 100644 index 000000000..521b8c4ac --- /dev/null +++ b/contracts/asset-proxy/contracts/asset-proxy/MixinAssetProxyDispatcher.sol @@ -0,0 +1,174 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; + +import "@0x/contracts-utils/contracts/utils/Ownable.sol"; +import "./mixins/MAssetProxyDispatcher.sol"; +import "@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAssetProxy.sol"; + + +contract MixinAssetProxyDispatcher is + Ownable, + MAssetProxyDispatcher +{ + // Mapping from Asset Proxy Id's to their respective Asset Proxy + mapping (bytes4 => IAssetProxy) public assetProxies; + + /// @dev Registers an asset proxy to its asset proxy id. + /// Once an asset proxy is registered, it cannot be unregistered. + /// @param assetProxy Address of new asset proxy to register. + function registerAssetProxy(address assetProxy) + external + onlyOwner + { + IAssetProxy assetProxyContract = IAssetProxy(assetProxy); + + // Ensure that no asset proxy exists with current id. + bytes4 assetProxyId = assetProxyContract.getProxyId(); + address currentAssetProxy = assetProxies[assetProxyId]; + require( + currentAssetProxy == address(0), + "ASSET_PROXY_ALREADY_EXISTS" + ); + + // Add asset proxy and log registration. + assetProxies[assetProxyId] = assetProxyContract; + emit AssetProxyRegistered( + assetProxyId, + assetProxy + ); + } + + /// @dev Gets an asset proxy. + /// @param assetProxyId Id of the asset proxy. + /// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered. + function getAssetProxy(bytes4 assetProxyId) + external + view + returns (address) + { + return assetProxies[assetProxyId]; + } + + /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws. + /// @param assetData Byte array encoded for the asset. + /// @param from Address to transfer token from. + /// @param to Address to transfer token to. + /// @param amount Amount of token to transfer. + function dispatchTransferFrom( + bytes memory assetData, + address from, + address to, + uint256 amount + ) + internal + { + // Do nothing if no amount should be transferred. + if (amount > 0 && from != to) { + // Ensure assetData length is valid + require( + assetData.length > 3, + "LENGTH_GREATER_THAN_3_REQUIRED" + ); + + // Lookup assetProxy. We do not use `LibBytes.readBytes4` for gas efficiency reasons. + bytes4 assetProxyId; + assembly { + assetProxyId := and(mload( + add(assetData, 32)), + 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 + ) + } + address assetProxy = assetProxies[assetProxyId]; + + // Ensure that assetProxy exists + require( + assetProxy != address(0), + "ASSET_PROXY_DOES_NOT_EXIST" + ); + + // We construct calldata for the `assetProxy.transferFrom` ABI. + // The layout of this calldata is in the table below. + // + // | Area | Offset | Length | Contents | + // | -------- |--------|---------|-------------------------------------------- | + // | Header | 0 | 4 | function selector | + // | Params | | 4 * 32 | function parameters: | + // | | 4 | | 1. offset to assetData (*) | + // | | 36 | | 2. from | + // | | 68 | | 3. to | + // | | 100 | | 4. amount | + // | Data | | | assetData: | + // | | 132 | 32 | assetData Length | + // | | 164 | ** | assetData Contents | + + assembly { + /////// Setup State /////// + // `cdStart` is the start of the calldata for `assetProxy.transferFrom` (equal to free memory ptr). + let cdStart := mload(64) + // `dataAreaLength` is the total number of words needed to store `assetData` + // As-per the ABI spec, this value is padded up to the nearest multiple of 32, + // and includes 32-bytes for length. + let dataAreaLength := and(add(mload(assetData), 63), 0xFFFFFFFFFFFE0) + // `cdEnd` is the end of the calldata for `assetProxy.transferFrom`. + let cdEnd := add(cdStart, add(132, dataAreaLength)) + + + /////// Setup Header Area /////// + // This area holds the 4-byte `transferFromSelector`. + // bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4 + mstore(cdStart, 0xa85e59e400000000000000000000000000000000000000000000000000000000) + + /////// Setup Params Area /////// + // Each parameter is padded to 32-bytes. The entire Params Area is 128 bytes. + // Notes: + // 1. The offset to `assetData` is the length of the Params Area (128 bytes). + // 2. A 20-byte mask is applied to addresses to zero-out the unused bytes. + mstore(add(cdStart, 4), 128) + mstore(add(cdStart, 36), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(cdStart, 68), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(cdStart, 100), amount) + + /////// Setup Data Area /////// + // This area holds `assetData`. + let dataArea := add(cdStart, 132) + // solhint-disable-next-line no-empty-blocks + for {} lt(dataArea, cdEnd) {} { + mstore(dataArea, mload(assetData)) + dataArea := add(dataArea, 32) + assetData := add(assetData, 32) + } + + /////// Call `assetProxy.transferFrom` using the constructed calldata /////// + let success := call( + gas, // forward all gas + assetProxy, // call address of asset proxy + 0, // don't send any ETH + cdStart, // pointer to start of input + sub(cdEnd, cdStart), // length of input + cdStart, // write output over input + 512 // reserve 512 bytes for output + ) + if iszero(success) { + revert(cdStart, returndatasize()) + } + } + } + } +} diff --git a/contracts/asset-proxy/contracts/asset-proxy/MixinAuthorizable.sol b/contracts/asset-proxy/contracts/asset-proxy/MixinAuthorizable.sol new file mode 100644 index 000000000..983ef91a5 --- /dev/null +++ b/contracts/asset-proxy/contracts/asset-proxy/MixinAuthorizable.sol @@ -0,0 +1,117 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; + +import "@0x/contracts-utils/contracts/utils/Ownable.sol"; +import "./mixins/MAuthorizable.sol"; + + +contract MixinAuthorizable is + Ownable, + MAuthorizable +{ + /// @dev Only authorized addresses can invoke functions with this modifier. + modifier onlyAuthorized { + require( + authorized[msg.sender], + "SENDER_NOT_AUTHORIZED" + ); + _; + } + + mapping (address => bool) public authorized; + address[] public authorities; + + /// @dev Authorizes an address. + /// @param target Address to authorize. + function addAuthorizedAddress(address target) + external + onlyOwner + { + require( + !authorized[target], + "TARGET_ALREADY_AUTHORIZED" + ); + + authorized[target] = true; + authorities.push(target); + emit AuthorizedAddressAdded(target, msg.sender); + } + + /// @dev Removes authorizion of an address. + /// @param target Address to remove authorization from. + function removeAuthorizedAddress(address target) + external + onlyOwner + { + require( + authorized[target], + "TARGET_NOT_AUTHORIZED" + ); + + delete authorized[target]; + for (uint256 i = 0; i < authorities.length; i++) { + if (authorities[i] == target) { + authorities[i] = authorities[authorities.length - 1]; + authorities.length -= 1; + break; + } + } + emit AuthorizedAddressRemoved(target, msg.sender); + } + + /// @dev Removes authorizion of an address. + /// @param target Address to remove authorization from. + /// @param index Index of target in authorities array. + function removeAuthorizedAddressAtIndex( + address target, + uint256 index + ) + external + onlyOwner + { + require( + authorized[target], + "TARGET_NOT_AUTHORIZED" + ); + require( + index < authorities.length, + "INDEX_OUT_OF_BOUNDS" + ); + require( + authorities[index] == target, + "AUTHORIZED_ADDRESS_MISMATCH" + ); + + delete authorized[target]; + authorities[index] = authorities[authorities.length - 1]; + authorities.length -= 1; + emit AuthorizedAddressRemoved(target, msg.sender); + } + + /// @dev Gets all authorized addresses. + /// @return Array of authorized addresses. + function getAuthorizedAddresses() + external + view + returns (address[] memory) + { + return authorities; + } +} diff --git a/contracts/asset-proxy/contracts/asset-proxy/MultiAssetProxy.sol b/contracts/asset-proxy/contracts/asset-proxy/MultiAssetProxy.sol new file mode 100644 index 000000000..0b2cb4134 --- /dev/null +++ b/contracts/asset-proxy/contracts/asset-proxy/MultiAssetProxy.sol @@ -0,0 +1,306 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "./MixinAssetProxyDispatcher.sol"; +import "./MixinAuthorizable.sol"; + + +contract MultiAssetProxy is + MixinAssetProxyDispatcher, + MixinAuthorizable +{ + // Id of this proxy. + bytes4 constant internal PROXY_ID = bytes4(keccak256("MultiAsset(uint256[],bytes[])")); + + // solhint-disable-next-line payable-fallback + function () + external + { + // NOTE: The below assembly assumes that clients do some input validation and that the input is properly encoded according to the AbiV2 specification. + // It is technically possible for inputs with very large lengths and offsets to cause overflows. However, this would make the calldata prohibitively + // expensive and we therefore do not check for overflows in these scenarios. + assembly { + // The first 4 bytes of calldata holds the function selector + let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000) + + // `transferFrom` will be called with the following parameters: + // assetData Encoded byte array. + // from Address to transfer asset from. + // to Address to transfer asset to. + // amount Amount of asset to transfer. + // bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4 + if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) { + + // To lookup a value in a mapping, we load from the storage location keccak256(k, p), + // where k is the key left padded to 32 bytes and p is the storage slot + mstore(0, caller) + mstore(32, authorized_slot) + + // Revert if authorized[msg.sender] == false + if iszero(sload(keccak256(0, 64))) { + // Revert with `Error("SENDER_NOT_AUTHORIZED")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000001553454e4445525f4e4f545f415554484f52495a454400000000000000) + mstore(96, 0) + revert(0, 100) + } + + // `transferFrom`. + // The function is marked `external`, so no abi decoding is done for + // us. Instead, we expect the `calldata` memory to contain the + // following: + // + // | Area | Offset | Length | Contents | + // |----------|--------|---------|-------------------------------------| + // | Header | 0 | 4 | function selector | + // | Params | | 4 * 32 | function parameters: | + // | | 4 | | 1. offset to assetData (*) | + // | | 36 | | 2. from | + // | | 68 | | 3. to | + // | | 100 | | 4. amount | + // | Data | | | assetData: | + // | | 132 | 32 | assetData Length | + // | | 164 | ** | assetData Contents | + // + // (*): offset is computed from start of function parameters, so offset + // by an additional 4 bytes in the calldata. + // + // (**): see table below to compute length of assetData Contents + // + // WARNING: The ABIv2 specification allows additional padding between + // the Params and Data section. This will result in a larger + // offset to assetData. + + // Load offset to `assetData` + let assetDataOffset := calldataload(4) + + // Asset data itself is encoded as follows: + // + // | Area | Offset | Length | Contents | + // |----------|-------------|---------|-------------------------------------| + // | Header | 0 | 4 | assetProxyId | + // | Params | | 2 * 32 | function parameters: | + // | | 4 | | 1. offset to amounts (*) | + // | | 36 | | 2. offset to nestedAssetData (*) | + // | Data | | | amounts: | + // | | 68 | 32 | amounts Length | + // | | 100 | a | amounts Contents | + // | | | | nestedAssetData: | + // | | 100 + a | 32 | nestedAssetData Length | + // | | 132 + a | b | nestedAssetData Contents (offsets) | + // | | 132 + a + b | | nestedAssetData[0, ..., len] | + + // In order to find the offset to `amounts`, we must add: + // 4 (function selector) + // + assetDataOffset + // + 32 (assetData len) + // + 4 (assetProxyId) + let amountsOffset := calldataload(add(assetDataOffset, 40)) + + // In order to find the offset to `nestedAssetData`, we must add: + // 4 (function selector) + // + assetDataOffset + // + 32 (assetData len) + // + 4 (assetProxyId) + // + 32 (amounts offset) + let nestedAssetDataOffset := calldataload(add(assetDataOffset, 72)) + + // In order to find the start of the `amounts` contents, we must add: + // 4 (function selector) + // + assetDataOffset + // + 32 (assetData len) + // + 4 (assetProxyId) + // + amountsOffset + // + 32 (amounts len) + let amountsContentsStart := add(assetDataOffset, add(amountsOffset, 72)) + + // Load number of elements in `amounts` + let amountsLen := calldataload(sub(amountsContentsStart, 32)) + + // In order to find the start of the `nestedAssetData` contents, we must add: + // 4 (function selector) + // + assetDataOffset + // + 32 (assetData len) + // + 4 (assetProxyId) + // + nestedAssetDataOffset + // + 32 (nestedAssetData len) + let nestedAssetDataContentsStart := add(assetDataOffset, add(nestedAssetDataOffset, 72)) + + // Load number of elements in `nestedAssetData` + let nestedAssetDataLen := calldataload(sub(nestedAssetDataContentsStart, 32)) + + // Revert if number of elements in `amounts` differs from number of elements in `nestedAssetData` + if sub(amountsLen, nestedAssetDataLen) { + // Revert with `Error("LENGTH_MISMATCH")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000000f4c454e4754485f4d49534d4154434800000000000000000000000000) + mstore(96, 0) + revert(0, 100) + } + + // Copy `transferFrom` selector, offset to `assetData`, `from`, and `to` from calldata to memory + calldatacopy( + 0, // memory can safely be overwritten from beginning + 0, // start of calldata + 100 // length of selector (4) and 3 params (32 * 3) + ) + + // Overwrite existing offset to `assetData` with our own + mstore(4, 128) + + // Load `amount` + let amount := calldataload(100) + + // Calculate number of bytes in `amounts` contents + let amountsByteLen := mul(amountsLen, 32) + + // Initialize `assetProxyId` and `assetProxy` to 0 + let assetProxyId := 0 + let assetProxy := 0 + + // Loop through `amounts` and `nestedAssetData`, calling `transferFrom` for each respective element + for {let i := 0} lt(i, amountsByteLen) {i := add(i, 32)} { + + // Calculate the total amount + let amountsElement := calldataload(add(amountsContentsStart, i)) + let totalAmount := mul(amountsElement, amount) + + // Revert if `amount` != 0 and multiplication resulted in an overflow + if iszero(or( + iszero(amount), + eq(div(totalAmount, amount), amountsElement) + )) { + // Revert with `Error("UINT256_OVERFLOW")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000001055494e543235365f4f564552464c4f57000000000000000000000000) + mstore(96, 0) + revert(0, 100) + } + + // Write `totalAmount` to memory + mstore(100, totalAmount) + + // Load offset to `nestedAssetData[i]` + let nestedAssetDataElementOffset := calldataload(add(nestedAssetDataContentsStart, i)) + + // In order to find the start of the `nestedAssetData[i]` contents, we must add: + // 4 (function selector) + // + assetDataOffset + // + 32 (assetData len) + // + 4 (assetProxyId) + // + nestedAssetDataOffset + // + 32 (nestedAssetData len) + // + nestedAssetDataElementOffset + // + 32 (nestedAssetDataElement len) + let nestedAssetDataElementContentsStart := add(assetDataOffset, add(nestedAssetDataOffset, add(nestedAssetDataElementOffset, 104))) + + // Load length of `nestedAssetData[i]` + let nestedAssetDataElementLenStart := sub(nestedAssetDataElementContentsStart, 32) + let nestedAssetDataElementLen := calldataload(nestedAssetDataElementLenStart) + + // Revert if the `nestedAssetData` does not contain a 4 byte `assetProxyId` + if lt(nestedAssetDataElementLen, 4) { + // Revert with `Error("LENGTH_GREATER_THAN_3_REQUIRED")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000001e4c454e4754485f475245415445525f5448414e5f335f524551554952) + mstore(96, 0x4544000000000000000000000000000000000000000000000000000000000000) + revert(0, 100) + } + + // Load AssetProxy id + let currentAssetProxyId := and( + calldataload(nestedAssetDataElementContentsStart), + 0xffffffff00000000000000000000000000000000000000000000000000000000 + ) + + // Only load `assetProxy` if `currentAssetProxyId` does not equal `assetProxyId` + // We do not need to check if `currentAssetProxyId` is 0 since `assetProxy` is also initialized to 0 + if sub(currentAssetProxyId, assetProxyId) { + // Update `assetProxyId` + assetProxyId := currentAssetProxyId + // To lookup a value in a mapping, we load from the storage location keccak256(k, p), + // where k is the key left padded to 32 bytes and p is the storage slot + mstore(132, assetProxyId) + mstore(164, assetProxies_slot) + assetProxy := sload(keccak256(132, 64)) + } + + // Revert if AssetProxy with given id does not exist + if iszero(assetProxy) { + // Revert with `Error("ASSET_PROXY_DOES_NOT_EXIST")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000001a41535345545f50524f58595f444f45535f4e4f545f45584953540000) + mstore(96, 0) + revert(0, 100) + } + + // Copy `nestedAssetData[i]` from calldata to memory + calldatacopy( + 132, // memory slot after `amounts[i]` + nestedAssetDataElementLenStart, // location of `nestedAssetData[i]` in calldata + add(nestedAssetDataElementLen, 32) // `nestedAssetData[i].length` plus 32 byte length + ) + + // call `assetProxy.transferFrom` + let success := call( + gas, // forward all gas + assetProxy, // call address of asset proxy + 0, // don't send any ETH + 0, // pointer to start of input + add(164, nestedAssetDataElementLen), // length of input + 0, // write output over memory that won't be reused + 0 // don't copy output to memory + ) + + // Revert with reason given by AssetProxy if `transferFrom` call failed + if iszero(success) { + returndatacopy( + 0, // copy to memory at 0 + 0, // copy from return data at 0 + returndatasize() // copy all return data + ) + revert(0, returndatasize()) + } + } + + // Return if no `transferFrom` calls reverted + return(0, 0) + } + + // Revert if undefined function is called + revert(0, 0) + } + } + + /// @dev Gets the proxy id associated with the proxy address. + /// @return Proxy id. + function getProxyId() + external + pure + returns (bytes4) + { + return PROXY_ID; + } +} diff --git a/contracts/asset-proxy/contracts/asset-proxy/mixins/MAssetProxyDispatcher.sol b/contracts/asset-proxy/contracts/asset-proxy/mixins/MAssetProxyDispatcher.sol new file mode 100644 index 000000000..05c2c4c0b --- /dev/null +++ b/contracts/asset-proxy/contracts/asset-proxy/mixins/MAssetProxyDispatcher.sol @@ -0,0 +1,45 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; + +import "@0x/contracts-interfaces/contracts/protocol/Exchange/IAssetProxyDispatcher.sol"; + + +contract MAssetProxyDispatcher is + IAssetProxyDispatcher +{ + // Logs registration of new asset proxy + event AssetProxyRegistered( + bytes4 id, // Id of new registered AssetProxy. + address assetProxy // Address of new registered AssetProxy. + ); + + /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws. + /// @param assetData Byte array encoded for the asset. + /// @param from Address to transfer token from. + /// @param to Address to transfer token to. + /// @param amount Amount of token to transfer. + function dispatchTransferFrom( + bytes memory assetData, + address from, + address to, + uint256 amount + ) + internal; +} diff --git a/contracts/asset-proxy/contracts/asset-proxy/mixins/MAuthorizable.sol b/contracts/asset-proxy/contracts/asset-proxy/mixins/MAuthorizable.sol new file mode 100644 index 000000000..6c3f70500 --- /dev/null +++ b/contracts/asset-proxy/contracts/asset-proxy/mixins/MAuthorizable.sol @@ -0,0 +1,41 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; + +import "@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAuthorizable.sol"; + + +contract MAuthorizable is + IAuthorizable +{ + // Event logged when a new address is authorized. + event AuthorizedAddressAdded( + address indexed target, + address indexed caller + ); + + // Event logged when a currently authorized address is unauthorized. + event AuthorizedAddressRemoved( + address indexed target, + address indexed caller + ); + + /// @dev Only authorized addresses can invoke functions with this modifier. + modifier onlyAuthorized { revert(); _; } +} diff --git a/contracts/asset-proxy/generated-artifacts/ERC20Proxy.json b/contracts/asset-proxy/generated-artifacts/ERC20Proxy.json new file mode 100644 index 000000000..695c2916f --- /dev/null +++ b/contracts/asset-proxy/generated-artifacts/ERC20Proxy.json @@ -0,0 +1,252 @@ +{ + "schemaVersion": "2.0.0", + "contractName": "ERC20Proxy", + "compilerOutput": { + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "addAuthorizedAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "authorities", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "removeAuthorizedAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + }, + { + "name": "index", + "type": "uint256" + } + ], + "name": "removeAuthorizedAddressAtIndex", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getProxyId", + "outputs": [ + { + "name": "", + "type": "bytes4" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "authorized", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getAuthorizedAddresses", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "payable": false, + "stateMutability": "nonpayable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "target", + "type": "address" + }, + { + "indexed": true, + "name": "caller", + "type": "address" + } + ], + "name": "AuthorizedAddressAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "target", + "type": "address" + }, + { + "indexed": true, + "name": "caller", + "type": "address" + } + ], + "name": "AuthorizedAddressRemoved", + "type": "event" + } + ], + "evm": { + "bytecode": { + "linkReferences": {}, + "object": "0x608060405260008054600160a060020a03191633179055610f4a806100256000396000f3006080604052600436106100985763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166342f1181e8114610248578063494503d41461027857806370712939146102b95780638da5cb5b146102e75780639ad26744146102fc578063ae25532e1461032d578063b918161114610377578063d39de6e9146103b9578063f2fde38b1461041e575b3480156100a457600080fd5b507fffffffff00000000000000000000000000000000000000000000000000000000600035167fa85e59e40000000000000000000000000000000000000000000000000000000081141561024357604080513381526001602082015290812054151561017b577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c1553454e4445525f4e4f545f415554484f52495a454400000000000000604052600060605260646000fd5b602860043501357f23b872dd0000000000000000000000000000000000000000000000000000000060005260606024600437602060006064600080855af1600080511160203d14163d15178116905080156101d257005b7f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c0f5452414e534645525f4641494c454400000000000000000000000000604052600060605260646000fd5b600080fd5b34801561025457600080fd5b5061027673ffffffffffffffffffffffffffffffffffffffff6004351661044c565b005b34801561028457600080fd5b50610290600435610638565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b3480156102c557600080fd5b5061027673ffffffffffffffffffffffffffffffffffffffff6004351661066d565b3480156102f357600080fd5b50610290610966565b34801561030857600080fd5b5061027673ffffffffffffffffffffffffffffffffffffffff60043516602435610982565b34801561033957600080fd5b50610342610d37565b604080517fffffffff000000000000000000000000000000000000000000000000000000009092168252519081900360200190f35b34801561038357600080fd5b506103a573ffffffffffffffffffffffffffffffffffffffff60043516610d6d565b604080519115158252519081900360200190f35b3480156103c557600080fd5b506103ce610d82565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561040a5781810151838201526020016103f2565b505050509050019250505060405180910390f35b34801561042a57600080fd5b5061027673ffffffffffffffffffffffffffffffffffffffff60043516610df1565b60005473ffffffffffffffffffffffffffffffffffffffff1633146104d257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811660009081526001602052604090205460ff161561056757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f5441524745545f414c52454144595f415554484f52495a454400000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116600081815260016020819052604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168317905560028054928301815583527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90910180547fffffffffffffffffffffffff00000000000000000000000000000000000000001684179055513392917f3147867c59d17e8fa9d522465651d44aae0a9e38f902f3475b97e58072f0ed4c91a350565b600280548290811061064657fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16905081565b6000805473ffffffffffffffffffffffffffffffffffffffff1633146106f457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604090205460ff16151561078a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b5073ffffffffffffffffffffffffffffffffffffffff8116600090815260016020526040812080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690555b60025481101561091f578173ffffffffffffffffffffffffffffffffffffffff1660028281548110151561080757fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16141561091757600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810190811061085f57fe5b6000918252602090912001546002805473ffffffffffffffffffffffffffffffffffffffff909216918390811061089257fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01906109119082610ed7565b5061091f565b6001016107d7565b604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b60005473ffffffffffffffffffffffffffffffffffffffff163314610a0857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604090205460ff161515610a9e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b6002548110610b0e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f494e4445585f4f55545f4f465f424f554e445300000000000000000000000000604482015290519081900360640190fd5b8173ffffffffffffffffffffffffffffffffffffffff16600282815481101515610b3457fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff1614610bc257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f415554484f52495a45445f414444524553535f4d49534d415443480000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260016020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101908110610c3d57fe5b6000918252602090912001546002805473ffffffffffffffffffffffffffffffffffffffff9092169183908110610c7057fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190610cef9082610ed7565b50604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b604080517f4552433230546f6b656e28616464726573732900000000000000000000000000815290519081900360130190205b90565b60016020526000908152604090205460ff1681565b60606002805480602002602001604051908101604052809291908181526020018280548015610de757602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610dbc575b5050505050905090565b60005473ffffffffffffffffffffffffffffffffffffffff163314610e7757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811615610ed457600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b815481835581811115610efb57600083815260209020610efb918101908301610f00565b505050565b610d6a91905b80821115610f1a5760008155600101610f06565b50905600a165627a7a723058205480b4819d926e6490a9429dbd05147e7ec78b7922db46953d6a31b853d9b9a10029", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 SLOAD PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB NOT AND CALLER OR SWAP1 SSTORE PUSH2 0xF4A DUP1 PUSH2 0x25 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN STOP PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x98 JUMPI PUSH4 0xFFFFFFFF PUSH29 0x100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 CALLDATALOAD DIV AND PUSH4 0x42F1181E DUP2 EQ PUSH2 0x248 JUMPI DUP1 PUSH4 0x494503D4 EQ PUSH2 0x278 JUMPI DUP1 PUSH4 0x70712939 EQ PUSH2 0x2B9 JUMPI DUP1 PUSH4 0x8DA5CB5B EQ PUSH2 0x2E7 JUMPI DUP1 PUSH4 0x9AD26744 EQ PUSH2 0x2FC JUMPI DUP1 PUSH4 0xAE25532E EQ PUSH2 0x32D JUMPI DUP1 PUSH4 0xB9181611 EQ PUSH2 0x377 JUMPI DUP1 PUSH4 0xD39DE6E9 EQ PUSH2 0x3B9 JUMPI DUP1 PUSH4 0xF2FDE38B EQ PUSH2 0x41E JUMPI JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0xA4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 PUSH1 0x0 CALLDATALOAD AND PUSH32 0xA85E59E400000000000000000000000000000000000000000000000000000000 DUP2 EQ ISZERO PUSH2 0x243 JUMPI PUSH1 0x40 DUP1 MLOAD CALLER DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 DUP3 ADD MSTORE SWAP1 DUP2 KECCAK256 SLOAD ISZERO ISZERO PUSH2 0x17B JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0x1553454E4445525F4E4F545F415554484F52495A454400000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x28 PUSH1 0x4 CALLDATALOAD ADD CALLDATALOAD PUSH32 0x23B872DD00000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x60 PUSH1 0x24 PUSH1 0x4 CALLDATACOPY PUSH1 0x20 PUSH1 0x0 PUSH1 0x64 PUSH1 0x0 DUP1 DUP6 GAS CALL PUSH1 0x0 DUP1 MLOAD GT PUSH1 0x20 RETURNDATASIZE EQ AND RETURNDATASIZE ISZERO OR DUP2 AND SWAP1 POP DUP1 ISZERO PUSH2 0x1D2 JUMPI STOP JUMPDEST PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0xF5452414E534645525F4641494C454400000000000000000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x254 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x276 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x44C JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x284 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x290 PUSH1 0x4 CALLDATALOAD PUSH2 0x638 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x2C5 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x276 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x66D JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x2F3 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x290 PUSH2 0x966 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x308 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x276 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH1 0x24 CALLDATALOAD PUSH2 0x982 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x339 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x342 PUSH2 0xD37 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x383 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x3A5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0xD6D JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD SWAP2 ISZERO ISZERO DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x3C5 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x3CE PUSH2 0xD82 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH1 0x20 DUP1 DUP3 MSTORE DUP4 MLOAD DUP2 DUP4 ADD MSTORE DUP4 MLOAD SWAP2 SWAP3 DUP4 SWAP3 SWAP1 DUP4 ADD SWAP2 DUP6 DUP2 ADD SWAP2 MUL DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x40A JUMPI DUP2 DUP2 ADD MLOAD DUP4 DUP3 ADD MSTORE PUSH1 0x20 ADD PUSH2 0x3F2 JUMP JUMPDEST POP POP POP POP SWAP1 POP ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x42A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x276 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0xDF1 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x4D2 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO PUSH2 0x567 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x19 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F414C52454144595F415554484F52495A454400000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 DUP1 DUP4 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND DUP4 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD SWAP3 DUP4 ADD DUP2 SSTORE DUP4 MSTORE PUSH32 0x405787FA12A823E0F2B7631CC41B3BA8828B3321CA811111FA75CD3AA3BB5ACE SWAP1 SWAP2 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND DUP5 OR SWAP1 SSTORE MLOAD CALLER SWAP3 SWAP2 PUSH32 0x3147867C59D17E8FA9D522465651D44AAE0A9E38F902F3475B97E58072F0ED4C SWAP2 LOG3 POP JUMP JUMPDEST PUSH1 0x2 DUP1 SLOAD DUP3 SWAP1 DUP2 LT PUSH2 0x646 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP DUP2 JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x6F4 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0x78A JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST POP PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 DUP2 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE JUMPDEST PUSH1 0x2 SLOAD DUP2 LT ISZERO PUSH2 0x91F JUMPI DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x2 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0x807 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x917 JUMPI PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0x85F JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x2 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0x892 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0x911 SWAP1 DUP3 PUSH2 0xED7 JUMP JUMPDEST POP PUSH2 0x91F JUMP JUMPDEST PUSH1 0x1 ADD PUSH2 0x7D7 JUMP JUMPDEST PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0xA08 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0xA9E JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH1 0x2 SLOAD DUP2 LT PUSH2 0xB0E JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x494E4445585F4F55545F4F465F424F554E445300000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x2 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0xB34 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0xBC2 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x1B PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x415554484F52495A45445F414444524553535F4D49534D415443480000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0xC3D JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x2 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0xC70 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0xCEF SWAP1 DUP3 PUSH2 0xED7 JUMP JUMPDEST POP PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH32 0x4552433230546F6B656E28616464726573732900000000000000000000000000 DUP2 MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x13 ADD SWAP1 KECCAK256 JUMPDEST SWAP1 JUMP JUMPDEST PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND DUP2 JUMP JUMPDEST PUSH1 0x60 PUSH1 0x2 DUP1 SLOAD DUP1 PUSH1 0x20 MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD DUP1 ISZERO PUSH2 0xDE7 JUMPI PUSH1 0x20 MUL DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x1 SWAP1 SWAP2 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0xDBC JUMPI JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0xE77 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND ISZERO PUSH2 0xED4 JUMPI PUSH1 0x0 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP4 AND OR SWAP1 SSTORE JUMPDEST POP JUMP JUMPDEST DUP2 SLOAD DUP2 DUP4 SSTORE DUP2 DUP2 GT ISZERO PUSH2 0xEFB JUMPI PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 SWAP1 KECCAK256 PUSH2 0xEFB SWAP2 DUP2 ADD SWAP1 DUP4 ADD PUSH2 0xF00 JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH2 0xD6A SWAP2 SWAP1 JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0xF1A JUMPI PUSH1 0x0 DUP2 SSTORE PUSH1 0x1 ADD PUSH2 0xF06 JUMP JUMPDEST POP SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 SLOAD DUP1 0xb4 DUP2 SWAP14 SWAP3 PUSH15 0x6490A9429DBD05147E7EC78B7922DB 0x46 SWAP6 RETURNDATASIZE PUSH11 0x31B853D9B9A10029000000 ", + "sourceMap": "641:8586:5:-;;;174:5:3;:18;;-1:-1:-1;;;;;;174:18:3;182:10;174:18;;;641:8586:5;;;;;;" + }, + "deployedBytecode": { + "linkReferences": {}, + "object": "0x6080604052600436106100985763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166342f1181e8114610248578063494503d41461027857806370712939146102b95780638da5cb5b146102e75780639ad26744146102fc578063ae25532e1461032d578063b918161114610377578063d39de6e9146103b9578063f2fde38b1461041e575b3480156100a457600080fd5b507fffffffff00000000000000000000000000000000000000000000000000000000600035167fa85e59e40000000000000000000000000000000000000000000000000000000081141561024357604080513381526001602082015290812054151561017b577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c1553454e4445525f4e4f545f415554484f52495a454400000000000000604052600060605260646000fd5b602860043501357f23b872dd0000000000000000000000000000000000000000000000000000000060005260606024600437602060006064600080855af1600080511160203d14163d15178116905080156101d257005b7f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c0f5452414e534645525f4641494c454400000000000000000000000000604052600060605260646000fd5b600080fd5b34801561025457600080fd5b5061027673ffffffffffffffffffffffffffffffffffffffff6004351661044c565b005b34801561028457600080fd5b50610290600435610638565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b3480156102c557600080fd5b5061027673ffffffffffffffffffffffffffffffffffffffff6004351661066d565b3480156102f357600080fd5b50610290610966565b34801561030857600080fd5b5061027673ffffffffffffffffffffffffffffffffffffffff60043516602435610982565b34801561033957600080fd5b50610342610d37565b604080517fffffffff000000000000000000000000000000000000000000000000000000009092168252519081900360200190f35b34801561038357600080fd5b506103a573ffffffffffffffffffffffffffffffffffffffff60043516610d6d565b604080519115158252519081900360200190f35b3480156103c557600080fd5b506103ce610d82565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561040a5781810151838201526020016103f2565b505050509050019250505060405180910390f35b34801561042a57600080fd5b5061027673ffffffffffffffffffffffffffffffffffffffff60043516610df1565b60005473ffffffffffffffffffffffffffffffffffffffff1633146104d257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811660009081526001602052604090205460ff161561056757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f5441524745545f414c52454144595f415554484f52495a454400000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116600081815260016020819052604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168317905560028054928301815583527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90910180547fffffffffffffffffffffffff00000000000000000000000000000000000000001684179055513392917f3147867c59d17e8fa9d522465651d44aae0a9e38f902f3475b97e58072f0ed4c91a350565b600280548290811061064657fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16905081565b6000805473ffffffffffffffffffffffffffffffffffffffff1633146106f457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604090205460ff16151561078a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b5073ffffffffffffffffffffffffffffffffffffffff8116600090815260016020526040812080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690555b60025481101561091f578173ffffffffffffffffffffffffffffffffffffffff1660028281548110151561080757fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16141561091757600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810190811061085f57fe5b6000918252602090912001546002805473ffffffffffffffffffffffffffffffffffffffff909216918390811061089257fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01906109119082610ed7565b5061091f565b6001016107d7565b604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b60005473ffffffffffffffffffffffffffffffffffffffff163314610a0857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604090205460ff161515610a9e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b6002548110610b0e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f494e4445585f4f55545f4f465f424f554e445300000000000000000000000000604482015290519081900360640190fd5b8173ffffffffffffffffffffffffffffffffffffffff16600282815481101515610b3457fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff1614610bc257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f415554484f52495a45445f414444524553535f4d49534d415443480000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260016020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101908110610c3d57fe5b6000918252602090912001546002805473ffffffffffffffffffffffffffffffffffffffff9092169183908110610c7057fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190610cef9082610ed7565b50604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b604080517f4552433230546f6b656e28616464726573732900000000000000000000000000815290519081900360130190205b90565b60016020526000908152604090205460ff1681565b60606002805480602002602001604051908101604052809291908181526020018280548015610de757602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610dbc575b5050505050905090565b60005473ffffffffffffffffffffffffffffffffffffffff163314610e7757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811615610ed457600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b815481835581811115610efb57600083815260209020610efb918101908301610f00565b505050565b610d6a91905b80821115610f1a5760008155600101610f06565b50905600a165627a7a723058205480b4819d926e6490a9429dbd05147e7ec78b7922db46953d6a31b853d9b9a10029", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x98 JUMPI PUSH4 0xFFFFFFFF PUSH29 0x100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 CALLDATALOAD DIV AND PUSH4 0x42F1181E DUP2 EQ PUSH2 0x248 JUMPI DUP1 PUSH4 0x494503D4 EQ PUSH2 0x278 JUMPI DUP1 PUSH4 0x70712939 EQ PUSH2 0x2B9 JUMPI DUP1 PUSH4 0x8DA5CB5B EQ PUSH2 0x2E7 JUMPI DUP1 PUSH4 0x9AD26744 EQ PUSH2 0x2FC JUMPI DUP1 PUSH4 0xAE25532E EQ PUSH2 0x32D JUMPI DUP1 PUSH4 0xB9181611 EQ PUSH2 0x377 JUMPI DUP1 PUSH4 0xD39DE6E9 EQ PUSH2 0x3B9 JUMPI DUP1 PUSH4 0xF2FDE38B EQ PUSH2 0x41E JUMPI JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0xA4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 PUSH1 0x0 CALLDATALOAD AND PUSH32 0xA85E59E400000000000000000000000000000000000000000000000000000000 DUP2 EQ ISZERO PUSH2 0x243 JUMPI PUSH1 0x40 DUP1 MLOAD CALLER DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 DUP3 ADD MSTORE SWAP1 DUP2 KECCAK256 SLOAD ISZERO ISZERO PUSH2 0x17B JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0x1553454E4445525F4E4F545F415554484F52495A454400000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x28 PUSH1 0x4 CALLDATALOAD ADD CALLDATALOAD PUSH32 0x23B872DD00000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x60 PUSH1 0x24 PUSH1 0x4 CALLDATACOPY PUSH1 0x20 PUSH1 0x0 PUSH1 0x64 PUSH1 0x0 DUP1 DUP6 GAS CALL PUSH1 0x0 DUP1 MLOAD GT PUSH1 0x20 RETURNDATASIZE EQ AND RETURNDATASIZE ISZERO OR DUP2 AND SWAP1 POP DUP1 ISZERO PUSH2 0x1D2 JUMPI STOP JUMPDEST PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0xF5452414E534645525F4641494C454400000000000000000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x254 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x276 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x44C JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x284 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x290 PUSH1 0x4 CALLDATALOAD PUSH2 0x638 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x2C5 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x276 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x66D JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x2F3 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x290 PUSH2 0x966 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x308 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x276 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH1 0x24 CALLDATALOAD PUSH2 0x982 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x339 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x342 PUSH2 0xD37 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x383 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x3A5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0xD6D JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD SWAP2 ISZERO ISZERO DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x3C5 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x3CE PUSH2 0xD82 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH1 0x20 DUP1 DUP3 MSTORE DUP4 MLOAD DUP2 DUP4 ADD MSTORE DUP4 MLOAD SWAP2 SWAP3 DUP4 SWAP3 SWAP1 DUP4 ADD SWAP2 DUP6 DUP2 ADD SWAP2 MUL DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x40A JUMPI DUP2 DUP2 ADD MLOAD DUP4 DUP3 ADD MSTORE PUSH1 0x20 ADD PUSH2 0x3F2 JUMP JUMPDEST POP POP POP POP SWAP1 POP ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x42A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x276 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0xDF1 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x4D2 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO PUSH2 0x567 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x19 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F414C52454144595F415554484F52495A454400000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 DUP1 DUP4 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND DUP4 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD SWAP3 DUP4 ADD DUP2 SSTORE DUP4 MSTORE PUSH32 0x405787FA12A823E0F2B7631CC41B3BA8828B3321CA811111FA75CD3AA3BB5ACE SWAP1 SWAP2 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND DUP5 OR SWAP1 SSTORE MLOAD CALLER SWAP3 SWAP2 PUSH32 0x3147867C59D17E8FA9D522465651D44AAE0A9E38F902F3475B97E58072F0ED4C SWAP2 LOG3 POP JUMP JUMPDEST PUSH1 0x2 DUP1 SLOAD DUP3 SWAP1 DUP2 LT PUSH2 0x646 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP DUP2 JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x6F4 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0x78A JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST POP PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 DUP2 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE JUMPDEST PUSH1 0x2 SLOAD DUP2 LT ISZERO PUSH2 0x91F JUMPI DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x2 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0x807 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x917 JUMPI PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0x85F JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x2 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0x892 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0x911 SWAP1 DUP3 PUSH2 0xED7 JUMP JUMPDEST POP PUSH2 0x91F JUMP JUMPDEST PUSH1 0x1 ADD PUSH2 0x7D7 JUMP JUMPDEST PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0xA08 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0xA9E JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH1 0x2 SLOAD DUP2 LT PUSH2 0xB0E JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x494E4445585F4F55545F4F465F424F554E445300000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x2 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0xB34 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0xBC2 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x1B PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x415554484F52495A45445F414444524553535F4D49534D415443480000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0xC3D JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x2 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0xC70 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0xCEF SWAP1 DUP3 PUSH2 0xED7 JUMP JUMPDEST POP PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH32 0x4552433230546F6B656E28616464726573732900000000000000000000000000 DUP2 MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x13 ADD SWAP1 KECCAK256 JUMPDEST SWAP1 JUMP JUMPDEST PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND DUP2 JUMP JUMPDEST PUSH1 0x60 PUSH1 0x2 DUP1 SLOAD DUP1 PUSH1 0x20 MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD DUP1 ISZERO PUSH2 0xDE7 JUMPI PUSH1 0x20 MUL DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x1 SWAP1 SWAP2 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0xDBC JUMPI JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0xE77 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND ISZERO PUSH2 0xED4 JUMPI PUSH1 0x0 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP4 AND OR SWAP1 SSTORE JUMPDEST POP JUMP JUMPDEST DUP2 SLOAD DUP2 DUP4 SSTORE DUP2 DUP2 GT ISZERO PUSH2 0xEFB JUMPI PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 SWAP1 KECCAK256 PUSH2 0xEFB SWAP2 DUP2 ADD SWAP1 DUP4 ADD PUSH2 0xF00 JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH2 0xD6A SWAP2 SWAP1 JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0xF1A JUMPI PUSH1 0x0 DUP2 SSTORE PUSH1 0x1 ADD PUSH2 0xF06 JUMP JUMPDEST POP SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 SLOAD DUP1 0xb4 DUP2 SWAP14 SWAP3 PUSH15 0x6490A9429DBD05147E7EC78B7922DB 0x46 SWAP6 RETURNDATASIZE PUSH11 0x31B853D9B9A10029000000 ", + "sourceMap": "641:8586:5:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;641:8586:5;1031:66;1027:1;1014:15;1010:88;1493:66;1483:8;1480:80;1477:2;;;1789;1783:9;;1827:6;1809:70;;1919:15;1914:2;1903:14;;1896:39;2030:20;;;2024:27;2017:35;2014:2;;;2153:66;2150:1;2143:77;2252:66;2248:2;2241:78;2351:66;2347:2;2340:78;2450:1;2446:2;2439:13;2483:3;2480:1;2473:14;2014:2;6445;6441:1;6428:15;6424:24;6411:38;6741:66;6738:1;6731:77;7046:2;7042;7039:1;7026:23;7551:2;7488:1;7433:3;7368:1;7310;7240:5;7185:3;7159:459;8361:1;8357;8351:8;8348:15;8319:2;8303:14;8300:22;8271:114;8234:14;8227:22;8203:200;8194:7;8190:214;8179:225;;8424:7;8421:2;;;8454:12;8421:2;8586:66;8583:1;8576:77;8681:66;8677:2;8670:78;8776:66;8772:2;8765:78;8871:1;8867:2;8860:13;8900:3;8897:1;8890:14;1477:2;8996:1;8993;8986:12;1162:320:8;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;1162:320:8;;;;;;;;;1047:28;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;1047:28:8;;;;;;;;;;;;;;;;;;;;;;;;1596:547;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;1596:547:8;;;;;;;103:20:3;;8:9:-1;5:2;;;30:1;27;20:12;5:2;103:20:3;;;;2316:643:8;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;2316:643:8;;;;;;;;;9112:113:5;;8:9:-1;5:2;;;30:1;27;20:12;5:2;9112:113:5;;;;;;;;;;;;;;;;;;;;;;;998:43:8;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;998:43:8;;;;;;;;;;;;;;;;;;;;;;;;;3056:138;;8:9:-1;5:2;;;30:1;27;20:12;5:2;3056:138:8;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:100:-1;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;3056:138:8;;;;;;;;;;;;;;;;;345:167:3;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;345:167:3;;;;;;;1162:320:8;271:5:3;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1279:18:8;;;;;;;:10;:18;;;;;;;;1278:19;1257:91;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1359:18;;;;;;;1380:4;1359:18;;;;;;;;:25;;;;;;;;1394:11;27:10:-1;;23:18;;;45:23;;1394:24:8;;;;;;;;;;;;;;1433:42;1464:10;;1359:18;1433:42;;;1162:320;:::o;1047:28::-;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1047:28:8;:::o;1596:547::-;1831:9;271:5:3;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1715:18:8;;;;;;;:10;:18;;;;;;;;1694:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1798:18:8;;;;;;;:10;:18;;;;;1791:25;;;;;;1826:252;1850:11;:18;1846:22;;1826:252;;;1911:6;1893:24;;:11;1905:1;1893:14;;;;;;;;;;;;;;;;;;;;;;:24;1889:179;;;1954:11;1966:18;;:22;;;;1954:35;;;;;;;;;;;;;;;;1937:11;:14;;1954:35;;;;;1949:1;;1937:14;;;;;;;;;;;;;;;:52;;;;;;;;;;;;;;;2007:11;:23;;;;;;;;;:::i;:::-;;2048:5;;1889:179;1870:3;;1826:252;;;2092:44;;2125:10;;2092:44;;;;;;;;;1596:547;;:::o;103:20:3:-;;;;;;:::o;2316:643:8:-;271:5:3;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2479:18:8;;;;;;;:10;:18;;;;;;;;2458:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2583:11;:18;2575:26;;2554:92;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2699:6;2677:28;;:11;2689:5;2677:18;;;;;;;;;;;;;;;;;;;;;;:28;2656:102;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2776:18;;;;;;;:10;:18;;;;;2769:25;;;;;;2825:11;2837:18;;:22;;;;2825:35;;;;;;;;;;;;;;;;2804:11;:18;;2825:35;;;;;2816:5;;2804:18;;;;;;;;;;;;;;;:56;;;;;;;;;;;;;;;2870:11;:23;;;;;;;;;:::i;:::-;-1:-1:-1;2908:44:8;;2941:10;;2908:44;;;;;;;;;2316:643;;:::o;9112:113:5:-;760:32;;;;;;;;;;;;;;;;9112:113;;:::o;998:43:8:-;;;;;;;;;;;;;;;:::o;3056:138::-;3137:9;3176:11;3169:18;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3056:138;:::o;345:167:3:-;271:5;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;441:22;;;;437:69;;479:5;:16;;;;;;;;;;437:69;345:167;:::o;641:8586:5:-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;" + } + } + }, + "sources": { + "asset-proxy/ERC20Proxy.sol": { + "id": 5 + }, + "asset-proxy/MixinAuthorizable.sol": { + "id": 8 + }, + "@0x/contracts-utils/contracts/utils/Ownable.sol": { + "id": 3 + }, + "@0x/contracts-utils/contracts/utils/interfaces/IOwnable.sol": { + "id": 4 + }, + "asset-proxy/mixins/MAuthorizable.sol": { + "id": 11 + }, + "@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAuthorizable.sol": { + "id": 1 + } + }, + "sourceCodes": { + "asset-proxy/ERC20Proxy.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity 0.4.24;\n\nimport \"./MixinAuthorizable.sol\";\n\n\ncontract ERC20Proxy is\n MixinAuthorizable\n{\n // Id of this proxy.\n bytes4 constant internal PROXY_ID = bytes4(keccak256(\"ERC20Token(address)\"));\n \n // solhint-disable-next-line payable-fallback\n function () \n external\n {\n assembly {\n // The first 4 bytes of calldata holds the function selector\n let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000)\n\n // `transferFrom` will be called with the following parameters:\n // assetData Encoded byte array.\n // from Address to transfer asset from.\n // to Address to transfer asset to.\n // amount Amount of asset to transfer.\n // bytes4(keccak256(\"transferFrom(bytes,address,address,uint256)\")) = 0xa85e59e4\n if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) {\n\n // To lookup a value in a mapping, we load from the storage location keccak256(k, p),\n // where k is the key left padded to 32 bytes and p is the storage slot\n let start := mload(64)\n mstore(start, and(caller, 0xffffffffffffffffffffffffffffffffffffffff))\n mstore(add(start, 32), authorized_slot)\n\n // Revert if authorized[msg.sender] == false\n if iszero(sload(keccak256(start, 64))) {\n // Revert with `Error(\"SENDER_NOT_AUTHORIZED\")`\n mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)\n mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)\n mstore(64, 0x0000001553454e4445525f4e4f545f415554484f52495a454400000000000000)\n mstore(96, 0)\n revert(0, 100)\n }\n\n // `transferFrom`.\n // The function is marked `external`, so no abi decodeding is done for\n // us. Instead, we expect the `calldata` memory to contain the\n // following:\n //\n // | Area | Offset | Length | Contents |\n // |----------|--------|---------|-------------------------------------|\n // | Header | 0 | 4 | function selector |\n // | Params | | 4 * 32 | function parameters: |\n // | | 4 | | 1. offset to assetData (*) |\n // | | 36 | | 2. from |\n // | | 68 | | 3. to |\n // | | 100 | | 4. amount |\n // | Data | | | assetData: |\n // | | 132 | 32 | assetData Length |\n // | | 164 | ** | assetData Contents |\n //\n // (*): offset is computed from start of function parameters, so offset\n // by an additional 4 bytes in the calldata.\n //\n // (**): see table below to compute length of assetData Contents\n //\n // WARNING: The ABIv2 specification allows additional padding between\n // the Params and Data section. This will result in a larger\n // offset to assetData.\n\n // Asset data itself is encoded as follows:\n //\n // | Area | Offset | Length | Contents |\n // |----------|--------|---------|-------------------------------------|\n // | Header | 0 | 4 | function selector |\n // | Params | | 1 * 32 | function parameters: |\n // | | 4 | 12 + 20 | 1. token address |\n\n // We construct calldata for the `token.transferFrom` ABI.\n // The layout of this calldata is in the table below.\n //\n // | Area | Offset | Length | Contents |\n // |----------|--------|---------|-------------------------------------|\n // | Header | 0 | 4 | function selector |\n // | Params | | 3 * 32 | function parameters: |\n // | | 4 | | 1. from |\n // | | 36 | | 2. to |\n // | | 68 | | 3. amount |\n\n /////// Read token address from calldata ///////\n // * The token address is stored in `assetData`.\n //\n // * The \"offset to assetData\" is stored at offset 4 in the calldata (table 1).\n // [assetDataOffsetFromParams = calldataload(4)]\n //\n // * Notes that the \"offset to assetData\" is relative to the \"Params\" area of calldata;\n // add 4 bytes to account for the length of the \"Header\" area (table 1).\n // [assetDataOffsetFromHeader = assetDataOffsetFromParams + 4]\n //\n // * The \"token address\" is offset 32+4=36 bytes into \"assetData\" (tables 1 & 2).\n // [tokenOffset = assetDataOffsetFromHeader + 36 = calldataload(4) + 4 + 36]\n let token := calldataload(add(calldataload(4), 40))\n \n /////// Setup Header Area ///////\n // This area holds the 4-byte `transferFrom` selector.\n // Any trailing data in transferFromSelector will be\n // overwritten in the next `mstore` call.\n mstore(0, 0x23b872dd00000000000000000000000000000000000000000000000000000000)\n \n /////// Setup Params Area ///////\n // We copy the fields `from`, `to` and `amount` in bulk\n // from our own calldata to the new calldata.\n calldatacopy(4, 36, 96)\n\n /////// Call `token.transferFrom` using the calldata ///////\n let success := call(\n gas, // forward all gas\n token, // call address of token contract\n 0, // don't send any ETH\n 0, // pointer to start of input\n 100, // length of input\n 0, // write output over input\n 32 // output size should be 32 bytes\n )\n\n /////// Check return data. ///////\n // If there is no return data, we assume the token incorrectly\n // does not return a bool. In this case we expect it to revert\n // on failure, which was handled above.\n // If the token does return data, we require that it is a single\n // nonzero 32 bytes value.\n // So the transfer succeeded if the call succeeded and either\n // returned nothing, or returned a non-zero 32 byte value. \n success := and(success, or(\n iszero(returndatasize),\n and(\n eq(returndatasize, 32),\n gt(mload(0), 0)\n )\n ))\n if success {\n return(0, 0)\n }\n \n // Revert with `Error(\"TRANSFER_FAILED\")`\n mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)\n mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)\n mstore(64, 0x0000000f5452414e534645525f4641494c454400000000000000000000000000)\n mstore(96, 0)\n revert(0, 100)\n }\n\n // Revert if undefined function is called\n revert(0, 0)\n }\n }\n\n /// @dev Gets the proxy id associated with the proxy address.\n /// @return Proxy id.\n function getProxyId()\n external\n pure\n returns (bytes4)\n {\n return PROXY_ID;\n }\n}\n", + "asset-proxy/MixinAuthorizable.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-utils/contracts/utils/Ownable.sol\";\nimport \"./mixins/MAuthorizable.sol\";\n\n\ncontract MixinAuthorizable is\n Ownable,\n MAuthorizable\n{\n /// @dev Only authorized addresses can invoke functions with this modifier.\n modifier onlyAuthorized {\n require(\n authorized[msg.sender],\n \"SENDER_NOT_AUTHORIZED\"\n );\n _;\n }\n\n mapping (address => bool) public authorized;\n address[] public authorities;\n\n /// @dev Authorizes an address.\n /// @param target Address to authorize.\n function addAuthorizedAddress(address target)\n external\n onlyOwner\n {\n require(\n !authorized[target],\n \"TARGET_ALREADY_AUTHORIZED\"\n );\n\n authorized[target] = true;\n authorities.push(target);\n emit AuthorizedAddressAdded(target, msg.sender);\n }\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n function removeAuthorizedAddress(address target)\n external\n onlyOwner\n {\n require(\n authorized[target],\n \"TARGET_NOT_AUTHORIZED\"\n );\n\n delete authorized[target];\n for (uint256 i = 0; i < authorities.length; i++) {\n if (authorities[i] == target) {\n authorities[i] = authorities[authorities.length - 1];\n authorities.length -= 1;\n break;\n }\n }\n emit AuthorizedAddressRemoved(target, msg.sender);\n }\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n /// @param index Index of target in authorities array.\n function removeAuthorizedAddressAtIndex(\n address target,\n uint256 index\n )\n external\n onlyOwner\n {\n require(\n authorized[target],\n \"TARGET_NOT_AUTHORIZED\"\n );\n require(\n index < authorities.length,\n \"INDEX_OUT_OF_BOUNDS\"\n );\n require(\n authorities[index] == target,\n \"AUTHORIZED_ADDRESS_MISMATCH\"\n );\n\n delete authorized[target];\n authorities[index] = authorities[authorities.length - 1];\n authorities.length -= 1;\n emit AuthorizedAddressRemoved(target, msg.sender);\n }\n\n /// @dev Gets all authorized addresses.\n /// @return Array of authorized addresses.\n function getAuthorizedAddresses()\n external\n view\n returns (address[] memory)\n {\n return authorities;\n }\n}\n", + "@0x/contracts-utils/contracts/utils/Ownable.sol": "pragma solidity ^0.4.24;\n\nimport \"./interfaces/IOwnable.sol\";\n\n\ncontract Ownable is\n IOwnable\n{\n address public owner;\n\n constructor ()\n public\n {\n owner = msg.sender;\n }\n\n modifier onlyOwner() {\n require(\n msg.sender == owner,\n \"ONLY_CONTRACT_OWNER\"\n );\n _;\n }\n\n function transferOwnership(address newOwner)\n public\n onlyOwner\n {\n if (newOwner != address(0)) {\n owner = newOwner;\n }\n }\n}\n", + "@0x/contracts-utils/contracts/utils/interfaces/IOwnable.sol": "pragma solidity ^0.4.24;\n\n\ncontract IOwnable {\n\n function transferOwnership(address newOwner)\n public;\n}\n", + "asset-proxy/mixins/MAuthorizable.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAuthorizable.sol\";\n\n\ncontract MAuthorizable is\n IAuthorizable\n{\n // Event logged when a new address is authorized.\n event AuthorizedAddressAdded(\n address indexed target,\n address indexed caller\n );\n\n // Event logged when a currently authorized address is unauthorized.\n event AuthorizedAddressRemoved(\n address indexed target,\n address indexed caller\n );\n\n /// @dev Only authorized addresses can invoke functions with this modifier.\n modifier onlyAuthorized { revert(); _; }\n}\n", + "@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAuthorizable.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-utils/contracts/utils/interfaces/IOwnable.sol\";\n\n\ncontract IAuthorizable is\n IOwnable\n{\n /// @dev Authorizes an address.\n /// @param target Address to authorize.\n function addAuthorizedAddress(address target)\n external;\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n function removeAuthorizedAddress(address target)\n external;\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n /// @param index Index of target in authorities array.\n function removeAuthorizedAddressAtIndex(\n address target,\n uint256 index\n )\n external;\n \n /// @dev Gets all authorized addresses.\n /// @return Array of authorized addresses.\n function getAuthorizedAddresses()\n external\n view\n returns (address[] memory);\n}\n" + }, + "sourceTreeHashHex": "0xe21f15f87ce1a14309a82f36bf13737ef77e19fb139d666518075b7af17cdf1b", + "compiler": { + "name": "solc", + "version": "soljson-v0.4.24+commit.e67f0147.js", + "settings": { + "optimizer": { + "enabled": true, + "runs": 1000000 + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + } + } + }, + "networks": {} +} diff --git a/contracts/asset-proxy/generated-artifacts/ERC721Proxy.json b/contracts/asset-proxy/generated-artifacts/ERC721Proxy.json new file mode 100644 index 000000000..7c2b5b1d4 --- /dev/null +++ b/contracts/asset-proxy/generated-artifacts/ERC721Proxy.json @@ -0,0 +1,252 @@ +{ + "schemaVersion": "2.0.0", + "contractName": "ERC721Proxy", + "compilerOutput": { + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "addAuthorizedAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "authorities", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "removeAuthorizedAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + }, + { + "name": "index", + "type": "uint256" + } + ], + "name": "removeAuthorizedAddressAtIndex", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getProxyId", + "outputs": [ + { + "name": "", + "type": "bytes4" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "authorized", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getAuthorizedAddresses", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "payable": false, + "stateMutability": "nonpayable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "target", + "type": "address" + }, + { + "indexed": true, + "name": "caller", + "type": "address" + } + ], + "name": "AuthorizedAddressAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "target", + "type": "address" + }, + { + "indexed": true, + "name": "caller", + "type": "address" + } + ], + "name": "AuthorizedAddressRemoved", + "type": "event" + } + ], + "evm": { + "bytecode": { + "linkReferences": {}, + "object": "0x608060405260008054600160a060020a03191633179055610fbe806100256000396000f3006080604052600436106100985763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166342f1181e81146102bc578063494503d4146102ec578063707129391461032d5780638da5cb5b1461035b5780639ad2674414610370578063ae25532e146103a1578063b9181611146103eb578063d39de6e91461042d578063f2fde38b14610492575b3480156100a457600080fd5b507fffffffff00000000000000000000000000000000000000000000000000000000600035167fa85e59e4000000000000000000000000000000000000000000000000000000008114156102b757604080513381526001602082015290812054151561017b577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c1553454e4445525f4e4f545f415554484f52495a454400000000000000604052600060605260646000fd5b600160643503156101f7577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c0e494e56414c49445f414d4f554e540000000000000000000000000000604052600060605260646000fd5b7f23b872dd000000000000000000000000000000000000000000000000000000006000526040602460043760043560206048820160443760288101356000806064600080855af1801561024657005b7f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c0f5452414e534645525f4641494c454400000000000000000000000000604052600060605260646000fd5b600080fd5b3480156102c857600080fd5b506102ea73ffffffffffffffffffffffffffffffffffffffff600435166104c0565b005b3480156102f857600080fd5b506103046004356106ac565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561033957600080fd5b506102ea73ffffffffffffffffffffffffffffffffffffffff600435166106e1565b34801561036757600080fd5b506103046109da565b34801561037c57600080fd5b506102ea73ffffffffffffffffffffffffffffffffffffffff600435166024356109f6565b3480156103ad57600080fd5b506103b6610dab565b604080517fffffffff000000000000000000000000000000000000000000000000000000009092168252519081900360200190f35b3480156103f757600080fd5b5061041973ffffffffffffffffffffffffffffffffffffffff60043516610de1565b604080519115158252519081900360200190f35b34801561043957600080fd5b50610442610df6565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561047e578181015183820152602001610466565b505050509050019250505060405180910390f35b34801561049e57600080fd5b506102ea73ffffffffffffffffffffffffffffffffffffffff60043516610e65565b60005473ffffffffffffffffffffffffffffffffffffffff16331461054657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811660009081526001602052604090205460ff16156105db57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f5441524745545f414c52454144595f415554484f52495a454400000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116600081815260016020819052604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168317905560028054928301815583527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90910180547fffffffffffffffffffffffff00000000000000000000000000000000000000001684179055513392917f3147867c59d17e8fa9d522465651d44aae0a9e38f902f3475b97e58072f0ed4c91a350565b60028054829081106106ba57fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16905081565b6000805473ffffffffffffffffffffffffffffffffffffffff16331461076857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604090205460ff1615156107fe57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b5073ffffffffffffffffffffffffffffffffffffffff8116600090815260016020526040812080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690555b600254811015610993578173ffffffffffffffffffffffffffffffffffffffff1660028281548110151561087b57fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16141561098b57600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81019081106108d357fe5b6000918252602090912001546002805473ffffffffffffffffffffffffffffffffffffffff909216918390811061090657fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01906109859082610f4b565b50610993565b60010161084b565b604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b60005473ffffffffffffffffffffffffffffffffffffffff163314610a7c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604090205460ff161515610b1257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b6002548110610b8257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f494e4445585f4f55545f4f465f424f554e445300000000000000000000000000604482015290519081900360640190fd5b8173ffffffffffffffffffffffffffffffffffffffff16600282815481101515610ba857fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff1614610c3657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f415554484f52495a45445f414444524553535f4d49534d415443480000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260016020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101908110610cb157fe5b6000918252602090912001546002805473ffffffffffffffffffffffffffffffffffffffff9092169183908110610ce457fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190610d639082610f4b565b50604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b604080517f455243373231546f6b656e28616464726573732c75696e7432353629000000008152905190819003601c0190205b90565b60016020526000908152604090205460ff1681565b60606002805480602002602001604051908101604052809291908181526020018280548015610e5b57602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610e30575b5050505050905090565b60005473ffffffffffffffffffffffffffffffffffffffff163314610eeb57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811615610f4857600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b815481835581811115610f6f57600083815260209020610f6f918101908301610f74565b505050565b610dde91905b80821115610f8e5760008155600101610f7a565b50905600a165627a7a72305820fae7fb45d60d983fbaa3db4bfdd6d78303e0ac1b668a4a2baf565cc432a7368a0029", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 SLOAD PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB NOT AND CALLER OR SWAP1 SSTORE PUSH2 0xFBE DUP1 PUSH2 0x25 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN STOP PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x98 JUMPI PUSH4 0xFFFFFFFF PUSH29 0x100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 CALLDATALOAD DIV AND PUSH4 0x42F1181E DUP2 EQ PUSH2 0x2BC JUMPI DUP1 PUSH4 0x494503D4 EQ PUSH2 0x2EC JUMPI DUP1 PUSH4 0x70712939 EQ PUSH2 0x32D JUMPI DUP1 PUSH4 0x8DA5CB5B EQ PUSH2 0x35B JUMPI DUP1 PUSH4 0x9AD26744 EQ PUSH2 0x370 JUMPI DUP1 PUSH4 0xAE25532E EQ PUSH2 0x3A1 JUMPI DUP1 PUSH4 0xB9181611 EQ PUSH2 0x3EB JUMPI DUP1 PUSH4 0xD39DE6E9 EQ PUSH2 0x42D JUMPI DUP1 PUSH4 0xF2FDE38B EQ PUSH2 0x492 JUMPI JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0xA4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 PUSH1 0x0 CALLDATALOAD AND PUSH32 0xA85E59E400000000000000000000000000000000000000000000000000000000 DUP2 EQ ISZERO PUSH2 0x2B7 JUMPI PUSH1 0x40 DUP1 MLOAD CALLER DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 DUP3 ADD MSTORE SWAP1 DUP2 KECCAK256 SLOAD ISZERO ISZERO PUSH2 0x17B JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0x1553454E4445525F4E4F545F415554484F52495A454400000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x1 PUSH1 0x64 CALLDATALOAD SUB ISZERO PUSH2 0x1F7 JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0xE494E56414C49445F414D4F554E540000000000000000000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x23B872DD00000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x40 PUSH1 0x24 PUSH1 0x4 CALLDATACOPY PUSH1 0x4 CALLDATALOAD PUSH1 0x20 PUSH1 0x48 DUP3 ADD PUSH1 0x44 CALLDATACOPY PUSH1 0x28 DUP2 ADD CALLDATALOAD PUSH1 0x0 DUP1 PUSH1 0x64 PUSH1 0x0 DUP1 DUP6 GAS CALL DUP1 ISZERO PUSH2 0x246 JUMPI STOP JUMPDEST PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0xF5452414E534645525F4641494C454400000000000000000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x2C8 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x2EA PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x4C0 JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x2F8 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x304 PUSH1 0x4 CALLDATALOAD PUSH2 0x6AC JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x339 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x2EA PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x6E1 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x367 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x304 PUSH2 0x9DA JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x37C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x2EA PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH1 0x24 CALLDATALOAD PUSH2 0x9F6 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x3AD JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x3B6 PUSH2 0xDAB JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x3F7 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x419 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0xDE1 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD SWAP2 ISZERO ISZERO DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x439 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x442 PUSH2 0xDF6 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH1 0x20 DUP1 DUP3 MSTORE DUP4 MLOAD DUP2 DUP4 ADD MSTORE DUP4 MLOAD SWAP2 SWAP3 DUP4 SWAP3 SWAP1 DUP4 ADD SWAP2 DUP6 DUP2 ADD SWAP2 MUL DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x47E JUMPI DUP2 DUP2 ADD MLOAD DUP4 DUP3 ADD MSTORE PUSH1 0x20 ADD PUSH2 0x466 JUMP JUMPDEST POP POP POP POP SWAP1 POP ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x49E JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x2EA PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0xE65 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x546 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO PUSH2 0x5DB JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x19 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F414C52454144595F415554484F52495A454400000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 DUP1 DUP4 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND DUP4 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD SWAP3 DUP4 ADD DUP2 SSTORE DUP4 MSTORE PUSH32 0x405787FA12A823E0F2B7631CC41B3BA8828B3321CA811111FA75CD3AA3BB5ACE SWAP1 SWAP2 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND DUP5 OR SWAP1 SSTORE MLOAD CALLER SWAP3 SWAP2 PUSH32 0x3147867C59D17E8FA9D522465651D44AAE0A9E38F902F3475B97E58072F0ED4C SWAP2 LOG3 POP JUMP JUMPDEST PUSH1 0x2 DUP1 SLOAD DUP3 SWAP1 DUP2 LT PUSH2 0x6BA JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP DUP2 JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x768 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0x7FE JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST POP PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 DUP2 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE JUMPDEST PUSH1 0x2 SLOAD DUP2 LT ISZERO PUSH2 0x993 JUMPI DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x2 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0x87B JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x98B JUMPI PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0x8D3 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x2 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0x906 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0x985 SWAP1 DUP3 PUSH2 0xF4B JUMP JUMPDEST POP PUSH2 0x993 JUMP JUMPDEST PUSH1 0x1 ADD PUSH2 0x84B JUMP JUMPDEST PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0xA7C JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0xB12 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH1 0x2 SLOAD DUP2 LT PUSH2 0xB82 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x494E4445585F4F55545F4F465F424F554E445300000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x2 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0xBA8 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0xC36 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x1B PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x415554484F52495A45445F414444524553535F4D49534D415443480000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0xCB1 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x2 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0xCE4 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0xD63 SWAP1 DUP3 PUSH2 0xF4B JUMP JUMPDEST POP PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH32 0x455243373231546F6B656E28616464726573732C75696E743235362900000000 DUP2 MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x1C ADD SWAP1 KECCAK256 JUMPDEST SWAP1 JUMP JUMPDEST PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND DUP2 JUMP JUMPDEST PUSH1 0x60 PUSH1 0x2 DUP1 SLOAD DUP1 PUSH1 0x20 MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD DUP1 ISZERO PUSH2 0xE5B JUMPI PUSH1 0x20 MUL DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x1 SWAP1 SWAP2 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0xE30 JUMPI JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0xEEB JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND ISZERO PUSH2 0xF48 JUMPI PUSH1 0x0 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP4 AND OR SWAP1 SSTORE JUMPDEST POP JUMP JUMPDEST DUP2 SLOAD DUP2 DUP4 SSTORE DUP2 DUP2 GT ISZERO PUSH2 0xF6F JUMPI PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 SWAP1 KECCAK256 PUSH2 0xF6F SWAP2 DUP2 ADD SWAP1 DUP4 ADD PUSH2 0xF74 JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH2 0xDDE SWAP2 SWAP1 JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0xF8E JUMPI PUSH1 0x0 DUP2 SSTORE PUSH1 0x1 ADD PUSH2 0xF7A JUMP JUMPDEST POP SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 STATICCALL 0xe7 CREATE2 GASLIMIT 0xd6 0xd SWAP9 0x3f 0xba LOG3 0xdb 0x4b REVERT 0xd6 0xd7 DUP4 SUB 0xe0 0xac SHL PUSH7 0x8A4A2BAF565CC4 ORIGIN 0xa7 CALLDATASIZE DUP11 STOP 0x29 ", + "sourceMap": "641:7861:6:-;;;174:5:3;:18;;-1:-1:-1;;;;;;174:18:3;182:10;174:18;;;641:7861:6;;;;;;" + }, + "deployedBytecode": { + "linkReferences": {}, + "object": "0x6080604052600436106100985763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166342f1181e81146102bc578063494503d4146102ec578063707129391461032d5780638da5cb5b1461035b5780639ad2674414610370578063ae25532e146103a1578063b9181611146103eb578063d39de6e91461042d578063f2fde38b14610492575b3480156100a457600080fd5b507fffffffff00000000000000000000000000000000000000000000000000000000600035167fa85e59e4000000000000000000000000000000000000000000000000000000008114156102b757604080513381526001602082015290812054151561017b577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c1553454e4445525f4e4f545f415554484f52495a454400000000000000604052600060605260646000fd5b600160643503156101f7577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c0e494e56414c49445f414d4f554e540000000000000000000000000000604052600060605260646000fd5b7f23b872dd000000000000000000000000000000000000000000000000000000006000526040602460043760043560206048820160443760288101356000806064600080855af1801561024657005b7f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c0f5452414e534645525f4641494c454400000000000000000000000000604052600060605260646000fd5b600080fd5b3480156102c857600080fd5b506102ea73ffffffffffffffffffffffffffffffffffffffff600435166104c0565b005b3480156102f857600080fd5b506103046004356106ac565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561033957600080fd5b506102ea73ffffffffffffffffffffffffffffffffffffffff600435166106e1565b34801561036757600080fd5b506103046109da565b34801561037c57600080fd5b506102ea73ffffffffffffffffffffffffffffffffffffffff600435166024356109f6565b3480156103ad57600080fd5b506103b6610dab565b604080517fffffffff000000000000000000000000000000000000000000000000000000009092168252519081900360200190f35b3480156103f757600080fd5b5061041973ffffffffffffffffffffffffffffffffffffffff60043516610de1565b604080519115158252519081900360200190f35b34801561043957600080fd5b50610442610df6565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561047e578181015183820152602001610466565b505050509050019250505060405180910390f35b34801561049e57600080fd5b506102ea73ffffffffffffffffffffffffffffffffffffffff60043516610e65565b60005473ffffffffffffffffffffffffffffffffffffffff16331461054657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811660009081526001602052604090205460ff16156105db57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f5441524745545f414c52454144595f415554484f52495a454400000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116600081815260016020819052604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168317905560028054928301815583527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90910180547fffffffffffffffffffffffff00000000000000000000000000000000000000001684179055513392917f3147867c59d17e8fa9d522465651d44aae0a9e38f902f3475b97e58072f0ed4c91a350565b60028054829081106106ba57fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16905081565b6000805473ffffffffffffffffffffffffffffffffffffffff16331461076857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604090205460ff1615156107fe57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b5073ffffffffffffffffffffffffffffffffffffffff8116600090815260016020526040812080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690555b600254811015610993578173ffffffffffffffffffffffffffffffffffffffff1660028281548110151561087b57fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16141561098b57600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81019081106108d357fe5b6000918252602090912001546002805473ffffffffffffffffffffffffffffffffffffffff909216918390811061090657fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01906109859082610f4b565b50610993565b60010161084b565b604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b60005473ffffffffffffffffffffffffffffffffffffffff163314610a7c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604090205460ff161515610b1257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b6002548110610b8257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f494e4445585f4f55545f4f465f424f554e445300000000000000000000000000604482015290519081900360640190fd5b8173ffffffffffffffffffffffffffffffffffffffff16600282815481101515610ba857fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff1614610c3657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f415554484f52495a45445f414444524553535f4d49534d415443480000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260016020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101908110610cb157fe5b6000918252602090912001546002805473ffffffffffffffffffffffffffffffffffffffff9092169183908110610ce457fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190610d639082610f4b565b50604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b604080517f455243373231546f6b656e28616464726573732c75696e7432353629000000008152905190819003601c0190205b90565b60016020526000908152604090205460ff1681565b60606002805480602002602001604051908101604052809291908181526020018280548015610e5b57602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610e30575b5050505050905090565b60005473ffffffffffffffffffffffffffffffffffffffff163314610eeb57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811615610f4857600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b815481835581811115610f6f57600083815260209020610f6f918101908301610f74565b505050565b610dde91905b80821115610f8e5760008155600101610f7a565b50905600a165627a7a72305820fae7fb45d60d983fbaa3db4bfdd6d78303e0ac1b668a4a2baf565cc432a7368a0029", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x98 JUMPI PUSH4 0xFFFFFFFF PUSH29 0x100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 CALLDATALOAD DIV AND PUSH4 0x42F1181E DUP2 EQ PUSH2 0x2BC JUMPI DUP1 PUSH4 0x494503D4 EQ PUSH2 0x2EC JUMPI DUP1 PUSH4 0x70712939 EQ PUSH2 0x32D JUMPI DUP1 PUSH4 0x8DA5CB5B EQ PUSH2 0x35B JUMPI DUP1 PUSH4 0x9AD26744 EQ PUSH2 0x370 JUMPI DUP1 PUSH4 0xAE25532E EQ PUSH2 0x3A1 JUMPI DUP1 PUSH4 0xB9181611 EQ PUSH2 0x3EB JUMPI DUP1 PUSH4 0xD39DE6E9 EQ PUSH2 0x42D JUMPI DUP1 PUSH4 0xF2FDE38B EQ PUSH2 0x492 JUMPI JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0xA4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 PUSH1 0x0 CALLDATALOAD AND PUSH32 0xA85E59E400000000000000000000000000000000000000000000000000000000 DUP2 EQ ISZERO PUSH2 0x2B7 JUMPI PUSH1 0x40 DUP1 MLOAD CALLER DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 DUP3 ADD MSTORE SWAP1 DUP2 KECCAK256 SLOAD ISZERO ISZERO PUSH2 0x17B JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0x1553454E4445525F4E4F545F415554484F52495A454400000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x1 PUSH1 0x64 CALLDATALOAD SUB ISZERO PUSH2 0x1F7 JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0xE494E56414C49445F414D4F554E540000000000000000000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH32 0x23B872DD00000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH1 0x40 PUSH1 0x24 PUSH1 0x4 CALLDATACOPY PUSH1 0x4 CALLDATALOAD PUSH1 0x20 PUSH1 0x48 DUP3 ADD PUSH1 0x44 CALLDATACOPY PUSH1 0x28 DUP2 ADD CALLDATALOAD PUSH1 0x0 DUP1 PUSH1 0x64 PUSH1 0x0 DUP1 DUP6 GAS CALL DUP1 ISZERO PUSH2 0x246 JUMPI STOP JUMPDEST PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0xF5452414E534645525F4641494C454400000000000000000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x2C8 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x2EA PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x4C0 JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x2F8 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x304 PUSH1 0x4 CALLDATALOAD PUSH2 0x6AC JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x339 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x2EA PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x6E1 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x367 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x304 PUSH2 0x9DA JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x37C JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x2EA PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH1 0x24 CALLDATALOAD PUSH2 0x9F6 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x3AD JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x3B6 PUSH2 0xDAB JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x3F7 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x419 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0xDE1 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD SWAP2 ISZERO ISZERO DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x439 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x442 PUSH2 0xDF6 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH1 0x20 DUP1 DUP3 MSTORE DUP4 MLOAD DUP2 DUP4 ADD MSTORE DUP4 MLOAD SWAP2 SWAP3 DUP4 SWAP3 SWAP1 DUP4 ADD SWAP2 DUP6 DUP2 ADD SWAP2 MUL DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x47E JUMPI DUP2 DUP2 ADD MLOAD DUP4 DUP3 ADD MSTORE PUSH1 0x20 ADD PUSH2 0x466 JUMP JUMPDEST POP POP POP POP SWAP1 POP ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x49E JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x2EA PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0xE65 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x546 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO PUSH2 0x5DB JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x19 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F414C52454144595F415554484F52495A454400000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 DUP1 DUP4 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND DUP4 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD SWAP3 DUP4 ADD DUP2 SSTORE DUP4 MSTORE PUSH32 0x405787FA12A823E0F2B7631CC41B3BA8828B3321CA811111FA75CD3AA3BB5ACE SWAP1 SWAP2 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND DUP5 OR SWAP1 SSTORE MLOAD CALLER SWAP3 SWAP2 PUSH32 0x3147867C59D17E8FA9D522465651D44AAE0A9E38F902F3475B97E58072F0ED4C SWAP2 LOG3 POP JUMP JUMPDEST PUSH1 0x2 DUP1 SLOAD DUP3 SWAP1 DUP2 LT PUSH2 0x6BA JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP DUP2 JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x768 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0x7FE JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST POP PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 DUP2 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE JUMPDEST PUSH1 0x2 SLOAD DUP2 LT ISZERO PUSH2 0x993 JUMPI DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x2 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0x87B JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x98B JUMPI PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0x8D3 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x2 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0x906 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0x985 SWAP1 DUP3 PUSH2 0xF4B JUMP JUMPDEST POP PUSH2 0x993 JUMP JUMPDEST PUSH1 0x1 ADD PUSH2 0x84B JUMP JUMPDEST PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0xA7C JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0xB12 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH1 0x2 SLOAD DUP2 LT PUSH2 0xB82 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x494E4445585F4F55545F4F465F424F554E445300000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x2 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0xBA8 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0xC36 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x1B PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x415554484F52495A45445F414444524553535F4D49534D415443480000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0xCB1 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x2 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0xCE4 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0xD63 SWAP1 DUP3 PUSH2 0xF4B JUMP JUMPDEST POP PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH32 0x455243373231546F6B656E28616464726573732C75696E743235362900000000 DUP2 MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x1C ADD SWAP1 KECCAK256 JUMPDEST SWAP1 JUMP JUMPDEST PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND DUP2 JUMP JUMPDEST PUSH1 0x60 PUSH1 0x2 DUP1 SLOAD DUP1 PUSH1 0x20 MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD DUP1 ISZERO PUSH2 0xE5B JUMPI PUSH1 0x20 MUL DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x1 SWAP1 SWAP2 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0xE30 JUMPI JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0xEEB JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND ISZERO PUSH2 0xF48 JUMPI PUSH1 0x0 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP4 AND OR SWAP1 SSTORE JUMPDEST POP JUMP JUMPDEST DUP2 SLOAD DUP2 DUP4 SSTORE DUP2 DUP2 GT ISZERO PUSH2 0xF6F JUMPI PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 SWAP1 KECCAK256 PUSH2 0xF6F SWAP2 DUP2 ADD SWAP1 DUP4 ADD PUSH2 0xF74 JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH2 0xDDE SWAP2 SWAP1 JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0xF8E JUMPI PUSH1 0x0 DUP2 SSTORE PUSH1 0x1 ADD PUSH2 0xF7A JUMP JUMPDEST POP SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 STATICCALL 0xe7 CREATE2 GASLIMIT 0xd6 0xd SWAP9 0x3f 0xba LOG3 0xdb 0x4b REVERT 0xd6 0xd7 DUP4 SUB 0xe0 0xac SHL PUSH7 0x8A4A2BAF565CC4 ORIGIN 0xa7 CALLDATASIZE DUP11 STOP 0x29 ", + "sourceMap": "641:7861:6:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;641:7861:6;1037:66;1033:1;1020:15;1016:88;1499:66;1489:8;1486:80;1483:2;;;1795;1789:9;;1833:6;1815:70;;1925:15;1920:2;1909:14;;1902:39;2036:20;;;2030:27;2023:35;2020:2;;;2159:66;2156:1;2149:77;2258:66;2254:2;2247:78;2357:66;2353:2;2346:78;2456:1;2452:2;2445:13;2489:3;2486:1;2479:14;2020:2;5829:1;5823:3;5810:17;5806:25;5803:2;;;5925:66;5922:1;5915:77;6024:66;6020:2;6013:78;6123:66;6119:2;6112:78;6222:1;6218:2;6211:13;6255:3;6252:1;6245:14;5803:2;6553:66;6550:1;6543:77;6848:2;6844;6841:1;6828:23;6988:1;6975:15;7050:2;7045;7028:15;7024:24;7020:2;7007:46;7195:2;7178:15;7174:24;7161:38;7620:1;7560;7505:3;7440:1;7382;7312:5;7257:3;7231:448;7699:7;7696:2;;;7729:12;7696:2;7861:66;7858:1;7851:77;7956:66;7952:2;7945:78;8051:66;8047:2;8040:78;8146:1;8142:2;8135:13;8175:3;8172:1;8165:14;1483:2;8271:1;8268;8261:12;1162:320:8;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;1162:320:8;;;;;;;;;1047:28;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;1047:28:8;;;;;;;;;;;;;;;;;;;;;;;;1596:547;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;1596:547:8;;;;;;;103:20:3;;8:9:-1;5:2;;;30:1;27;20:12;5:2;103:20:3;;;;2316:643:8;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;2316:643:8;;;;;;;;;8387:113:6;;8:9:-1;5:2;;;30:1;27;20:12;5:2;8387:113:6;;;;;;;;;;;;;;;;;;;;;;;998:43:8;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;998:43:8;;;;;;;;;;;;;;;;;;;;;;;;;3056:138;;8:9:-1;5:2;;;30:1;27;20:12;5:2;3056:138:8;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:100:-1;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;3056:138:8;;;;;;;;;;;;;;;;;345:167:3;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;345:167:3;;;;;;;1162:320:8;271:5:3;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1279:18:8;;;;;;;:10;:18;;;;;;;;1278:19;1257:91;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1359:18;;;;;;;1380:4;1359:18;;;;;;;;:25;;;;;;;;1394:11;27:10:-1;;23:18;;;45:23;;1394:24:8;;;;;;;;;;;;;;1433:42;1464:10;;1359:18;1433:42;;;1162:320;:::o;1047:28::-;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1047:28:8;:::o;1596:547::-;1831:9;271:5:3;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1715:18:8;;;;;;;:10;:18;;;;;;;;1694:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1798:18:8;;;;;;;:10;:18;;;;;1791:25;;;;;;1826:252;1850:11;:18;1846:22;;1826:252;;;1911:6;1893:24;;:11;1905:1;1893:14;;;;;;;;;;;;;;;;;;;;;;:24;1889:179;;;1954:11;1966:18;;:22;;;;1954:35;;;;;;;;;;;;;;;;1937:11;:14;;1954:35;;;;;1949:1;;1937:14;;;;;;;;;;;;;;;:52;;;;;;;;;;;;;;;2007:11;:23;;;;;;;;;:::i;:::-;;2048:5;;1889:179;1870:3;;1826:252;;;2092:44;;2125:10;;2092:44;;;;;;;;;1596:547;;:::o;103:20:3:-;;;;;;:::o;2316:643:8:-;271:5:3;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2479:18:8;;;;;;;:10;:18;;;;;;;;2458:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2583:11;:18;2575:26;;2554:92;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2699:6;2677:28;;:11;2689:5;2677:18;;;;;;;;;;;;;;;;;;;;;;:28;2656:102;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2776:18;;;;;;;:10;:18;;;;;2769:25;;;;;;2825:11;2837:18;;:22;;;;2825:35;;;;;;;;;;;;;;;;2804:11;:18;;2825:35;;;;;2816:5;;2804:18;;;;;;;;;;;;;;;:56;;;;;;;;;;;;;;;2870:11;:23;;;;;;;;;:::i;:::-;-1:-1:-1;2908:44:8;;2941:10;;2908:44;;;;;;;;;2316:643;;:::o;8387:113:6:-;761:41;;;;;;;;;;;;;;;;8387:113;;:::o;998:43:8:-;;;;;;;;;;;;;;;:::o;3056:138::-;3137:9;3176:11;3169:18;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3056:138;:::o;345:167:3:-;271:5;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;441:22;;;;437:69;;479:5;:16;;;;;;;;;;437:69;345:167;:::o;641:7861:6:-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;" + } + } + }, + "sources": { + "asset-proxy/ERC721Proxy.sol": { + "id": 6 + }, + "asset-proxy/MixinAuthorizable.sol": { + "id": 8 + }, + "@0x/contracts-utils/contracts/utils/Ownable.sol": { + "id": 3 + }, + "@0x/contracts-utils/contracts/utils/interfaces/IOwnable.sol": { + "id": 4 + }, + "asset-proxy/mixins/MAuthorizable.sol": { + "id": 11 + }, + "@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAuthorizable.sol": { + "id": 1 + } + }, + "sourceCodes": { + "asset-proxy/ERC721Proxy.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity 0.4.24;\n\nimport \"./MixinAuthorizable.sol\";\n\n\ncontract ERC721Proxy is\n MixinAuthorizable\n{\n // Id of this proxy.\n bytes4 constant internal PROXY_ID = bytes4(keccak256(\"ERC721Token(address,uint256)\"));\n\n // solhint-disable-next-line payable-fallback\n function () \n external\n {\n assembly {\n // The first 4 bytes of calldata holds the function selector\n let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000)\n\n // `transferFrom` will be called with the following parameters:\n // assetData Encoded byte array.\n // from Address to transfer asset from.\n // to Address to transfer asset to.\n // amount Amount of asset to transfer.\n // bytes4(keccak256(\"transferFrom(bytes,address,address,uint256)\")) = 0xa85e59e4\n if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) {\n\n // To lookup a value in a mapping, we load from the storage location keccak256(k, p),\n // where k is the key left padded to 32 bytes and p is the storage slot\n let start := mload(64)\n mstore(start, and(caller, 0xffffffffffffffffffffffffffffffffffffffff))\n mstore(add(start, 32), authorized_slot)\n\n // Revert if authorized[msg.sender] == false\n if iszero(sload(keccak256(start, 64))) {\n // Revert with `Error(\"SENDER_NOT_AUTHORIZED\")`\n mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)\n mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)\n mstore(64, 0x0000001553454e4445525f4e4f545f415554484f52495a454400000000000000)\n mstore(96, 0)\n revert(0, 100)\n }\n\n // `transferFrom`.\n // The function is marked `external`, so no abi decodeding is done for\n // us. Instead, we expect the `calldata` memory to contain the\n // following:\n //\n // | Area | Offset | Length | Contents |\n // |----------|--------|---------|-------------------------------------|\n // | Header | 0 | 4 | function selector |\n // | Params | | 4 * 32 | function parameters: |\n // | | 4 | | 1. offset to assetData (*) |\n // | | 36 | | 2. from |\n // | | 68 | | 3. to |\n // | | 100 | | 4. amount |\n // | Data | | | assetData: |\n // | | 132 | 32 | assetData Length |\n // | | 164 | ** | assetData Contents |\n //\n // (*): offset is computed from start of function parameters, so offset\n // by an additional 4 bytes in the calldata.\n //\n // (**): see table below to compute length of assetData Contents\n //\n // WARNING: The ABIv2 specification allows additional padding between\n // the Params and Data section. This will result in a larger\n // offset to assetData.\n\n // Asset data itself is encoded as follows:\n //\n // | Area | Offset | Length | Contents |\n // |----------|--------|---------|-------------------------------------|\n // | Header | 0 | 4 | function selector |\n // | Params | | 2 * 32 | function parameters: |\n // | | 4 | 12 + 20 | 1. token address |\n // | | 36 | | 2. tokenId |\n \n // We construct calldata for the `token.transferFrom` ABI.\n // The layout of this calldata is in the table below.\n // \n // | Area | Offset | Length | Contents |\n // |----------|--------|---------|-------------------------------------|\n // | Header | 0 | 4 | function selector |\n // | Params | | 3 * 32 | function parameters: |\n // | | 4 | | 1. from |\n // | | 36 | | 2. to |\n // | | 68 | | 3. tokenId |\n\n // There exists only 1 of each token.\n // require(amount == 1, \"INVALID_AMOUNT\")\n if sub(calldataload(100), 1) {\n // Revert with `Error(\"INVALID_AMOUNT\")`\n mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)\n mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)\n mstore(64, 0x0000000e494e56414c49445f414d4f554e540000000000000000000000000000)\n mstore(96, 0)\n revert(0, 100)\n }\n\n /////// Setup Header Area ///////\n // This area holds the 4-byte `transferFrom` selector.\n // Any trailing data in transferFromSelector will be\n // overwritten in the next `mstore` call.\n mstore(0, 0x23b872dd00000000000000000000000000000000000000000000000000000000)\n \n /////// Setup Params Area ///////\n // We copy the fields `from` and `to` in bulk\n // from our own calldata to the new calldata.\n calldatacopy(4, 36, 64)\n\n // Copy `tokenId` field from our own calldata to the new calldata.\n let assetDataOffset := calldataload(4)\n calldatacopy(68, add(assetDataOffset, 72), 32)\n\n /////// Call `token.transferFrom` using the calldata ///////\n let token := calldataload(add(assetDataOffset, 40))\n let success := call(\n gas, // forward all gas\n token, // call address of token contract\n 0, // don't send any ETH\n 0, // pointer to start of input\n 100, // length of input\n 0, // write output to null\n 0 // output size is 0 bytes\n )\n if success {\n return(0, 0)\n }\n \n // Revert with `Error(\"TRANSFER_FAILED\")`\n mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)\n mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)\n mstore(64, 0x0000000f5452414e534645525f4641494c454400000000000000000000000000)\n mstore(96, 0)\n revert(0, 100)\n }\n\n // Revert if undefined function is called\n revert(0, 0)\n }\n }\n\n /// @dev Gets the proxy id associated with the proxy address.\n /// @return Proxy id.\n function getProxyId()\n external\n pure\n returns (bytes4)\n {\n return PROXY_ID;\n }\n}\n", + "asset-proxy/MixinAuthorizable.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-utils/contracts/utils/Ownable.sol\";\nimport \"./mixins/MAuthorizable.sol\";\n\n\ncontract MixinAuthorizable is\n Ownable,\n MAuthorizable\n{\n /// @dev Only authorized addresses can invoke functions with this modifier.\n modifier onlyAuthorized {\n require(\n authorized[msg.sender],\n \"SENDER_NOT_AUTHORIZED\"\n );\n _;\n }\n\n mapping (address => bool) public authorized;\n address[] public authorities;\n\n /// @dev Authorizes an address.\n /// @param target Address to authorize.\n function addAuthorizedAddress(address target)\n external\n onlyOwner\n {\n require(\n !authorized[target],\n \"TARGET_ALREADY_AUTHORIZED\"\n );\n\n authorized[target] = true;\n authorities.push(target);\n emit AuthorizedAddressAdded(target, msg.sender);\n }\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n function removeAuthorizedAddress(address target)\n external\n onlyOwner\n {\n require(\n authorized[target],\n \"TARGET_NOT_AUTHORIZED\"\n );\n\n delete authorized[target];\n for (uint256 i = 0; i < authorities.length; i++) {\n if (authorities[i] == target) {\n authorities[i] = authorities[authorities.length - 1];\n authorities.length -= 1;\n break;\n }\n }\n emit AuthorizedAddressRemoved(target, msg.sender);\n }\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n /// @param index Index of target in authorities array.\n function removeAuthorizedAddressAtIndex(\n address target,\n uint256 index\n )\n external\n onlyOwner\n {\n require(\n authorized[target],\n \"TARGET_NOT_AUTHORIZED\"\n );\n require(\n index < authorities.length,\n \"INDEX_OUT_OF_BOUNDS\"\n );\n require(\n authorities[index] == target,\n \"AUTHORIZED_ADDRESS_MISMATCH\"\n );\n\n delete authorized[target];\n authorities[index] = authorities[authorities.length - 1];\n authorities.length -= 1;\n emit AuthorizedAddressRemoved(target, msg.sender);\n }\n\n /// @dev Gets all authorized addresses.\n /// @return Array of authorized addresses.\n function getAuthorizedAddresses()\n external\n view\n returns (address[] memory)\n {\n return authorities;\n }\n}\n", + "@0x/contracts-utils/contracts/utils/Ownable.sol": "pragma solidity ^0.4.24;\n\nimport \"./interfaces/IOwnable.sol\";\n\n\ncontract Ownable is\n IOwnable\n{\n address public owner;\n\n constructor ()\n public\n {\n owner = msg.sender;\n }\n\n modifier onlyOwner() {\n require(\n msg.sender == owner,\n \"ONLY_CONTRACT_OWNER\"\n );\n _;\n }\n\n function transferOwnership(address newOwner)\n public\n onlyOwner\n {\n if (newOwner != address(0)) {\n owner = newOwner;\n }\n }\n}\n", + "@0x/contracts-utils/contracts/utils/interfaces/IOwnable.sol": "pragma solidity ^0.4.24;\n\n\ncontract IOwnable {\n\n function transferOwnership(address newOwner)\n public;\n}\n", + "asset-proxy/mixins/MAuthorizable.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAuthorizable.sol\";\n\n\ncontract MAuthorizable is\n IAuthorizable\n{\n // Event logged when a new address is authorized.\n event AuthorizedAddressAdded(\n address indexed target,\n address indexed caller\n );\n\n // Event logged when a currently authorized address is unauthorized.\n event AuthorizedAddressRemoved(\n address indexed target,\n address indexed caller\n );\n\n /// @dev Only authorized addresses can invoke functions with this modifier.\n modifier onlyAuthorized { revert(); _; }\n}\n", + "@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAuthorizable.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-utils/contracts/utils/interfaces/IOwnable.sol\";\n\n\ncontract IAuthorizable is\n IOwnable\n{\n /// @dev Authorizes an address.\n /// @param target Address to authorize.\n function addAuthorizedAddress(address target)\n external;\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n function removeAuthorizedAddress(address target)\n external;\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n /// @param index Index of target in authorities array.\n function removeAuthorizedAddressAtIndex(\n address target,\n uint256 index\n )\n external;\n \n /// @dev Gets all authorized addresses.\n /// @return Array of authorized addresses.\n function getAuthorizedAddresses()\n external\n view\n returns (address[] memory);\n}\n" + }, + "sourceTreeHashHex": "0x3fb5a7bf2e78d42aae6b0fe0f902636eaef450da4a88a804651c8ef321bdae6a", + "compiler": { + "name": "solc", + "version": "soljson-v0.4.24+commit.e67f0147.js", + "settings": { + "optimizer": { + "enabled": true, + "runs": 1000000 + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + } + } + }, + "networks": {} +} diff --git a/contracts/asset-proxy/generated-artifacts/MixinAuthorizable.json b/contracts/asset-proxy/generated-artifacts/MixinAuthorizable.json new file mode 100644 index 000000000..8e7ad1c3b --- /dev/null +++ b/contracts/asset-proxy/generated-artifacts/MixinAuthorizable.json @@ -0,0 +1,229 @@ +{ + "schemaVersion": "2.0.0", + "contractName": "MixinAuthorizable", + "compilerOutput": { + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "addAuthorizedAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "authorities", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "removeAuthorizedAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + }, + { + "name": "index", + "type": "uint256" + } + ], + "name": "removeAuthorizedAddressAtIndex", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "authorized", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getAuthorizedAddresses", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "target", + "type": "address" + }, + { + "indexed": true, + "name": "caller", + "type": "address" + } + ], + "name": "AuthorizedAddressAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "target", + "type": "address" + }, + { + "indexed": true, + "name": "caller", + "type": "address" + } + ], + "name": "AuthorizedAddressRemoved", + "type": "event" + } + ], + "evm": { + "bytecode": { + "linkReferences": {}, + "object": "0x608060405260008054600160a060020a03191633179055610d15806100256000396000f30060806040526004361061008d5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166342f1181e8114610092578063494503d4146100c257806370712939146101035780638da5cb5b146101315780639ad2674414610146578063b918161114610177578063d39de6e9146101b9578063f2fde38b1461021e575b600080fd5b34801561009e57600080fd5b506100c073ffffffffffffffffffffffffffffffffffffffff6004351661024c565b005b3480156100ce57600080fd5b506100da600435610438565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561010f57600080fd5b506100c073ffffffffffffffffffffffffffffffffffffffff6004351661046d565b34801561013d57600080fd5b506100da610766565b34801561015257600080fd5b506100c073ffffffffffffffffffffffffffffffffffffffff60043516602435610782565b34801561018357600080fd5b506101a573ffffffffffffffffffffffffffffffffffffffff60043516610b37565b604080519115158252519081900360200190f35b3480156101c557600080fd5b506101ce610b4c565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561020a5781810151838201526020016101f2565b505050509050019250505060405180910390f35b34801561022a57600080fd5b506100c073ffffffffffffffffffffffffffffffffffffffff60043516610bbc565b60005473ffffffffffffffffffffffffffffffffffffffff1633146102d257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811660009081526001602052604090205460ff161561036757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f5441524745545f414c52454144595f415554484f52495a454400000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116600081815260016020819052604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168317905560028054928301815583527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90910180547fffffffffffffffffffffffff00000000000000000000000000000000000000001684179055513392917f3147867c59d17e8fa9d522465651d44aae0a9e38f902f3475b97e58072f0ed4c91a350565b600280548290811061044657fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16905081565b6000805473ffffffffffffffffffffffffffffffffffffffff1633146104f457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604090205460ff16151561058a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b5073ffffffffffffffffffffffffffffffffffffffff8116600090815260016020526040812080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690555b60025481101561071f578173ffffffffffffffffffffffffffffffffffffffff1660028281548110151561060757fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16141561071757600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810190811061065f57fe5b6000918252602090912001546002805473ffffffffffffffffffffffffffffffffffffffff909216918390811061069257fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01906107119082610ca2565b5061071f565b6001016105d7565b604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b60005473ffffffffffffffffffffffffffffffffffffffff16331461080857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604090205460ff16151561089e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b600254811061090e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f494e4445585f4f55545f4f465f424f554e445300000000000000000000000000604482015290519081900360640190fd5b8173ffffffffffffffffffffffffffffffffffffffff1660028281548110151561093457fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16146109c257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f415554484f52495a45445f414444524553535f4d49534d415443480000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260016020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101908110610a3d57fe5b6000918252602090912001546002805473ffffffffffffffffffffffffffffffffffffffff9092169183908110610a7057fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190610aef9082610ca2565b50604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b60016020526000908152604090205460ff1681565b60606002805480602002602001604051908101604052809291908181526020018280548015610bb157602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610b86575b505050505090505b90565b60005473ffffffffffffffffffffffffffffffffffffffff163314610c4257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811615610c9f57600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b815481835581811115610cc657600083815260209020610cc6918101908301610ccb565b505050565b610bb991905b80821115610ce55760008155600101610cd1565b50905600a165627a7a72305820354503852d1a76b37560abab0e9b59a7ad6ec77b60b7b0924b402d7abdce5af00029", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 SLOAD PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB NOT AND CALLER OR SWAP1 SSTORE PUSH2 0xD15 DUP1 PUSH2 0x25 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN STOP PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x8D JUMPI PUSH4 0xFFFFFFFF PUSH29 0x100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 CALLDATALOAD DIV AND PUSH4 0x42F1181E DUP2 EQ PUSH2 0x92 JUMPI DUP1 PUSH4 0x494503D4 EQ PUSH2 0xC2 JUMPI DUP1 PUSH4 0x70712939 EQ PUSH2 0x103 JUMPI DUP1 PUSH4 0x8DA5CB5B EQ PUSH2 0x131 JUMPI DUP1 PUSH4 0x9AD26744 EQ PUSH2 0x146 JUMPI DUP1 PUSH4 0xB9181611 EQ PUSH2 0x177 JUMPI DUP1 PUSH4 0xD39DE6E9 EQ PUSH2 0x1B9 JUMPI DUP1 PUSH4 0xF2FDE38B EQ PUSH2 0x21E JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x9E JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xC0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x24C JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0xCE JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xDA PUSH1 0x4 CALLDATALOAD PUSH2 0x438 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x10F JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xC0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x46D JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x13D JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xDA PUSH2 0x766 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x152 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xC0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH1 0x24 CALLDATALOAD PUSH2 0x782 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x183 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x1A5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0xB37 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD SWAP2 ISZERO ISZERO DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x1C5 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x1CE PUSH2 0xB4C JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH1 0x20 DUP1 DUP3 MSTORE DUP4 MLOAD DUP2 DUP4 ADD MSTORE DUP4 MLOAD SWAP2 SWAP3 DUP4 SWAP3 SWAP1 DUP4 ADD SWAP2 DUP6 DUP2 ADD SWAP2 MUL DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x20A JUMPI DUP2 DUP2 ADD MLOAD DUP4 DUP3 ADD MSTORE PUSH1 0x20 ADD PUSH2 0x1F2 JUMP JUMPDEST POP POP POP POP SWAP1 POP ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x22A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xC0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0xBBC JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x2D2 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO PUSH2 0x367 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x19 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F414C52454144595F415554484F52495A454400000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 DUP1 DUP4 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND DUP4 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD SWAP3 DUP4 ADD DUP2 SSTORE DUP4 MSTORE PUSH32 0x405787FA12A823E0F2B7631CC41B3BA8828B3321CA811111FA75CD3AA3BB5ACE SWAP1 SWAP2 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND DUP5 OR SWAP1 SSTORE MLOAD CALLER SWAP3 SWAP2 PUSH32 0x3147867C59D17E8FA9D522465651D44AAE0A9E38F902F3475B97E58072F0ED4C SWAP2 LOG3 POP JUMP JUMPDEST PUSH1 0x2 DUP1 SLOAD DUP3 SWAP1 DUP2 LT PUSH2 0x446 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP DUP2 JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x4F4 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0x58A JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST POP PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 DUP2 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE JUMPDEST PUSH1 0x2 SLOAD DUP2 LT ISZERO PUSH2 0x71F JUMPI DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x2 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0x607 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x717 JUMPI PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0x65F JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x2 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0x692 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0x711 SWAP1 DUP3 PUSH2 0xCA2 JUMP JUMPDEST POP PUSH2 0x71F JUMP JUMPDEST PUSH1 0x1 ADD PUSH2 0x5D7 JUMP JUMPDEST PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x808 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0x89E JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH1 0x2 SLOAD DUP2 LT PUSH2 0x90E JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x494E4445585F4F55545F4F465F424F554E445300000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x2 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0x934 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x9C2 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x1B PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x415554484F52495A45445F414444524553535F4D49534D415443480000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0xA3D JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x2 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0xA70 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0xAEF SWAP1 DUP3 PUSH2 0xCA2 JUMP JUMPDEST POP PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND DUP2 JUMP JUMPDEST PUSH1 0x60 PUSH1 0x2 DUP1 SLOAD DUP1 PUSH1 0x20 MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD DUP1 ISZERO PUSH2 0xBB1 JUMPI PUSH1 0x20 MUL DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x1 SWAP1 SWAP2 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0xB86 JUMPI JUMPDEST POP POP POP POP POP SWAP1 POP JUMPDEST SWAP1 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0xC42 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND ISZERO PUSH2 0xC9F JUMPI PUSH1 0x0 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP4 AND OR SWAP1 SSTORE JUMPDEST POP JUMP JUMPDEST DUP2 SLOAD DUP2 DUP4 SSTORE DUP2 DUP2 GT ISZERO PUSH2 0xCC6 JUMPI PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 SWAP1 KECCAK256 PUSH2 0xCC6 SWAP2 DUP2 ADD SWAP1 DUP4 ADD PUSH2 0xCCB JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH2 0xBB9 SWAP2 SWAP1 JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0xCE5 JUMPI PUSH1 0x0 DUP2 SSTORE PUSH1 0x1 ADD PUSH2 0xCD1 JUMP JUMPDEST POP SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 CALLDATALOAD GASLIMIT SUB DUP6 0x2d BYTE PUSH23 0xB37560ABAB0E9B59A7AD6EC77B60B7B0924B402D7ABDCE GAS CREATE STOP 0x29 ", + "sourceMap": "703:2493:3:-;;;174:5:1;:18;;-1:-1:-1;;;;;;174:18:1;182:10;174:18;;;703:2493:3;;;;;;" + }, + "deployedBytecode": { + "linkReferences": {}, + "object": "0x60806040526004361061008d5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166342f1181e8114610092578063494503d4146100c257806370712939146101035780638da5cb5b146101315780639ad2674414610146578063b918161114610177578063d39de6e9146101b9578063f2fde38b1461021e575b600080fd5b34801561009e57600080fd5b506100c073ffffffffffffffffffffffffffffffffffffffff6004351661024c565b005b3480156100ce57600080fd5b506100da600435610438565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561010f57600080fd5b506100c073ffffffffffffffffffffffffffffffffffffffff6004351661046d565b34801561013d57600080fd5b506100da610766565b34801561015257600080fd5b506100c073ffffffffffffffffffffffffffffffffffffffff60043516602435610782565b34801561018357600080fd5b506101a573ffffffffffffffffffffffffffffffffffffffff60043516610b37565b604080519115158252519081900360200190f35b3480156101c557600080fd5b506101ce610b4c565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561020a5781810151838201526020016101f2565b505050509050019250505060405180910390f35b34801561022a57600080fd5b506100c073ffffffffffffffffffffffffffffffffffffffff60043516610bbc565b60005473ffffffffffffffffffffffffffffffffffffffff1633146102d257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811660009081526001602052604090205460ff161561036757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f5441524745545f414c52454144595f415554484f52495a454400000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116600081815260016020819052604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168317905560028054928301815583527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90910180547fffffffffffffffffffffffff00000000000000000000000000000000000000001684179055513392917f3147867c59d17e8fa9d522465651d44aae0a9e38f902f3475b97e58072f0ed4c91a350565b600280548290811061044657fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16905081565b6000805473ffffffffffffffffffffffffffffffffffffffff1633146104f457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604090205460ff16151561058a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b5073ffffffffffffffffffffffffffffffffffffffff8116600090815260016020526040812080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690555b60025481101561071f578173ffffffffffffffffffffffffffffffffffffffff1660028281548110151561060757fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16141561071757600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff810190811061065f57fe5b6000918252602090912001546002805473ffffffffffffffffffffffffffffffffffffffff909216918390811061069257fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01906107119082610ca2565b5061071f565b6001016105d7565b604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b60005473ffffffffffffffffffffffffffffffffffffffff16331461080857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604090205460ff16151561089e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b600254811061090e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f494e4445585f4f55545f4f465f424f554e445300000000000000000000000000604482015290519081900360640190fd5b8173ffffffffffffffffffffffffffffffffffffffff1660028281548110151561093457fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16146109c257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f415554484f52495a45445f414444524553535f4d49534d415443480000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260016020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101908110610a3d57fe5b6000918252602090912001546002805473ffffffffffffffffffffffffffffffffffffffff9092169183908110610a7057fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190610aef9082610ca2565b50604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b60016020526000908152604090205460ff1681565b60606002805480602002602001604051908101604052809291908181526020018280548015610bb157602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311610b86575b505050505090505b90565b60005473ffffffffffffffffffffffffffffffffffffffff163314610c4257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811615610c9f57600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b815481835581811115610cc657600083815260209020610cc6918101908301610ccb565b505050565b610bb991905b80821115610ce55760008155600101610cd1565b50905600a165627a7a72305820354503852d1a76b37560abab0e9b59a7ad6ec77b60b7b0924b402d7abdce5af00029", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x8D JUMPI PUSH4 0xFFFFFFFF PUSH29 0x100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 CALLDATALOAD DIV AND PUSH4 0x42F1181E DUP2 EQ PUSH2 0x92 JUMPI DUP1 PUSH4 0x494503D4 EQ PUSH2 0xC2 JUMPI DUP1 PUSH4 0x70712939 EQ PUSH2 0x103 JUMPI DUP1 PUSH4 0x8DA5CB5B EQ PUSH2 0x131 JUMPI DUP1 PUSH4 0x9AD26744 EQ PUSH2 0x146 JUMPI DUP1 PUSH4 0xB9181611 EQ PUSH2 0x177 JUMPI DUP1 PUSH4 0xD39DE6E9 EQ PUSH2 0x1B9 JUMPI DUP1 PUSH4 0xF2FDE38B EQ PUSH2 0x21E JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x9E JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xC0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x24C JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0xCE JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xDA PUSH1 0x4 CALLDATALOAD PUSH2 0x438 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x10F JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xC0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x46D JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x13D JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xDA PUSH2 0x766 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x152 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xC0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH1 0x24 CALLDATALOAD PUSH2 0x782 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x183 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x1A5 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0xB37 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD SWAP2 ISZERO ISZERO DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x1C5 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x1CE PUSH2 0xB4C JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH1 0x20 DUP1 DUP3 MSTORE DUP4 MLOAD DUP2 DUP4 ADD MSTORE DUP4 MLOAD SWAP2 SWAP3 DUP4 SWAP3 SWAP1 DUP4 ADD SWAP2 DUP6 DUP2 ADD SWAP2 MUL DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x20A JUMPI DUP2 DUP2 ADD MLOAD DUP4 DUP3 ADD MSTORE PUSH1 0x20 ADD PUSH2 0x1F2 JUMP JUMPDEST POP POP POP POP SWAP1 POP ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x22A JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xC0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0xBBC JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x2D2 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO PUSH2 0x367 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x19 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F414C52454144595F415554484F52495A454400000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 DUP1 DUP4 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND DUP4 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD SWAP3 DUP4 ADD DUP2 SSTORE DUP4 MSTORE PUSH32 0x405787FA12A823E0F2B7631CC41B3BA8828B3321CA811111FA75CD3AA3BB5ACE SWAP1 SWAP2 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND DUP5 OR SWAP1 SSTORE MLOAD CALLER SWAP3 SWAP2 PUSH32 0x3147867C59D17E8FA9D522465651D44AAE0A9E38F902F3475B97E58072F0ED4C SWAP2 LOG3 POP JUMP JUMPDEST PUSH1 0x2 DUP1 SLOAD DUP3 SWAP1 DUP2 LT PUSH2 0x446 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP DUP2 JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x4F4 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0x58A JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST POP PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 DUP2 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE JUMPDEST PUSH1 0x2 SLOAD DUP2 LT ISZERO PUSH2 0x71F JUMPI DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x2 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0x607 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0x717 JUMPI PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0x65F JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x2 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0x692 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0x711 SWAP1 DUP3 PUSH2 0xCA2 JUMP JUMPDEST POP PUSH2 0x71F JUMP JUMPDEST PUSH1 0x1 ADD PUSH2 0x5D7 JUMP JUMPDEST PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x808 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0x89E JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH1 0x2 SLOAD DUP2 LT PUSH2 0x90E JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x494E4445585F4F55545F4F465F424F554E445300000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x2 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0x934 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x9C2 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x1B PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x415554484F52495A45445F414444524553535F4D49534D415443480000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0xA3D JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x2 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0xA70 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x2 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0xAEF SWAP1 DUP3 PUSH2 0xCA2 JUMP JUMPDEST POP PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND DUP2 JUMP JUMPDEST PUSH1 0x60 PUSH1 0x2 DUP1 SLOAD DUP1 PUSH1 0x20 MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD DUP1 ISZERO PUSH2 0xBB1 JUMPI PUSH1 0x20 MUL DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x1 SWAP1 SWAP2 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0xB86 JUMPI JUMPDEST POP POP POP POP POP SWAP1 POP JUMPDEST SWAP1 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0xC42 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND ISZERO PUSH2 0xC9F JUMPI PUSH1 0x0 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP4 AND OR SWAP1 SSTORE JUMPDEST POP JUMP JUMPDEST DUP2 SLOAD DUP2 DUP4 SSTORE DUP2 DUP2 GT ISZERO PUSH2 0xCC6 JUMPI PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 SWAP1 KECCAK256 PUSH2 0xCC6 SWAP2 DUP2 ADD SWAP1 DUP4 ADD PUSH2 0xCCB JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH2 0xBB9 SWAP2 SWAP1 JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0xCE5 JUMPI PUSH1 0x0 DUP2 SSTORE PUSH1 0x1 ADD PUSH2 0xCD1 JUMP JUMPDEST POP SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 CALLDATALOAD GASLIMIT SUB DUP6 0x2d BYTE PUSH23 0xB37560ABAB0E9B59A7AD6EC77B60B7B0924B402D7ABDCE GAS CREATE STOP 0x29 ", + "sourceMap": "703:2493:3:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1162:320;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;1162:320:3;;;;;;;;;1047:28;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;1047:28:3;;;;;;;;;;;;;;;;;;;;;;;;1596:547;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;1596:547:3;;;;;;;103:20:1;;8:9:-1;5:2;;;30:1;27;20:12;5:2;103:20:1;;;;2316:643:3;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;2316:643:3;;;;;;;;;998:43;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;998:43:3;;;;;;;;;;;;;;;;;;;;;;;;;3056:138;;8:9:-1;5:2;;;30:1;27;20:12;5:2;3056:138:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:100:-1;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;3056:138:3;;;;;;;;;;;;;;;;;345:167:1;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;345:167:1;;;;;;;1162:320:3;271:5:1;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1279:18:3;;;;;;;:10;:18;;;;;;;;1278:19;1257:91;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1359:18;;;;;;;1380:4;1359:18;;;;;;;;:25;;;;;;;;1394:11;27:10:-1;;23:18;;;45:23;;1394:24:3;;;;;;;;;;;;;;1433:42;1464:10;;1359:18;1433:42;;;1162:320;:::o;1047:28::-;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1047:28:3;:::o;1596:547::-;1831:9;271:5:1;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1715:18:3;;;;;;;:10;:18;;;;;;;;1694:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1798:18:3;;;;;;;:10;:18;;;;;1791:25;;;;;;1826:252;1850:11;:18;1846:22;;1826:252;;;1911:6;1893:24;;:11;1905:1;1893:14;;;;;;;;;;;;;;;;;;;;;;:24;1889:179;;;1954:11;1966:18;;:22;;;;1954:35;;;;;;;;;;;;;;;;1937:11;:14;;1954:35;;;;;1949:1;;1937:14;;;;;;;;;;;;;;;:52;;;;;;;;;;;;;;;2007:11;:23;;;;;;;;;:::i;:::-;;2048:5;;1889:179;1870:3;;1826:252;;;2092:44;;2125:10;;2092:44;;;;;;;;;1596:547;;:::o;103:20:1:-;;;;;;:::o;2316:643:3:-;271:5:1;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2479:18:3;;;;;;;:10;:18;;;;;;;;2458:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2583:11;:18;2575:26;;2554:92;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2699:6;2677:28;;:11;2689:5;2677:18;;;;;;;;;;;;;;;;;;;;;;:28;2656:102;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2776:18;;;;;;;:10;:18;;;;;2769:25;;;;;;2825:11;2837:18;;:22;;;;2825:35;;;;;;;;;;;;;;;;2804:11;:18;;2825:35;;;;;2816:5;;2804:18;;;;;;;;;;;;;;;:56;;;;;;;;;;;;;;;2870:11;:23;;;;;;;;;:::i;:::-;-1:-1:-1;2908:44:3;;2941:10;;2908:44;;;;;;;;;2316:643;;:::o;998:43::-;;;;;;;;;;;;;;;:::o;3056:138::-;3137:9;3176:11;3169:18;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3056:138;;:::o;345:167:1:-;271:5;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;441:22;;;;437:69;;479:5;:16;;;;;;;;;;437:69;345:167;:::o;703:2493:3:-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;" + } + } + }, + "sources": { + "asset-proxy/MixinAuthorizable.sol": { + "id": 3 + }, + "@0x/contracts-utils/contracts/utils/Ownable.sol": { + "id": 1 + }, + "@0x/contracts-utils/contracts/utils/interfaces/IOwnable.sol": { + "id": 2 + }, + "asset-proxy/mixins/MAuthorizable.sol": { + "id": 4 + }, + "@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAuthorizable.sol": { + "id": 0 + } + }, + "sourceCodes": { + "asset-proxy/MixinAuthorizable.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-utils/contracts/utils/Ownable.sol\";\nimport \"./mixins/MAuthorizable.sol\";\n\n\ncontract MixinAuthorizable is\n Ownable,\n MAuthorizable\n{\n /// @dev Only authorized addresses can invoke functions with this modifier.\n modifier onlyAuthorized {\n require(\n authorized[msg.sender],\n \"SENDER_NOT_AUTHORIZED\"\n );\n _;\n }\n\n mapping (address => bool) public authorized;\n address[] public authorities;\n\n /// @dev Authorizes an address.\n /// @param target Address to authorize.\n function addAuthorizedAddress(address target)\n external\n onlyOwner\n {\n require(\n !authorized[target],\n \"TARGET_ALREADY_AUTHORIZED\"\n );\n\n authorized[target] = true;\n authorities.push(target);\n emit AuthorizedAddressAdded(target, msg.sender);\n }\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n function removeAuthorizedAddress(address target)\n external\n onlyOwner\n {\n require(\n authorized[target],\n \"TARGET_NOT_AUTHORIZED\"\n );\n\n delete authorized[target];\n for (uint256 i = 0; i < authorities.length; i++) {\n if (authorities[i] == target) {\n authorities[i] = authorities[authorities.length - 1];\n authorities.length -= 1;\n break;\n }\n }\n emit AuthorizedAddressRemoved(target, msg.sender);\n }\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n /// @param index Index of target in authorities array.\n function removeAuthorizedAddressAtIndex(\n address target,\n uint256 index\n )\n external\n onlyOwner\n {\n require(\n authorized[target],\n \"TARGET_NOT_AUTHORIZED\"\n );\n require(\n index < authorities.length,\n \"INDEX_OUT_OF_BOUNDS\"\n );\n require(\n authorities[index] == target,\n \"AUTHORIZED_ADDRESS_MISMATCH\"\n );\n\n delete authorized[target];\n authorities[index] = authorities[authorities.length - 1];\n authorities.length -= 1;\n emit AuthorizedAddressRemoved(target, msg.sender);\n }\n\n /// @dev Gets all authorized addresses.\n /// @return Array of authorized addresses.\n function getAuthorizedAddresses()\n external\n view\n returns (address[] memory)\n {\n return authorities;\n }\n}\n", + "@0x/contracts-utils/contracts/utils/Ownable.sol": "pragma solidity ^0.4.24;\n\nimport \"./interfaces/IOwnable.sol\";\n\n\ncontract Ownable is\n IOwnable\n{\n address public owner;\n\n constructor ()\n public\n {\n owner = msg.sender;\n }\n\n modifier onlyOwner() {\n require(\n msg.sender == owner,\n \"ONLY_CONTRACT_OWNER\"\n );\n _;\n }\n\n function transferOwnership(address newOwner)\n public\n onlyOwner\n {\n if (newOwner != address(0)) {\n owner = newOwner;\n }\n }\n}\n", + "@0x/contracts-utils/contracts/utils/interfaces/IOwnable.sol": "pragma solidity ^0.4.24;\n\n\ncontract IOwnable {\n\n function transferOwnership(address newOwner)\n public;\n}\n", + "asset-proxy/mixins/MAuthorizable.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAuthorizable.sol\";\n\n\ncontract MAuthorizable is\n IAuthorizable\n{\n // Event logged when a new address is authorized.\n event AuthorizedAddressAdded(\n address indexed target,\n address indexed caller\n );\n\n // Event logged when a currently authorized address is unauthorized.\n event AuthorizedAddressRemoved(\n address indexed target,\n address indexed caller\n );\n\n /// @dev Only authorized addresses can invoke functions with this modifier.\n modifier onlyAuthorized { revert(); _; }\n}\n", + "@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAuthorizable.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-utils/contracts/utils/interfaces/IOwnable.sol\";\n\n\ncontract IAuthorizable is\n IOwnable\n{\n /// @dev Authorizes an address.\n /// @param target Address to authorize.\n function addAuthorizedAddress(address target)\n external;\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n function removeAuthorizedAddress(address target)\n external;\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n /// @param index Index of target in authorities array.\n function removeAuthorizedAddressAtIndex(\n address target,\n uint256 index\n )\n external;\n \n /// @dev Gets all authorized addresses.\n /// @return Array of authorized addresses.\n function getAuthorizedAddresses()\n external\n view\n returns (address[] memory);\n}\n" + }, + "sourceTreeHashHex": "0xc6a5b47a7ee10c994820c81959cd8554163f9fe5a24750eebff9c205c8095446", + "compiler": { + "name": "solc", + "version": "soljson-v0.4.25+commit.59dbf8f1.js", + "settings": { + "optimizer": { + "enabled": true, + "runs": 1000000 + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + } + } + }, + "networks": {} +} diff --git a/contracts/asset-proxy/generated-artifacts/MultiAssetProxy.json b/contracts/asset-proxy/generated-artifacts/MultiAssetProxy.json new file mode 100644 index 000000000..8e23901ba --- /dev/null +++ b/contracts/asset-proxy/generated-artifacts/MultiAssetProxy.json @@ -0,0 +1,337 @@ +{ + "schemaVersion": "2.0.0", + "contractName": "MultiAssetProxy", + "compilerOutput": { + "abi": [ + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "bytes4" + } + ], + "name": "assetProxies", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "addAuthorizedAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "authorities", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "assetProxyId", + "type": "bytes4" + } + ], + "name": "getAssetProxy", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + } + ], + "name": "removeAuthorizedAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "target", + "type": "address" + }, + { + "name": "index", + "type": "uint256" + } + ], + "name": "removeAuthorizedAddressAtIndex", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getProxyId", + "outputs": [ + { + "name": "", + "type": "bytes4" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "authorized", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "assetProxy", + "type": "address" + } + ], + "name": "registerAssetProxy", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getAuthorizedAddresses", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "payable": false, + "stateMutability": "nonpayable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "target", + "type": "address" + }, + { + "indexed": true, + "name": "caller", + "type": "address" + } + ], + "name": "AuthorizedAddressAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "target", + "type": "address" + }, + { + "indexed": true, + "name": "caller", + "type": "address" + } + ], + "name": "AuthorizedAddressRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "id", + "type": "bytes4" + }, + { + "indexed": false, + "name": "assetProxy", + "type": "address" + } + ], + "name": "AssetProxyRegistered", + "type": "event" + } + ], + "evm": { + "bytecode": { + "linkReferences": {}, + "object": "0x608060405260008054600160a060020a03191633179055611527806100256000396000f3006080604052600436106100b95763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416633fd3c997811461048457806342f1181e146104e7578063494503d414610517578063607041081461052f57806370712939146105695780638da5cb5b146105975780639ad26744146105ac578063ae25532e146105dd578063b918161114610627578063c585bb9314610669578063d39de6e914610697578063f2fde38b146106fc575b3480156100c557600080fd5b507fffffffff00000000000000000000000000000000000000000000000000000000600035167fa85e59e40000000000000000000000000000000000000000000000000000000081141561047f573360005260026020526040600020541515610199577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c1553454e4445525f4e4f545f415554484f52495a454400000000000000604052600060605260646000fd5b600435602881013560488201356048820183016020810335604883018501602081033580830315610235577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c0f4c454e4754485f4d49534d4154434800000000000000000000000000604052600060605260646000fd5b60646000803760806004526064356020840260008060005b8381101561047c5780890135858102818782041487151715156102db577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c1055494e543235365f4f564552464c4f57000000000000000000000000604052600060605260646000fd5b8060645282890135606881018d018f01602081038035600481101561038a577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c1e4c454e4754485f475245415445525f5448414e5f335f5245515549526040527f454400000000000000000000000000000000000000000000000000000000000060605260646000fd5b7fffffffff00000000000000000000000000000000000000000000000000000000833516898103156103cb5780995089608452600160a45260406084205498505b881515610443577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c1a41535345545f50524f58595f444f45535f4e4f545f45584953540000604052600060605260646000fd5b60208201836084376000808360a4016000808d5af1801515610469573d6000803e3d6000fd5b505050505050505060208101905061024d565b50005b600080fd5b34801561049057600080fd5b506104be7fffffffff000000000000000000000000000000000000000000000000000000006004351661072a565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b3480156104f357600080fd5b5061051573ffffffffffffffffffffffffffffffffffffffff60043516610752565b005b34801561052357600080fd5b506104be60043561093e565b34801561053b57600080fd5b506104be7fffffffff0000000000000000000000000000000000000000000000000000000060043516610973565b34801561057557600080fd5b5061051573ffffffffffffffffffffffffffffffffffffffff600435166109bd565b3480156105a357600080fd5b506104be610cb6565b3480156105b857600080fd5b5061051573ffffffffffffffffffffffffffffffffffffffff60043516602435610cd2565b3480156105e957600080fd5b506105f2611087565b604080517fffffffff000000000000000000000000000000000000000000000000000000009092168252519081900360200190f35b34801561063357600080fd5b5061065573ffffffffffffffffffffffffffffffffffffffff600435166110bd565b604080519115158252519081900360200190f35b34801561067557600080fd5b5061051573ffffffffffffffffffffffffffffffffffffffff600435166110d2565b3480156106a357600080fd5b506106ac61135f565b60408051602080825283518183015283519192839290830191858101910280838360005b838110156106e85781810151838201526020016106d0565b505050509050019250505060405180910390f35b34801561070857600080fd5b5061051573ffffffffffffffffffffffffffffffffffffffff600435166113ce565b60016020526000908152604090205473ffffffffffffffffffffffffffffffffffffffff1681565b60005473ffffffffffffffffffffffffffffffffffffffff1633146107d857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811660009081526002602052604090205460ff161561086d57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f5441524745545f414c52454144595f415554484f52495a454400000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811660008181526002602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915560038054918201815583527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0180547fffffffffffffffffffffffff00000000000000000000000000000000000000001684179055513392917f3147867c59d17e8fa9d522465651d44aae0a9e38f902f3475b97e58072f0ed4c91a350565b600380548290811061094c57fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16905081565b7fffffffff000000000000000000000000000000000000000000000000000000001660009081526001602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b6000805473ffffffffffffffffffffffffffffffffffffffff163314610a4457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526002602052604090205460ff161515610ada57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b5073ffffffffffffffffffffffffffffffffffffffff8116600090815260026020526040812080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690555b600354811015610c6f578173ffffffffffffffffffffffffffffffffffffffff16600382815481101515610b5757fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff161415610c6757600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101908110610baf57fe5b6000918252602090912001546003805473ffffffffffffffffffffffffffffffffffffffff9092169183908110610be257fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190610c6190826114b4565b50610c6f565b600101610b27565b604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b60005473ffffffffffffffffffffffffffffffffffffffff163314610d5857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526002602052604090205460ff161515610dee57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b6003548110610e5e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f494e4445585f4f55545f4f465f424f554e445300000000000000000000000000604482015290519081900360640190fd5b8173ffffffffffffffffffffffffffffffffffffffff16600382815481101515610e8457fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff1614610f1257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f415554484f52495a45445f414444524553535f4d49534d415443480000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260026020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101908110610f8d57fe5b6000918252602090912001546003805473ffffffffffffffffffffffffffffffffffffffff9092169183908110610fc057fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff019061103f90826114b4565b50604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b604080517f4d756c746941737365742875696e743235365b5d2c62797465735b5d290000008152905190819003601d0190205b90565b60026020526000908152604090205460ff1681565b600080548190819073ffffffffffffffffffffffffffffffffffffffff16331461115d57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b8392508273ffffffffffffffffffffffffffffffffffffffff1663ae25532e6040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b1580156111c457600080fd5b505af11580156111d8573d6000803e3d6000fd5b505050506040513d60208110156111ee57600080fd5b50517fffffffff00000000000000000000000000000000000000000000000000000000811660009081526001602052604090205490925073ffffffffffffffffffffffffffffffffffffffff16905080156112aa57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f41535345545f50524f58595f414c52454144595f455849535453000000000000604482015290519081900360640190fd5b7fffffffff00000000000000000000000000000000000000000000000000000000821660008181526001602090815260409182902080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff88811691909117909155825193845287169083015280517fd2c6b762299c609bdb96520b58a49bfb80186934d4f71a86a367571a15c031949281900390910190a150505050565b606060038054806020026020016040519081016040528092919081815260200182805480156113c457602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311611399575b5050505050905090565b60005473ffffffffffffffffffffffffffffffffffffffff16331461145457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116156114b157600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b8154818355818111156114d8576000838152602090206114d89181019083016114dd565b505050565b6110ba91905b808211156114f757600081556001016114e3565b50905600a165627a7a72305820c5bd5d3519e2ad8575c212b982251f650dfb03d49222c78e0abb1f7a5ad7c1800029", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 SLOAD PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB NOT AND CALLER OR SWAP1 SSTORE PUSH2 0x1527 DUP1 PUSH2 0x25 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN STOP PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0xB9 JUMPI PUSH4 0xFFFFFFFF PUSH29 0x100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 CALLDATALOAD DIV AND PUSH4 0x3FD3C997 DUP2 EQ PUSH2 0x484 JUMPI DUP1 PUSH4 0x42F1181E EQ PUSH2 0x4E7 JUMPI DUP1 PUSH4 0x494503D4 EQ PUSH2 0x517 JUMPI DUP1 PUSH4 0x60704108 EQ PUSH2 0x52F JUMPI DUP1 PUSH4 0x70712939 EQ PUSH2 0x569 JUMPI DUP1 PUSH4 0x8DA5CB5B EQ PUSH2 0x597 JUMPI DUP1 PUSH4 0x9AD26744 EQ PUSH2 0x5AC JUMPI DUP1 PUSH4 0xAE25532E EQ PUSH2 0x5DD JUMPI DUP1 PUSH4 0xB9181611 EQ PUSH2 0x627 JUMPI DUP1 PUSH4 0xC585BB93 EQ PUSH2 0x669 JUMPI DUP1 PUSH4 0xD39DE6E9 EQ PUSH2 0x697 JUMPI DUP1 PUSH4 0xF2FDE38B EQ PUSH2 0x6FC JUMPI JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0xC5 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 PUSH1 0x0 CALLDATALOAD AND PUSH32 0xA85E59E400000000000000000000000000000000000000000000000000000000 DUP2 EQ ISZERO PUSH2 0x47F JUMPI CALLER PUSH1 0x0 MSTORE PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x40 PUSH1 0x0 KECCAK256 SLOAD ISZERO ISZERO PUSH2 0x199 JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0x1553454E4445525F4E4F545F415554484F52495A454400000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x4 CALLDATALOAD PUSH1 0x28 DUP2 ADD CALLDATALOAD PUSH1 0x48 DUP3 ADD CALLDATALOAD PUSH1 0x48 DUP3 ADD DUP4 ADD PUSH1 0x20 DUP2 SUB CALLDATALOAD PUSH1 0x48 DUP4 ADD DUP6 ADD PUSH1 0x20 DUP2 SUB CALLDATALOAD DUP1 DUP4 SUB ISZERO PUSH2 0x235 JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0xF4C454E4754485F4D49534D4154434800000000000000000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x64 PUSH1 0x0 DUP1 CALLDATACOPY PUSH1 0x80 PUSH1 0x4 MSTORE PUSH1 0x64 CALLDATALOAD PUSH1 0x20 DUP5 MUL PUSH1 0x0 DUP1 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x47C JUMPI DUP1 DUP10 ADD CALLDATALOAD DUP6 DUP2 MUL DUP2 DUP8 DUP3 DIV EQ DUP8 ISZERO OR ISZERO ISZERO PUSH2 0x2DB JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0x1055494E543235365F4F564552464C4F57000000000000000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST DUP1 PUSH1 0x64 MSTORE DUP3 DUP10 ADD CALLDATALOAD PUSH1 0x68 DUP2 ADD DUP14 ADD DUP16 ADD PUSH1 0x20 DUP2 SUB DUP1 CALLDATALOAD PUSH1 0x4 DUP2 LT ISZERO PUSH2 0x38A JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0x1E4C454E4754485F475245415445525F5448414E5F335F524551554952 PUSH1 0x40 MSTORE PUSH32 0x4544000000000000000000000000000000000000000000000000000000000000 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 DUP4 CALLDATALOAD AND DUP10 DUP2 SUB ISZERO PUSH2 0x3CB JUMPI DUP1 SWAP10 POP DUP10 PUSH1 0x84 MSTORE PUSH1 0x1 PUSH1 0xA4 MSTORE PUSH1 0x40 PUSH1 0x84 KECCAK256 SLOAD SWAP9 POP JUMPDEST DUP9 ISZERO ISZERO PUSH2 0x443 JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0x1A41535345545F50524F58595F444F45535F4E4F545F45584953540000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x20 DUP3 ADD DUP4 PUSH1 0x84 CALLDATACOPY PUSH1 0x0 DUP1 DUP4 PUSH1 0xA4 ADD PUSH1 0x0 DUP1 DUP14 GAS CALL DUP1 ISZERO ISZERO PUSH2 0x469 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP POP POP POP POP PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x24D JUMP JUMPDEST POP STOP JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x490 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x4BE PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 PUSH1 0x4 CALLDATALOAD AND PUSH2 0x72A JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x4F3 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x515 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x752 JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x523 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x4BE PUSH1 0x4 CALLDATALOAD PUSH2 0x93E JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x53B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x4BE PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 PUSH1 0x4 CALLDATALOAD AND PUSH2 0x973 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x575 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x515 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x9BD JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x5A3 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x4BE PUSH2 0xCB6 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x5B8 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x515 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH1 0x24 CALLDATALOAD PUSH2 0xCD2 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x5E9 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x5F2 PUSH2 0x1087 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x633 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x655 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x10BD JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD SWAP2 ISZERO ISZERO DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x675 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x515 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x10D2 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x6A3 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x6AC PUSH2 0x135F JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH1 0x20 DUP1 DUP3 MSTORE DUP4 MLOAD DUP2 DUP4 ADD MSTORE DUP4 MLOAD SWAP2 SWAP3 DUP4 SWAP3 SWAP1 DUP4 ADD SWAP2 DUP6 DUP2 ADD SWAP2 MUL DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x6E8 JUMPI DUP2 DUP2 ADD MLOAD DUP4 DUP3 ADD MSTORE PUSH1 0x20 ADD PUSH2 0x6D0 JUMP JUMPDEST POP POP POP POP SWAP1 POP ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x708 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x515 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x13CE JUMP JUMPDEST PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x7D8 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO PUSH2 0x86D JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x19 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F414C52454144595F415554484F52495A454400000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x40 DUP1 DUP3 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND PUSH1 0x1 SWAP1 DUP2 OR SWAP1 SWAP2 SSTORE PUSH1 0x3 DUP1 SLOAD SWAP2 DUP3 ADD DUP2 SSTORE DUP4 MSTORE PUSH32 0xC2575A0E9E593C00F959F8C92F12DB2869C3395A3B0502D05E2516446F71F85B ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND DUP5 OR SWAP1 SSTORE MLOAD CALLER SWAP3 SWAP2 PUSH32 0x3147867C59D17E8FA9D522465651D44AAE0A9E38F902F3475B97E58072F0ED4C SWAP2 LOG3 POP JUMP JUMPDEST PUSH1 0x3 DUP1 SLOAD DUP3 SWAP1 DUP2 LT PUSH2 0x94C JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP DUP2 JUMP JUMPDEST PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0xA44 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0xADA JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST POP PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x40 DUP2 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE JUMPDEST PUSH1 0x3 SLOAD DUP2 LT ISZERO PUSH2 0xC6F JUMPI DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x3 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0xB57 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0xC67 JUMPI PUSH1 0x3 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0xBAF JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x3 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0xBE2 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x3 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0xC61 SWAP1 DUP3 PUSH2 0x14B4 JUMP JUMPDEST POP PUSH2 0xC6F JUMP JUMPDEST PUSH1 0x1 ADD PUSH2 0xB27 JUMP JUMPDEST PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0xD58 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0xDEE JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH1 0x3 SLOAD DUP2 LT PUSH2 0xE5E JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x494E4445585F4F55545F4F465F424F554E445300000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x3 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0xE84 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0xF12 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x1B PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x415554484F52495A45445F414444524553535F4D49534D415443480000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE PUSH1 0x3 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0xF8D JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x3 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0xFC0 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x3 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0x103F SWAP1 DUP3 PUSH2 0x14B4 JUMP JUMPDEST POP PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH32 0x4D756C746941737365742875696E743235365B5D2C62797465735B5D29000000 DUP2 MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x1D ADD SWAP1 KECCAK256 JUMPDEST SWAP1 JUMP JUMPDEST PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND DUP2 JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD DUP2 SWAP1 DUP2 SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x115D JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST DUP4 SWAP3 POP DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0xAE25532E PUSH1 0x40 MLOAD DUP2 PUSH4 0xFFFFFFFF AND PUSH29 0x100000000000000000000000000000000000000000000000000000000 MUL DUP2 MSTORE PUSH1 0x4 ADD PUSH1 0x20 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x11C4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0x11D8 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x11EE JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP MLOAD PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD SWAP1 SWAP3 POP PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP DUP1 ISZERO PUSH2 0x12AA JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x1A PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x41535345545F50524F58595F414C52454144595F455849535453000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 DUP3 AND PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 SWAP1 DUP2 MSTORE PUSH1 0x40 SWAP2 DUP3 SWAP1 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP9 DUP2 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SWAP2 SSTORE DUP3 MLOAD SWAP4 DUP5 MSTORE DUP8 AND SWAP1 DUP4 ADD MSTORE DUP1 MLOAD PUSH32 0xD2C6B762299C609BDB96520B58A49BFB80186934D4F71A86A367571A15C03194 SWAP3 DUP2 SWAP1 SUB SWAP1 SWAP2 ADD SWAP1 LOG1 POP POP POP POP JUMP JUMPDEST PUSH1 0x60 PUSH1 0x3 DUP1 SLOAD DUP1 PUSH1 0x20 MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD DUP1 ISZERO PUSH2 0x13C4 JUMPI PUSH1 0x20 MUL DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x1 SWAP1 SWAP2 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x1399 JUMPI JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x1454 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND ISZERO PUSH2 0x14B1 JUMPI PUSH1 0x0 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP4 AND OR SWAP1 SSTORE JUMPDEST POP JUMP JUMPDEST DUP2 SLOAD DUP2 DUP4 SSTORE DUP2 DUP2 GT ISZERO PUSH2 0x14D8 JUMPI PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 SWAP1 KECCAK256 PUSH2 0x14D8 SWAP2 DUP2 ADD SWAP1 DUP4 ADD PUSH2 0x14DD JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH2 0x10BA SWAP2 SWAP1 JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0x14F7 JUMPI PUSH1 0x0 DUP2 SSTORE PUSH1 0x1 ADD PUSH2 0x14E3 JUMP JUMPDEST POP SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 0xc5 0xbd 0x5d CALLDATALOAD NOT 0xe2 0xad DUP6 PUSH22 0xC212B982251F650DFB03D49222C78E0ABB1F7A5AD7C1 DUP1 STOP 0x29 ", + "sourceMap": "683:15453:9:-;;;174:5:3;:18;;-1:-1:-1;;;;;;174:18:3;182:10;174:18;;;683:15453:9;;;;;;" + }, + "deployedBytecode": { + "linkReferences": {}, + "object": "0x6080604052600436106100b95763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416633fd3c997811461048457806342f1181e146104e7578063494503d414610517578063607041081461052f57806370712939146105695780638da5cb5b146105975780639ad26744146105ac578063ae25532e146105dd578063b918161114610627578063c585bb9314610669578063d39de6e914610697578063f2fde38b146106fc575b3480156100c557600080fd5b507fffffffff00000000000000000000000000000000000000000000000000000000600035167fa85e59e40000000000000000000000000000000000000000000000000000000081141561047f573360005260026020526040600020541515610199577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c1553454e4445525f4e4f545f415554484f52495a454400000000000000604052600060605260646000fd5b600435602881013560488201356048820183016020810335604883018501602081033580830315610235577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c0f4c454e4754485f4d49534d4154434800000000000000000000000000604052600060605260646000fd5b60646000803760806004526064356020840260008060005b8381101561047c5780890135858102818782041487151715156102db577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c1055494e543235365f4f564552464c4f57000000000000000000000000604052600060605260646000fd5b8060645282890135606881018d018f01602081038035600481101561038a577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c1e4c454e4754485f475245415445525f5448414e5f335f5245515549526040527f454400000000000000000000000000000000000000000000000000000000000060605260646000fd5b7fffffffff00000000000000000000000000000000000000000000000000000000833516898103156103cb5780995089608452600160a45260406084205498505b881515610443577f08c379a0000000000000000000000000000000000000000000000000000000006000527c20000000000000000000000000000000000000000000000000000000006020527c1a41535345545f50524f58595f444f45535f4e4f545f45584953540000604052600060605260646000fd5b60208201836084376000808360a4016000808d5af1801515610469573d6000803e3d6000fd5b505050505050505060208101905061024d565b50005b600080fd5b34801561049057600080fd5b506104be7fffffffff000000000000000000000000000000000000000000000000000000006004351661072a565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b3480156104f357600080fd5b5061051573ffffffffffffffffffffffffffffffffffffffff60043516610752565b005b34801561052357600080fd5b506104be60043561093e565b34801561053b57600080fd5b506104be7fffffffff0000000000000000000000000000000000000000000000000000000060043516610973565b34801561057557600080fd5b5061051573ffffffffffffffffffffffffffffffffffffffff600435166109bd565b3480156105a357600080fd5b506104be610cb6565b3480156105b857600080fd5b5061051573ffffffffffffffffffffffffffffffffffffffff60043516602435610cd2565b3480156105e957600080fd5b506105f2611087565b604080517fffffffff000000000000000000000000000000000000000000000000000000009092168252519081900360200190f35b34801561063357600080fd5b5061065573ffffffffffffffffffffffffffffffffffffffff600435166110bd565b604080519115158252519081900360200190f35b34801561067557600080fd5b5061051573ffffffffffffffffffffffffffffffffffffffff600435166110d2565b3480156106a357600080fd5b506106ac61135f565b60408051602080825283518183015283519192839290830191858101910280838360005b838110156106e85781810151838201526020016106d0565b505050509050019250505060405180910390f35b34801561070857600080fd5b5061051573ffffffffffffffffffffffffffffffffffffffff600435166113ce565b60016020526000908152604090205473ffffffffffffffffffffffffffffffffffffffff1681565b60005473ffffffffffffffffffffffffffffffffffffffff1633146107d857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811660009081526002602052604090205460ff161561086d57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f5441524745545f414c52454144595f415554484f52495a454400000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff811660008181526002602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915560038054918201815583527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0180547fffffffffffffffffffffffff00000000000000000000000000000000000000001684179055513392917f3147867c59d17e8fa9d522465651d44aae0a9e38f902f3475b97e58072f0ed4c91a350565b600380548290811061094c57fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff16905081565b7fffffffff000000000000000000000000000000000000000000000000000000001660009081526001602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b6000805473ffffffffffffffffffffffffffffffffffffffff163314610a4457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526002602052604090205460ff161515610ada57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b5073ffffffffffffffffffffffffffffffffffffffff8116600090815260026020526040812080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690555b600354811015610c6f578173ffffffffffffffffffffffffffffffffffffffff16600382815481101515610b5757fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff161415610c6757600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101908110610baf57fe5b6000918252602090912001546003805473ffffffffffffffffffffffffffffffffffffffff9092169183908110610be257fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190610c6190826114b4565b50610c6f565b600101610b27565b604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b60005473ffffffffffffffffffffffffffffffffffffffff1681565b60005473ffffffffffffffffffffffffffffffffffffffff163314610d5857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526002602052604090205460ff161515610dee57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f5441524745545f4e4f545f415554484f52495a45440000000000000000000000604482015290519081900360640190fd5b6003548110610e5e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f494e4445585f4f55545f4f465f424f554e445300000000000000000000000000604482015290519081900360640190fd5b8173ffffffffffffffffffffffffffffffffffffffff16600382815481101515610e8457fe5b60009182526020909120015473ffffffffffffffffffffffffffffffffffffffff1614610f1257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f415554484f52495a45445f414444524553535f4d49534d415443480000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260026020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8101908110610f8d57fe5b6000918252602090912001546003805473ffffffffffffffffffffffffffffffffffffffff9092169183908110610fc057fe5b600091825260209091200180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff019061103f90826114b4565b50604051339073ffffffffffffffffffffffffffffffffffffffff8416907f1f32c1b084e2de0713b8fb16bd46bb9df710a3dbeae2f3ca93af46e016dcc6b090600090a35050565b604080517f4d756c746941737365742875696e743235365b5d2c62797465735b5d290000008152905190819003601d0190205b90565b60026020526000908152604090205460ff1681565b600080548190819073ffffffffffffffffffffffffffffffffffffffff16331461115d57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b8392508273ffffffffffffffffffffffffffffffffffffffff1663ae25532e6040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b1580156111c457600080fd5b505af11580156111d8573d6000803e3d6000fd5b505050506040513d60208110156111ee57600080fd5b50517fffffffff00000000000000000000000000000000000000000000000000000000811660009081526001602052604090205490925073ffffffffffffffffffffffffffffffffffffffff16905080156112aa57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f41535345545f50524f58595f414c52454144595f455849535453000000000000604482015290519081900360640190fd5b7fffffffff00000000000000000000000000000000000000000000000000000000821660008181526001602090815260409182902080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff88811691909117909155825193845287169083015280517fd2c6b762299c609bdb96520b58a49bfb80186934d4f71a86a367571a15c031949281900390910190a150505050565b606060038054806020026020016040519081016040528092919081815260200182805480156113c457602002820191906000526020600020905b815473ffffffffffffffffffffffffffffffffffffffff168152600190910190602001808311611399575b5050505050905090565b60005473ffffffffffffffffffffffffffffffffffffffff16331461145457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601360248201527f4f4e4c595f434f4e54524143545f4f574e455200000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116156114b157600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83161790555b50565b8154818355818111156114d8576000838152602090206114d89181019083016114dd565b505050565b6110ba91905b808211156114f757600081556001016114e3565b50905600a165627a7a72305820c5bd5d3519e2ad8575c212b982251f650dfb03d49222c78e0abb1f7a5ad7c1800029", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0xB9 JUMPI PUSH4 0xFFFFFFFF PUSH29 0x100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 CALLDATALOAD DIV AND PUSH4 0x3FD3C997 DUP2 EQ PUSH2 0x484 JUMPI DUP1 PUSH4 0x42F1181E EQ PUSH2 0x4E7 JUMPI DUP1 PUSH4 0x494503D4 EQ PUSH2 0x517 JUMPI DUP1 PUSH4 0x60704108 EQ PUSH2 0x52F JUMPI DUP1 PUSH4 0x70712939 EQ PUSH2 0x569 JUMPI DUP1 PUSH4 0x8DA5CB5B EQ PUSH2 0x597 JUMPI DUP1 PUSH4 0x9AD26744 EQ PUSH2 0x5AC JUMPI DUP1 PUSH4 0xAE25532E EQ PUSH2 0x5DD JUMPI DUP1 PUSH4 0xB9181611 EQ PUSH2 0x627 JUMPI DUP1 PUSH4 0xC585BB93 EQ PUSH2 0x669 JUMPI DUP1 PUSH4 0xD39DE6E9 EQ PUSH2 0x697 JUMPI DUP1 PUSH4 0xF2FDE38B EQ PUSH2 0x6FC JUMPI JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0xC5 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 PUSH1 0x0 CALLDATALOAD AND PUSH32 0xA85E59E400000000000000000000000000000000000000000000000000000000 DUP2 EQ ISZERO PUSH2 0x47F JUMPI CALLER PUSH1 0x0 MSTORE PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x40 PUSH1 0x0 KECCAK256 SLOAD ISZERO ISZERO PUSH2 0x199 JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0x1553454E4445525F4E4F545F415554484F52495A454400000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x4 CALLDATALOAD PUSH1 0x28 DUP2 ADD CALLDATALOAD PUSH1 0x48 DUP3 ADD CALLDATALOAD PUSH1 0x48 DUP3 ADD DUP4 ADD PUSH1 0x20 DUP2 SUB CALLDATALOAD PUSH1 0x48 DUP4 ADD DUP6 ADD PUSH1 0x20 DUP2 SUB CALLDATALOAD DUP1 DUP4 SUB ISZERO PUSH2 0x235 JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0xF4C454E4754485F4D49534D4154434800000000000000000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x64 PUSH1 0x0 DUP1 CALLDATACOPY PUSH1 0x80 PUSH1 0x4 MSTORE PUSH1 0x64 CALLDATALOAD PUSH1 0x20 DUP5 MUL PUSH1 0x0 DUP1 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x47C JUMPI DUP1 DUP10 ADD CALLDATALOAD DUP6 DUP2 MUL DUP2 DUP8 DUP3 DIV EQ DUP8 ISZERO OR ISZERO ISZERO PUSH2 0x2DB JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0x1055494E543235365F4F564552464C4F57000000000000000000000000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST DUP1 PUSH1 0x64 MSTORE DUP3 DUP10 ADD CALLDATALOAD PUSH1 0x68 DUP2 ADD DUP14 ADD DUP16 ADD PUSH1 0x20 DUP2 SUB DUP1 CALLDATALOAD PUSH1 0x4 DUP2 LT ISZERO PUSH2 0x38A JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0x1E4C454E4754485F475245415445525F5448414E5F335F524551554952 PUSH1 0x40 MSTORE PUSH32 0x4544000000000000000000000000000000000000000000000000000000000000 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 DUP4 CALLDATALOAD AND DUP10 DUP2 SUB ISZERO PUSH2 0x3CB JUMPI DUP1 SWAP10 POP DUP10 PUSH1 0x84 MSTORE PUSH1 0x1 PUSH1 0xA4 MSTORE PUSH1 0x40 PUSH1 0x84 KECCAK256 SLOAD SWAP9 POP JUMPDEST DUP9 ISZERO ISZERO PUSH2 0x443 JUMPI PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 PUSH1 0x0 MSTORE PUSH29 0x2000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 MSTORE PUSH29 0x1A41535345545F50524F58595F444F45535F4E4F545F45584953540000 PUSH1 0x40 MSTORE PUSH1 0x0 PUSH1 0x60 MSTORE PUSH1 0x64 PUSH1 0x0 REVERT JUMPDEST PUSH1 0x20 DUP3 ADD DUP4 PUSH1 0x84 CALLDATACOPY PUSH1 0x0 DUP1 DUP4 PUSH1 0xA4 ADD PUSH1 0x0 DUP1 DUP14 GAS CALL DUP1 ISZERO ISZERO PUSH2 0x469 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP POP POP POP POP PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x24D JUMP JUMPDEST POP STOP JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x490 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x4BE PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 PUSH1 0x4 CALLDATALOAD AND PUSH2 0x72A JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x4F3 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x515 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x752 JUMP JUMPDEST STOP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x523 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x4BE PUSH1 0x4 CALLDATALOAD PUSH2 0x93E JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x53B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x4BE PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 PUSH1 0x4 CALLDATALOAD AND PUSH2 0x973 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x575 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x515 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x9BD JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x5A3 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x4BE PUSH2 0xCB6 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x5B8 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x515 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH1 0x24 CALLDATALOAD PUSH2 0xCD2 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x5E9 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x5F2 PUSH2 0x1087 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x633 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x655 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x10BD JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD SWAP2 ISZERO ISZERO DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x675 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x515 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x10D2 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x6A3 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x6AC PUSH2 0x135F JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH1 0x20 DUP1 DUP3 MSTORE DUP4 MLOAD DUP2 DUP4 ADD MSTORE DUP4 MLOAD SWAP2 SWAP3 DUP4 SWAP3 SWAP1 DUP4 ADD SWAP2 DUP6 DUP2 ADD SWAP2 MUL DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x6E8 JUMPI DUP2 DUP2 ADD MLOAD DUP4 DUP3 ADD MSTORE PUSH1 0x20 ADD PUSH2 0x6D0 JUMP JUMPDEST POP POP POP POP SWAP1 POP ADD SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x708 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x515 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF PUSH1 0x4 CALLDATALOAD AND PUSH2 0x13CE JUMP JUMPDEST PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x7D8 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO PUSH2 0x86D JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x19 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F414C52454144595F415554484F52495A454400000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x40 DUP1 DUP3 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND PUSH1 0x1 SWAP1 DUP2 OR SWAP1 SWAP2 SSTORE PUSH1 0x3 DUP1 SLOAD SWAP2 DUP3 ADD DUP2 SSTORE DUP4 MSTORE PUSH32 0xC2575A0E9E593C00F959F8C92F12DB2869C3395A3B0502D05E2516446F71F85B ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND DUP5 OR SWAP1 SSTORE MLOAD CALLER SWAP3 SWAP2 PUSH32 0x3147867C59D17E8FA9D522465651D44AAE0A9E38F902F3475B97E58072F0ED4C SWAP2 LOG3 POP JUMP JUMPDEST PUSH1 0x3 DUP1 SLOAD DUP3 SWAP1 DUP2 LT PUSH2 0x94C JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP DUP2 JUMP JUMPDEST PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0xA44 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0xADA JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST POP PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x40 DUP2 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE JUMPDEST PUSH1 0x3 SLOAD DUP2 LT ISZERO PUSH2 0xC6F JUMPI DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x3 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0xB57 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ ISZERO PUSH2 0xC67 JUMPI PUSH1 0x3 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0xBAF JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x3 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0xBE2 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x3 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0xC61 SWAP1 DUP3 PUSH2 0x14B4 JUMP JUMPDEST POP PUSH2 0xC6F JUMP JUMPDEST PUSH1 0x1 ADD PUSH2 0xB27 JUMP JUMPDEST PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0xD58 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND ISZERO ISZERO PUSH2 0xDEE JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x15 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x5441524745545F4E4F545F415554484F52495A45440000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH1 0x3 SLOAD DUP2 LT PUSH2 0xE5E JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x494E4445585F4F55545F4F465F424F554E445300000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH1 0x3 DUP3 DUP2 SLOAD DUP2 LT ISZERO ISZERO PUSH2 0xE84 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0xF12 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x1B PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x415554484F52495A45445F414444524553535F4D49534D415443480000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 AND SWAP1 SSTORE PUSH1 0x3 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 ADD SWAP1 DUP2 LT PUSH2 0xF8D JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD SLOAD PUSH1 0x3 DUP1 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP1 SWAP3 AND SWAP2 DUP4 SWAP1 DUP2 LT PUSH2 0xFC0 JUMPI INVALID JUMPDEST PUSH1 0x0 SWAP2 DUP3 MSTORE PUSH1 0x20 SWAP1 SWAP2 KECCAK256 ADD DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SWAP3 SWAP1 SWAP3 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SSTORE PUSH1 0x3 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ADD SWAP1 PUSH2 0x103F SWAP1 DUP3 PUSH2 0x14B4 JUMP JUMPDEST POP PUSH1 0x40 MLOAD CALLER SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP5 AND SWAP1 PUSH32 0x1F32C1B084E2DE0713B8FB16BD46BB9DF710A3DBEAE2F3CA93AF46E016DCC6B0 SWAP1 PUSH1 0x0 SWAP1 LOG3 POP POP JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH32 0x4D756C746941737365742875696E743235365B5D2C62797465735B5D29000000 DUP2 MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x1D ADD SWAP1 KECCAK256 JUMPDEST SWAP1 JUMP JUMPDEST PUSH1 0x2 PUSH1 0x20 MSTORE PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD PUSH1 0xFF AND DUP2 JUMP JUMPDEST PUSH1 0x0 DUP1 SLOAD DUP2 SWAP1 DUP2 SWAP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x115D JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST DUP4 SWAP3 POP DUP3 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH4 0xAE25532E PUSH1 0x40 MLOAD DUP2 PUSH4 0xFFFFFFFF AND PUSH29 0x100000000000000000000000000000000000000000000000000000000 MUL DUP2 MSTORE PUSH1 0x4 ADD PUSH1 0x20 PUSH1 0x40 MLOAD DUP1 DUP4 SUB DUP2 PUSH1 0x0 DUP8 DUP1 EXTCODESIZE ISZERO DUP1 ISZERO PUSH2 0x11C4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP GAS CALL ISZERO DUP1 ISZERO PUSH2 0x11D8 JUMPI RETURNDATASIZE PUSH1 0x0 DUP1 RETURNDATACOPY RETURNDATASIZE PUSH1 0x0 REVERT JUMPDEST POP POP POP POP PUSH1 0x40 MLOAD RETURNDATASIZE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x11EE JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP MLOAD PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 DUP2 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD SWAP1 SWAP3 POP PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SWAP1 POP DUP1 ISZERO PUSH2 0x12AA JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x1A PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x41535345545F50524F58595F414C52454144595F455849535453000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH32 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 DUP3 AND PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 SWAP1 DUP2 MSTORE PUSH1 0x40 SWAP2 DUP3 SWAP1 KECCAK256 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP9 DUP2 AND SWAP2 SWAP1 SWAP2 OR SWAP1 SWAP2 SSTORE DUP3 MLOAD SWAP4 DUP5 MSTORE DUP8 AND SWAP1 DUP4 ADD MSTORE DUP1 MLOAD PUSH32 0xD2C6B762299C609BDB96520B58A49BFB80186934D4F71A86A367571A15C03194 SWAP3 DUP2 SWAP1 SUB SWAP1 SWAP2 ADD SWAP1 LOG1 POP POP POP POP JUMP JUMPDEST PUSH1 0x60 PUSH1 0x3 DUP1 SLOAD DUP1 PUSH1 0x20 MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD DUP1 ISZERO PUSH2 0x13C4 JUMPI PUSH1 0x20 MUL DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND DUP2 MSTORE PUSH1 0x1 SWAP1 SWAP2 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x1399 JUMPI JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST PUSH1 0x0 SLOAD PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER EQ PUSH2 0x1454 JUMPI PUSH1 0x40 DUP1 MLOAD PUSH32 0x8C379A000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE PUSH1 0x20 PUSH1 0x4 DUP3 ADD MSTORE PUSH1 0x13 PUSH1 0x24 DUP3 ADD MSTORE PUSH32 0x4F4E4C595F434F4E54524143545F4F574E455200000000000000000000000000 PUSH1 0x44 DUP3 ADD MSTORE SWAP1 MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x64 ADD SWAP1 REVERT JUMPDEST PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP2 AND ISZERO PUSH2 0x14B1 JUMPI PUSH1 0x0 DUP1 SLOAD PUSH32 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP4 AND OR SWAP1 SSTORE JUMPDEST POP JUMP JUMPDEST DUP2 SLOAD DUP2 DUP4 SSTORE DUP2 DUP2 GT ISZERO PUSH2 0x14D8 JUMPI PUSH1 0x0 DUP4 DUP2 MSTORE PUSH1 0x20 SWAP1 KECCAK256 PUSH2 0x14D8 SWAP2 DUP2 ADD SWAP1 DUP4 ADD PUSH2 0x14DD JUMP JUMPDEST POP POP POP JUMP JUMPDEST PUSH2 0x10BA SWAP2 SWAP1 JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0x14F7 JUMPI PUSH1 0x0 DUP2 SSTORE PUSH1 0x1 ADD PUSH2 0x14E3 JUMP JUMPDEST POP SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 0xc5 0xbd 0x5d CALLDATALOAD NOT 0xe2 0xad DUP6 PUSH22 0xC212B982251F650DFB03D49222C78E0ABB1F7A5AD7C1 DUP1 STOP 0x29 ", + "sourceMap": "683:15453:9:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;683:15453:9;1515:66;1511:1;1498:15;1494:88;1977:66;1967:8;1964:80;1961:2;;;2264:6;2261:1;2254:17;2299:15;2295:2;2288:27;2423:2;2420:1;2410:16;2404:23;2397:31;2394:2;;;2529:66;2526:1;2519:77;2628:66;2624:2;2617:78;2727:66;2723:2;2716:78;2826:1;2822:2;2815:13;2859:3;2856:1;2849:14;2394:2;4722:1;4709:15;6330:2;6313:15;6309:24;6296:38;6694:2;6677:15;6673:24;6660:38;7102:2;7087:13;7083:22;7066:15;7062:44;7237:2;7215:20;7211:29;7198:43;7685:2;7662:21;7658:30;7641:15;7637:52;7844:2;7814:28;7810:37;7797:51;7999:18;7987:10;7983:35;7980:2;;;8113:66;8110:1;8103:77;8212:66;8208:2;8201:78;8311:66;8307:2;8300:78;8410:1;8406:2;8399:13;8443:3;8440:1;8433:14;7980:2;8752:3;8706:1;8630;8596:226;8923:3;8920:1;8913:14;9021:3;9008:17;9156:2;9144:10;9140:19;9264:1;9300;9449;9435:6286;9458:14;9455:1;9452:21;9435:6286;;;9626:1;9604:20;9600:28;9587:42;9689:6;9673:14;9669:27;9916:14;9907:6;9894:11;9890:24;9887:44;9854:6;9847:14;9819:134;9812:142;9809:2;;;10058:66;10055:1;10048:77;10161:66;10157:2;10150:78;10264:66;10260:2;10253:78;10367:1;10363:2;10356:13;10404:3;10401:1;10394:14;9809:2;10517:11;10512:3;10505:24;10693:1;10663:28;10659:36;10646:50;11326:3;11296:28;11292:38;11269:21;11265:66;11248:15;11244:88;11492:2;11455:35;11451:44;11562:30;11549:44;11744:1;11717:25;11714:32;11711:2;;;11864:66;11861:1;11854:77;11967:66;11963:2;11956:78;12070:66;12066:2;12059:78;12173:66;12169:2;12162:78;12275:3;12272:1;12265:14;11711:2;12496:66;12434:35;12421:49;12392:192;12856:12;12835:19;12831:38;12828:2;;;12961:19;12945:35;;13223:12;13218:3;13211:25;13273:17;13268:3;13261:30;13351:2;13346:3;13336:18;13330:25;13316:39;;12828:2;13502:10;13495:18;13492:2;;;13627:66;13624:1;13617:77;13730:66;13726:2;13719:78;13833:66;13829:2;13822:78;13936:1;13932:2;13925:13;13973:3;13970:1;13963:14;13492:2;14369;14342:25;14338:34;14230:30;14136:3;14098:348;15122:1;15009;14933:25;14928:3;14924:35;14831:1;14745;14650:10;14567:3;14537:677;15338:7;15331:15;15328:2;;;15562:16;15486:1;15417;15373:256;15664:16;15661:1;15654:27;15328:2;9492:6229;;;;;;;;9487:2;9484:1;9480:10;9475:15;;9435:6286;;;-1:-1:-1;15801:12:9;1961:2;15905:1;15902;15895:12;944:51:7;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;944:51:7;;;;;;;;;;;;;;;;;;;;;;;;;;1162:320:8;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;1162:320:8;;;;;;;;;1047:28;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;1047:28:8;;;;;2060:154:7;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;2060:154:7;;;;;;;1596:547:8;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;1596:547:8;;;;;;;103:20:3;;8:9:-1;5:2;;;30:1;27;20:12;5:2;103:20:3;;;;2316:643:8;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;2316:643:8;;;;;;;;;16021:113:9;;8:9:-1;5:2;;;30:1;27;20:12;5:2;16021:113:9;;;;;;;;;;;;;;;;;;;;;;;998:43:8;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;998:43:8;;;;;;;;;;;;;;;;;;;;;;;;;1204:666:7;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;1204:666:7;;;;;;;3056:138:8;;8:9:-1;5:2;;;30:1;27;20:12;5:2;3056:138:8;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:100:-1;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;3056:138:8;;;;;;;;;;;;;;;;;345:167:3;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;345:167:3;;;;;;;944:51:7;;;;;;;;;;;;;;;:::o;1162:320:8:-;271:5:3;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1279:18:8;;;;;;;:10;:18;;;;;;;;1278:19;1257:91;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1359:18;;;;;;;:10;:18;;;;;;:25;;;;1380:4;1359:25;;;;;;1394:11;27:10:-1;;23:18;;;45:23;;1394:24:8;;;;;;;;;;;;1433:42;1464:10;;1359:18;1433:42;;;1162:320;:::o;1047:28::-;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1047:28:8;:::o;2060:154:7:-;2181:26;;2151:7;2181:26;;;:12;:26;;;;;;;;;2060:154::o;1596:547:8:-;1831:9;271:5:3;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1715:18:8;;;;;;;:10;:18;;;;;;;;1694:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1798:18:8;;;;;;;:10;:18;;;;;1791:25;;;;;;1826:252;1850:11;:18;1846:22;;1826:252;;;1911:6;1893:24;;:11;1905:1;1893:14;;;;;;;;;;;;;;;;;;;;;;:24;1889:179;;;1954:11;1966:18;;:22;;;;1954:35;;;;;;;;;;;;;;;;1937:11;:14;;1954:35;;;;;1949:1;;1937:14;;;;;;;;;;;;;;;:52;;;;;;;;;;;;;;;2007:11;:23;;;;;;;;;:::i;:::-;;2048:5;;1889:179;1870:3;;1826:252;;;2092:44;;2125:10;;2092:44;;;;;;;;;1596:547;;:::o;103:20:3:-;;;;;;:::o;2316:643:8:-;271:5:3;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2479:18:8;;;;;;;:10;:18;;;;;;;;2458:86;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2583:11;:18;2575:26;;2554:92;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2699:6;2677:28;;:11;2689:5;2677:18;;;;;;;;;;;;;;;;;;;;;;:28;2656:102;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2776:18;;;;;;;:10;:18;;;;;2769:25;;;;;;2825:11;2837:18;;:22;;;;2825:35;;;;;;;;;;;;;;;;2804:11;:18;;2825:35;;;;;2816:5;;2804:18;;;;;;;;;;;;;;;:56;;;;;;;;;;;;;;;2870:11;:23;;;;;;;;;:::i;:::-;-1:-1:-1;2908:44:8;;2941:10;;2908:44;;;;;;;;;2316:643;;:::o;16021:113:9:-;838:42;;;;;;;;;;;;;;;;16021:113;;:::o;998:43:8:-;;;;;;;;;;;;;;;:::o;1204:666:7:-;1301:30;271:5:3;;1301:30:7;;;;271:5:3;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1346:10:7;1301:56;;1452:18;:29;;;:31;;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;1452:31:7;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;1452:31:7;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;-1:-1;1452:31:7;1521:26;;;;;;;:12;1452:31;1521:26;;;;;1452:31;;-1:-1:-1;1521:26:7;;;-1:-1:-1;1578:31:7;;1557:104;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1721:26;;;;;;;:12;:26;;;;;;;;;:47;;;;;;;;;;;;;;;1783:80;;;;;;;;;;;;;;;;;;;;;;;1204:666;;;;:::o;3056:138:8:-;3137:9;3176:11;3169:18;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3056:138;:::o;345:167:3:-;271:5;;;;257:10;:19;236:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;441:22;;;;437:69;;479:5;:16;;;;;;;;;;437:69;345:167;:::o;683:15453:9:-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;" + } + } + }, + "sources": { + "asset-proxy/MultiAssetProxy.sol": { + "id": 9 + }, + "asset-proxy/MixinAssetProxyDispatcher.sol": { + "id": 7 + }, + "@0x/contracts-utils/contracts/utils/Ownable.sol": { + "id": 3 + }, + "@0x/contracts-utils/contracts/utils/interfaces/IOwnable.sol": { + "id": 4 + }, + "asset-proxy/mixins/MAssetProxyDispatcher.sol": { + "id": 10 + }, + "@0x/contracts-interfaces/contracts/protocol/Exchange/IAssetProxyDispatcher.sol": { + "id": 2 + }, + "@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAssetProxy.sol": { + "id": 0 + }, + "@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAuthorizable.sol": { + "id": 1 + }, + "asset-proxy/MixinAuthorizable.sol": { + "id": 8 + }, + "asset-proxy/mixins/MAuthorizable.sol": { + "id": 11 + } + }, + "sourceCodes": { + "asset-proxy/MultiAssetProxy.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity 0.4.24;\n\nimport \"./MixinAssetProxyDispatcher.sol\";\nimport \"./MixinAuthorizable.sol\";\n\n\ncontract MultiAssetProxy is\n MixinAssetProxyDispatcher,\n MixinAuthorizable\n{\n // Id of this proxy.\n bytes4 constant internal PROXY_ID = bytes4(keccak256(\"MultiAsset(uint256[],bytes[])\"));\n\n // solhint-disable-next-line payable-fallback\n function ()\n external\n {\n // NOTE: The below assembly assumes that clients do some input validation and that the input is properly encoded according to the AbiV2 specification.\n // It is technically possible for inputs with very large lengths and offsets to cause overflows. However, this would make the calldata prohibitively\n // expensive and we therefore do not check for overflows in these scenarios.\n assembly {\n // The first 4 bytes of calldata holds the function selector\n let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000)\n\n // `transferFrom` will be called with the following parameters:\n // assetData Encoded byte array.\n // from Address to transfer asset from.\n // to Address to transfer asset to.\n // amount Amount of asset to transfer.\n // bytes4(keccak256(\"transferFrom(bytes,address,address,uint256)\")) = 0xa85e59e4\n if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) {\n\n // To lookup a value in a mapping, we load from the storage location keccak256(k, p),\n // where k is the key left padded to 32 bytes and p is the storage slot\n mstore(0, caller)\n mstore(32, authorized_slot)\n\n // Revert if authorized[msg.sender] == false\n if iszero(sload(keccak256(0, 64))) {\n // Revert with `Error(\"SENDER_NOT_AUTHORIZED\")`\n mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)\n mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)\n mstore(64, 0x0000001553454e4445525f4e4f545f415554484f52495a454400000000000000)\n mstore(96, 0)\n revert(0, 100)\n }\n\n // `transferFrom`.\n // The function is marked `external`, so no abi decoding is done for\n // us. Instead, we expect the `calldata` memory to contain the\n // following:\n //\n // | Area | Offset | Length | Contents |\n // |----------|--------|---------|-------------------------------------|\n // | Header | 0 | 4 | function selector |\n // | Params | | 4 * 32 | function parameters: |\n // | | 4 | | 1. offset to assetData (*) |\n // | | 36 | | 2. from |\n // | | 68 | | 3. to |\n // | | 100 | | 4. amount |\n // | Data | | | assetData: |\n // | | 132 | 32 | assetData Length |\n // | | 164 | ** | assetData Contents |\n //\n // (*): offset is computed from start of function parameters, so offset\n // by an additional 4 bytes in the calldata.\n //\n // (**): see table below to compute length of assetData Contents\n //\n // WARNING: The ABIv2 specification allows additional padding between\n // the Params and Data section. This will result in a larger\n // offset to assetData.\n\n // Load offset to `assetData`\n let assetDataOffset := calldataload(4)\n\n // Asset data itself is encoded as follows:\n //\n // | Area | Offset | Length | Contents |\n // |----------|-------------|---------|-------------------------------------|\n // | Header | 0 | 4 | assetProxyId |\n // | Params | | 2 * 32 | function parameters: |\n // | | 4 | | 1. offset to amounts (*) |\n // | | 36 | | 2. offset to nestedAssetData (*) |\n // | Data | | | amounts: |\n // | | 68 | 32 | amounts Length |\n // | | 100 | a | amounts Contents | \n // | | | | nestedAssetData: |\n // | | 100 + a | 32 | nestedAssetData Length |\n // | | 132 + a | b | nestedAssetData Contents (offsets) |\n // | | 132 + a + b | | nestedAssetData[0, ..., len] |\n\n // In order to find the offset to `amounts`, we must add:\n // 4 (function selector)\n // + assetDataOffset\n // + 32 (assetData len)\n // + 4 (assetProxyId)\n let amountsOffset := calldataload(add(assetDataOffset, 40))\n\n // In order to find the offset to `nestedAssetData`, we must add:\n // 4 (function selector)\n // + assetDataOffset\n // + 32 (assetData len)\n // + 4 (assetProxyId)\n // + 32 (amounts offset)\n let nestedAssetDataOffset := calldataload(add(assetDataOffset, 72))\n\n // In order to find the start of the `amounts` contents, we must add: \n // 4 (function selector) \n // + assetDataOffset \n // + 32 (assetData len)\n // + 4 (assetProxyId)\n // + amountsOffset\n // + 32 (amounts len)\n let amountsContentsStart := add(assetDataOffset, add(amountsOffset, 72))\n\n // Load number of elements in `amounts`\n let amountsLen := calldataload(sub(amountsContentsStart, 32))\n\n // In order to find the start of the `nestedAssetData` contents, we must add: \n // 4 (function selector) \n // + assetDataOffset \n // + 32 (assetData len)\n // + 4 (assetProxyId)\n // + nestedAssetDataOffset\n // + 32 (nestedAssetData len)\n let nestedAssetDataContentsStart := add(assetDataOffset, add(nestedAssetDataOffset, 72))\n\n // Load number of elements in `nestedAssetData`\n let nestedAssetDataLen := calldataload(sub(nestedAssetDataContentsStart, 32))\n\n // Revert if number of elements in `amounts` differs from number of elements in `nestedAssetData`\n if sub(amountsLen, nestedAssetDataLen) {\n // Revert with `Error(\"LENGTH_MISMATCH\")`\n mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)\n mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)\n mstore(64, 0x0000000f4c454e4754485f4d49534d4154434800000000000000000000000000)\n mstore(96, 0)\n revert(0, 100)\n }\n\n // Copy `transferFrom` selector, offset to `assetData`, `from`, and `to` from calldata to memory\n calldatacopy(\n 0, // memory can safely be overwritten from beginning\n 0, // start of calldata\n 100 // length of selector (4) and 3 params (32 * 3)\n )\n\n // Overwrite existing offset to `assetData` with our own\n mstore(4, 128)\n \n // Load `amount`\n let amount := calldataload(100)\n \n // Calculate number of bytes in `amounts` contents\n let amountsByteLen := mul(amountsLen, 32)\n\n // Initialize `assetProxyId` and `assetProxy` to 0\n let assetProxyId := 0\n let assetProxy := 0\n\n // Loop through `amounts` and `nestedAssetData`, calling `transferFrom` for each respective element\n for {let i := 0} lt(i, amountsByteLen) {i := add(i, 32)} {\n\n // Calculate the total amount\n let amountsElement := calldataload(add(amountsContentsStart, i))\n let totalAmount := mul(amountsElement, amount)\n\n // Revert if `amount` != 0 and multiplication resulted in an overflow \n if iszero(or(\n iszero(amount),\n eq(div(totalAmount, amount), amountsElement)\n )) {\n // Revert with `Error(\"UINT256_OVERFLOW\")`\n mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)\n mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)\n mstore(64, 0x0000001055494e543235365f4f564552464c4f57000000000000000000000000)\n mstore(96, 0)\n revert(0, 100)\n }\n\n // Write `totalAmount` to memory\n mstore(100, totalAmount)\n\n // Load offset to `nestedAssetData[i]`\n let nestedAssetDataElementOffset := calldataload(add(nestedAssetDataContentsStart, i))\n\n // In order to find the start of the `nestedAssetData[i]` contents, we must add:\n // 4 (function selector) \n // + assetDataOffset \n // + 32 (assetData len)\n // + 4 (assetProxyId)\n // + nestedAssetDataOffset\n // + 32 (nestedAssetData len)\n // + nestedAssetDataElementOffset\n // + 32 (nestedAssetDataElement len)\n let nestedAssetDataElementContentsStart := add(assetDataOffset, add(nestedAssetDataOffset, add(nestedAssetDataElementOffset, 104)))\n\n // Load length of `nestedAssetData[i]`\n let nestedAssetDataElementLenStart := sub(nestedAssetDataElementContentsStart, 32)\n let nestedAssetDataElementLen := calldataload(nestedAssetDataElementLenStart)\n\n // Revert if the `nestedAssetData` does not contain a 4 byte `assetProxyId`\n if lt(nestedAssetDataElementLen, 4) {\n // Revert with `Error(\"LENGTH_GREATER_THAN_3_REQUIRED\")`\n mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)\n mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)\n mstore(64, 0x0000001e4c454e4754485f475245415445525f5448414e5f335f524551554952)\n mstore(96, 0x4544000000000000000000000000000000000000000000000000000000000000)\n revert(0, 100)\n }\n\n // Load AssetProxy id\n let currentAssetProxyId := and(\n calldataload(nestedAssetDataElementContentsStart),\n 0xffffffff00000000000000000000000000000000000000000000000000000000\n )\n\n // Only load `assetProxy` if `currentAssetProxyId` does not equal `assetProxyId`\n // We do not need to check if `currentAssetProxyId` is 0 since `assetProxy` is also initialized to 0\n if sub(currentAssetProxyId, assetProxyId) {\n // Update `assetProxyId`\n assetProxyId := currentAssetProxyId\n // To lookup a value in a mapping, we load from the storage location keccak256(k, p),\n // where k is the key left padded to 32 bytes and p is the storage slot\n mstore(132, assetProxyId)\n mstore(164, assetProxies_slot)\n assetProxy := sload(keccak256(132, 64))\n }\n \n // Revert if AssetProxy with given id does not exist\n if iszero(assetProxy) {\n // Revert with `Error(\"ASSET_PROXY_DOES_NOT_EXIST\")`\n mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000)\n mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000)\n mstore(64, 0x0000001a41535345545f50524f58595f444f45535f4e4f545f45584953540000)\n mstore(96, 0)\n revert(0, 100)\n }\n \n // Copy `nestedAssetData[i]` from calldata to memory\n calldatacopy(\n 132, // memory slot after `amounts[i]`\n nestedAssetDataElementLenStart, // location of `nestedAssetData[i]` in calldata\n add(nestedAssetDataElementLen, 32) // `nestedAssetData[i].length` plus 32 byte length\n )\n\n // call `assetProxy.transferFrom`\n let success := call(\n gas, // forward all gas\n assetProxy, // call address of asset proxy\n 0, // don't send any ETH\n 0, // pointer to start of input\n add(164, nestedAssetDataElementLen), // length of input \n 0, // write output over memory that won't be reused\n 0 // don't copy output to memory\n )\n\n // Revert with reason given by AssetProxy if `transferFrom` call failed\n if iszero(success) {\n returndatacopy(\n 0, // copy to memory at 0\n 0, // copy from return data at 0\n returndatasize() // copy all return data\n )\n revert(0, returndatasize())\n }\n }\n\n // Return if no `transferFrom` calls reverted\n return(0, 0)\n }\n\n // Revert if undefined function is called\n revert(0, 0)\n }\n }\n\n /// @dev Gets the proxy id associated with the proxy address.\n /// @return Proxy id.\n function getProxyId()\n external\n pure\n returns (bytes4)\n {\n return PROXY_ID;\n }\n}\n", + "asset-proxy/MixinAssetProxyDispatcher.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-utils/contracts/utils/Ownable.sol\";\nimport \"./mixins/MAssetProxyDispatcher.sol\";\nimport \"@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAssetProxy.sol\";\n\n\ncontract MixinAssetProxyDispatcher is\n Ownable,\n MAssetProxyDispatcher\n{\n // Mapping from Asset Proxy Id's to their respective Asset Proxy\n mapping (bytes4 => IAssetProxy) public assetProxies;\n\n /// @dev Registers an asset proxy to its asset proxy id.\n /// Once an asset proxy is registered, it cannot be unregistered.\n /// @param assetProxy Address of new asset proxy to register.\n function registerAssetProxy(address assetProxy)\n external\n onlyOwner\n {\n IAssetProxy assetProxyContract = IAssetProxy(assetProxy);\n\n // Ensure that no asset proxy exists with current id.\n bytes4 assetProxyId = assetProxyContract.getProxyId();\n address currentAssetProxy = assetProxies[assetProxyId];\n require(\n currentAssetProxy == address(0),\n \"ASSET_PROXY_ALREADY_EXISTS\"\n );\n\n // Add asset proxy and log registration.\n assetProxies[assetProxyId] = assetProxyContract;\n emit AssetProxyRegistered(\n assetProxyId,\n assetProxy\n );\n }\n\n /// @dev Gets an asset proxy.\n /// @param assetProxyId Id of the asset proxy.\n /// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered.\n function getAssetProxy(bytes4 assetProxyId)\n external\n view\n returns (address)\n {\n return assetProxies[assetProxyId];\n }\n\n /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws.\n /// @param assetData Byte array encoded for the asset.\n /// @param from Address to transfer token from.\n /// @param to Address to transfer token to.\n /// @param amount Amount of token to transfer.\n function dispatchTransferFrom(\n bytes memory assetData,\n address from,\n address to,\n uint256 amount\n )\n internal\n {\n // Do nothing if no amount should be transferred.\n if (amount > 0 && from != to) {\n // Ensure assetData length is valid\n require(\n assetData.length > 3,\n \"LENGTH_GREATER_THAN_3_REQUIRED\"\n );\n \n // Lookup assetProxy. We do not use `LibBytes.readBytes4` for gas efficiency reasons.\n bytes4 assetProxyId;\n assembly {\n assetProxyId := and(mload(\n add(assetData, 32)),\n 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000\n )\n }\n address assetProxy = assetProxies[assetProxyId];\n\n // Ensure that assetProxy exists\n require(\n assetProxy != address(0),\n \"ASSET_PROXY_DOES_NOT_EXIST\"\n );\n \n // We construct calldata for the `assetProxy.transferFrom` ABI.\n // The layout of this calldata is in the table below.\n // \n // | Area | Offset | Length | Contents |\n // | -------- |--------|---------|-------------------------------------------- |\n // | Header | 0 | 4 | function selector |\n // | Params | | 4 * 32 | function parameters: |\n // | | 4 | | 1. offset to assetData (*) |\n // | | 36 | | 2. from |\n // | | 68 | | 3. to |\n // | | 100 | | 4. amount |\n // | Data | | | assetData: |\n // | | 132 | 32 | assetData Length |\n // | | 164 | ** | assetData Contents |\n\n assembly {\n /////// Setup State ///////\n // `cdStart` is the start of the calldata for `assetProxy.transferFrom` (equal to free memory ptr).\n let cdStart := mload(64)\n // `dataAreaLength` is the total number of words needed to store `assetData`\n // As-per the ABI spec, this value is padded up to the nearest multiple of 32,\n // and includes 32-bytes for length.\n let dataAreaLength := and(add(mload(assetData), 63), 0xFFFFFFFFFFFE0)\n // `cdEnd` is the end of the calldata for `assetProxy.transferFrom`.\n let cdEnd := add(cdStart, add(132, dataAreaLength))\n\n \n /////// Setup Header Area ///////\n // This area holds the 4-byte `transferFromSelector`.\n // bytes4(keccak256(\"transferFrom(bytes,address,address,uint256)\")) = 0xa85e59e4\n mstore(cdStart, 0xa85e59e400000000000000000000000000000000000000000000000000000000)\n \n /////// Setup Params Area ///////\n // Each parameter is padded to 32-bytes. The entire Params Area is 128 bytes.\n // Notes:\n // 1. The offset to `assetData` is the length of the Params Area (128 bytes).\n // 2. A 20-byte mask is applied to addresses to zero-out the unused bytes.\n mstore(add(cdStart, 4), 128)\n mstore(add(cdStart, 36), and(from, 0xffffffffffffffffffffffffffffffffffffffff))\n mstore(add(cdStart, 68), and(to, 0xffffffffffffffffffffffffffffffffffffffff))\n mstore(add(cdStart, 100), amount)\n \n /////// Setup Data Area ///////\n // This area holds `assetData`.\n let dataArea := add(cdStart, 132)\n // solhint-disable-next-line no-empty-blocks\n for {} lt(dataArea, cdEnd) {} {\n mstore(dataArea, mload(assetData))\n dataArea := add(dataArea, 32)\n assetData := add(assetData, 32)\n }\n\n /////// Call `assetProxy.transferFrom` using the constructed calldata ///////\n let success := call(\n gas, // forward all gas\n assetProxy, // call address of asset proxy\n 0, // don't send any ETH\n cdStart, // pointer to start of input\n sub(cdEnd, cdStart), // length of input \n cdStart, // write output over input\n 512 // reserve 512 bytes for output\n )\n if iszero(success) {\n revert(cdStart, returndatasize())\n }\n }\n }\n }\n}\n", + "@0x/contracts-utils/contracts/utils/Ownable.sol": "pragma solidity ^0.4.24;\n\nimport \"./interfaces/IOwnable.sol\";\n\n\ncontract Ownable is\n IOwnable\n{\n address public owner;\n\n constructor ()\n public\n {\n owner = msg.sender;\n }\n\n modifier onlyOwner() {\n require(\n msg.sender == owner,\n \"ONLY_CONTRACT_OWNER\"\n );\n _;\n }\n\n function transferOwnership(address newOwner)\n public\n onlyOwner\n {\n if (newOwner != address(0)) {\n owner = newOwner;\n }\n }\n}\n", + "@0x/contracts-utils/contracts/utils/interfaces/IOwnable.sol": "pragma solidity ^0.4.24;\n\n\ncontract IOwnable {\n\n function transferOwnership(address newOwner)\n public;\n}\n", + "asset-proxy/mixins/MAssetProxyDispatcher.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-interfaces/contracts/protocol/Exchange/IAssetProxyDispatcher.sol\";\n\n\ncontract MAssetProxyDispatcher is\n IAssetProxyDispatcher\n{\n // Logs registration of new asset proxy\n event AssetProxyRegistered(\n bytes4 id, // Id of new registered AssetProxy.\n address assetProxy // Address of new registered AssetProxy.\n );\n\n /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws.\n /// @param assetData Byte array encoded for the asset.\n /// @param from Address to transfer token from.\n /// @param to Address to transfer token to.\n /// @param amount Amount of token to transfer.\n function dispatchTransferFrom(\n bytes memory assetData,\n address from,\n address to,\n uint256 amount\n )\n internal;\n}\n", + "@0x/contracts-interfaces/contracts/protocol/Exchange/IAssetProxyDispatcher.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\n\ncontract IAssetProxyDispatcher {\n\n /// @dev Registers an asset proxy to its asset proxy id.\n /// Once an asset proxy is registered, it cannot be unregistered.\n /// @param assetProxy Address of new asset proxy to register.\n function registerAssetProxy(address assetProxy)\n external;\n\n /// @dev Gets an asset proxy.\n /// @param assetProxyId Id of the asset proxy.\n /// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered.\n function getAssetProxy(bytes4 assetProxyId)\n external\n view\n returns (address);\n}\n", + "@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAssetProxy.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"./IAuthorizable.sol\";\n\n\ncontract IAssetProxy is\n IAuthorizable\n{\n /// @dev Transfers assets. Either succeeds or throws.\n /// @param assetData Byte array encoded for the respective asset proxy.\n /// @param from Address to transfer asset from.\n /// @param to Address to transfer asset to.\n /// @param amount Amount of asset to transfer.\n function transferFrom(\n bytes assetData,\n address from,\n address to,\n uint256 amount\n )\n external;\n \n /// @dev Gets the proxy id associated with the proxy address.\n /// @return Proxy id.\n function getProxyId()\n external\n pure\n returns (bytes4);\n}\n", + "@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAuthorizable.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-utils/contracts/utils/interfaces/IOwnable.sol\";\n\n\ncontract IAuthorizable is\n IOwnable\n{\n /// @dev Authorizes an address.\n /// @param target Address to authorize.\n function addAuthorizedAddress(address target)\n external;\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n function removeAuthorizedAddress(address target)\n external;\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n /// @param index Index of target in authorities array.\n function removeAuthorizedAddressAtIndex(\n address target,\n uint256 index\n )\n external;\n \n /// @dev Gets all authorized addresses.\n /// @return Array of authorized addresses.\n function getAuthorizedAddresses()\n external\n view\n returns (address[] memory);\n}\n", + "asset-proxy/MixinAuthorizable.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-utils/contracts/utils/Ownable.sol\";\nimport \"./mixins/MAuthorizable.sol\";\n\n\ncontract MixinAuthorizable is\n Ownable,\n MAuthorizable\n{\n /// @dev Only authorized addresses can invoke functions with this modifier.\n modifier onlyAuthorized {\n require(\n authorized[msg.sender],\n \"SENDER_NOT_AUTHORIZED\"\n );\n _;\n }\n\n mapping (address => bool) public authorized;\n address[] public authorities;\n\n /// @dev Authorizes an address.\n /// @param target Address to authorize.\n function addAuthorizedAddress(address target)\n external\n onlyOwner\n {\n require(\n !authorized[target],\n \"TARGET_ALREADY_AUTHORIZED\"\n );\n\n authorized[target] = true;\n authorities.push(target);\n emit AuthorizedAddressAdded(target, msg.sender);\n }\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n function removeAuthorizedAddress(address target)\n external\n onlyOwner\n {\n require(\n authorized[target],\n \"TARGET_NOT_AUTHORIZED\"\n );\n\n delete authorized[target];\n for (uint256 i = 0; i < authorities.length; i++) {\n if (authorities[i] == target) {\n authorities[i] = authorities[authorities.length - 1];\n authorities.length -= 1;\n break;\n }\n }\n emit AuthorizedAddressRemoved(target, msg.sender);\n }\n\n /// @dev Removes authorizion of an address.\n /// @param target Address to remove authorization from.\n /// @param index Index of target in authorities array.\n function removeAuthorizedAddressAtIndex(\n address target,\n uint256 index\n )\n external\n onlyOwner\n {\n require(\n authorized[target],\n \"TARGET_NOT_AUTHORIZED\"\n );\n require(\n index < authorities.length,\n \"INDEX_OUT_OF_BOUNDS\"\n );\n require(\n authorities[index] == target,\n \"AUTHORIZED_ADDRESS_MISMATCH\"\n );\n\n delete authorized[target];\n authorities[index] = authorities[authorities.length - 1];\n authorities.length -= 1;\n emit AuthorizedAddressRemoved(target, msg.sender);\n }\n\n /// @dev Gets all authorized addresses.\n /// @return Array of authorized addresses.\n function getAuthorizedAddresses()\n external\n view\n returns (address[] memory)\n {\n return authorities;\n }\n}\n", + "asset-proxy/mixins/MAuthorizable.sol": "/*\n\n Copyright 2018 ZeroEx Intl.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n*/\n\npragma solidity ^0.4.24;\n\nimport \"@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAuthorizable.sol\";\n\n\ncontract MAuthorizable is\n IAuthorizable\n{\n // Event logged when a new address is authorized.\n event AuthorizedAddressAdded(\n address indexed target,\n address indexed caller\n );\n\n // Event logged when a currently authorized address is unauthorized.\n event AuthorizedAddressRemoved(\n address indexed target,\n address indexed caller\n );\n\n /// @dev Only authorized addresses can invoke functions with this modifier.\n modifier onlyAuthorized { revert(); _; }\n}\n" + }, + "sourceTreeHashHex": "0x851c7eccc8e6e989033f1db5bcde01779253d9e8d9e9ebcff0db08ac8463589e", + "compiler": { + "name": "solc", + "version": "soljson-v0.4.24+commit.e67f0147.js", + "settings": { + "optimizer": { + "enabled": true, + "runs": 1000000 + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + } + } + }, + "networks": {} +} diff --git a/contracts/asset-proxy/package.json b/contracts/asset-proxy/package.json new file mode 100644 index 000000000..cab146601 --- /dev/null +++ b/contracts/asset-proxy/package.json @@ -0,0 +1,90 @@ +{ + "name": "@0x/contracts-asset-proxy", + "version": "2.2.3", + "engines": { + "node": ">=6.12" + }, + "description": "Smart contract components of 0x protocol", + "main": "lib/src/index.js", + "directories": { + "test": "test" + }, + "scripts": { + "build": "yarn pre_build && tsc -b", + "build:ci": "yarn build", + "pre_build": "run-s compile generate_contract_wrappers", + "test": "yarn run_mocha", + "rebuild_and_test": "run-s build test", + "test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov", + "test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html", + "test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha", + "run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit", + "compile": "sol-compiler", + "watch": "sol-compiler -w", + "clean": "shx rm -rf lib generated-artifacts generated-wrappers", + "generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../../node_modules/@0x/abi-gen-templates/contract.handlebars --partials '../../node_modules/@0x/abi-gen-templates/partials/**/*.handlebars' --output generated-wrappers --backend ethers", + "lint": "tslint --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts", + "coverage:report:text": "istanbul report text", + "coverage:report:html": "istanbul report html && open coverage/index.html", + "profiler:report:html": "istanbul report html && open coverage/index.html", + "coverage:report:lcov": "istanbul report lcov", + "test:circleci": "yarn test", + "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" + }, + "config": { + "abis": "generated-artifacts/@(ERC20Proxy|ERC721Proxy|MixinAuthorizable|MultiAssetProxy).json" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x-monorepo.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x-monorepo/issues" + }, + "homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md", + "devDependencies": { + "@0x/abi-gen": "^1.0.22", + "@0x/dev-utils": "^1.0.24", + "@0x/sol-compiler": "^2.0.2", + "@0x/subproviders": "^2.1.11", + "@0x/tslint-config": "^2.0.2", + "@types/bn.js": "^4.11.0", + "@types/lodash": "4.14.104", + "@types/node": "*", + "@types/yargs": "^10.0.0", + "chai": "^4.0.1", + "chai-as-promised": "^7.1.0", + "chai-bignumber": "^3.0.0", + "dirty-chai": "^2.0.1", + "ethereumjs-abi": "0.6.5", + "make-promises-safe": "^1.1.0", + "mocha": "^4.1.0", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "solhint": "^1.4.1", + "tslint": "5.11.0", + "typescript": "3.0.1", + "yargs": "^10.0.3" + }, + "dependencies": { + "@0x/base-contract": "^3.0.13", + "@0x/contracts-interfaces": "^1.0.6", + "@0x/contracts-test-utils": "^2.0.1", + "@0x/contracts-tokens": "^1.0.6", + "@0x/contracts-utils": "^1.0.6", + "@0x/order-utils": "^3.1.2", + "@0x/types": "^1.5.2", + "@0x/typescript-typings": "^3.0.8", + "@0x/utils": "^3.0.1", + "@0x/web3-wrapper": "^3.2.4", + "@types/js-combinatorics": "^0.5.29", + "bn.js": "^4.11.8", + "ethereum-types": "^1.1.6", + "ethereumjs-util": "^5.1.1", + "lodash": "^4.17.5" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/contracts/asset-proxy/src/artifacts/index.ts b/contracts/asset-proxy/src/artifacts/index.ts new file mode 100644 index 000000000..3ed8a453d --- /dev/null +++ b/contracts/asset-proxy/src/artifacts/index.ts @@ -0,0 +1,13 @@ +import { ContractArtifact } from 'ethereum-types'; + +import * as ERC20Proxy from '../../generated-artifacts/ERC20Proxy.json'; +import * as ERC721Proxy from '../../generated-artifacts/ERC721Proxy.json'; +import * as MixinAuthorizable from '../../generated-artifacts/MixinAuthorizable.json'; +import * as MultiAssetProxy from '../../generated-artifacts/MultiAssetProxy.json'; + +export const artifacts = { + ERC20Proxy: ERC20Proxy as ContractArtifact, + ERC721Proxy: ERC721Proxy as ContractArtifact, + MixinAuthorizable: MixinAuthorizable as ContractArtifact, + MultiAssetProxy: MultiAssetProxy as ContractArtifact, +}; diff --git a/contracts/asset-proxy/src/index.ts b/contracts/asset-proxy/src/index.ts new file mode 100644 index 000000000..ba813e7ca --- /dev/null +++ b/contracts/asset-proxy/src/index.ts @@ -0,0 +1,3 @@ +export * from './artifacts'; +export * from './wrappers'; +export * from '../test/utils'; diff --git a/contracts/asset-proxy/src/wrappers/index.ts b/contracts/asset-proxy/src/wrappers/index.ts new file mode 100644 index 000000000..a5d749be3 --- /dev/null +++ b/contracts/asset-proxy/src/wrappers/index.ts @@ -0,0 +1,4 @@ +export * from '../../generated-wrappers/erc20_proxy'; +export * from '../../generated-wrappers/erc721_proxy'; +export * from '../../generated-wrappers/mixin_authorizable'; +export * from '../../generated-wrappers/multi_asset_proxy'; diff --git a/contracts/asset-proxy/test/authorizable.ts b/contracts/asset-proxy/test/authorizable.ts new file mode 100644 index 000000000..23fcf0931 --- /dev/null +++ b/contracts/asset-proxy/test/authorizable.ts @@ -0,0 +1,211 @@ +import { + chaiSetup, + constants, + expectTransactionFailedAsync, + provider, + txDefaults, + web3Wrapper, +} from '@0x/contracts-test-utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { RevertReason } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; +import * as _ from 'lodash'; + +import { MixinAuthorizableContract } from '../generated-wrappers/mixin_authorizable'; +import { artifacts } from '../src/artifacts'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('Authorizable', () => { + let owner: string; + let notOwner: string; + let address: string; + let authorizable: MixinAuthorizableContract; + + before(async () => { + await blockchainLifecycle.startAsync(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + before(async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + [owner, address, notOwner] = _.slice(accounts, 0, 3); + authorizable = await MixinAuthorizableContract.deployFrom0xArtifactAsync( + artifacts.MixinAuthorizable, + provider, + txDefaults, + ); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('addAuthorizedAddress', () => { + it('should throw if not called by owner', async () => { + return expectTransactionFailedAsync( + authorizable.addAuthorizedAddress.sendTransactionAsync(notOwner, { from: notOwner }), + RevertReason.OnlyContractOwner, + ); + }); + it('should allow owner to add an authorized address', async () => { + await web3Wrapper.awaitTransactionSuccessAsync( + await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const isAuthorized = await authorizable.authorized.callAsync(address); + expect(isAuthorized).to.be.true(); + }); + it('should throw if owner attempts to authorize a duplicate address', async () => { + await web3Wrapper.awaitTransactionSuccessAsync( + await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + return expectTransactionFailedAsync( + authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), + RevertReason.TargetAlreadyAuthorized, + ); + }); + }); + + describe('removeAuthorizedAddress', () => { + it('should throw if not called by owner', async () => { + await web3Wrapper.awaitTransactionSuccessAsync( + await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + return expectTransactionFailedAsync( + authorizable.removeAuthorizedAddress.sendTransactionAsync(address, { + from: notOwner, + }), + RevertReason.OnlyContractOwner, + ); + }); + + it('should allow owner to remove an authorized address', async () => { + await web3Wrapper.awaitTransactionSuccessAsync( + await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await authorizable.removeAuthorizedAddress.sendTransactionAsync(address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const isAuthorized = await authorizable.authorized.callAsync(address); + expect(isAuthorized).to.be.false(); + }); + + it('should throw if owner attempts to remove an address that is not authorized', async () => { + return expectTransactionFailedAsync( + authorizable.removeAuthorizedAddress.sendTransactionAsync(address, { + from: owner, + }), + RevertReason.TargetNotAuthorized, + ); + }); + }); + + describe('removeAuthorizedAddressAtIndex', () => { + it('should throw if not called by owner', async () => { + await web3Wrapper.awaitTransactionSuccessAsync( + await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const index = new BigNumber(0); + return expectTransactionFailedAsync( + authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, { + from: notOwner, + }), + RevertReason.OnlyContractOwner, + ); + }); + it('should throw if index is >= authorities.length', async () => { + await web3Wrapper.awaitTransactionSuccessAsync( + await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const index = new BigNumber(1); + return expectTransactionFailedAsync( + authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, { + from: owner, + }), + RevertReason.IndexOutOfBounds, + ); + }); + it('should throw if owner attempts to remove an address that is not authorized', async () => { + const index = new BigNumber(0); + return expectTransactionFailedAsync( + authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, { + from: owner, + }), + RevertReason.TargetNotAuthorized, + ); + }); + it('should throw if address at index does not match target', async () => { + const address1 = address; + const address2 = notOwner; + await web3Wrapper.awaitTransactionSuccessAsync( + await authorizable.addAuthorizedAddress.sendTransactionAsync(address1, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await authorizable.addAuthorizedAddress.sendTransactionAsync(address2, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const address1Index = new BigNumber(0); + return expectTransactionFailedAsync( + authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address2, address1Index, { + from: owner, + }), + RevertReason.AuthorizedAddressMismatch, + ); + }); + it('should allow owner to remove an authorized address', async () => { + await web3Wrapper.awaitTransactionSuccessAsync( + await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const index = new BigNumber(0); + await web3Wrapper.awaitTransactionSuccessAsync( + await authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const isAuthorized = await authorizable.authorized.callAsync(address); + expect(isAuthorized).to.be.false(); + }); + }); + + describe('getAuthorizedAddresses', () => { + it('should return all authorized addresses', async () => { + const initial = await authorizable.getAuthorizedAddresses.callAsync(); + expect(initial).to.have.length(0); + await web3Wrapper.awaitTransactionSuccessAsync( + await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const afterAdd = await authorizable.getAuthorizedAddresses.callAsync(); + expect(afterAdd).to.have.length(1); + expect(afterAdd).to.include(address); + + await web3Wrapper.awaitTransactionSuccessAsync( + await authorizable.removeAuthorizedAddress.sendTransactionAsync(address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const afterRemove = await authorizable.getAuthorizedAddresses.callAsync(); + expect(afterRemove).to.have.length(0); + }); + }); +}); diff --git a/contracts/asset-proxy/test/global_hooks.ts b/contracts/asset-proxy/test/global_hooks.ts new file mode 100644 index 000000000..f8ace376a --- /dev/null +++ b/contracts/asset-proxy/test/global_hooks.ts @@ -0,0 +1,17 @@ +import { env, EnvVars } from '@0x/dev-utils'; + +import { coverage, profiler, provider } from '@0x/contracts-test-utils'; +before('start web3 provider', () => { + provider.start(); +}); +after('generate coverage report', async () => { + if (env.parseBoolean(EnvVars.SolidityCoverage)) { + const coverageSubprovider = coverage.getCoverageSubproviderSingleton(); + await coverageSubprovider.writeCoverageAsync(); + } + if (env.parseBoolean(EnvVars.SolidityProfiler)) { + const profilerSubprovider = profiler.getProfilerSubproviderSingleton(); + await profilerSubprovider.writeProfilerOutputAsync(); + } + provider.stop(); +}); diff --git a/contracts/asset-proxy/test/proxies.ts b/contracts/asset-proxy/test/proxies.ts new file mode 100644 index 000000000..44ab77087 --- /dev/null +++ b/contracts/asset-proxy/test/proxies.ts @@ -0,0 +1,1286 @@ +import { artifacts as interfacesArtifacts, IAssetDataContract, IAssetProxyContract } from '@0x/contracts-interfaces'; +import { + chaiSetup, + constants, + expectTransactionFailedAsync, + expectTransactionFailedWithoutReasonAsync, + LogDecoder, + provider, + txDefaults, + web3Wrapper, +} from '@0x/contracts-test-utils'; +import { + artifacts as tokensArtifacts, + DummyERC20TokenContract, + DummyERC20TokenTransferEventArgs, + DummyERC721ReceiverContract, + DummyERC721TokenContract, + DummyMultipleReturnERC20TokenContract, + DummyNoReturnERC20TokenContract, +} from '@0x/contracts-tokens'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils } from '@0x/order-utils'; +import { RevertReason } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; +import { LogWithDecodedArgs } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { ERC20ProxyContract } from '../generated-wrappers/erc20_proxy'; +import { ERC721ProxyContract } from '../generated-wrappers/erc721_proxy'; +import { MultiAssetProxyContract } from '../generated-wrappers/multi_asset_proxy'; +import { artifacts } from '../src/artifacts'; + +import { ERC20Wrapper } from './utils/erc20_wrapper'; +import { ERC721Wrapper } from './utils/erc721_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +const assetProxyInterface = new IAssetProxyContract( + interfacesArtifacts.IAssetProxy.compilerOutput.abi, + constants.NULL_ADDRESS, + provider, +); +const assetDataInterface = new IAssetDataContract( + interfacesArtifacts.IAssetData.compilerOutput.abi, + constants.NULL_ADDRESS, + provider, +); + +// tslint:disable:no-unnecessary-type-assertion +describe('Asset Transfer Proxies', () => { + let owner: string; + let notAuthorized: string; + let authorized: string; + let fromAddress: string; + let toAddress: string; + + let erc20TokenA: DummyERC20TokenContract; + let erc20TokenB: DummyERC20TokenContract; + let erc721TokenA: DummyERC721TokenContract; + let erc721TokenB: DummyERC721TokenContract; + let erc721Receiver: DummyERC721ReceiverContract; + let erc20Proxy: ERC20ProxyContract; + let erc721Proxy: ERC721ProxyContract; + let noReturnErc20Token: DummyNoReturnERC20TokenContract; + let multipleReturnErc20Token: DummyMultipleReturnERC20TokenContract; + let multiAssetProxy: MultiAssetProxyContract; + + let erc20Wrapper: ERC20Wrapper; + let erc721Wrapper: ERC721Wrapper; + let erc721AFromTokenId: BigNumber; + let erc721BFromTokenId: BigNumber; + + before(async () => { + await blockchainLifecycle.startAsync(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + before(async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const usedAddresses = ([owner, notAuthorized, authorized, fromAddress, toAddress] = _.slice(accounts, 0, 5)); + + erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); + + // Deploy AssetProxies + erc20Proxy = await erc20Wrapper.deployProxyAsync(); + erc721Proxy = await erc721Wrapper.deployProxyAsync(); + multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync( + artifacts.MultiAssetProxy, + provider, + txDefaults, + ); + + // Configure ERC20Proxy + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + // Configure ERC721Proxy + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + // Configure MultiAssetProxy + await web3Wrapper.awaitTransactionSuccessAsync( + await multiAssetProxy.addAuthorizedAddress.sendTransactionAsync(authorized, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc721Proxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + // Deploy and configure ERC20 tokens + const numDummyErc20ToDeploy = 2; + [erc20TokenA, erc20TokenB] = await erc20Wrapper.deployDummyTokensAsync( + numDummyErc20ToDeploy, + constants.DUMMY_TOKEN_DECIMALS, + ); + noReturnErc20Token = await DummyNoReturnERC20TokenContract.deployFrom0xArtifactAsync( + tokensArtifacts.DummyNoReturnERC20Token, + provider, + txDefaults, + constants.DUMMY_TOKEN_NAME, + constants.DUMMY_TOKEN_SYMBOL, + constants.DUMMY_TOKEN_DECIMALS, + constants.DUMMY_TOKEN_TOTAL_SUPPLY, + ); + multipleReturnErc20Token = await DummyMultipleReturnERC20TokenContract.deployFrom0xArtifactAsync( + tokensArtifacts.DummyMultipleReturnERC20Token, + provider, + txDefaults, + constants.DUMMY_TOKEN_NAME, + constants.DUMMY_TOKEN_SYMBOL, + constants.DUMMY_TOKEN_DECIMALS, + constants.DUMMY_TOKEN_TOTAL_SUPPLY, + ); + + await erc20Wrapper.setBalancesAndAllowancesAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await noReturnErc20Token.setBalance.sendTransactionAsync(fromAddress, constants.INITIAL_ERC20_BALANCE), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await noReturnErc20Token.approve.sendTransactionAsync( + erc20Proxy.address, + constants.INITIAL_ERC20_ALLOWANCE, + { from: fromAddress }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await multipleReturnErc20Token.setBalance.sendTransactionAsync( + fromAddress, + constants.INITIAL_ERC20_BALANCE, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await multipleReturnErc20Token.approve.sendTransactionAsync( + erc20Proxy.address, + constants.INITIAL_ERC20_ALLOWANCE, + { from: fromAddress }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + // Deploy and configure ERC721 tokens and receiver + [erc721TokenA, erc721TokenB] = await erc721Wrapper.deployDummyTokensAsync(); + erc721Receiver = await DummyERC721ReceiverContract.deployFrom0xArtifactAsync( + tokensArtifacts.DummyERC721Receiver, + provider, + txDefaults, + ); + + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + erc721AFromTokenId = erc721Balances[fromAddress][erc721TokenA.address][0]; + erc721BFromTokenId = erc721Balances[fromAddress][erc721TokenB.address][0]; + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + + describe('ERC20Proxy', () => { + it('should revert if undefined function is called', async () => { + const undefinedSelector = '0x01020304'; + await expectTransactionFailedWithoutReasonAsync( + web3Wrapper.sendTransactionAsync({ + from: owner, + to: erc20Proxy.address, + value: constants.ZERO_AMOUNT, + data: undefinedSelector, + }), + ); + }); + it('should have an id of 0xf47261b0', async () => { + const proxyId = await erc20Proxy.getProxyId.callAsync(); + const expectedProxyId = '0xf47261b0'; + expect(proxyId).to.equal(expectedProxyId); + }); + describe('transferFrom', () => { + it('should successfully transfer tokens', async () => { + // Construct ERC20 asset data + const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + // Perform a transfer from fromAddress to toAddress + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const amount = new BigNumber(10); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + encodedAssetData, + fromAddress, + toAddress, + amount, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: erc20Proxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Verify transfer was successful + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(amount), + ); + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address].plus(amount), + ); + }); + + it('should successfully transfer tokens that do not return a value', async () => { + // Construct ERC20 asset data + const encodedAssetData = assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address); + // Perform a transfer from fromAddress to toAddress + const initialFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress); + const initialToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress); + const amount = new BigNumber(10); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + encodedAssetData, + fromAddress, + toAddress, + amount, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: erc20Proxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Verify transfer was successful + const newFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress); + const newToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress); + expect(newFromBalance).to.be.bignumber.equal(initialFromBalance.minus(amount)); + expect(newToBalance).to.be.bignumber.equal(initialToBalance.plus(amount)); + }); + + it('should successfully transfer tokens and ignore extra assetData', async () => { + // Construct ERC20 asset data + const extraData = '0102030405060708'; + const encodedAssetData = `${assetDataUtils.encodeERC20AssetData(erc20TokenA.address)}${extraData}`; + // Perform a transfer from fromAddress to toAddress + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const amount = new BigNumber(10); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + encodedAssetData, + fromAddress, + toAddress, + amount, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: erc20Proxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Verify transfer was successful + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(amount), + ); + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address].plus(amount), + ); + }); + + it('should do nothing if transferring 0 amount of a token', async () => { + // Construct ERC20 asset data + const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + // Perform a transfer from fromAddress to toAddress + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const amount = new BigNumber(0); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + encodedAssetData, + fromAddress, + toAddress, + amount, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: erc20Proxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Verify transfer was successful + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address], + ); + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address], + ); + }); + + it('should revert if allowances are too low', async () => { + // Construct ERC20 asset data + const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + // Create allowance less than transfer amount. Set allowance on proxy. + const allowance = new BigNumber(0); + const amount = new BigNumber(10); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + encodedAssetData, + fromAddress, + toAddress, + amount, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, allowance, { + from: fromAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + // Perform a transfer; expect this to fail. + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: erc20Proxy.address, + data, + from: authorized, + }), + RevertReason.TransferFailed, + ); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.deep.equal(erc20Balances); + }); + + it('should revert if allowances are too low and token does not return a value', async () => { + // Construct ERC20 asset data + const encodedAssetData = assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address); + // Create allowance less than transfer amount. Set allowance on proxy. + const allowance = new BigNumber(0); + const amount = new BigNumber(10); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + encodedAssetData, + fromAddress, + toAddress, + amount, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await noReturnErc20Token.approve.sendTransactionAsync(erc20Proxy.address, allowance, { + from: fromAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const initialFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress); + const initialToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress); + // Perform a transfer; expect this to fail. + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: erc20Proxy.address, + data, + from: authorized, + }), + RevertReason.TransferFailed, + ); + const newFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress); + const newToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress); + expect(newFromBalance).to.be.bignumber.equal(initialFromBalance); + expect(newToBalance).to.be.bignumber.equal(initialToBalance); + }); + + it('should revert if caller is not authorized', async () => { + // Construct ERC20 asset data + const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + // Perform a transfer from fromAddress to toAddress + const amount = new BigNumber(10); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + encodedAssetData, + fromAddress, + toAddress, + amount, + ); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: erc20Proxy.address, + data, + from: notAuthorized, + }), + RevertReason.SenderNotAuthorized, + ); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.deep.equal(erc20Balances); + }); + + it('should revert if token returns more than 32 bytes', async () => { + // Construct ERC20 asset data + const encodedAssetData = assetDataUtils.encodeERC20AssetData(multipleReturnErc20Token.address); + const amount = new BigNumber(10); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + encodedAssetData, + fromAddress, + toAddress, + amount, + ); + const initialFromBalance = await multipleReturnErc20Token.balanceOf.callAsync(fromAddress); + const initialToBalance = await multipleReturnErc20Token.balanceOf.callAsync(toAddress); + // Perform a transfer; expect this to fail. + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: erc20Proxy.address, + data, + from: authorized, + }), + RevertReason.TransferFailed, + ); + const newFromBalance = await multipleReturnErc20Token.balanceOf.callAsync(fromAddress); + const newToBalance = await multipleReturnErc20Token.balanceOf.callAsync(toAddress); + expect(newFromBalance).to.be.bignumber.equal(initialFromBalance); + expect(newToBalance).to.be.bignumber.equal(initialToBalance); + }); + }); + }); + + describe('ERC721Proxy', () => { + it('should revert if undefined function is called', async () => { + const undefinedSelector = '0x01020304'; + await expectTransactionFailedWithoutReasonAsync( + web3Wrapper.sendTransactionAsync({ + from: owner, + to: erc721Proxy.address, + value: constants.ZERO_AMOUNT, + data: undefinedSelector, + }), + ); + }); + it('should have an id of 0x02571792', async () => { + const proxyId = await erc721Proxy.getProxyId.callAsync(); + const expectedProxyId = '0x02571792'; + expect(proxyId).to.equal(expectedProxyId); + }); + describe('transferFrom', () => { + it('should successfully transfer tokens', async () => { + // Construct ERC721 asset data + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + // Verify pre-condition + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + // Perform a transfer from fromAddress to toAddress + const amount = new BigNumber(1); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + encodedAssetData, + fromAddress, + toAddress, + amount, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: erc721Proxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Verify transfer was successful + const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwnerFromAsset).to.be.bignumber.equal(toAddress); + }); + + it('should successfully transfer tokens and ignore extra assetData', async () => { + // Construct ERC721 asset data + const extraData = '0102030405060708'; + const encodedAssetData = `${assetDataUtils.encodeERC721AssetData( + erc721TokenA.address, + erc721AFromTokenId, + )}${extraData}`; + // Verify pre-condition + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + // Perform a transfer from fromAddress to toAddress + const amount = new BigNumber(1); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + encodedAssetData, + fromAddress, + toAddress, + amount, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: erc721Proxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Verify transfer was successful + const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwnerFromAsset).to.be.bignumber.equal(toAddress); + }); + + it('should not call onERC721Received when transferring to a smart contract', async () => { + // Construct ERC721 asset data + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + // Verify pre-condition + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + // Perform a transfer from fromAddress to toAddress + const amount = new BigNumber(1); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + encodedAssetData, + fromAddress, + erc721Receiver.address, + amount, + ); + const logDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...tokensArtifacts }); + const tx = await logDecoder.getTxWithDecodedLogsAsync( + await web3Wrapper.sendTransactionAsync({ + to: erc721Proxy.address, + data, + from: authorized, + gas: constants.MAX_TRANSFER_FROM_GAS, + }), + ); + // Verify that no log was emitted by erc721 receiver + expect(tx.logs.length).to.be.equal(1); + // Verify transfer was successful + const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwnerFromAsset).to.be.bignumber.equal(erc721Receiver.address); + }); + + it('should revert if transferring 0 amount of a token', async () => { + // Construct ERC721 asset data + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + // Verify pre-condition + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + // Perform a transfer from fromAddress to toAddress + const amount = new BigNumber(0); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + encodedAssetData, + fromAddress, + toAddress, + amount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: erc721Proxy.address, + data, + from: authorized, + }), + RevertReason.InvalidAmount, + ); + const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwner).to.be.equal(ownerFromAsset); + }); + + it('should revert if transferring > 1 amount of a token', async () => { + // Construct ERC721 asset data + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + // Verify pre-condition + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + // Perform a transfer from fromAddress to toAddress + const amount = new BigNumber(500); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + encodedAssetData, + fromAddress, + toAddress, + amount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: erc721Proxy.address, + data, + from: authorized, + }), + RevertReason.InvalidAmount, + ); + const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwner).to.be.equal(ownerFromAsset); + }); + + it('should revert if allowances are too low', async () => { + // Construct ERC721 asset data + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + // Verify pre-condition + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + // Remove transfer approval for fromAddress. + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721TokenA.approve.sendTransactionAsync(constants.NULL_ADDRESS, erc721AFromTokenId, { + from: fromAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Perform a transfer; expect this to fail. + const amount = new BigNumber(1); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + encodedAssetData, + fromAddress, + toAddress, + amount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: erc721Proxy.address, + data, + from: authorized, + }), + RevertReason.TransferFailed, + ); + const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwner).to.be.equal(ownerFromAsset); + }); + + it('should revert if caller is not authorized', async () => { + // Construct ERC721 asset data + const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + // Verify pre-condition + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + // Perform a transfer from fromAddress to toAddress + const amount = new BigNumber(1); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + encodedAssetData, + fromAddress, + toAddress, + amount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: erc721Proxy.address, + data, + from: notAuthorized, + }), + RevertReason.SenderNotAuthorized, + ); + const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwner).to.be.equal(ownerFromAsset); + }); + }); + }); + describe('MultiAssetProxy', () => { + it('should revert if undefined function is called', async () => { + const undefinedSelector = '0x01020304'; + await expectTransactionFailedWithoutReasonAsync( + web3Wrapper.sendTransactionAsync({ + from: owner, + to: multiAssetProxy.address, + value: constants.ZERO_AMOUNT, + data: undefinedSelector, + }), + ); + }); + it('should have an id of 0x94cfcdd7', async () => { + const proxyId = await multiAssetProxy.getProxyId.callAsync(); + // first 4 bytes of `keccak256('MultiAsset(uint256[],bytes[])')` + const expectedProxyId = '0x94cfcdd7'; + expect(proxyId).to.equal(expectedProxyId); + }); + describe('transferFrom', () => { + it('should transfer a single ERC20 token', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount = new BigNumber(10); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const amounts = [erc20Amount]; + const nestedAssetData = [erc20AssetData]; + const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + 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), + ); + }); + it('should dispatch an ERC20 transfer when input amount is 0', async () => { + const inputAmount = constants.ZERO_AMOUNT; + const erc20Amount = new BigNumber(10); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const amounts = [erc20Amount]; + const nestedAssetData = [erc20AssetData]; + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const logDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...tokensArtifacts }); + const tx = await logDecoder.getTxWithDecodedLogsAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + ); + expect(tx.logs.length).to.be.equal(1); + const log = tx.logs[0] as LogWithDecodedArgs; + const transferEventName = 'Transfer'; + expect(log.event).to.equal(transferEventName); + expect(log.args._value).to.be.bignumber.equal(constants.ZERO_AMOUNT); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.deep.equal(erc20Balances); + }); + it('should successfully transfer multiple of the same ERC20 token', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount1 = new BigNumber(10); + const erc20Amount2 = new BigNumber(20); + const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const amounts = [erc20Amount1, erc20Amount2]; + const nestedAssetData = [erc20AssetData1, erc20AssetData2]; + const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const totalAmount = inputAmount.times(erc20Amount1).plus(inputAmount.times(erc20Amount2)); + 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), + ); + }); + it('should successfully transfer multiple different ERC20 tokens', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount1 = new BigNumber(10); + const erc20Amount2 = new BigNumber(20); + const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address); + const amounts = [erc20Amount1, erc20Amount2]; + const nestedAssetData = [erc20AssetData1, erc20AssetData2]; + const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const totalErc20AAmount = inputAmount.times(erc20Amount1); + const totalErc20BAmount = inputAmount.times(erc20Amount2); + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(totalErc20AAmount), + ); + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address].plus(totalErc20AAmount), + ); + expect(newBalances[fromAddress][erc20TokenB.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenB.address].minus(totalErc20BAmount), + ); + expect(newBalances[toAddress][erc20TokenB.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenB.address].plus(totalErc20BAmount), + ); + }); + it('should transfer a single ERC721 token', async () => { + const inputAmount = new BigNumber(1); + const erc721Amount = new BigNumber(1); + const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const amounts = [erc721Amount]; + const nestedAssetData = [erc721AssetData]; + const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(newOwnerFromAsset).to.be.equal(toAddress); + }); + it('should successfully transfer multiple of the same ERC721 token', async () => { + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + const erc721AFromTokenId2 = erc721Balances[fromAddress][erc721TokenA.address][1]; + const erc721AssetData1 = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const erc721AssetData2 = assetDataUtils.encodeERC721AssetData( + erc721TokenA.address, + erc721AFromTokenId2, + ); + const inputAmount = new BigNumber(1); + const erc721Amount = new BigNumber(1); + const amounts = [erc721Amount, erc721Amount]; + const nestedAssetData = [erc721AssetData1, erc721AssetData2]; + const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const ownerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset1).to.be.equal(fromAddress); + const ownerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2); + expect(ownerFromAsset2).to.be.equal(fromAddress); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + gas: constants.MAX_TRANSFER_FROM_GAS, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const newOwnerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + const newOwnerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2); + expect(newOwnerFromAsset1).to.be.equal(toAddress); + expect(newOwnerFromAsset2).to.be.equal(toAddress); + }); + it('should successfully transfer multiple different ERC721 tokens', async () => { + const erc721AssetData1 = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const erc721AssetData2 = assetDataUtils.encodeERC721AssetData(erc721TokenB.address, erc721BFromTokenId); + const inputAmount = new BigNumber(1); + const erc721Amount = new BigNumber(1); + const amounts = [erc721Amount, erc721Amount]; + const nestedAssetData = [erc721AssetData1, erc721AssetData2]; + const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const ownerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset1).to.be.equal(fromAddress); + const ownerFromAsset2 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId); + expect(ownerFromAsset2).to.be.equal(fromAddress); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + gas: constants.MAX_TRANSFER_FROM_GAS, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const newOwnerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + const newOwnerFromAsset2 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId); + expect(newOwnerFromAsset1).to.be.equal(toAddress); + expect(newOwnerFromAsset2).to.be.equal(toAddress); + }); + it('should successfully transfer a combination of ERC20 and ERC721 tokens', async () => { + 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 amounts = [erc20Amount, erc721Amount]; + const nestedAssetData = [erc20AssetData, erc721AssetData]; + const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + 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); + }); + it('should successfully transfer tokens and ignore extra assetData', async () => { + 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 amounts = [erc20Amount, erc721Amount]; + const nestedAssetData = [erc20AssetData, erc721AssetData]; + const extraData = '0102030405060708'; + const assetData = `${assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData)}${extraData}`; + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset).to.be.equal(fromAddress); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + 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); + }); + it('should successfully transfer correct amounts when the `amount` > 1', async () => { + const inputAmount = new BigNumber(100); + const erc20Amount1 = new BigNumber(10); + const erc20Amount2 = new BigNumber(20); + const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address); + const amounts = [erc20Amount1, erc20Amount2]; + const nestedAssetData = [erc20AssetData1, erc20AssetData2]; + const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const totalErc20AAmount = inputAmount.times(erc20Amount1); + const totalErc20BAmount = inputAmount.times(erc20Amount2); + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(totalErc20AAmount), + ); + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address].plus(totalErc20AAmount), + ); + expect(newBalances[fromAddress][erc20TokenB.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenB.address].minus(totalErc20BAmount), + ); + expect(newBalances[toAddress][erc20TokenB.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenB.address].plus(totalErc20BAmount), + ); + }); + it('should successfully transfer a large amount of tokens', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount1 = new BigNumber(10); + const erc20Amount2 = new BigNumber(20); + const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address); + const erc721Amount = new BigNumber(1); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + const erc721AFromTokenId2 = erc721Balances[fromAddress][erc721TokenA.address][1]; + const erc721BFromTokenId2 = erc721Balances[fromAddress][erc721TokenB.address][1]; + const erc721AssetData1 = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const erc721AssetData2 = assetDataUtils.encodeERC721AssetData( + erc721TokenA.address, + erc721AFromTokenId2, + ); + const erc721AssetData3 = assetDataUtils.encodeERC721AssetData(erc721TokenB.address, erc721BFromTokenId); + const erc721AssetData4 = assetDataUtils.encodeERC721AssetData( + erc721TokenB.address, + erc721BFromTokenId2, + ); + const amounts = [erc721Amount, erc20Amount1, erc721Amount, erc20Amount2, erc721Amount, erc721Amount]; + const nestedAssetData = [ + erc721AssetData1, + erc20AssetData1, + erc721AssetData2, + erc20AssetData2, + erc721AssetData3, + erc721AssetData4, + ]; + const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + const ownerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + expect(ownerFromAsset1).to.be.equal(fromAddress); + const ownerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2); + expect(ownerFromAsset2).to.be.equal(fromAddress); + const ownerFromAsset3 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId); + expect(ownerFromAsset3).to.be.equal(fromAddress); + const ownerFromAsset4 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId2); + expect(ownerFromAsset4).to.be.equal(fromAddress); + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + gas: constants.MAX_EXECUTE_TRANSACTION_GAS, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const newOwnerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); + const newOwnerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2); + const newOwnerFromAsset3 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId); + const newOwnerFromAsset4 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId2); + expect(newOwnerFromAsset1).to.be.equal(toAddress); + expect(newOwnerFromAsset2).to.be.equal(toAddress); + expect(newOwnerFromAsset3).to.be.equal(toAddress); + expect(newOwnerFromAsset4).to.be.equal(toAddress); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const totalErc20AAmount = inputAmount.times(erc20Amount1); + const totalErc20BAmount = inputAmount.times(erc20Amount2); + expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenA.address].minus(totalErc20AAmount), + ); + expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenA.address].plus(totalErc20AAmount), + ); + expect(newBalances[fromAddress][erc20TokenB.address]).to.be.bignumber.equal( + erc20Balances[fromAddress][erc20TokenB.address].minus(totalErc20BAmount), + ); + expect(newBalances[toAddress][erc20TokenB.address]).to.be.bignumber.equal( + erc20Balances[toAddress][erc20TokenB.address].plus(totalErc20BAmount), + ); + }); + it('should revert if a single transfer fails', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount = new BigNumber(10); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + // 2 is an invalid erc721 amount + const erc721Amount = new BigNumber(2); + const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const amounts = [erc20Amount, erc721Amount]; + const nestedAssetData = [erc20AssetData, erc721AssetData]; + const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + RevertReason.InvalidAmount, + ); + }); + it('should revert if an AssetProxy is not registered', async () => { + 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 invalidProxyId = '0x12345678'; + const invalidErc721AssetData = `${invalidProxyId}${erc721AssetData.slice(10)}`; + const amounts = [erc20Amount, erc721Amount]; + const nestedAssetData = [erc20AssetData, invalidErc721AssetData]; + // HACK: This is used to get around validation built into assetDataUtils + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + RevertReason.AssetProxyDoesNotExist, + ); + }); + it('should revert if the length of `amounts` does not match the length of `nestedAssetData`', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount = new BigNumber(10); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); + const amounts = [erc20Amount]; + const nestedAssetData = [erc20AssetData, erc721AssetData]; + // HACK: This is used to get around validation built into assetDataUtils + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + RevertReason.LengthMismatch, + ); + }); + it('should revert if amounts multiplication results in an overflow', async () => { + const inputAmount = new BigNumber(2).pow(128); + const erc20Amount = new BigNumber(2).pow(128); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const amounts = [erc20Amount]; + const nestedAssetData = [erc20AssetData]; + const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + RevertReason.Uint256Overflow, + ); + }); + it('should revert if an element of `nestedAssetData` is < 4 bytes long', async () => { + const inputAmount = new BigNumber(1); + const erc20Amount = new BigNumber(10); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); + const erc721Amount = new BigNumber(1); + const erc721AssetData = '0x123456'; + const amounts = [erc20Amount, erc721Amount]; + const nestedAssetData = [erc20AssetData, erc721AssetData]; + // HACK: This is used to get around validation built into assetDataUtils + const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: authorized, + }), + RevertReason.LengthGreaterThan3Required, + ); + }); + it('should revert if caller is not authorized', async () => { + 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 amounts = [erc20Amount, erc721Amount]; + const nestedAssetData = [erc20AssetData, erc721AssetData]; + const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); + const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( + assetData, + fromAddress, + toAddress, + inputAmount, + ); + await expectTransactionFailedAsync( + web3Wrapper.sendTransactionAsync({ + to: multiAssetProxy.address, + data, + from: notAuthorized, + }), + RevertReason.SenderNotAuthorized, + ); + }); + }); + }); +}); +// tslint:enable:no-unnecessary-type-assertion +// tslint:disable:max-file-line-count diff --git a/contracts/asset-proxy/test/utils/erc20_wrapper.ts b/contracts/asset-proxy/test/utils/erc20_wrapper.ts new file mode 100644 index 000000000..e5ab0e36c --- /dev/null +++ b/contracts/asset-proxy/test/utils/erc20_wrapper.ts @@ -0,0 +1,179 @@ +import { constants, ERC20BalancesByOwner, txDefaults } from '@0x/contracts-test-utils'; +import { artifacts as tokensArtifacts, DummyERC20TokenContract } from '@0x/contracts-tokens'; +import { assetDataUtils } from '@0x/order-utils'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import { Provider } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; +import { artifacts } from '../../src/artifacts'; + +export class ERC20Wrapper { + private readonly _tokenOwnerAddresses: string[]; + private readonly _contractOwnerAddress: string; + private readonly _web3Wrapper: Web3Wrapper; + private readonly _provider: Provider; + private readonly _dummyTokenContracts: DummyERC20TokenContract[]; + private _proxyContract?: ERC20ProxyContract; + private _proxyIdIfExists?: string; + /** + * Instanitates an ERC20Wrapper + * @param provider Web3 provider to use for all JSON RPC requests + * @param tokenOwnerAddresses Addresses that we want to endow as owners for dummy ERC20 tokens + * @param contractOwnerAddress Desired owner of the contract + * Instance of ERC20Wrapper + */ + constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) { + this._dummyTokenContracts = []; + this._web3Wrapper = new Web3Wrapper(provider); + this._provider = provider; + this._tokenOwnerAddresses = tokenOwnerAddresses; + this._contractOwnerAddress = contractOwnerAddress; + } + public async deployDummyTokensAsync( + numberToDeploy: number, + decimals: BigNumber, + ): Promise { + for (let i = 0; i < numberToDeploy; i++) { + this._dummyTokenContracts.push( + await DummyERC20TokenContract.deployFrom0xArtifactAsync( + tokensArtifacts.DummyERC20Token, + this._provider, + txDefaults, + constants.DUMMY_TOKEN_NAME, + constants.DUMMY_TOKEN_SYMBOL, + decimals, + constants.DUMMY_TOKEN_TOTAL_SUPPLY, + ), + ); + } + return this._dummyTokenContracts; + } + public async deployProxyAsync(): Promise { + this._proxyContract = await ERC20ProxyContract.deployFrom0xArtifactAsync( + artifacts.ERC20Proxy, + this._provider, + txDefaults, + ); + this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync(); + return this._proxyContract; + } + public getProxyId(): string { + this._validateProxyContractExistsOrThrow(); + return this._proxyIdIfExists as string; + } + public async setBalancesAndAllowancesAsync(): Promise { + this._validateDummyTokenContractsExistOrThrow(); + this._validateProxyContractExistsOrThrow(); + for (const dummyTokenContract of this._dummyTokenContracts) { + for (const tokenOwnerAddress of this._tokenOwnerAddresses) { + await this._web3Wrapper.awaitTransactionSuccessAsync( + await dummyTokenContract.setBalance.sendTransactionAsync( + tokenOwnerAddress, + constants.INITIAL_ERC20_BALANCE, + { from: this._contractOwnerAddress }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await dummyTokenContract.approve.sendTransactionAsync( + (this._proxyContract as ERC20ProxyContract).address, + constants.INITIAL_ERC20_ALLOWANCE, + { from: tokenOwnerAddress }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + } + } + public async getBalanceAsync(userAddress: string, assetData: string): Promise { + const tokenContract = this._getTokenContractFromAssetData(assetData); + const balance = new BigNumber(await tokenContract.balanceOf.callAsync(userAddress)); + return balance; + } + public async setBalanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise { + const tokenContract = this._getTokenContractFromAssetData(assetData); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.setBalance.sendTransactionAsync(userAddress, amount, { + from: this._contractOwnerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async getProxyAllowanceAsync(userAddress: string, assetData: string): Promise { + const tokenContract = this._getTokenContractFromAssetData(assetData); + const proxyAddress = (this._proxyContract as ERC20ProxyContract).address; + const allowance = new BigNumber(await tokenContract.allowance.callAsync(userAddress, proxyAddress)); + return allowance; + } + public async setAllowanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise { + const tokenContract = this._getTokenContractFromAssetData(assetData); + const proxyAddress = (this._proxyContract as ERC20ProxyContract).address; + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.approve.sendTransactionAsync(proxyAddress, amount, { + from: userAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async getBalancesAsync(): Promise { + this._validateDummyTokenContractsExistOrThrow(); + const balancesByOwner: ERC20BalancesByOwner = {}; + const balances: BigNumber[] = []; + const balanceInfo: Array<{ tokenOwnerAddress: string; tokenAddress: string }> = []; + for (const dummyTokenContract of this._dummyTokenContracts) { + for (const tokenOwnerAddress of this._tokenOwnerAddresses) { + balances.push(await dummyTokenContract.balanceOf.callAsync(tokenOwnerAddress)); + balanceInfo.push({ + tokenOwnerAddress, + tokenAddress: dummyTokenContract.address, + }); + } + } + _.forEach(balances, (balance, balanceIndex) => { + const tokenAddress = balanceInfo[balanceIndex].tokenAddress; + const tokenOwnerAddress = balanceInfo[balanceIndex].tokenOwnerAddress; + if (_.isUndefined(balancesByOwner[tokenOwnerAddress])) { + balancesByOwner[tokenOwnerAddress] = {}; + } + const wrappedBalance = new BigNumber(balance); + balancesByOwner[tokenOwnerAddress][tokenAddress] = wrappedBalance; + }); + return balancesByOwner; + } + public addDummyTokenContract(dummy: DummyERC20TokenContract): void { + if (!_.isUndefined(this._dummyTokenContracts)) { + this._dummyTokenContracts.push(dummy); + } + } + public addTokenOwnerAddress(address: string): void { + this._tokenOwnerAddresses.push(address); + } + public getTokenOwnerAddresses(): string[] { + return this._tokenOwnerAddresses; + } + public getTokenAddresses(): string[] { + const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address); + return tokenAddresses; + } + private _getTokenContractFromAssetData(assetData: string): DummyERC20TokenContract { + const erc20ProxyData = assetDataUtils.decodeERC20AssetData(assetData); + const tokenAddress = erc20ProxyData.tokenAddress; + const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress); + if (_.isUndefined(tokenContractIfExists)) { + throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`); + } + return tokenContractIfExists; + } + private _validateDummyTokenContractsExistOrThrow(): void { + if (_.isUndefined(this._dummyTokenContracts)) { + throw new Error('Dummy ERC20 tokens not yet deployed, please call "deployDummyTokensAsync"'); + } + } + private _validateProxyContractExistsOrThrow(): void { + if (_.isUndefined(this._proxyContract)) { + throw new Error('ERC20 proxy contract not yet deployed, please call "deployProxyAsync"'); + } + } +} diff --git a/contracts/asset-proxy/test/utils/erc721_wrapper.ts b/contracts/asset-proxy/test/utils/erc721_wrapper.ts new file mode 100644 index 000000000..1f658ce86 --- /dev/null +++ b/contracts/asset-proxy/test/utils/erc721_wrapper.ts @@ -0,0 +1,236 @@ +import { constants, ERC721TokenIdsByOwner, txDefaults } from '@0x/contracts-test-utils'; +import { artifacts as tokensArtifacts, DummyERC721TokenContract } from '@0x/contracts-tokens'; +import { generatePseudoRandomSalt } from '@0x/order-utils'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import { Provider } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; +import { artifacts } from '../../src/artifacts'; + +export class ERC721Wrapper { + private readonly _tokenOwnerAddresses: string[]; + private readonly _contractOwnerAddress: string; + private readonly _web3Wrapper: Web3Wrapper; + private readonly _provider: Provider; + private readonly _dummyTokenContracts: DummyERC721TokenContract[]; + private _proxyContract?: ERC721ProxyContract; + private _proxyIdIfExists?: string; + private _initialTokenIdsByOwner: ERC721TokenIdsByOwner = {}; + constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) { + this._web3Wrapper = new Web3Wrapper(provider); + this._provider = provider; + this._dummyTokenContracts = []; + this._tokenOwnerAddresses = tokenOwnerAddresses; + this._contractOwnerAddress = contractOwnerAddress; + } + public async deployDummyTokensAsync(): Promise { + // tslint:disable-next-line:no-unused-variable + for (const i of _.times(constants.NUM_DUMMY_ERC721_TO_DEPLOY)) { + this._dummyTokenContracts.push( + await DummyERC721TokenContract.deployFrom0xArtifactAsync( + tokensArtifacts.DummyERC721Token, + this._provider, + txDefaults, + constants.DUMMY_TOKEN_NAME, + constants.DUMMY_TOKEN_SYMBOL, + ), + ); + } + return this._dummyTokenContracts; + } + public async deployProxyAsync(): Promise { + this._proxyContract = await ERC721ProxyContract.deployFrom0xArtifactAsync( + artifacts.ERC721Proxy, + this._provider, + txDefaults, + ); + this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync(); + return this._proxyContract; + } + public getProxyId(): string { + this._validateProxyContractExistsOrThrow(); + return this._proxyIdIfExists as string; + } + public async setBalancesAndAllowancesAsync(): Promise { + this._validateDummyTokenContractsExistOrThrow(); + this._validateProxyContractExistsOrThrow(); + this._initialTokenIdsByOwner = {}; + for (const dummyTokenContract of this._dummyTokenContracts) { + for (const tokenOwnerAddress of this._tokenOwnerAddresses) { + // tslint:disable-next-line:no-unused-variable + for (const i of _.times(constants.NUM_ERC721_TOKENS_TO_MINT)) { + const tokenId = generatePseudoRandomSalt(); + await this.mintAsync(dummyTokenContract.address, tokenId, tokenOwnerAddress); + if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress])) { + this._initialTokenIdsByOwner[tokenOwnerAddress] = { + [dummyTokenContract.address]: [], + }; + } + if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address])) { + this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address] = []; + } + this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address].push(tokenId); + + await this.approveProxyAsync(dummyTokenContract.address, tokenId); + } + } + } + } + public async doesTokenExistAsync(tokenAddress: string, tokenId: BigNumber): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const owner = await tokenContract.ownerOf.callAsync(tokenId); + const doesExist = owner !== constants.NULL_ADDRESS; + return doesExist; + } + public async approveProxyAsync(tokenAddress: string, tokenId: BigNumber): Promise { + const proxyAddress = (this._proxyContract as ERC721ProxyContract).address; + await this.approveAsync(proxyAddress, tokenAddress, tokenId); + } + public async approveProxyForAllAsync(tokenAddress: string, tokenId: BigNumber, isApproved: boolean): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId); + const proxyAddress = (this._proxyContract as ERC721ProxyContract).address; + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.setApprovalForAll.sendTransactionAsync(proxyAddress, isApproved, { + from: tokenOwner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async approveAsync(to: string, tokenAddress: string, tokenId: BigNumber): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.approve.sendTransactionAsync(to, tokenId, { + from: tokenOwner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async transferFromAsync( + tokenAddress: string, + tokenId: BigNumber, + currentOwner: string, + userAddress: string, + ): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.transferFrom.sendTransactionAsync(currentOwner, userAddress, tokenId, { + from: currentOwner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async mintAsync(tokenAddress: string, tokenId: BigNumber, userAddress: string): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.mint.sendTransactionAsync(userAddress, tokenId, { + from: this._contractOwnerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async burnAsync(tokenAddress: string, tokenId: BigNumber, owner: string): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.burn.sendTransactionAsync(owner, tokenId, { + from: this._contractOwnerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async ownerOfAsync(tokenAddress: string, tokenId: BigNumber): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const owner = await tokenContract.ownerOf.callAsync(tokenId); + return owner; + } + public async isOwnerAsync(userAddress: string, tokenAddress: string, tokenId: BigNumber): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const tokenOwner = await tokenContract.ownerOf.callAsync(tokenId); + const isOwner = tokenOwner === userAddress; + return isOwner; + } + public async isProxyApprovedForAllAsync(userAddress: string, tokenAddress: string): Promise { + this._validateProxyContractExistsOrThrow(); + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const operator = (this._proxyContract as ERC721ProxyContract).address; + const didApproveAll = await tokenContract.isApprovedForAll.callAsync(userAddress, operator); + return didApproveAll; + } + public async isProxyApprovedAsync(tokenAddress: string, tokenId: BigNumber): Promise { + this._validateProxyContractExistsOrThrow(); + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const approvedAddress = await tokenContract.getApproved.callAsync(tokenId); + const proxyAddress = (this._proxyContract as ERC721ProxyContract).address; + const isProxyAnApprovedOperator = approvedAddress === proxyAddress; + return isProxyAnApprovedOperator; + } + public async getBalancesAsync(): Promise { + this._validateDummyTokenContractsExistOrThrow(); + this._validateBalancesAndAllowancesSetOrThrow(); + const tokenIdsByOwner: ERC721TokenIdsByOwner = {}; + const tokenOwnerAddresses: string[] = []; + const tokenInfo: Array<{ tokenId: BigNumber; tokenAddress: string }> = []; + for (const dummyTokenContract of this._dummyTokenContracts) { + for (const tokenOwnerAddress of this._tokenOwnerAddresses) { + const initialTokenOwnerIds = this._initialTokenIdsByOwner[tokenOwnerAddress][ + dummyTokenContract.address + ]; + for (const tokenId of initialTokenOwnerIds) { + tokenOwnerAddresses.push(await dummyTokenContract.ownerOf.callAsync(tokenId)); + tokenInfo.push({ + tokenId, + tokenAddress: dummyTokenContract.address, + }); + } + } + } + _.forEach(tokenOwnerAddresses, (tokenOwnerAddress, ownerIndex) => { + const tokenAddress = tokenInfo[ownerIndex].tokenAddress; + const tokenId = tokenInfo[ownerIndex].tokenId; + if (_.isUndefined(tokenIdsByOwner[tokenOwnerAddress])) { + tokenIdsByOwner[tokenOwnerAddress] = { + [tokenAddress]: [], + }; + } + if (_.isUndefined(tokenIdsByOwner[tokenOwnerAddress][tokenAddress])) { + tokenIdsByOwner[tokenOwnerAddress][tokenAddress] = []; + } + tokenIdsByOwner[tokenOwnerAddress][tokenAddress].push(tokenId); + }); + return tokenIdsByOwner; + } + public getTokenOwnerAddresses(): string[] { + return this._tokenOwnerAddresses; + } + public getTokenAddresses(): string[] { + const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address); + return tokenAddresses; + } + private _getTokenContractFromAssetData(tokenAddress: string): DummyERC721TokenContract { + const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress); + if (_.isUndefined(tokenContractIfExists)) { + throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`); + } + return tokenContractIfExists; + } + private _validateDummyTokenContractsExistOrThrow(): void { + if (_.isUndefined(this._dummyTokenContracts)) { + throw new Error('Dummy ERC721 tokens not yet deployed, please call "deployDummyTokensAsync"'); + } + } + private _validateProxyContractExistsOrThrow(): void { + if (_.isUndefined(this._proxyContract)) { + throw new Error('ERC721 proxy contract not yet deployed, please call "deployProxyAsync"'); + } + } + private _validateBalancesAndAllowancesSetOrThrow(): void { + if (_.keys(this._initialTokenIdsByOwner).length === 0) { + throw new Error( + 'Dummy ERC721 balances and allowances not yet set, please call "setBalancesAndAllowancesAsync"', + ); + } + } +} diff --git a/contracts/asset-proxy/test/utils/index.ts b/contracts/asset-proxy/test/utils/index.ts new file mode 100644 index 000000000..b11f6a45d --- /dev/null +++ b/contracts/asset-proxy/test/utils/index.ts @@ -0,0 +1,2 @@ +export * from './erc20_wrapper'; +export * from './erc721_wrapper'; diff --git a/contracts/asset-proxy/tsconfig.json b/contracts/asset-proxy/tsconfig.json new file mode 100644 index 000000000..3bc0753e4 --- /dev/null +++ b/contracts/asset-proxy/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": ".", + "resolveJsonModule": true + }, + "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], + "files": [ + "./generated-artifacts/ERC20Proxy.json", + "./generated-artifacts/ERC721Proxy.json", + "./generated-artifacts/MixinAuthorizable.json", + "./generated-artifacts/MultiAssetProxy.json" + ], + "exclude": ["./deploy/solc/solc_bin"] +} diff --git a/contracts/asset-proxy/tslint.json b/contracts/asset-proxy/tslint.json new file mode 100644 index 000000000..1bb3ac2a2 --- /dev/null +++ b/contracts/asset-proxy/tslint.json @@ -0,0 +1,6 @@ +{ + "extends": ["@0x/tslint-config"], + "rules": { + "custom-no-magic-numbers": false + } +} diff --git a/contracts/exchange/CHANGELOG.json b/contracts/exchange/CHANGELOG.json new file mode 100644 index 000000000..0d513cc17 --- /dev/null +++ b/contracts/exchange/CHANGELOG.json @@ -0,0 +1,65 @@ +[ + { + "version": "3.0.0", + "changes": [ + { + "note": "Upgrade the bignumber.js to v8.0.2", + "pr": 1517 + } + ] + }, + { + "timestamp": 1547747677, + "version": "2.2.3", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1547561734, + "version": "2.2.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "timestamp": 1547225310, + "version": "2.2.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, + { + "version": "2.2.0", + "changes": [ + { + "note": "Added LibAddressArray", + "pr": 1383 + }, + { + "note": "Add validation and comments to MultiAssetProxy", + "pr": 1455 + }, + { + "note": "Move OrderValidator to extensions", + "pr": 1464 + } + ], + "timestamp": 1547040760 + }, + { + "timestamp": 1544741676, + "version": "2.1.59", + "changes": [ + { + "note": "Dependencies updated" + } + ] + } +] diff --git a/contracts/exchange/CHANGELOG.md b/contracts/exchange/CHANGELOG.md new file mode 100644 index 000000000..779eb68a7 --- /dev/null +++ b/contracts/exchange/CHANGELOG.md @@ -0,0 +1,28 @@ + + +CHANGELOG + +## v2.2.3 - _January 17, 2019_ + + * Dependencies updated + +## v2.2.2 - _January 15, 2019_ + + * Dependencies updated + +## v2.2.1 - _January 11, 2019_ + + * Dependencies updated + +## v2.2.0 - _January 9, 2019_ + + * Added LibAddressArray (#1383) + * Add validation and comments to MultiAssetProxy (#1455) + * Move OrderValidator to extensions (#1464) + +## v2.1.59 - _December 13, 2018_ + + * Dependencies updated diff --git a/contracts/exchange/DEPLOYS.json b/contracts/exchange/DEPLOYS.json new file mode 100644 index 000000000..5c24ae59c --- /dev/null +++ b/contracts/exchange/DEPLOYS.json @@ -0,0 +1,92 @@ +[ + { + "name": "MultiAssetProxy", + "version": "1.0.0", + "changes": [ + { + "note": "Add MultiAssetProxy implementation", + "pr": 1224 + } + ] + }, + { + "name": "OrderValidator", + "version": "1.0.0", + "changes": [ + { + "note": "remove `getApproved` check from ERC721 approval query", + "pr": 1149 + } + ] + }, + { + "name": "OrderValidator", + "version": "1.0.0", + "changes": [ + { + "note": "protocol v2 deploy", + "networks": { + "1": "0x9463e518dea6810309563c81d5266c1b1d149138", + "3": "0x90431a90516ab49af23a0530e04e8c7836e7122f", + "42": "0xb389da3d204b412df2f75c6afb3d0a7ce0bc283d" + } + } + ] + }, + { + "name": "Exchange", + "version": "2.0.0", + "changes": [ + { + "note": "protocol v2 deploy", + "networks": { + "1": "0x4f833a24e1f95d70f028921e27040ca56e09ab0b", + "3": "0x4530c0483a1633c7a1c97d2c53721caff2caaaaf", + "42": "0x35dd2932454449b14cee11a94d3674a936d5d7b2" + } + } + ] + }, + { + "name": "ERC20Proxy", + "version": "1.0.0", + "changes": [ + { + "note": "protocol v2 deploy", + "networks": { + "1": "0x2240dab907db71e64d3e0dba4800c83b5c502d4e", + "3": "0xb1408f4c245a23c31b98d2c626777d4c0d766caa", + "42": "0xf1ec01d6236d3cd881a0bf0130ea25fe4234003e" + } + } + ] + }, + { + "name": "ERC721Proxy", + "version": "1.0.0", + "changes": [ + { + "note": "protocol v2 deploy", + "networks": { + "1": "0x208e41fb445f1bb1b6780d58356e81405f3e6127", + "3": "0xe654aac058bfbf9f83fcaee7793311dd82f6ddb4", + "42": "0x2a9127c745688a165106c11cd4d647d2220af821" + } + } + ] + }, + { + "name": "AssetProxyOwner", + "version": "1.0.0", + "changes": [ + { + "note": "protocol v2 deploy", + "networks": { + "1": "0x17992e4ffb22730138e4b62aaa6367fa9d3699a6", + "3": "0xf5fa5b5fed2727a0e44ac67f6772e97977aa358b", + "42": "0x2c824d2882baa668e0d5202b1e7f2922278703f8" + } + } + ] + } +] diff --git a/contracts/exchange/README.md b/contracts/exchange/README.md new file mode 100644 index 000000000..dc1f32c98 --- /dev/null +++ b/contracts/exchange/README.md @@ -0,0 +1,74 @@ +## Contracts + +Smart contracts that implement the 0x protocol. Addresses of the deployed contracts can be found in the 0x [wiki](https://0xproject.com/wiki#Deployed-Addresses) or the [CHANGELOG](./CHANGELOG.json) of this package. + +## Usage + +Contracts that make up and interact with version 2.0.0 of the protocol can be found in the [contracts](./contracts) directory. The contents of this directory are broken down into the following subdirectories: + +- [protocol](./contracts/protocol) + - This directory contains the contracts that make up version 2.0.0. A full specification can be found [here](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md). +- [test](./contracts/test) + - This directory contains mocks and other contracts that are used solely for testing contracts within the other directories. + +## Bug bounty + +A bug bounty for the 2.0.0 contracts is ongoing! Instructions can be found [here](https://0xproject.com/wiki#Bug-Bounty). + +## Contributing + +We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. + +For proposals regarding the 0x protocol's smart contract architecture, message format, or additional functionality, go to the [0x Improvement Proposals (ZEIPs)](https://github.com/0xProject/ZEIPs) repository and follow the contribution guidelines provided therein. + +Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. + +### Install Dependencies + +If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them: + +```bash +yarn config set workspaces-experimental true +``` + +Then install dependencies + +```bash +yarn install +``` + +### Build + +To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: + +```bash +PKG=@0x/contracts-protocol yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0x/contracts-protocol yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` + +#### Testing options + +Contracts testing options like coverage, profiling, revert traces or backing node choosing - are described [here](../TESTING.md). diff --git a/contracts/exchange/compiler.json b/contracts/exchange/compiler.json new file mode 100644 index 000000000..ac4d85f3a --- /dev/null +++ b/contracts/exchange/compiler.json @@ -0,0 +1,28 @@ +{ + "artifactsDir": "./generated-artifacts", + "contractsDir": "./contracts", + "compilerSettings": { + "optimizer": { + "enabled": true, + "runs": 1000000 + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + } + }, + "contracts": [ + "Exchange", + "TestAssetProxyDispatcher", + "TestExchangeInternals", + "TestSignatureValidator", + "TestStaticCallReceiver" + ] +} diff --git a/contracts/exchange/contracts/exchange/Exchange.sol b/contracts/exchange/contracts/exchange/Exchange.sol new file mode 100644 index 000000000..87a7fbe4e --- /dev/null +++ b/contracts/exchange/contracts/exchange/Exchange.sol @@ -0,0 +1,53 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibConstants.sol"; +import "./MixinExchangeCore.sol"; +import "./MixinSignatureValidator.sol"; +import "./MixinWrapperFunctions.sol"; +import "./MixinAssetProxyDispatcher.sol"; +import "./MixinTransactions.sol"; +import "./MixinMatchOrders.sol"; + + +// solhint-disable no-empty-blocks +contract Exchange is + MixinExchangeCore, + MixinMatchOrders, + MixinSignatureValidator, + MixinTransactions, + MixinAssetProxyDispatcher, + MixinWrapperFunctions +{ + string constant public VERSION = "2.0.1-alpha"; + + // Mixins are instantiated in the order they are inherited + constructor (bytes memory _zrxAssetData) + public + LibConstants(_zrxAssetData) // @TODO: Remove when we deploy. + MixinExchangeCore() + MixinMatchOrders() + MixinSignatureValidator() + MixinTransactions() + MixinAssetProxyDispatcher() + MixinWrapperFunctions() + {} +} diff --git a/contracts/exchange/contracts/exchange/MixinAssetProxyDispatcher.sol b/contracts/exchange/contracts/exchange/MixinAssetProxyDispatcher.sol new file mode 100644 index 000000000..521b8c4ac --- /dev/null +++ b/contracts/exchange/contracts/exchange/MixinAssetProxyDispatcher.sol @@ -0,0 +1,174 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; + +import "@0x/contracts-utils/contracts/utils/Ownable.sol"; +import "./mixins/MAssetProxyDispatcher.sol"; +import "@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAssetProxy.sol"; + + +contract MixinAssetProxyDispatcher is + Ownable, + MAssetProxyDispatcher +{ + // Mapping from Asset Proxy Id's to their respective Asset Proxy + mapping (bytes4 => IAssetProxy) public assetProxies; + + /// @dev Registers an asset proxy to its asset proxy id. + /// Once an asset proxy is registered, it cannot be unregistered. + /// @param assetProxy Address of new asset proxy to register. + function registerAssetProxy(address assetProxy) + external + onlyOwner + { + IAssetProxy assetProxyContract = IAssetProxy(assetProxy); + + // Ensure that no asset proxy exists with current id. + bytes4 assetProxyId = assetProxyContract.getProxyId(); + address currentAssetProxy = assetProxies[assetProxyId]; + require( + currentAssetProxy == address(0), + "ASSET_PROXY_ALREADY_EXISTS" + ); + + // Add asset proxy and log registration. + assetProxies[assetProxyId] = assetProxyContract; + emit AssetProxyRegistered( + assetProxyId, + assetProxy + ); + } + + /// @dev Gets an asset proxy. + /// @param assetProxyId Id of the asset proxy. + /// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered. + function getAssetProxy(bytes4 assetProxyId) + external + view + returns (address) + { + return assetProxies[assetProxyId]; + } + + /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws. + /// @param assetData Byte array encoded for the asset. + /// @param from Address to transfer token from. + /// @param to Address to transfer token to. + /// @param amount Amount of token to transfer. + function dispatchTransferFrom( + bytes memory assetData, + address from, + address to, + uint256 amount + ) + internal + { + // Do nothing if no amount should be transferred. + if (amount > 0 && from != to) { + // Ensure assetData length is valid + require( + assetData.length > 3, + "LENGTH_GREATER_THAN_3_REQUIRED" + ); + + // Lookup assetProxy. We do not use `LibBytes.readBytes4` for gas efficiency reasons. + bytes4 assetProxyId; + assembly { + assetProxyId := and(mload( + add(assetData, 32)), + 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 + ) + } + address assetProxy = assetProxies[assetProxyId]; + + // Ensure that assetProxy exists + require( + assetProxy != address(0), + "ASSET_PROXY_DOES_NOT_EXIST" + ); + + // We construct calldata for the `assetProxy.transferFrom` ABI. + // The layout of this calldata is in the table below. + // + // | Area | Offset | Length | Contents | + // | -------- |--------|---------|-------------------------------------------- | + // | Header | 0 | 4 | function selector | + // | Params | | 4 * 32 | function parameters: | + // | | 4 | | 1. offset to assetData (*) | + // | | 36 | | 2. from | + // | | 68 | | 3. to | + // | | 100 | | 4. amount | + // | Data | | | assetData: | + // | | 132 | 32 | assetData Length | + // | | 164 | ** | assetData Contents | + + assembly { + /////// Setup State /////// + // `cdStart` is the start of the calldata for `assetProxy.transferFrom` (equal to free memory ptr). + let cdStart := mload(64) + // `dataAreaLength` is the total number of words needed to store `assetData` + // As-per the ABI spec, this value is padded up to the nearest multiple of 32, + // and includes 32-bytes for length. + let dataAreaLength := and(add(mload(assetData), 63), 0xFFFFFFFFFFFE0) + // `cdEnd` is the end of the calldata for `assetProxy.transferFrom`. + let cdEnd := add(cdStart, add(132, dataAreaLength)) + + + /////// Setup Header Area /////// + // This area holds the 4-byte `transferFromSelector`. + // bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4 + mstore(cdStart, 0xa85e59e400000000000000000000000000000000000000000000000000000000) + + /////// Setup Params Area /////// + // Each parameter is padded to 32-bytes. The entire Params Area is 128 bytes. + // Notes: + // 1. The offset to `assetData` is the length of the Params Area (128 bytes). + // 2. A 20-byte mask is applied to addresses to zero-out the unused bytes. + mstore(add(cdStart, 4), 128) + mstore(add(cdStart, 36), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(cdStart, 68), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(cdStart, 100), amount) + + /////// Setup Data Area /////// + // This area holds `assetData`. + let dataArea := add(cdStart, 132) + // solhint-disable-next-line no-empty-blocks + for {} lt(dataArea, cdEnd) {} { + mstore(dataArea, mload(assetData)) + dataArea := add(dataArea, 32) + assetData := add(assetData, 32) + } + + /////// Call `assetProxy.transferFrom` using the constructed calldata /////// + let success := call( + gas, // forward all gas + assetProxy, // call address of asset proxy + 0, // don't send any ETH + cdStart, // pointer to start of input + sub(cdEnd, cdStart), // length of input + cdStart, // write output over input + 512 // reserve 512 bytes for output + ) + if iszero(success) { + revert(cdStart, returndatasize()) + } + } + } + } +} diff --git a/contracts/exchange/contracts/exchange/MixinExchangeCore.sol b/contracts/exchange/contracts/exchange/MixinExchangeCore.sol new file mode 100644 index 000000000..ef72195f5 --- /dev/null +++ b/contracts/exchange/contracts/exchange/MixinExchangeCore.sol @@ -0,0 +1,529 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/utils/ReentrancyGuard.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibConstants.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibFillResults.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibOrder.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibMath.sol"; +import "./mixins/MExchangeCore.sol"; +import "./mixins/MSignatureValidator.sol"; +import "./mixins/MTransactions.sol"; +import "./mixins/MAssetProxyDispatcher.sol"; + + +contract MixinExchangeCore is + ReentrancyGuard, + LibConstants, + LibMath, + LibOrder, + LibFillResults, + MAssetProxyDispatcher, + MExchangeCore, + MSignatureValidator, + MTransactions +{ + // Mapping of orderHash => amount of takerAsset already bought by maker + mapping (bytes32 => uint256) public filled; + + // Mapping of orderHash => cancelled + mapping (bytes32 => bool) public cancelled; + + // Mapping of makerAddress => senderAddress => lowest salt an order can have in order to be fillable + // Orders with specified senderAddress and with a salt less than their epoch are considered cancelled + mapping (address => mapping (address => uint256)) public orderEpoch; + + /// @dev Cancels all orders created by makerAddress with a salt less than or equal to the targetOrderEpoch + /// and senderAddress equal to msg.sender (or null address if msg.sender == makerAddress). + /// @param targetOrderEpoch Orders created with a salt less or equal to this value will be cancelled. + function cancelOrdersUpTo(uint256 targetOrderEpoch) + external + nonReentrant + { + address makerAddress = getCurrentContextAddress(); + // If this function is called via `executeTransaction`, we only update the orderEpoch for the makerAddress/msg.sender combination. + // This allows external filter contracts to add rules to how orders are cancelled via this function. + address senderAddress = makerAddress == msg.sender ? address(0) : msg.sender; + + // orderEpoch is initialized to 0, so to cancelUpTo we need salt + 1 + uint256 newOrderEpoch = targetOrderEpoch + 1; + uint256 oldOrderEpoch = orderEpoch[makerAddress][senderAddress]; + + // Ensure orderEpoch is monotonically increasing + require( + newOrderEpoch > oldOrderEpoch, + "INVALID_NEW_ORDER_EPOCH" + ); + + // Update orderEpoch + orderEpoch[makerAddress][senderAddress] = newOrderEpoch; + emit CancelUpTo( + makerAddress, + senderAddress, + newOrderEpoch + ); + } + + /// @dev Fills the input order. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return Amounts filled and fees paid by maker and taker. + function fillOrder( + Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + public + nonReentrant + returns (FillResults memory fillResults) + { + fillResults = fillOrderInternal( + order, + takerAssetFillAmount, + signature + ); + return fillResults; + } + + /// @dev After calling, the order can not be filled anymore. + /// Throws if order is invalid or sender does not have permission to cancel. + /// @param order Order to cancel. Order must be OrderStatus.FILLABLE. + function cancelOrder(Order memory order) + public + nonReentrant + { + cancelOrderInternal(order); + } + + /// @dev Gets information about an order: status, hash, and amount filled. + /// @param order Order to gather information on. + /// @return OrderInfo Information about the order and its state. + /// See LibOrder.OrderInfo for a complete description. + function getOrderInfo(Order memory order) + public + view + returns (OrderInfo memory orderInfo) + { + // Compute the order hash + orderInfo.orderHash = getOrderHash(order); + + // Fetch filled amount + orderInfo.orderTakerAssetFilledAmount = filled[orderInfo.orderHash]; + + // If order.makerAssetAmount is zero, we also reject the order. + // While the Exchange contract handles them correctly, they create + // edge cases in the supporting infrastructure because they have + // an 'infinite' price when computed by a simple division. + if (order.makerAssetAmount == 0) { + orderInfo.orderStatus = uint8(OrderStatus.INVALID_MAKER_ASSET_AMOUNT); + return orderInfo; + } + + // If order.takerAssetAmount is zero, then the order will always + // be considered filled because 0 == takerAssetAmount == orderTakerAssetFilledAmount + // Instead of distinguishing between unfilled and filled zero taker + // amount orders, we choose not to support them. + if (order.takerAssetAmount == 0) { + orderInfo.orderStatus = uint8(OrderStatus.INVALID_TAKER_ASSET_AMOUNT); + return orderInfo; + } + + // Validate order availability + if (orderInfo.orderTakerAssetFilledAmount >= order.takerAssetAmount) { + orderInfo.orderStatus = uint8(OrderStatus.FULLY_FILLED); + return orderInfo; + } + + // Validate order expiration + // solhint-disable-next-line not-rely-on-time + if (block.timestamp >= order.expirationTimeSeconds) { + orderInfo.orderStatus = uint8(OrderStatus.EXPIRED); + return orderInfo; + } + + // Check if order has been cancelled + if (cancelled[orderInfo.orderHash]) { + orderInfo.orderStatus = uint8(OrderStatus.CANCELLED); + return orderInfo; + } + if (orderEpoch[order.makerAddress][order.senderAddress] > order.salt) { + orderInfo.orderStatus = uint8(OrderStatus.CANCELLED); + return orderInfo; + } + + // All other statuses are ruled out: order is Fillable + orderInfo.orderStatus = uint8(OrderStatus.FILLABLE); + return orderInfo; + } + + /// @dev Fills the input order. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return Amounts filled and fees paid by maker and taker. + function fillOrderInternal( + Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + internal + returns (FillResults memory fillResults) + { + // Fetch order info + OrderInfo memory orderInfo = getOrderInfo(order); + + // Fetch taker address + address takerAddress = getCurrentContextAddress(); + + // Assert that the order is fillable by taker + assertFillableOrder( + order, + orderInfo, + takerAddress, + signature + ); + + // Get amount of takerAsset to fill + uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, orderInfo.orderTakerAssetFilledAmount); + uint256 takerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount); + + // Validate context + assertValidFill( + order, + orderInfo, + takerAssetFillAmount, + takerAssetFilledAmount, + fillResults.makerAssetFilledAmount + ); + + // Compute proportional fill amounts + fillResults = calculateFillResults(order, takerAssetFilledAmount); + + // Update exchange internal state + updateFilledState( + order, + takerAddress, + orderInfo.orderHash, + orderInfo.orderTakerAssetFilledAmount, + fillResults + ); + + // Settle order + settleOrder( + order, + takerAddress, + fillResults + ); + + return fillResults; + } + + /// @dev After calling, the order can not be filled anymore. + /// Throws if order is invalid or sender does not have permission to cancel. + /// @param order Order to cancel. Order must be OrderStatus.FILLABLE. + function cancelOrderInternal(Order memory order) + internal + { + // Fetch current order status + OrderInfo memory orderInfo = getOrderInfo(order); + + // Validate context + assertValidCancel(order, orderInfo); + + // Perform cancel + updateCancelledState(order, orderInfo.orderHash); + } + + /// @dev Updates state with results of a fill order. + /// @param order that was filled. + /// @param takerAddress Address of taker who filled the order. + /// @param orderTakerAssetFilledAmount Amount of order already filled. + function updateFilledState( + Order memory order, + address takerAddress, + bytes32 orderHash, + uint256 orderTakerAssetFilledAmount, + FillResults memory fillResults + ) + internal + { + // Update state + filled[orderHash] = safeAdd(orderTakerAssetFilledAmount, fillResults.takerAssetFilledAmount); + + // Log order + emit Fill( + order.makerAddress, + order.feeRecipientAddress, + takerAddress, + msg.sender, + fillResults.makerAssetFilledAmount, + fillResults.takerAssetFilledAmount, + fillResults.makerFeePaid, + fillResults.takerFeePaid, + orderHash, + order.makerAssetData, + order.takerAssetData + ); + } + + /// @dev Updates state with results of cancelling an order. + /// State is only updated if the order is currently fillable. + /// Otherwise, updating state would have no effect. + /// @param order that was cancelled. + /// @param orderHash Hash of order that was cancelled. + function updateCancelledState( + Order memory order, + bytes32 orderHash + ) + internal + { + // Perform cancel + cancelled[orderHash] = true; + + // Log cancel + emit Cancel( + order.makerAddress, + order.feeRecipientAddress, + msg.sender, + orderHash, + order.makerAssetData, + order.takerAssetData + ); + } + + /// @dev Validates context for fillOrder. Succeeds or throws. + /// @param order to be filled. + /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. + /// @param takerAddress Address of order taker. + /// @param signature Proof that the orders was created by its maker. + function assertFillableOrder( + Order memory order, + OrderInfo memory orderInfo, + address takerAddress, + bytes memory signature + ) + internal + view + { + // An order can only be filled if its status is FILLABLE. + require( + orderInfo.orderStatus == uint8(OrderStatus.FILLABLE), + "ORDER_UNFILLABLE" + ); + + // Validate sender is allowed to fill this order + if (order.senderAddress != address(0)) { + require( + order.senderAddress == msg.sender, + "INVALID_SENDER" + ); + } + + // Validate taker is allowed to fill this order + if (order.takerAddress != address(0)) { + require( + order.takerAddress == takerAddress, + "INVALID_TAKER" + ); + } + + // Validate Maker signature (check only if first time seen) + if (orderInfo.orderTakerAssetFilledAmount == 0) { + require( + isValidSignature( + orderInfo.orderHash, + order.makerAddress, + signature + ), + "INVALID_ORDER_SIGNATURE" + ); + } + } + + /// @dev Validates context for fillOrder. Succeeds or throws. + /// @param order to be filled. + /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. + /// @param takerAssetFillAmount Desired amount of order to fill by taker. + /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. + /// @param makerAssetFilledAmount Amount of makerAsset that will be transfered. + function assertValidFill( + Order memory order, + OrderInfo memory orderInfo, + uint256 takerAssetFillAmount, // TODO: use FillResults + uint256 takerAssetFilledAmount, + uint256 makerAssetFilledAmount + ) + internal + view + { + // Revert if fill amount is invalid + // TODO: reconsider necessity for v2.1 + require( + takerAssetFillAmount != 0, + "INVALID_TAKER_AMOUNT" + ); + + // Make sure taker does not pay more than desired amount + // NOTE: This assertion should never fail, it is here + // as an extra defence against potential bugs. + require( + takerAssetFilledAmount <= takerAssetFillAmount, + "TAKER_OVERPAY" + ); + + // Make sure order is not overfilled + // NOTE: This assertion should never fail, it is here + // as an extra defence against potential bugs. + require( + safeAdd(orderInfo.orderTakerAssetFilledAmount, takerAssetFilledAmount) <= order.takerAssetAmount, + "ORDER_OVERFILL" + ); + + // Make sure order is filled at acceptable price. + // The order has an implied price from the makers perspective: + // order price = order.makerAssetAmount / order.takerAssetAmount + // i.e. the number of makerAsset maker is paying per takerAsset. The + // maker is guaranteed to get this price or a better (lower) one. The + // actual price maker is getting in this fill is: + // fill price = makerAssetFilledAmount / takerAssetFilledAmount + // We need `fill price <= order price` for the fill to be fair to maker. + // This amounts to: + // makerAssetFilledAmount order.makerAssetAmount + // ------------------------ <= ----------------------- + // takerAssetFilledAmount order.takerAssetAmount + // or, equivalently: + // makerAssetFilledAmount * order.takerAssetAmount <= + // order.makerAssetAmount * takerAssetFilledAmount + // NOTE: This assertion should never fail, it is here + // as an extra defence against potential bugs. + require( + safeMul(makerAssetFilledAmount, order.takerAssetAmount) + <= + safeMul(order.makerAssetAmount, takerAssetFilledAmount), + "INVALID_FILL_PRICE" + ); + } + + /// @dev Validates context for cancelOrder. Succeeds or throws. + /// @param order to be cancelled. + /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. + function assertValidCancel( + Order memory order, + OrderInfo memory orderInfo + ) + internal + view + { + // Ensure order is valid + // An order can only be cancelled if its status is FILLABLE. + require( + orderInfo.orderStatus == uint8(OrderStatus.FILLABLE), + "ORDER_UNFILLABLE" + ); + + // Validate sender is allowed to cancel this order + if (order.senderAddress != address(0)) { + require( + order.senderAddress == msg.sender, + "INVALID_SENDER" + ); + } + + // Validate transaction signed by maker + address makerAddress = getCurrentContextAddress(); + require( + order.makerAddress == makerAddress, + "INVALID_MAKER" + ); + } + + /// @dev Calculates amounts filled and fees paid by maker and taker. + /// @param order to be filled. + /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. + /// @return fillResults Amounts filled and fees paid by maker and taker. + function calculateFillResults( + Order memory order, + uint256 takerAssetFilledAmount + ) + internal + pure + returns (FillResults memory fillResults) + { + // Compute proportional transfer amounts + fillResults.takerAssetFilledAmount = takerAssetFilledAmount; + fillResults.makerAssetFilledAmount = safeGetPartialAmountFloor( + takerAssetFilledAmount, + order.takerAssetAmount, + order.makerAssetAmount + ); + fillResults.makerFeePaid = safeGetPartialAmountFloor( + fillResults.makerAssetFilledAmount, + order.makerAssetAmount, + order.makerFee + ); + fillResults.takerFeePaid = safeGetPartialAmountFloor( + takerAssetFilledAmount, + order.takerAssetAmount, + order.takerFee + ); + + return fillResults; + } + + /// @dev Settles an order by transferring assets between counterparties. + /// @param order Order struct containing order specifications. + /// @param takerAddress Address selling takerAsset and buying makerAsset. + /// @param fillResults Amounts to be filled and fees paid by maker and taker. + function settleOrder( + LibOrder.Order memory order, + address takerAddress, + LibFillResults.FillResults memory fillResults + ) + private + { + bytes memory zrxAssetData = ZRX_ASSET_DATA; + dispatchTransferFrom( + order.makerAssetData, + order.makerAddress, + takerAddress, + fillResults.makerAssetFilledAmount + ); + dispatchTransferFrom( + order.takerAssetData, + takerAddress, + order.makerAddress, + fillResults.takerAssetFilledAmount + ); + dispatchTransferFrom( + zrxAssetData, + order.makerAddress, + order.feeRecipientAddress, + fillResults.makerFeePaid + ); + dispatchTransferFrom( + zrxAssetData, + takerAddress, + order.feeRecipientAddress, + fillResults.takerFeePaid + ); + } +} diff --git a/contracts/exchange/contracts/exchange/MixinMatchOrders.sol b/contracts/exchange/contracts/exchange/MixinMatchOrders.sol new file mode 100644 index 000000000..9ec8d3654 --- /dev/null +++ b/contracts/exchange/contracts/exchange/MixinMatchOrders.sol @@ -0,0 +1,335 @@ +/* + Copyright 2018 ZeroEx Intl. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +pragma solidity ^0.4.24; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/utils/ReentrancyGuard.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibConstants.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibMath.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibOrder.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibFillResults.sol"; +import "./mixins/MExchangeCore.sol"; +import "./mixins/MMatchOrders.sol"; +import "./mixins/MTransactions.sol"; +import "./mixins/MAssetProxyDispatcher.sol"; + + +contract MixinMatchOrders is + ReentrancyGuard, + LibConstants, + LibMath, + MAssetProxyDispatcher, + MExchangeCore, + MMatchOrders, + MTransactions +{ + /// @dev Match two complementary orders that have a profitable spread. + /// Each order is filled at their respective price point. However, the calculations are + /// carried out as though the orders are both being filled at the right order's price point. + /// The profit made by the left order goes to the taker (who matched the two orders). + /// @param leftOrder First order to match. + /// @param rightOrder Second order to match. + /// @param leftSignature Proof that order was created by the left maker. + /// @param rightSignature Proof that order was created by the right maker. + /// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders. + function matchOrders( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + bytes memory leftSignature, + bytes memory rightSignature + ) + public + nonReentrant + returns (LibFillResults.MatchedFillResults memory matchedFillResults) + { + // We assume that rightOrder.takerAssetData == leftOrder.makerAssetData and rightOrder.makerAssetData == leftOrder.takerAssetData. + // If this assumption isn't true, the match will fail at signature validation. + rightOrder.makerAssetData = leftOrder.takerAssetData; + rightOrder.takerAssetData = leftOrder.makerAssetData; + + // Get left & right order info + LibOrder.OrderInfo memory leftOrderInfo = getOrderInfo(leftOrder); + LibOrder.OrderInfo memory rightOrderInfo = getOrderInfo(rightOrder); + + // Fetch taker address + address takerAddress = getCurrentContextAddress(); + + // Either our context is valid or we revert + assertFillableOrder( + leftOrder, + leftOrderInfo, + takerAddress, + leftSignature + ); + assertFillableOrder( + rightOrder, + rightOrderInfo, + takerAddress, + rightSignature + ); + assertValidMatch(leftOrder, rightOrder); + + // Compute proportional fill amounts + matchedFillResults = calculateMatchedFillResults( + leftOrder, + rightOrder, + leftOrderInfo.orderTakerAssetFilledAmount, + rightOrderInfo.orderTakerAssetFilledAmount + ); + + // Validate fill contexts + assertValidFill( + leftOrder, + leftOrderInfo, + matchedFillResults.left.takerAssetFilledAmount, + matchedFillResults.left.takerAssetFilledAmount, + matchedFillResults.left.makerAssetFilledAmount + ); + assertValidFill( + rightOrder, + rightOrderInfo, + matchedFillResults.right.takerAssetFilledAmount, + matchedFillResults.right.takerAssetFilledAmount, + matchedFillResults.right.makerAssetFilledAmount + ); + + // Update exchange state + updateFilledState( + leftOrder, + takerAddress, + leftOrderInfo.orderHash, + leftOrderInfo.orderTakerAssetFilledAmount, + matchedFillResults.left + ); + updateFilledState( + rightOrder, + takerAddress, + rightOrderInfo.orderHash, + rightOrderInfo.orderTakerAssetFilledAmount, + matchedFillResults.right + ); + + // Settle matched orders. Succeeds or throws. + settleMatchedOrders( + leftOrder, + rightOrder, + takerAddress, + matchedFillResults + ); + + return matchedFillResults; + } + + /// @dev Validates context for matchOrders. Succeeds or throws. + /// @param leftOrder First order to match. + /// @param rightOrder Second order to match. + function assertValidMatch( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder + ) + internal + pure + { + // Make sure there is a profitable spread. + // There is a profitable spread iff the cost per unit bought (OrderA.MakerAmount/OrderA.TakerAmount) for each order is greater + // than the profit per unit sold of the matched order (OrderB.TakerAmount/OrderB.MakerAmount). + // This is satisfied by the equations below: + // / >= / + // AND + // / >= / + // These equations can be combined to get the following: + require( + safeMul(leftOrder.makerAssetAmount, rightOrder.makerAssetAmount) >= + safeMul(leftOrder.takerAssetAmount, rightOrder.takerAssetAmount), + "NEGATIVE_SPREAD_REQUIRED" + ); + } + + /// @dev Calculates fill amounts for the matched orders. + /// Each order is filled at their respective price point. However, the calculations are + /// carried out as though the orders are both being filled at the right order's price point. + /// The profit made by the leftOrder order goes to the taker (who matched the two orders). + /// @param leftOrder First order to match. + /// @param rightOrder Second order to match. + /// @param leftOrderTakerAssetFilledAmount Amount of left order already filled. + /// @param rightOrderTakerAssetFilledAmount Amount of right order already filled. + /// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders. + function calculateMatchedFillResults( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + uint256 leftOrderTakerAssetFilledAmount, + uint256 rightOrderTakerAssetFilledAmount + ) + internal + pure + returns (LibFillResults.MatchedFillResults memory matchedFillResults) + { + // Derive maker asset amounts for left & right orders, given store taker assert amounts + uint256 leftTakerAssetAmountRemaining = safeSub(leftOrder.takerAssetAmount, leftOrderTakerAssetFilledAmount); + uint256 leftMakerAssetAmountRemaining = safeGetPartialAmountFloor( + leftOrder.makerAssetAmount, + leftOrder.takerAssetAmount, + leftTakerAssetAmountRemaining + ); + uint256 rightTakerAssetAmountRemaining = safeSub(rightOrder.takerAssetAmount, rightOrderTakerAssetFilledAmount); + uint256 rightMakerAssetAmountRemaining = safeGetPartialAmountFloor( + rightOrder.makerAssetAmount, + rightOrder.takerAssetAmount, + rightTakerAssetAmountRemaining + ); + + // Calculate fill results for maker and taker assets: at least one order will be fully filled. + // The maximum amount the left maker can buy is `leftTakerAssetAmountRemaining` + // The maximum amount the right maker can sell is `rightMakerAssetAmountRemaining` + // We have two distinct cases for calculating the fill results: + // Case 1. + // If the left maker can buy more than the right maker can sell, then only the right order is fully filled. + // If the left maker can buy exactly what the right maker can sell, then both orders are fully filled. + // Case 2. + // If the left maker cannot buy more than the right maker can sell, then only the left order is fully filled. + if (leftTakerAssetAmountRemaining >= rightMakerAssetAmountRemaining) { + // Case 1: Right order is fully filled + matchedFillResults.right.makerAssetFilledAmount = rightMakerAssetAmountRemaining; + matchedFillResults.right.takerAssetFilledAmount = rightTakerAssetAmountRemaining; + matchedFillResults.left.takerAssetFilledAmount = matchedFillResults.right.makerAssetFilledAmount; + // Round down to ensure the maker's exchange rate does not exceed the price specified by the order. + // We favor the maker when the exchange rate must be rounded. + matchedFillResults.left.makerAssetFilledAmount = safeGetPartialAmountFloor( + leftOrder.makerAssetAmount, + leftOrder.takerAssetAmount, + matchedFillResults.left.takerAssetFilledAmount + ); + } else { + // Case 2: Left order is fully filled + matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining; + matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining; + matchedFillResults.right.makerAssetFilledAmount = matchedFillResults.left.takerAssetFilledAmount; + // Round up to ensure the maker's exchange rate does not exceed the price specified by the order. + // We favor the maker when the exchange rate must be rounded. + matchedFillResults.right.takerAssetFilledAmount = safeGetPartialAmountCeil( + rightOrder.takerAssetAmount, + rightOrder.makerAssetAmount, + matchedFillResults.right.makerAssetFilledAmount + ); + } + + // Calculate amount given to taker + matchedFillResults.leftMakerAssetSpreadAmount = safeSub( + matchedFillResults.left.makerAssetFilledAmount, + matchedFillResults.right.takerAssetFilledAmount + ); + + // Compute fees for left order + matchedFillResults.left.makerFeePaid = safeGetPartialAmountFloor( + matchedFillResults.left.makerAssetFilledAmount, + leftOrder.makerAssetAmount, + leftOrder.makerFee + ); + matchedFillResults.left.takerFeePaid = safeGetPartialAmountFloor( + matchedFillResults.left.takerAssetFilledAmount, + leftOrder.takerAssetAmount, + leftOrder.takerFee + ); + + // Compute fees for right order + matchedFillResults.right.makerFeePaid = safeGetPartialAmountFloor( + matchedFillResults.right.makerAssetFilledAmount, + rightOrder.makerAssetAmount, + rightOrder.makerFee + ); + matchedFillResults.right.takerFeePaid = safeGetPartialAmountFloor( + matchedFillResults.right.takerAssetFilledAmount, + rightOrder.takerAssetAmount, + rightOrder.takerFee + ); + + // Return fill results + return matchedFillResults; + } + + /// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient. + /// @param leftOrder First matched order. + /// @param rightOrder Second matched order. + /// @param takerAddress Address that matched the orders. The taker receives the spread between orders as profit. + /// @param matchedFillResults Struct holding amounts to transfer between makers, taker, and fee recipients. + function settleMatchedOrders( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + address takerAddress, + LibFillResults.MatchedFillResults memory matchedFillResults + ) + private + { + bytes memory zrxAssetData = ZRX_ASSET_DATA; + // Order makers and taker + dispatchTransferFrom( + leftOrder.makerAssetData, + leftOrder.makerAddress, + rightOrder.makerAddress, + matchedFillResults.right.takerAssetFilledAmount + ); + dispatchTransferFrom( + rightOrder.makerAssetData, + rightOrder.makerAddress, + leftOrder.makerAddress, + matchedFillResults.left.takerAssetFilledAmount + ); + dispatchTransferFrom( + leftOrder.makerAssetData, + leftOrder.makerAddress, + takerAddress, + matchedFillResults.leftMakerAssetSpreadAmount + ); + + // Maker fees + dispatchTransferFrom( + zrxAssetData, + leftOrder.makerAddress, + leftOrder.feeRecipientAddress, + matchedFillResults.left.makerFeePaid + ); + dispatchTransferFrom( + zrxAssetData, + rightOrder.makerAddress, + rightOrder.feeRecipientAddress, + matchedFillResults.right.makerFeePaid + ); + + // Taker fees + if (leftOrder.feeRecipientAddress == rightOrder.feeRecipientAddress) { + dispatchTransferFrom( + zrxAssetData, + takerAddress, + leftOrder.feeRecipientAddress, + safeAdd( + matchedFillResults.left.takerFeePaid, + matchedFillResults.right.takerFeePaid + ) + ); + } else { + dispatchTransferFrom( + zrxAssetData, + takerAddress, + leftOrder.feeRecipientAddress, + matchedFillResults.left.takerFeePaid + ); + dispatchTransferFrom( + zrxAssetData, + takerAddress, + rightOrder.feeRecipientAddress, + matchedFillResults.right.takerFeePaid + ); + } + } +} diff --git a/contracts/exchange/contracts/exchange/MixinSignatureValidator.sol b/contracts/exchange/contracts/exchange/MixinSignatureValidator.sol new file mode 100644 index 000000000..91e47bb11 --- /dev/null +++ b/contracts/exchange/contracts/exchange/MixinSignatureValidator.sol @@ -0,0 +1,324 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; + +import "@0x/contracts-utils/contracts/utils/LibBytes.sol"; +import "@0x/contracts-utils/contracts/utils/ReentrancyGuard.sol"; +import "./mixins/MSignatureValidator.sol"; +import "./mixins/MTransactions.sol"; +import "@0x/contracts-interfaces/contracts/protocol/Exchange/IWallet.sol"; +import "@0x/contracts-interfaces/contracts/protocol/Exchange/IValidator.sol"; + + +contract MixinSignatureValidator is + ReentrancyGuard, + MSignatureValidator, + MTransactions +{ + using LibBytes for bytes; + + // Mapping of hash => signer => signed + mapping (bytes32 => mapping (address => bool)) public preSigned; + + // Mapping of signer => validator => approved + mapping (address => mapping (address => bool)) public allowedValidators; + + /// @dev Approves a hash on-chain using any valid signature type. + /// After presigning a hash, the preSign signature type will become valid for that hash and signer. + /// @param signerAddress Address that should have signed the given hash. + /// @param signature Proof that the hash has been signed by signer. + function preSign( + bytes32 hash, + address signerAddress, + bytes signature + ) + external + { + if (signerAddress != msg.sender) { + require( + isValidSignature( + hash, + signerAddress, + signature + ), + "INVALID_SIGNATURE" + ); + } + preSigned[hash][signerAddress] = true; + } + + /// @dev Approves/unnapproves a Validator contract to verify signatures on signer's behalf. + /// @param validatorAddress Address of Validator contract. + /// @param approval Approval or disapproval of Validator contract. + function setSignatureValidatorApproval( + address validatorAddress, + bool approval + ) + external + nonReentrant + { + address signerAddress = getCurrentContextAddress(); + allowedValidators[signerAddress][validatorAddress] = approval; + emit SignatureValidatorApproval( + signerAddress, + validatorAddress, + approval + ); + } + + /// @dev Verifies that a hash has been signed by the given signer. + /// @param hash Any 32 byte hash. + /// @param signerAddress Address that should have signed the given hash. + /// @param signature Proof that the hash has been signed by signer. + /// @return True if the address recovered from the provided signature matches the input signer address. + function isValidSignature( + bytes32 hash, + address signerAddress, + bytes memory signature + ) + public + view + returns (bool isValid) + { + require( + signature.length > 0, + "LENGTH_GREATER_THAN_0_REQUIRED" + ); + + // Pop last byte off of signature byte array. + uint8 signatureTypeRaw = uint8(signature.popLastByte()); + + // Ensure signature is supported + require( + signatureTypeRaw < uint8(SignatureType.NSignatureTypes), + "SIGNATURE_UNSUPPORTED" + ); + + SignatureType signatureType = SignatureType(signatureTypeRaw); + + // Variables are not scoped in Solidity. + uint8 v; + bytes32 r; + bytes32 s; + address recovered; + + // Always illegal signature. + // This is always an implicit option since a signer can create a + // signature array with invalid type or length. We may as well make + // it an explicit option. This aids testing and analysis. It is + // also the initialization value for the enum type. + if (signatureType == SignatureType.Illegal) { + revert("SIGNATURE_ILLEGAL"); + + // Always invalid signature. + // Like Illegal, this is always implicitly available and therefore + // offered explicitly. It can be implicitly created by providing + // a correctly formatted but incorrect signature. + } else if (signatureType == SignatureType.Invalid) { + require( + signature.length == 0, + "LENGTH_0_REQUIRED" + ); + isValid = false; + return isValid; + + // Signature using EIP712 + } else if (signatureType == SignatureType.EIP712) { + require( + signature.length == 65, + "LENGTH_65_REQUIRED" + ); + v = uint8(signature[0]); + r = signature.readBytes32(1); + s = signature.readBytes32(33); + recovered = ecrecover( + hash, + v, + r, + s + ); + isValid = signerAddress == recovered; + return isValid; + + // Signed using web3.eth_sign + } else if (signatureType == SignatureType.EthSign) { + require( + signature.length == 65, + "LENGTH_65_REQUIRED" + ); + v = uint8(signature[0]); + r = signature.readBytes32(1); + s = signature.readBytes32(33); + recovered = ecrecover( + keccak256(abi.encodePacked( + "\x19Ethereum Signed Message:\n32", + hash + )), + v, + r, + s + ); + isValid = signerAddress == recovered; + return isValid; + + // Signature verified by wallet contract. + // If used with an order, the maker of the order is the wallet contract. + } else if (signatureType == SignatureType.Wallet) { + isValid = isValidWalletSignature( + hash, + signerAddress, + signature + ); + return isValid; + + // Signature verified by validator contract. + // If used with an order, the maker of the order can still be an EOA. + // A signature using this type should be encoded as: + // | Offset | Length | Contents | + // | 0x00 | x | Signature to validate | + // | 0x00 + x | 20 | Address of validator contract | + // | 0x14 + x | 1 | Signature type is always "\x06" | + } else if (signatureType == SignatureType.Validator) { + // Pop last 20 bytes off of signature byte array. + address validatorAddress = signature.popLast20Bytes(); + + // Ensure signer has approved validator. + if (!allowedValidators[signerAddress][validatorAddress]) { + return false; + } + isValid = isValidValidatorSignature( + validatorAddress, + hash, + signerAddress, + signature + ); + return isValid; + + // Signer signed hash previously using the preSign function. + } else if (signatureType == SignatureType.PreSigned) { + isValid = preSigned[hash][signerAddress]; + return isValid; + } + + // Anything else is illegal (We do not return false because + // the signature may actually be valid, just not in a format + // that we currently support. In this case returning false + // may lead the caller to incorrectly believe that the + // signature was invalid.) + revert("SIGNATURE_UNSUPPORTED"); + } + + /// @dev Verifies signature using logic defined by Wallet contract. + /// @param hash Any 32 byte hash. + /// @param walletAddress Address that should have signed the given hash + /// and defines its own signature verification method. + /// @param signature Proof that the hash has been signed by signer. + /// @return True if signature is valid for given wallet.. + function isValidWalletSignature( + bytes32 hash, + address walletAddress, + bytes signature + ) + internal + view + returns (bool isValid) + { + bytes memory callData = abi.encodeWithSelector( + IWallet(walletAddress).isValidSignature.selector, + hash, + signature + ); + assembly { + let cdStart := add(callData, 32) + let success := staticcall( + gas, // forward all gas + walletAddress, // address of Wallet contract + cdStart, // pointer to start of input + mload(callData), // length of input + cdStart, // write output over input + 32 // output size is 32 bytes + ) + + switch success + case 0 { + // Revert with `Error("WALLET_ERROR")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000000c57414c4c45545f4552524f5200000000000000000000000000000000) + mstore(96, 0) + revert(0, 100) + } + case 1 { + // Signature is valid if call did not revert and returned true + isValid := mload(cdStart) + } + } + return isValid; + } + + /// @dev Verifies signature using logic defined by Validator contract. + /// @param validatorAddress Address of validator contract. + /// @param hash Any 32 byte hash. + /// @param signerAddress Address that should have signed the given hash. + /// @param signature Proof that the hash has been signed by signer. + /// @return True if the address recovered from the provided signature matches the input signer address. + function isValidValidatorSignature( + address validatorAddress, + bytes32 hash, + address signerAddress, + bytes signature + ) + internal + view + returns (bool isValid) + { + bytes memory callData = abi.encodeWithSelector( + IValidator(signerAddress).isValidSignature.selector, + hash, + signerAddress, + signature + ); + assembly { + let cdStart := add(callData, 32) + let success := staticcall( + gas, // forward all gas + validatorAddress, // address of Validator contract + cdStart, // pointer to start of input + mload(callData), // length of input + cdStart, // write output over input + 32 // output size is 32 bytes + ) + + switch success + case 0 { + // Revert with `Error("VALIDATOR_ERROR")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000000f56414c494441544f525f4552524f5200000000000000000000000000) + mstore(96, 0) + revert(0, 100) + } + case 1 { + // Signature is valid if call did not revert and returned true + isValid := mload(cdStart) + } + } + return isValid; + } +} diff --git a/contracts/exchange/contracts/exchange/MixinTransactions.sol b/contracts/exchange/contracts/exchange/MixinTransactions.sol new file mode 100644 index 000000000..40d4e6f36 --- /dev/null +++ b/contracts/exchange/contracts/exchange/MixinTransactions.sol @@ -0,0 +1,152 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +pragma solidity ^0.4.24; + +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibExchangeErrors.sol"; +import "./mixins/MSignatureValidator.sol"; +import "./mixins/MTransactions.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibEIP712.sol"; + + +contract MixinTransactions is + LibEIP712, + MSignatureValidator, + MTransactions +{ + // Mapping of transaction hash => executed + // This prevents transactions from being executed more than once. + mapping (bytes32 => bool) public transactions; + + // Address of current transaction signer + address public currentContextAddress; + + /// @dev Executes an exchange method call in the context of signer. + /// @param salt Arbitrary number to ensure uniqueness of transaction hash. + /// @param signerAddress Address of transaction signer. + /// @param data AbiV2 encoded calldata. + /// @param signature Proof of signer transaction by signer. + function executeTransaction( + uint256 salt, + address signerAddress, + bytes data, + bytes signature + ) + external + { + // Prevent reentrancy + require( + currentContextAddress == address(0), + "REENTRANCY_ILLEGAL" + ); + + bytes32 transactionHash = hashEIP712Message(hashZeroExTransaction( + salt, + signerAddress, + data + )); + + // Validate transaction has not been executed + require( + !transactions[transactionHash], + "INVALID_TX_HASH" + ); + + // Transaction always valid if signer is sender of transaction + if (signerAddress != msg.sender) { + // Validate signature + require( + isValidSignature( + transactionHash, + signerAddress, + signature + ), + "INVALID_TX_SIGNATURE" + ); + + // Set the current transaction signer + currentContextAddress = signerAddress; + } + + // Execute transaction + transactions[transactionHash] = true; + require( + address(this).delegatecall(data), + "FAILED_EXECUTION" + ); + + // Reset current transaction signer if it was previously updated + if (signerAddress != msg.sender) { + currentContextAddress = address(0); + } + } + + /// @dev Calculates EIP712 hash of the Transaction. + /// @param salt Arbitrary number to ensure uniqueness of transaction hash. + /// @param signerAddress Address of transaction signer. + /// @param data AbiV2 encoded calldata. + /// @return EIP712 hash of the Transaction. + function hashZeroExTransaction( + uint256 salt, + address signerAddress, + bytes memory data + ) + internal + pure + returns (bytes32 result) + { + bytes32 schemaHash = EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH; + bytes32 dataHash = keccak256(data); + + // Assembly for more efficiently computing: + // keccak256(abi.encodePacked( + // EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH, + // salt, + // bytes32(signerAddress), + // keccak256(data) + // )); + + assembly { + // Load free memory pointer + let memPtr := mload(64) + + mstore(memPtr, schemaHash) // hash of schema + mstore(add(memPtr, 32), salt) // salt + mstore(add(memPtr, 64), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff)) // signerAddress + mstore(add(memPtr, 96), dataHash) // hash of data + + // Compute hash + result := keccak256(memPtr, 128) + } + return result; + } + + /// @dev The current function will be called in the context of this address (either 0x transaction signer or `msg.sender`). + /// If calling a fill function, this address will represent the taker. + /// If calling a cancel function, this address will represent the maker. + /// @return Signer of 0x transaction if entry point is `executeTransaction`. + /// `msg.sender` if entry point is any other function. + function getCurrentContextAddress() + internal + view + returns (address) + { + address currentContextAddress_ = currentContextAddress; + address contextAddress = currentContextAddress_ == address(0) ? msg.sender : currentContextAddress_; + return contextAddress; + } +} diff --git a/contracts/exchange/contracts/exchange/MixinWrapperFunctions.sol b/contracts/exchange/contracts/exchange/MixinWrapperFunctions.sol new file mode 100644 index 000000000..e5a512d7c --- /dev/null +++ b/contracts/exchange/contracts/exchange/MixinWrapperFunctions.sol @@ -0,0 +1,426 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/utils/ReentrancyGuard.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibMath.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibOrder.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibFillResults.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibAbiEncoder.sol"; +import "./mixins/MExchangeCore.sol"; +import "./mixins/MWrapperFunctions.sol"; + + +contract MixinWrapperFunctions is + ReentrancyGuard, + LibMath, + LibFillResults, + LibAbiEncoder, + MExchangeCore, + MWrapperFunctions +{ + /// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + function fillOrKillOrder( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + public + nonReentrant + returns (FillResults memory fillResults) + { + fillResults = fillOrKillOrderInternal( + order, + takerAssetFillAmount, + signature + ); + return fillResults; + } + + /// @dev Fills the input order. + /// Returns false if the transaction would otherwise revert. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return Amounts filled and fees paid by maker and taker. + function fillOrderNoThrow( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + public + returns (FillResults memory fillResults) + { + // ABI encode calldata for `fillOrder` + bytes memory fillOrderCalldata = abiEncodeFillOrder( + order, + takerAssetFillAmount, + signature + ); + + // Delegate to `fillOrder` and handle any exceptions gracefully + assembly { + let success := delegatecall( + gas, // forward all gas + address, // call address of this contract + add(fillOrderCalldata, 32), // pointer to start of input (skip array length in first 32 bytes) + mload(fillOrderCalldata), // length of input + fillOrderCalldata, // write output over input + 128 // output size is 128 bytes + ) + if success { + mstore(fillResults, mload(fillOrderCalldata)) + mstore(add(fillResults, 32), mload(add(fillOrderCalldata, 32))) + mstore(add(fillResults, 64), mload(add(fillOrderCalldata, 64))) + mstore(add(fillResults, 96), mload(add(fillOrderCalldata, 96))) + } + } + // fillResults values will be 0 by default if call was unsuccessful + return fillResults; + } + + /// @dev Synchronously executes multiple calls of fillOrder. + /// @param orders Array of order specifications. + /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders. + /// @param signatures Proofs that orders have been created by makers. + /// @return Amounts filled and fees paid by makers and taker. + /// NOTE: makerAssetFilledAmount and takerAssetFilledAmount may include amounts filled of different assets. + function batchFillOrders( + LibOrder.Order[] memory orders, + uint256[] memory takerAssetFillAmounts, + bytes[] memory signatures + ) + public + nonReentrant + returns (FillResults memory totalFillResults) + { + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + FillResults memory singleFillResults = fillOrderInternal( + orders[i], + takerAssetFillAmounts[i], + signatures[i] + ); + addFillResults(totalFillResults, singleFillResults); + } + return totalFillResults; + } + + /// @dev Synchronously executes multiple calls of fillOrKill. + /// @param orders Array of order specifications. + /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders. + /// @param signatures Proofs that orders have been created by makers. + /// @return Amounts filled and fees paid by makers and taker. + /// NOTE: makerAssetFilledAmount and takerAssetFilledAmount may include amounts filled of different assets. + function batchFillOrKillOrders( + LibOrder.Order[] memory orders, + uint256[] memory takerAssetFillAmounts, + bytes[] memory signatures + ) + public + nonReentrant + returns (FillResults memory totalFillResults) + { + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + FillResults memory singleFillResults = fillOrKillOrderInternal( + orders[i], + takerAssetFillAmounts[i], + signatures[i] + ); + addFillResults(totalFillResults, singleFillResults); + } + return totalFillResults; + } + + /// @dev Fills an order with specified parameters and ECDSA signature. + /// Returns false if the transaction would otherwise revert. + /// @param orders Array of order specifications. + /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders. + /// @param signatures Proofs that orders have been created by makers. + /// @return Amounts filled and fees paid by makers and taker. + /// NOTE: makerAssetFilledAmount and takerAssetFilledAmount may include amounts filled of different assets. + function batchFillOrdersNoThrow( + LibOrder.Order[] memory orders, + uint256[] memory takerAssetFillAmounts, + bytes[] memory signatures + ) + public + returns (FillResults memory totalFillResults) + { + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + FillResults memory singleFillResults = fillOrderNoThrow( + orders[i], + takerAssetFillAmounts[i], + signatures[i] + ); + addFillResults(totalFillResults, singleFillResults); + } + return totalFillResults; + } + + /// @dev Synchronously executes multiple calls of fillOrder until total amount of takerAsset is sold by taker. + /// @param orders Array of order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signatures Proofs that orders have been created by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketSellOrders( + LibOrder.Order[] memory orders, + uint256 takerAssetFillAmount, + bytes[] memory signatures + ) + public + nonReentrant + returns (FillResults memory totalFillResults) + { + bytes memory takerAssetData = orders[0].takerAssetData; + + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + + // We assume that asset being sold by taker is the same for each order. + // Rather than passing this in as calldata, we use the takerAssetData from the first order in all later orders. + orders[i].takerAssetData = takerAssetData; + + // Calculate the remaining amount of takerAsset to sell + uint256 remainingTakerAssetFillAmount = safeSub(takerAssetFillAmount, totalFillResults.takerAssetFilledAmount); + + // Attempt to sell the remaining amount of takerAsset + FillResults memory singleFillResults = fillOrderInternal( + orders[i], + remainingTakerAssetFillAmount, + signatures[i] + ); + + // Update amounts filled and fees paid by maker and taker + addFillResults(totalFillResults, singleFillResults); + + // Stop execution if the entire amount of takerAsset has been sold + if (totalFillResults.takerAssetFilledAmount >= takerAssetFillAmount) { + break; + } + } + return totalFillResults; + } + + /// @dev Synchronously executes multiple calls of fillOrder until total amount of takerAsset is sold by taker. + /// Returns false if the transaction would otherwise revert. + /// @param orders Array of order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketSellOrdersNoThrow( + LibOrder.Order[] memory orders, + uint256 takerAssetFillAmount, + bytes[] memory signatures + ) + public + returns (FillResults memory totalFillResults) + { + bytes memory takerAssetData = orders[0].takerAssetData; + + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + + // We assume that asset being sold by taker is the same for each order. + // Rather than passing this in as calldata, we use the takerAssetData from the first order in all later orders. + orders[i].takerAssetData = takerAssetData; + + // Calculate the remaining amount of takerAsset to sell + uint256 remainingTakerAssetFillAmount = safeSub(takerAssetFillAmount, totalFillResults.takerAssetFilledAmount); + + // Attempt to sell the remaining amount of takerAsset + FillResults memory singleFillResults = fillOrderNoThrow( + orders[i], + remainingTakerAssetFillAmount, + signatures[i] + ); + + // Update amounts filled and fees paid by maker and taker + addFillResults(totalFillResults, singleFillResults); + + // Stop execution if the entire amount of takerAsset has been sold + if (totalFillResults.takerAssetFilledAmount >= takerAssetFillAmount) { + break; + } + } + return totalFillResults; + } + + /// @dev Synchronously executes multiple calls of fillOrder until total amount of makerAsset is bought by taker. + /// @param orders Array of order specifications. + /// @param makerAssetFillAmount Desired amount of makerAsset to buy. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketBuyOrders( + LibOrder.Order[] memory orders, + uint256 makerAssetFillAmount, + bytes[] memory signatures + ) + public + nonReentrant + returns (FillResults memory totalFillResults) + { + bytes memory makerAssetData = orders[0].makerAssetData; + + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + + // We assume that asset being bought by taker is the same for each order. + // Rather than passing this in as calldata, we copy the makerAssetData from the first order onto all later orders. + orders[i].makerAssetData = makerAssetData; + + // Calculate the remaining amount of makerAsset to buy + uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount); + + // Convert the remaining amount of makerAsset to buy into remaining amount + // of takerAsset to sell, assuming entire amount can be sold in the current order + uint256 remainingTakerAssetFillAmount = getPartialAmountFloor( + orders[i].takerAssetAmount, + orders[i].makerAssetAmount, + remainingMakerAssetFillAmount + ); + + // Attempt to sell the remaining amount of takerAsset + FillResults memory singleFillResults = fillOrderInternal( + orders[i], + remainingTakerAssetFillAmount, + signatures[i] + ); + + // Update amounts filled and fees paid by maker and taker + addFillResults(totalFillResults, singleFillResults); + + // Stop execution if the entire amount of makerAsset has been bought + if (totalFillResults.makerAssetFilledAmount >= makerAssetFillAmount) { + break; + } + } + return totalFillResults; + } + + /// @dev Synchronously executes multiple fill orders in a single transaction until total amount is bought by taker. + /// Returns false if the transaction would otherwise revert. + /// @param orders Array of order specifications. + /// @param makerAssetFillAmount Desired amount of makerAsset to buy. + /// @param signatures Proofs that orders have been signed by makers. + /// @return Amounts filled and fees paid by makers and taker. + function marketBuyOrdersNoThrow( + LibOrder.Order[] memory orders, + uint256 makerAssetFillAmount, + bytes[] memory signatures + ) + public + returns (FillResults memory totalFillResults) + { + bytes memory makerAssetData = orders[0].makerAssetData; + + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + + // We assume that asset being bought by taker is the same for each order. + // Rather than passing this in as calldata, we copy the makerAssetData from the first order onto all later orders. + orders[i].makerAssetData = makerAssetData; + + // Calculate the remaining amount of makerAsset to buy + uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount); + + // Convert the remaining amount of makerAsset to buy into remaining amount + // of takerAsset to sell, assuming entire amount can be sold in the current order + uint256 remainingTakerAssetFillAmount = getPartialAmountFloor( + orders[i].takerAssetAmount, + orders[i].makerAssetAmount, + remainingMakerAssetFillAmount + ); + + // Attempt to sell the remaining amount of takerAsset + FillResults memory singleFillResults = fillOrderNoThrow( + orders[i], + remainingTakerAssetFillAmount, + signatures[i] + ); + + // Update amounts filled and fees paid by maker and taker + addFillResults(totalFillResults, singleFillResults); + + // Stop execution if the entire amount of makerAsset has been bought + if (totalFillResults.makerAssetFilledAmount >= makerAssetFillAmount) { + break; + } + } + return totalFillResults; + } + + /// @dev Synchronously cancels multiple orders in a single transaction. + /// @param orders Array of order specifications. + function batchCancelOrders(LibOrder.Order[] memory orders) + public + nonReentrant + { + uint256 ordersLength = orders.length; + for (uint256 i = 0; i != ordersLength; i++) { + cancelOrderInternal(orders[i]); + } + } + + /// @dev Fetches information for all passed in orders. + /// @param orders Array of order specifications. + /// @return Array of OrderInfo instances that correspond to each order. + function getOrdersInfo(LibOrder.Order[] memory orders) + public + view + returns (LibOrder.OrderInfo[] memory) + { + uint256 ordersLength = orders.length; + LibOrder.OrderInfo[] memory ordersInfo = new LibOrder.OrderInfo[](ordersLength); + for (uint256 i = 0; i != ordersLength; i++) { + ordersInfo[i] = getOrderInfo(orders[i]); + } + return ordersInfo; + } + + /// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + function fillOrKillOrderInternal( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + internal + returns (FillResults memory fillResults) + { + fillResults = fillOrderInternal( + order, + takerAssetFillAmount, + signature + ); + require( + fillResults.takerAssetFilledAmount == takerAssetFillAmount, + "COMPLETE_FILL_FAILED" + ); + return fillResults; + } +} diff --git a/contracts/exchange/contracts/exchange/mixins/MAssetProxyDispatcher.sol b/contracts/exchange/contracts/exchange/mixins/MAssetProxyDispatcher.sol new file mode 100644 index 000000000..05c2c4c0b --- /dev/null +++ b/contracts/exchange/contracts/exchange/mixins/MAssetProxyDispatcher.sol @@ -0,0 +1,45 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; + +import "@0x/contracts-interfaces/contracts/protocol/Exchange/IAssetProxyDispatcher.sol"; + + +contract MAssetProxyDispatcher is + IAssetProxyDispatcher +{ + // Logs registration of new asset proxy + event AssetProxyRegistered( + bytes4 id, // Id of new registered AssetProxy. + address assetProxy // Address of new registered AssetProxy. + ); + + /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws. + /// @param assetData Byte array encoded for the asset. + /// @param from Address to transfer token from. + /// @param to Address to transfer token to. + /// @param amount Amount of token to transfer. + function dispatchTransferFrom( + bytes memory assetData, + address from, + address to, + uint256 amount + ) + internal; +} diff --git a/contracts/exchange/contracts/exchange/mixins/MExchangeCore.sol b/contracts/exchange/contracts/exchange/mixins/MExchangeCore.sol new file mode 100644 index 000000000..122cdc8a0 --- /dev/null +++ b/contracts/exchange/contracts/exchange/mixins/MExchangeCore.sol @@ -0,0 +1,157 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibOrder.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibFillResults.sol"; +import "@0x/contracts-interfaces/contracts/protocol/Exchange/IExchangeCore.sol"; + + +contract MExchangeCore is + IExchangeCore +{ + // Fill event is emitted whenever an order is filled. + event Fill( + address indexed makerAddress, // Address that created the order. + address indexed feeRecipientAddress, // Address that received fees. + address takerAddress, // Address that filled the order. + address senderAddress, // Address that called the Exchange contract (msg.sender). + uint256 makerAssetFilledAmount, // Amount of makerAsset sold by maker and bought by taker. + uint256 takerAssetFilledAmount, // Amount of takerAsset sold by taker and bought by maker. + uint256 makerFeePaid, // Amount of ZRX paid to feeRecipient by maker. + uint256 takerFeePaid, // Amount of ZRX paid to feeRecipient by taker. + bytes32 indexed orderHash, // EIP712 hash of order (see LibOrder.getOrderHash). + bytes makerAssetData, // Encoded data specific to makerAsset. + bytes takerAssetData // Encoded data specific to takerAsset. + ); + + // Cancel event is emitted whenever an individual order is cancelled. + event Cancel( + address indexed makerAddress, // Address that created the order. + address indexed feeRecipientAddress, // Address that would have recieved fees if order was filled. + address senderAddress, // Address that called the Exchange contract (msg.sender). + bytes32 indexed orderHash, // EIP712 hash of order (see LibOrder.getOrderHash). + bytes makerAssetData, // Encoded data specific to makerAsset. + bytes takerAssetData // Encoded data specific to takerAsset. + ); + + // CancelUpTo event is emitted whenever `cancelOrdersUpTo` is executed succesfully. + event CancelUpTo( + address indexed makerAddress, // Orders cancelled must have been created by this address. + address indexed senderAddress, // Orders cancelled must have a `senderAddress` equal to this address. + uint256 orderEpoch // Orders with specified makerAddress and senderAddress with a salt less than this value are considered cancelled. + ); + + /// @dev Fills the input order. + /// @param order Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + /// @return Amounts filled and fees paid by maker and taker. + function fillOrderInternal( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + internal + returns (LibFillResults.FillResults memory fillResults); + + /// @dev After calling, the order can not be filled anymore. + /// @param order Order struct containing order specifications. + function cancelOrderInternal(LibOrder.Order memory order) + internal; + + /// @dev Updates state with results of a fill order. + /// @param order that was filled. + /// @param takerAddress Address of taker who filled the order. + /// @param orderTakerAssetFilledAmount Amount of order already filled. + /// @return fillResults Amounts filled and fees paid by maker and taker. + function updateFilledState( + LibOrder.Order memory order, + address takerAddress, + bytes32 orderHash, + uint256 orderTakerAssetFilledAmount, + LibFillResults.FillResults memory fillResults + ) + internal; + + /// @dev Updates state with results of cancelling an order. + /// State is only updated if the order is currently fillable. + /// Otherwise, updating state would have no effect. + /// @param order that was cancelled. + /// @param orderHash Hash of order that was cancelled. + function updateCancelledState( + LibOrder.Order memory order, + bytes32 orderHash + ) + internal; + + /// @dev Validates context for fillOrder. Succeeds or throws. + /// @param order to be filled. + /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. + /// @param takerAddress Address of order taker. + /// @param signature Proof that the orders was created by its maker. + function assertFillableOrder( + LibOrder.Order memory order, + LibOrder.OrderInfo memory orderInfo, + address takerAddress, + bytes memory signature + ) + internal + view; + + /// @dev Validates context for fillOrder. Succeeds or throws. + /// @param order to be filled. + /// @param orderInfo Status, orderHash, and amount already filled of order. + /// @param takerAssetFillAmount Desired amount of order to fill by taker. + /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. + /// @param makerAssetFilledAmount Amount of makerAsset that will be transfered. + function assertValidFill( + LibOrder.Order memory order, + LibOrder.OrderInfo memory orderInfo, + uint256 takerAssetFillAmount, + uint256 takerAssetFilledAmount, + uint256 makerAssetFilledAmount + ) + internal + view; + + /// @dev Validates context for cancelOrder. Succeeds or throws. + /// @param order to be cancelled. + /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. + function assertValidCancel( + LibOrder.Order memory order, + LibOrder.OrderInfo memory orderInfo + ) + internal + view; + + /// @dev Calculates amounts filled and fees paid by maker and taker. + /// @param order to be filled. + /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. + /// @return fillResults Amounts filled and fees paid by maker and taker. + function calculateFillResults( + LibOrder.Order memory order, + uint256 takerAssetFilledAmount + ) + internal + pure + returns (LibFillResults.FillResults memory fillResults); + +} diff --git a/contracts/exchange/contracts/exchange/mixins/MMatchOrders.sol b/contracts/exchange/contracts/exchange/mixins/MMatchOrders.sol new file mode 100644 index 000000000..4a056b980 --- /dev/null +++ b/contracts/exchange/contracts/exchange/mixins/MMatchOrders.sol @@ -0,0 +1,58 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +pragma solidity ^0.4.24; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibOrder.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibFillResults.sol"; +import "@0x/contracts-interfaces/contracts/protocol/Exchange/IMatchOrders.sol"; + + +contract MMatchOrders is + IMatchOrders +{ + /// @dev Validates context for matchOrders. Succeeds or throws. + /// @param leftOrder First order to match. + /// @param rightOrder Second order to match. + function assertValidMatch( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder + ) + internal + pure; + + /// @dev Calculates fill amounts for the matched orders. + /// Each order is filled at their respective price point. However, the calculations are + /// carried out as though the orders are both being filled at the right order's price point. + /// The profit made by the leftOrder order goes to the taker (who matched the two orders). + /// @param leftOrder First order to match. + /// @param rightOrder Second order to match. + /// @param leftOrderTakerAssetFilledAmount Amount of left order already filled. + /// @param rightOrderTakerAssetFilledAmount Amount of right order already filled. + /// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders. + function calculateMatchedFillResults( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + uint256 leftOrderTakerAssetFilledAmount, + uint256 rightOrderTakerAssetFilledAmount + ) + internal + pure + returns (LibFillResults.MatchedFillResults memory matchedFillResults); + +} diff --git a/contracts/exchange/contracts/exchange/mixins/MSignatureValidator.sol b/contracts/exchange/contracts/exchange/mixins/MSignatureValidator.sol new file mode 100644 index 000000000..6407760d4 --- /dev/null +++ b/contracts/exchange/contracts/exchange/mixins/MSignatureValidator.sol @@ -0,0 +1,75 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; + +import "@0x/contracts-interfaces/contracts/protocol/Exchange/ISignatureValidator.sol"; + + +contract MSignatureValidator is + ISignatureValidator +{ + event SignatureValidatorApproval( + address indexed signerAddress, // Address that approves or disapproves a contract to verify signatures. + address indexed validatorAddress, // Address of signature validator contract. + bool approved // Approval or disapproval of validator contract. + ); + + // Allowed signature types. + enum SignatureType { + Illegal, // 0x00, default value + Invalid, // 0x01 + EIP712, // 0x02 + EthSign, // 0x03 + Wallet, // 0x04 + Validator, // 0x05 + PreSigned, // 0x06 + NSignatureTypes // 0x07, number of signature types. Always leave at end. + } + + /// @dev Verifies signature using logic defined by Wallet contract. + /// @param hash Any 32 byte hash. + /// @param walletAddress Address that should have signed the given hash + /// and defines its own signature verification method. + /// @param signature Proof that the hash has been signed by signer. + /// @return True if the address recovered from the provided signature matches the input signer address. + function isValidWalletSignature( + bytes32 hash, + address walletAddress, + bytes signature + ) + internal + view + returns (bool isValid); + + /// @dev Verifies signature using logic defined by Validator contract. + /// @param validatorAddress Address of validator contract. + /// @param hash Any 32 byte hash. + /// @param signerAddress Address that should have signed the given hash. + /// @param signature Proof that the hash has been signed by signer. + /// @return True if the address recovered from the provided signature matches the input signer address. + function isValidValidatorSignature( + address validatorAddress, + bytes32 hash, + address signerAddress, + bytes signature + ) + internal + view + returns (bool isValid); +} diff --git a/contracts/exchange/contracts/exchange/mixins/MTransactions.sol b/contracts/exchange/contracts/exchange/mixins/MTransactions.sol new file mode 100644 index 000000000..04dd716d7 --- /dev/null +++ b/contracts/exchange/contracts/exchange/mixins/MTransactions.sol @@ -0,0 +1,58 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +pragma solidity ^0.4.24; + +import "@0x/contracts-interfaces/contracts/protocol/Exchange/ITransactions.sol"; + + +contract MTransactions is + ITransactions +{ + // Hash for the EIP712 ZeroEx Transaction Schema + bytes32 constant internal EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = keccak256(abi.encodePacked( + "ZeroExTransaction(", + "uint256 salt,", + "address signerAddress,", + "bytes data", + ")" + )); + + /// @dev Calculates EIP712 hash of the Transaction. + /// @param salt Arbitrary number to ensure uniqueness of transaction hash. + /// @param signerAddress Address of transaction signer. + /// @param data AbiV2 encoded calldata. + /// @return EIP712 hash of the Transaction. + function hashZeroExTransaction( + uint256 salt, + address signerAddress, + bytes memory data + ) + internal + pure + returns (bytes32 result); + + /// @dev The current function will be called in the context of this address (either 0x transaction signer or `msg.sender`). + /// If calling a fill function, this address will represent the taker. + /// If calling a cancel function, this address will represent the maker. + /// @return Signer of 0x transaction if entry point is `executeTransaction`. + /// `msg.sender` if entry point is any other function. + function getCurrentContextAddress() + internal + view + returns (address); +} diff --git a/contracts/exchange/contracts/exchange/mixins/MWrapperFunctions.sol b/contracts/exchange/contracts/exchange/mixins/MWrapperFunctions.sol new file mode 100644 index 000000000..6bf9fd331 --- /dev/null +++ b/contracts/exchange/contracts/exchange/mixins/MWrapperFunctions.sol @@ -0,0 +1,41 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibOrder.sol"; +import "@0x/contracts-exchange-libs/contracts/exchange-libs/LibFillResults.sol"; +import "@0x/contracts-interfaces/contracts/protocol/Exchange/IWrapperFunctions.sol"; + + +contract MWrapperFunctions is + IWrapperFunctions +{ + /// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled. + /// @param order LibOrder.Order struct containing order specifications. + /// @param takerAssetFillAmount Desired amount of takerAsset to sell. + /// @param signature Proof that order has been created by maker. + function fillOrKillOrderInternal( + LibOrder.Order memory order, + uint256 takerAssetFillAmount, + bytes memory signature + ) + internal + returns (LibFillResults.FillResults memory fillResults); +} diff --git a/contracts/exchange/contracts/test/TestAssetProxyDispatcher.sol b/contracts/exchange/contracts/test/TestAssetProxyDispatcher.sol new file mode 100644 index 000000000..dbd6df148 --- /dev/null +++ b/contracts/exchange/contracts/test/TestAssetProxyDispatcher.sol @@ -0,0 +1,37 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../exchange/MixinAssetProxyDispatcher.sol"; + + +contract TestAssetProxyDispatcher is + MixinAssetProxyDispatcher +{ + function publicDispatchTransferFrom( + bytes memory assetData, + address from, + address to, + uint256 amount + ) + public + { + dispatchTransferFrom(assetData, from, to, amount); + } +} diff --git a/contracts/exchange/contracts/test/TestExchangeInternals.sol b/contracts/exchange/contracts/test/TestExchangeInternals.sol new file mode 100644 index 000000000..ab3e23f91 --- /dev/null +++ b/contracts/exchange/contracts/test/TestExchangeInternals.sol @@ -0,0 +1,191 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../exchange/Exchange.sol"; + + +// solhint-disable no-empty-blocks +contract TestExchangeInternals is + Exchange +{ + constructor () + public + Exchange("") + {} + + /// @dev Adds properties of both FillResults instances. + /// Modifies the first FillResults instance specified. + /// Note that this function has been modified from the original + // internal version to return the FillResults. + /// @param totalFillResults Fill results instance that will be added onto. + /// @param singleFillResults Fill results instance that will be added to totalFillResults. + /// @return newTotalFillResults The result of adding singleFillResults to totalFilResults. + function publicAddFillResults(FillResults memory totalFillResults, FillResults memory singleFillResults) + public + pure + returns (FillResults memory) + { + addFillResults(totalFillResults, singleFillResults); + return totalFillResults; + } + + /// @dev Calculates amounts filled and fees paid by maker and taker. + /// @param order to be filled. + /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. + /// @return fillResults Amounts filled and fees paid by maker and taker. + function publicCalculateFillResults( + Order memory order, + uint256 takerAssetFilledAmount + ) + public + pure + returns (FillResults memory fillResults) + { + return calculateFillResults(order, takerAssetFilledAmount); + } + + /// @dev Calculates partial value given a numerator and denominator. + /// Reverts if rounding error is >= 0.1% + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to calculate partial of. + /// @return Partial value of target. + function publicSafeGetPartialAmountFloor( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (uint256 partialAmount) + { + return safeGetPartialAmountFloor(numerator, denominator, target); + } + + /// @dev Calculates partial value given a numerator and denominator. + /// Reverts if rounding error is >= 0.1% + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to calculate partial of. + /// @return Partial value of target. + function publicSafeGetPartialAmountCeil( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (uint256 partialAmount) + { + return safeGetPartialAmountCeil(numerator, denominator, target); + } + + /// @dev Calculates partial value given a numerator and denominator. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to calculate partial of. + /// @return Partial value of target. + function publicGetPartialAmountFloor( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (uint256 partialAmount) + { + return getPartialAmountFloor(numerator, denominator, target); + } + + /// @dev Calculates partial value given a numerator and denominator. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to calculate partial of. + /// @return Partial value of target. + function publicGetPartialAmountCeil( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (uint256 partialAmount) + { + return getPartialAmountCeil(numerator, denominator, target); + } + + /// @dev Checks if rounding error >= 0.1%. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to multiply with numerator/denominator. + /// @return Rounding error is present. + function publicIsRoundingErrorFloor( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (bool isError) + { + return isRoundingErrorFloor(numerator, denominator, target); + } + + /// @dev Checks if rounding error >= 0.1%. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to multiply with numerator/denominator. + /// @return Rounding error is present. + function publicIsRoundingErrorCeil( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (bool isError) + { + return isRoundingErrorCeil(numerator, denominator, target); + } + + /// @dev Updates state with results of a fill order. + /// @param order that was filled. + /// @param takerAddress Address of taker who filled the order. + /// @param orderTakerAssetFilledAmount Amount of order already filled. + /// @return fillResults Amounts filled and fees paid by maker and taker. + function publicUpdateFilledState( + Order memory order, + address takerAddress, + bytes32 orderHash, + uint256 orderTakerAssetFilledAmount, + FillResults memory fillResults + ) + public + { + updateFilledState( + order, + takerAddress, + orderHash, + orderTakerAssetFilledAmount, + fillResults + ); + } +} diff --git a/contracts/exchange/contracts/test/TestSignatureValidator.sol b/contracts/exchange/contracts/test/TestSignatureValidator.sol new file mode 100644 index 000000000..1e12a2592 --- /dev/null +++ b/contracts/exchange/contracts/test/TestSignatureValidator.sol @@ -0,0 +1,45 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../exchange/MixinSignatureValidator.sol"; +import "../exchange/MixinTransactions.sol"; + + +contract TestSignatureValidator is + MixinSignatureValidator, + MixinTransactions +{ + function publicIsValidSignature( + bytes32 hash, + address signer, + bytes memory signature + ) + public + view + returns (bool isValid) + { + isValid = isValidSignature( + hash, + signer, + signature + ); + return isValid; + } +} diff --git a/contracts/exchange/contracts/test/TestStaticCallReceiver.sol b/contracts/exchange/contracts/test/TestStaticCallReceiver.sol new file mode 100644 index 000000000..d08da7303 --- /dev/null +++ b/contracts/exchange/contracts/test/TestStaticCallReceiver.sol @@ -0,0 +1,81 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "@0x/contracts-tokens/contracts/tokens/ERC20Token/IERC20Token.sol"; + + +// solhint-disable no-unused-vars +contract TestStaticCallReceiver { + + uint256 internal state = 1; + + /// @dev Updates state and returns true. Intended to be used with `Validator` signature type. + /// @param hash Message hash that is signed. + /// @param signerAddress Address that should have signed the given hash. + /// @param signature Proof of signing. + /// @return Validity of order signature. + function isValidSignature( + bytes32 hash, + address signerAddress, + bytes signature + ) + external + returns (bool isValid) + { + updateState(); + return true; + } + + /// @dev Updates state and returns true. Intended to be used with `Wallet` signature type. + /// @param hash Message hash that is signed. + /// @param signature Proof of signing. + /// @return Validity of order signature. + function isValidSignature( + bytes32 hash, + bytes signature + ) + external + returns (bool isValid) + { + updateState(); + return true; + } + + /// @dev Approves an ERC20 token to spend tokens from this address. + /// @param token Address of ERC20 token. + /// @param spender Address that will spend tokens. + /// @param value Amount of tokens spender is approved to spend. + function approveERC20( + address token, + address spender, + uint256 value + ) + external + { + IERC20Token(token).approve(spender, value); + } + + /// @dev Increments state variable. + function updateState() + internal + { + state++; + } +} diff --git a/contracts/exchange/package.json b/contracts/exchange/package.json new file mode 100644 index 000000000..daaba038f --- /dev/null +++ b/contracts/exchange/package.json @@ -0,0 +1,94 @@ +{ + "name": "@0x/contracts-exchange", + "version": "2.2.3", + "engines": { + "node": ">=6.12" + }, + "description": "Smart contract components of 0x protocol", + "main": "lib/src/index.js", + "directories": { + "test": "test" + }, + "scripts": { + "build": "yarn pre_build && tsc -b", + "build:ci": "yarn build", + "pre_build": "run-s compile generate_contract_wrappers", + "test": "yarn run_mocha", + "rebuild_and_test": "run-s build test", + "test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov", + "test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html", + "test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha", + "run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit", + "compile": "sol-compiler", + "watch": "sol-compiler -w", + "clean": "shx rm -rf lib generated-artifacts generated-wrappers", + "generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../../node_modules/@0x/abi-gen-templates/contract.handlebars --partials '../../node_modules/@0x/abi-gen-templates/partials/**/*.handlebars' --output generated-wrappers --backend ethers", + "lint": "tslint --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts", + "coverage:report:text": "istanbul report text", + "coverage:report:html": "istanbul report html && open coverage/index.html", + "profiler:report:html": "istanbul report html && open coverage/index.html", + "coverage:report:lcov": "istanbul report lcov", + "test:circleci": "yarn test", + "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" + }, + "config": { + "abis": "generated-artifacts/@(ERC20Proxy|ERC721Proxy|Exchange|MixinAuthorizable|MultiAssetProxy|TestSignatureValidator|TestAssetProxyDispatcher|TestExchangeInternals|TestStaticCallReceiver).json" + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x-monorepo.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x-monorepo/issues" + }, + "homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md", + "devDependencies": { + "@0x/abi-gen": "^1.0.22", + "@0x/dev-utils": "^1.0.24", + "@0x/sol-compiler": "^2.0.2", + "@0x/subproviders": "^2.1.11", + "@0x/tslint-config": "^2.0.2", + "@types/bn.js": "^4.11.0", + "@types/lodash": "4.14.104", + "@types/node": "*", + "@types/yargs": "^10.0.0", + "chai": "^4.0.1", + "chai-as-promised": "^7.1.0", + "chai-bignumber": "^3.0.0", + "dirty-chai": "^2.0.1", + "ethereumjs-abi": "0.6.5", + "make-promises-safe": "^1.1.0", + "mocha": "^4.1.0", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "solhint": "^1.4.1", + "tslint": "5.11.0", + "typescript": "3.0.1", + "yargs": "^10.0.3" + }, + "dependencies": { + "@0x/base-contract": "^3.0.13", + "@0x/contracts-examples": "^1.0.6", + "@0x/contracts-asset-proxy": "^2.2.3", + "@0x/contracts-interfaces": "^1.0.6", + "@0x/contracts-libs": "^1.0.6", + "@0x/contracts-multisig": "^1.0.6", + "@0x/contracts-test-utils": "^2.0.1", + "@0x/contracts-tokens": "^1.0.6", + "@0x/contracts-utils": "^1.0.6", + "@0x/order-utils": "^3.1.2", + "@0x/types": "^1.5.2", + "@0x/typescript-typings": "^3.0.8", + "@0x/utils": "^3.0.1", + "@0x/web3-wrapper": "^3.2.4", + "@types/js-combinatorics": "^0.5.29", + "bn.js": "^4.11.8", + "ethereum-types": "^1.1.6", + "ethereumjs-util": "^5.1.1", + "lodash": "^4.17.5" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/contracts/exchange/src/artifacts/index.ts b/contracts/exchange/src/artifacts/index.ts new file mode 100644 index 000000000..43dd77e58 --- /dev/null +++ b/contracts/exchange/src/artifacts/index.ts @@ -0,0 +1,15 @@ +import { ContractArtifact } from 'ethereum-types'; + +import * as Exchange from '../../generated-artifacts/Exchange.json'; +import * as TestAssetProxyDispatcher from '../../generated-artifacts/TestAssetProxyDispatcher.json'; +import * as TestExchangeInternals from '../../generated-artifacts/TestExchangeInternals.json'; +import * as TestSignatureValidator from '../../generated-artifacts/TestSignatureValidator.json'; +import * as TestStaticCallReceiver from '../../generated-artifacts/TestStaticCallReceiver.json'; + +export const artifacts = { + Exchange: Exchange as ContractArtifact, + TestAssetProxyDispatcher: TestAssetProxyDispatcher as ContractArtifact, + TestExchangeInternals: TestExchangeInternals as ContractArtifact, + TestSignatureValidator: TestSignatureValidator as ContractArtifact, + TestStaticCallReceiver: TestStaticCallReceiver as ContractArtifact, +}; diff --git a/contracts/exchange/src/index.ts b/contracts/exchange/src/index.ts new file mode 100644 index 000000000..ba813e7ca --- /dev/null +++ b/contracts/exchange/src/index.ts @@ -0,0 +1,3 @@ +export * from './artifacts'; +export * from './wrappers'; +export * from '../test/utils'; diff --git a/contracts/exchange/src/wrappers/index.ts b/contracts/exchange/src/wrappers/index.ts new file mode 100644 index 000000000..7ff5efca2 --- /dev/null +++ b/contracts/exchange/src/wrappers/index.ts @@ -0,0 +1,5 @@ +export * from '../../generated-wrappers/exchange'; +export * from '../../generated-wrappers/test_asset_proxy_dispatcher'; +export * from '../../generated-wrappers/test_exchange_internals'; +export * from '../../generated-wrappers/test_signature_validator'; +export * from '../../generated-wrappers/test_static_call_receiver'; diff --git a/contracts/exchange/test/core.ts b/contracts/exchange/test/core.ts new file mode 100644 index 000000000..294976bf6 --- /dev/null +++ b/contracts/exchange/test/core.ts @@ -0,0 +1,1161 @@ +import { + artifacts as proxyArtifacts, + ERC20ProxyContract, + ERC721ProxyContract, + MultiAssetProxyContract, +} from '@0x/contracts-asset-proxy'; +import { + chaiSetup, + constants, + ERC20BalancesByOwner, + expectTransactionFailedAsync, + getLatestBlockTimestampAsync, + increaseTimeAndMineBlockAsync, + OrderFactory, + OrderStatus, + provider, + txDefaults, + web3Wrapper, +} from '@0x/contracts-test-utils'; +import { + artifacts as tokensArtifacts, + DummyERC20TokenContract, + DummyERC20TokenTransferEventArgs, + DummyERC721TokenContract, + DummyNoReturnERC20TokenContract, + ReentrantERC20TokenContract, +} from '@0x/contracts-tokens'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; +import { RevertReason, SignatureType, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import { LogWithDecodedArgs } from 'ethereum-types'; +import ethUtil = require('ethereumjs-util'); +import * as _ from 'lodash'; + +import { ExchangeCancelEventArgs, ExchangeContract } from '../generated-wrappers/exchange'; +import { TestStaticCallReceiverContract } from '../generated-wrappers/test_static_call_receiver'; +import { artifacts } from '../src/artifacts'; + +import { ERC20Wrapper } from './utils/erc20_wrapper'; +import { ERC721Wrapper } from './utils/erc721_wrapper'; +import { ExchangeWrapper } from './utils/exchange_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +// tslint:disable:no-unnecessary-type-assertion +describe('Exchange core', () => { + let makerAddress: string; + let owner: string; + let takerAddress: string; + let feeRecipientAddress: string; + + let erc20TokenA: DummyERC20TokenContract; + let erc20TokenB: DummyERC20TokenContract; + let zrxToken: DummyERC20TokenContract; + let erc721Token: DummyERC721TokenContract; + let noReturnErc20Token: DummyNoReturnERC20TokenContract; + let reentrantErc20Token: ReentrantERC20TokenContract; + let exchange: ExchangeContract; + let erc20Proxy: ERC20ProxyContract; + let erc721Proxy: ERC721ProxyContract; + let multiAssetProxy: MultiAssetProxyContract; + let maliciousWallet: TestStaticCallReceiverContract; + let maliciousValidator: TestStaticCallReceiverContract; + + let signedOrder: SignedOrder; + let erc20Balances: ERC20BalancesByOwner; + let exchangeWrapper: ExchangeWrapper; + let erc20Wrapper: ERC20Wrapper; + let erc721Wrapper: ERC721Wrapper; + let orderFactory: OrderFactory; + + let erc721MakerAssetIds: BigNumber[]; + let erc721TakerAssetIds: BigNumber[]; + + let defaultMakerAssetAddress: string; + let defaultTakerAssetAddress: string; + + before(async () => { + await blockchainLifecycle.startAsync(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + before(async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = _.slice(accounts, 0, 4)); + + erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); + + // Deploy AssetProxies, Exchange, tokens, and malicious contracts + erc20Proxy = await erc20Wrapper.deployProxyAsync(); + erc721Proxy = await erc721Wrapper.deployProxyAsync(); + multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync( + proxyArtifacts.MultiAssetProxy, + provider, + txDefaults, + ); + const numDummyErc20ToDeploy = 3; + [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + numDummyErc20ToDeploy, + constants.DUMMY_TOKEN_DECIMALS, + ); + [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); + exchange = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + assetDataUtils.encodeERC20AssetData(zrxToken.address), + ); + maliciousWallet = maliciousValidator = await TestStaticCallReceiverContract.deployFrom0xArtifactAsync( + artifacts.TestStaticCallReceiver, + provider, + txDefaults, + ); + reentrantErc20Token = await ReentrantERC20TokenContract.deployFrom0xArtifactAsync( + tokensArtifacts.ReentrantERC20Token, + provider, + txDefaults, + exchange.address, + ); + + // Configure ERC20Proxy + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + // Configure ERC721Proxy + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + // Configure MultiAssetProxy + await web3Wrapper.awaitTransactionSuccessAsync( + await multiAssetProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc721Proxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + // Configure Exchange + exchangeWrapper = new ExchangeWrapper(exchange, provider); + await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); + await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); + await exchangeWrapper.registerAssetProxyAsync(multiAssetProxy.address, owner); + + // Configure ERC20 tokens + await erc20Wrapper.setBalancesAndAllowancesAsync(); + + // Configure ERC721 tokens + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address]; + erc721TakerAssetIds = erc721Balances[takerAddress][erc721Token.address]; + + // Configure order defaults + defaultMakerAssetAddress = erc20TokenA.address; + defaultTakerAssetAddress = erc20TokenB.address; + const defaultOrderParams = { + ...constants.STATIC_ORDER_PARAMS, + exchangeAddress: exchange.address, + makerAddress, + feeRecipientAddress, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), + }; + const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; + orderFactory = new OrderFactory(privateKey, defaultOrderParams); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('fillOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + signedOrder = await orderFactory.newSignedOrderAsync(); + }); + + const reentrancyTest = (functionNames: string[]) => { + _.forEach(functionNames, async (functionName: string, functionId: number) => { + const description = `should not allow fillOrder to reenter the Exchange contract via ${functionName}`; + it(description, async () => { + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), + }); + await web3Wrapper.awaitTransactionSuccessAsync( + await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await expectTransactionFailedAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), + RevertReason.TransferFailed, + ); + }); + }); + }; + describe('fillOrder reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); + + it('should throw if signature is invalid', async () => { + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + }); + + const v = ethUtil.toBuffer(signedOrder.signature.slice(0, 4)); + const invalidR = ethUtil.sha3('invalidR'); + const invalidS = ethUtil.sha3('invalidS'); + const signatureType = ethUtil.toBuffer(`0x${signedOrder.signature.slice(-2)}`); + const invalidSigBuff = Buffer.concat([v, invalidR, invalidS, signatureType]); + const invalidSigHex = `0x${invalidSigBuff.toString('hex')}`; + signedOrder.signature = invalidSigHex; + return expectTransactionFailedAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), + RevertReason.InvalidOrderSignature, + ); + }); + + it('should throw if no value is filled', async () => { + signedOrder = await orderFactory.newSignedOrderAsync(); + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); + return expectTransactionFailedAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), + RevertReason.OrderUnfillable, + ); + }); + + it('should revert if `isValidSignature` tries to update state when SignatureType=Wallet', async () => { + const maliciousMakerAddress = maliciousWallet.address; + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20TokenA.setBalance.sendTransactionAsync( + maliciousMakerAddress, + constants.INITIAL_ERC20_BALANCE, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await maliciousWallet.approveERC20.sendTransactionAsync( + erc20TokenA.address, + erc20Proxy.address, + constants.INITIAL_ERC20_ALLOWANCE, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAddress: maliciousMakerAddress, + makerFee: constants.ZERO_AMOUNT, + }); + signedOrder.signature = `0x0${SignatureType.Wallet}`; + await expectTransactionFailedAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), + RevertReason.WalletError, + ); + }); + + it('should revert if `isValidSignature` tries to update state when SignatureType=Validator', async () => { + const isApproved = true; + await web3Wrapper.awaitTransactionSuccessAsync( + await exchange.setSignatureValidatorApproval.sendTransactionAsync( + maliciousValidator.address, + isApproved, + { from: makerAddress }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + signedOrder.signature = `${maliciousValidator.address}0${SignatureType.Validator}`; + await expectTransactionFailedAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), + RevertReason.ValidatorError, + ); + }); + + it('should not emit transfer events for transfers where from == to', async () => { + const txReceipt = await exchangeWrapper.fillOrderAsync(signedOrder, makerAddress); + const logs = txReceipt.logs; + const transferLogs = _.filter( + logs, + log => (log as LogWithDecodedArgs).event === 'Transfer', + ); + expect(transferLogs.length).to.be.equal(2); + expect((transferLogs[0] as LogWithDecodedArgs).address).to.be.equal( + zrxToken.address, + ); + expect((transferLogs[0] as LogWithDecodedArgs).args._from).to.be.equal( + makerAddress, + ); + expect((transferLogs[0] as LogWithDecodedArgs).args._to).to.be.equal( + feeRecipientAddress, + ); + expect( + (transferLogs[0] as LogWithDecodedArgs).args._value, + ).to.be.bignumber.equal(signedOrder.makerFee); + expect((transferLogs[1] as LogWithDecodedArgs).address).to.be.equal( + zrxToken.address, + ); + expect((transferLogs[1] as LogWithDecodedArgs).args._from).to.be.equal( + makerAddress, + ); + expect((transferLogs[1] as LogWithDecodedArgs).args._to).to.be.equal( + feeRecipientAddress, + ); + expect( + (transferLogs[1] as LogWithDecodedArgs).args._value, + ).to.be.bignumber.equal(signedOrder.takerFee); + }); + }); + + describe('Testing exchange of ERC20 tokens with no return values', () => { + before(async () => { + noReturnErc20Token = await DummyNoReturnERC20TokenContract.deployFrom0xArtifactAsync( + tokensArtifacts.DummyNoReturnERC20Token, + provider, + txDefaults, + constants.DUMMY_TOKEN_NAME, + constants.DUMMY_TOKEN_SYMBOL, + constants.DUMMY_TOKEN_DECIMALS, + constants.DUMMY_TOKEN_TOTAL_SUPPLY, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await noReturnErc20Token.setBalance.sendTransactionAsync(makerAddress, constants.INITIAL_ERC20_BALANCE), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await noReturnErc20Token.approve.sendTransactionAsync( + erc20Proxy.address, + constants.INITIAL_ERC20_ALLOWANCE, + { from: makerAddress }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + }); + it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => { + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), + }); + + const initialMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); + const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const initialTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); + const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + const initialFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); + + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); + + const finalMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); + const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const finalTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); + const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + const finalFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); + + expect(finalMakerBalanceA).to.be.bignumber.equal(initialMakerBalanceA.minus(signedOrder.makerAssetAmount)); + expect(finalMakerBalanceB).to.be.bignumber.equal(initialMakerBalanceB.plus(signedOrder.takerAssetAmount)); + expect(finalTakerBalanceA).to.be.bignumber.equal(initialTakerBalanceA.plus(signedOrder.makerAssetAmount)); + expect(finalTakerBalanceB).to.be.bignumber.equal(initialTakerBalanceB.minus(signedOrder.takerAssetAmount)); + expect(finalMakerZrxBalance).to.be.bignumber.equal(initialMakerZrxBalance.minus(signedOrder.makerFee)); + expect(finalTakerZrxBalance).to.be.bignumber.equal(initialTakerZrxBalance.minus(signedOrder.takerFee)); + expect(finalFeeRecipientZrxBalance).to.be.bignumber.equal( + initialFeeRecipientZrxBalance.plus(signedOrder.makerFee.plus(signedOrder.takerFee)), + ); + }); + it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => { + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), + }); + + const initialMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); + const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const initialTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); + const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + const initialFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); + + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); + + const finalMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); + const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const finalTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); + const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + const finalFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); + + expect(finalMakerBalanceA).to.be.bignumber.equal(initialMakerBalanceA.minus(signedOrder.makerAssetAmount)); + expect(finalMakerBalanceB).to.be.bignumber.equal(initialMakerBalanceB.plus(signedOrder.takerAssetAmount)); + expect(finalTakerBalanceA).to.be.bignumber.equal(initialTakerBalanceA.plus(signedOrder.makerAssetAmount)); + expect(finalTakerBalanceB).to.be.bignumber.equal(initialTakerBalanceB.minus(signedOrder.takerAssetAmount)); + expect(finalMakerZrxBalance).to.be.bignumber.equal(initialMakerZrxBalance.minus(signedOrder.makerFee)); + expect(finalTakerZrxBalance).to.be.bignumber.equal(initialTakerZrxBalance.minus(signedOrder.takerFee)); + expect(finalFeeRecipientZrxBalance).to.be.bignumber.equal( + initialFeeRecipientZrxBalance.plus(signedOrder.makerFee.plus(signedOrder.takerFee)), + ); + }); + it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount', async () => { + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), + }); + + const initialMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); + const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const initialTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); + const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + const initialFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); + + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); + + const finalMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); + const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const finalTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); + const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + const finalFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); + + expect(finalMakerBalanceA).to.be.bignumber.equal(initialMakerBalanceA.minus(signedOrder.makerAssetAmount)); + expect(finalMakerBalanceB).to.be.bignumber.equal(initialMakerBalanceB.plus(signedOrder.takerAssetAmount)); + expect(finalTakerBalanceA).to.be.bignumber.equal(initialTakerBalanceA.plus(signedOrder.makerAssetAmount)); + expect(finalTakerBalanceB).to.be.bignumber.equal(initialTakerBalanceB.minus(signedOrder.takerAssetAmount)); + expect(finalMakerZrxBalance).to.be.bignumber.equal(initialMakerZrxBalance.minus(signedOrder.makerFee)); + expect(finalTakerZrxBalance).to.be.bignumber.equal(initialTakerZrxBalance.minus(signedOrder.takerFee)); + expect(finalFeeRecipientZrxBalance).to.be.bignumber.equal( + initialFeeRecipientZrxBalance.plus(signedOrder.makerFee.plus(signedOrder.takerFee)), + ); + }); + }); + + describe('cancelOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + signedOrder = await orderFactory.newSignedOrderAsync(); + }); + + it('should throw if not sent by maker', async () => { + return expectTransactionFailedAsync( + exchangeWrapper.cancelOrderAsync(signedOrder, takerAddress), + RevertReason.InvalidMaker, + ); + }); + + it('should throw if makerAssetAmount is 0', async () => { + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: new BigNumber(0), + }); + + return expectTransactionFailedAsync( + exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), + RevertReason.OrderUnfillable, + ); + }); + + it('should throw if takerAssetAmount is 0', async () => { + signedOrder = await orderFactory.newSignedOrderAsync({ + takerAssetAmount: new BigNumber(0), + }); + + return expectTransactionFailedAsync( + exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), + RevertReason.OrderUnfillable, + ); + }); + + it('should be able to cancel a full order', async () => { + await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); + return expectTransactionFailedAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount: signedOrder.takerAssetAmount.div(2), + }), + RevertReason.OrderUnfillable, + ); + }); + + it('should log 1 event with correct arguments', async () => { + const res = await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); + expect(res.logs).to.have.length(1); + + const log = res.logs[0] as LogWithDecodedArgs; + const logArgs = log.args; + + expect(signedOrder.makerAddress).to.be.equal(logArgs.makerAddress); + expect(signedOrder.makerAddress).to.be.equal(logArgs.senderAddress); + expect(signedOrder.feeRecipientAddress).to.be.equal(logArgs.feeRecipientAddress); + expect(signedOrder.makerAssetData).to.be.equal(logArgs.makerAssetData); + expect(signedOrder.takerAssetData).to.be.equal(logArgs.takerAssetData); + expect(orderHashUtils.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash); + }); + + it('should throw if already cancelled', async () => { + await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); + return expectTransactionFailedAsync( + exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), + RevertReason.OrderUnfillable, + ); + }); + + it('should throw if order is expired', async () => { + const currentTimestamp = await getLatestBlockTimestampAsync(); + signedOrder = await orderFactory.newSignedOrderAsync({ + expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10), + }); + return expectTransactionFailedAsync( + exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), + RevertReason.OrderUnfillable, + ); + }); + + it('should throw if rounding error is greater than 0.1%', async () => { + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: new BigNumber(1001), + takerAssetAmount: new BigNumber(3), + }); + + const fillTakerAssetAmount1 = new BigNumber(2); + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount: fillTakerAssetAmount1, + }); + + const fillTakerAssetAmount2 = new BigNumber(1); + return expectTransactionFailedAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount: fillTakerAssetAmount2, + }), + RevertReason.RoundingError, + ); + }); + }); + + describe('cancelOrdersUpTo', () => { + it('should fail to set orderEpoch less than current orderEpoch', async () => { + const orderEpoch = new BigNumber(1); + await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress); + const lesserOrderEpoch = new BigNumber(0); + return expectTransactionFailedAsync( + exchangeWrapper.cancelOrdersUpToAsync(lesserOrderEpoch, makerAddress), + RevertReason.InvalidNewOrderEpoch, + ); + }); + + it('should fail to set orderEpoch equal to existing orderEpoch', async () => { + const orderEpoch = new BigNumber(1); + await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress); + return expectTransactionFailedAsync( + exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress), + RevertReason.InvalidNewOrderEpoch, + ); + }); + + it('should cancel only orders with a orderEpoch less than existing orderEpoch', async () => { + // Cancel all transactions with a orderEpoch less than 1 + const orderEpoch = new BigNumber(1); + await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress); + + // Create 3 orders with orderEpoch values: 0,1,2,3 + // Since we cancelled with orderEpoch=1, orders with orderEpoch<=1 will not be processed + erc20Balances = await erc20Wrapper.getBalancesAsync(); + const signedOrders = [ + await orderFactory.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(9), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(9), 18), + salt: new BigNumber(0), + }), + await orderFactory.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(79), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(79), 18), + salt: new BigNumber(1), + }), + await orderFactory.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(979), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(979), 18), + salt: new BigNumber(2), + }), + await orderFactory.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(7979), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(7979), 18), + salt: new BigNumber(3), + }), + ]; + await exchangeWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress, { + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + const fillMakerAssetAmount = signedOrders[2].makerAssetAmount.plus(signedOrders[3].makerAssetAmount); + const fillTakerAssetAmount = signedOrders[2].takerAssetAmount.plus(signedOrders[3].takerAssetAmount); + const makerFee = signedOrders[2].makerFee.plus(signedOrders[3].makerFee); + const takerFee = signedOrders[2].takerFee.plus(signedOrders[3].takerFee); + expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultMakerAssetAddress].minus(fillMakerAssetAmount), + ); + expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultTakerAssetAddress].plus(fillTakerAssetAmount), + ); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address].minus(makerFee), + ); + expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultTakerAssetAddress].minus(fillTakerAssetAmount), + ); + expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultMakerAssetAddress].plus(fillMakerAssetAmount), + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].minus(takerFee), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), + ); + }); + }); + + describe('Testing Exchange of ERC721 Tokens', () => { + it('should throw when maker does not own the token with id makerAssetId', async () => { + // Construct Exchange parameters + const makerAssetId = erc721TakerAssetIds[0]; + const takerAssetId = erc721TakerAssetIds[1]; + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: new BigNumber(1), + takerAssetAmount: new BigNumber(1), + makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), + takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), + }); + // Verify pre-conditions + const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); + expect(initialOwnerMakerAsset).to.be.bignumber.not.equal(makerAddress); + const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); + expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); + // Call Exchange + const takerAssetFillAmount = signedOrder.takerAssetAmount; + return expectTransactionFailedAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), + RevertReason.TransferFailed, + ); + }); + + it('should throw when taker does not own the token with id takerAssetId', async () => { + // Construct Exchange parameters + const makerAssetId = erc721MakerAssetIds[0]; + const takerAssetId = erc721MakerAssetIds[1]; + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: new BigNumber(1), + takerAssetAmount: new BigNumber(1), + makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), + takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), + }); + // Verify pre-conditions + const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); + expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress); + const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); + expect(initialOwnerTakerAsset).to.be.bignumber.not.equal(takerAddress); + // Call Exchange + const takerAssetFillAmount = signedOrder.takerAssetAmount; + return expectTransactionFailedAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), + RevertReason.TransferFailed, + ); + }); + + it('should throw when makerAssetAmount is greater than 1', async () => { + // Construct Exchange parameters + const makerAssetId = erc721MakerAssetIds[0]; + const takerAssetId = erc721TakerAssetIds[0]; + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: new BigNumber(2), + takerAssetAmount: new BigNumber(1), + makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), + takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), + }); + // Verify pre-conditions + const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); + expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress); + const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); + expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); + // Call Exchange + const takerAssetFillAmount = signedOrder.takerAssetAmount; + return expectTransactionFailedAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), + RevertReason.InvalidAmount, + ); + }); + + it('should throw when takerAssetAmount is greater than 1', async () => { + // Construct Exchange parameters + const makerAssetId = erc721MakerAssetIds[0]; + const takerAssetId = erc721TakerAssetIds[0]; + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: new BigNumber(1), + takerAssetAmount: new BigNumber(500), + makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), + takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), + }); + // Verify pre-conditions + const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); + expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress); + const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); + expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); + // Call Exchange + const takerAssetFillAmount = signedOrder.takerAssetAmount; + return expectTransactionFailedAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), + RevertReason.InvalidAmount, + ); + }); + + it('should throw on partial fill', async () => { + // Construct Exchange parameters + const makerAssetId = erc721MakerAssetIds[0]; + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: new BigNumber(1), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), + makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), + }); + // Call Exchange + const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); + return expectTransactionFailedAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), + RevertReason.RoundingError, + ); + }); + }); + + describe('Testing exchange of multiple assets', () => { + it('should allow multiple assets to be exchanged for a single asset', async () => { + const makerAmounts = [new BigNumber(10), new BigNumber(20)]; + const makerNestedAssetData = [ + assetDataUtils.encodeERC20AssetData(erc20TokenA.address), + assetDataUtils.encodeERC20AssetData(erc20TokenB.address), + ]; + const makerAssetData = assetDataUtils.encodeMultiAssetData(makerAmounts, makerNestedAssetData); + const makerAssetAmount = new BigNumber(1); + const takerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + const takerAssetAmount = new BigNumber(10); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + + const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); + + const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + expect(finalMakerBalanceA).to.be.bignumber.equal( + initialMakerBalanceA.minus(makerAmounts[0].times(makerAssetAmount)), + ); + expect(finalMakerBalanceB).to.be.bignumber.equal( + initialMakerBalanceB.minus(makerAmounts[1].times(makerAssetAmount)), + ); + expect(finalMakerZrxBalance).to.be.bignumber.equal(initialMakerZrxBalance.plus(takerAssetAmount)); + expect(finalTakerBalanceA).to.be.bignumber.equal( + initialTakerBalanceA.plus(makerAmounts[0].times(makerAssetAmount)), + ); + expect(finalTakerBalanceB).to.be.bignumber.equal( + initialTakerBalanceB.plus(makerAmounts[1].times(makerAssetAmount)), + ); + expect(finalTakerZrxBalance).to.be.bignumber.equal(initialTakerZrxBalance.minus(takerAssetAmount)); + }); + it('should allow multiple assets to be exchanged for multiple assets', async () => { + const makerAmounts = [new BigNumber(10), new BigNumber(20)]; + const makerNestedAssetData = [ + assetDataUtils.encodeERC20AssetData(erc20TokenA.address), + assetDataUtils.encodeERC20AssetData(erc20TokenB.address), + ]; + const makerAssetData = assetDataUtils.encodeMultiAssetData(makerAmounts, makerNestedAssetData); + const makerAssetAmount = new BigNumber(1); + const takerAmounts = [new BigNumber(10), new BigNumber(1)]; + const takerAssetId = erc721TakerAssetIds[0]; + const takerNestedAssetData = [ + assetDataUtils.encodeERC20AssetData(zrxToken.address), + assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), + ]; + const takerAssetData = assetDataUtils.encodeMultiAssetData(takerAmounts, takerNestedAssetData); + const takerAssetAmount = new BigNumber(1); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + + const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); + expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); + + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); + + const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + const finalOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); + + expect(finalMakerBalanceA).to.be.bignumber.equal( + initialMakerBalanceA.minus(makerAmounts[0].times(makerAssetAmount)), + ); + expect(finalMakerBalanceB).to.be.bignumber.equal( + initialMakerBalanceB.minus(makerAmounts[1].times(makerAssetAmount)), + ); + expect(finalMakerZrxBalance).to.be.bignumber.equal( + initialMakerZrxBalance.plus(takerAmounts[0].times(takerAssetAmount)), + ); + expect(finalTakerBalanceA).to.be.bignumber.equal( + initialTakerBalanceA.plus(makerAmounts[0].times(makerAssetAmount)), + ); + expect(finalTakerBalanceB).to.be.bignumber.equal( + initialTakerBalanceB.plus(makerAmounts[1].times(makerAssetAmount)), + ); + expect(finalTakerZrxBalance).to.be.bignumber.equal( + initialTakerZrxBalance.minus(takerAmounts[0].times(takerAssetAmount)), + ); + expect(finalOwnerTakerAsset).to.be.equal(makerAddress); + }); + it('should allow an order selling multiple assets to be partially filled', async () => { + const makerAmounts = [new BigNumber(10), new BigNumber(20)]; + const makerNestedAssetData = [ + assetDataUtils.encodeERC20AssetData(erc20TokenA.address), + assetDataUtils.encodeERC20AssetData(erc20TokenB.address), + ]; + const makerAssetData = assetDataUtils.encodeMultiAssetData(makerAmounts, makerNestedAssetData); + const makerAssetAmount = new BigNumber(30); + const takerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + const takerAssetAmount = new BigNumber(10); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + + const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + const takerAssetFillAmount = takerAssetAmount.dividedToIntegerBy(2); + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount, + }); + + const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + expect(finalMakerBalanceA).to.be.bignumber.equal( + initialMakerBalanceA.minus( + makerAmounts[0].times( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalMakerBalanceB).to.be.bignumber.equal( + initialMakerBalanceB.minus( + makerAmounts[1].times( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalMakerZrxBalance).to.be.bignumber.equal( + initialMakerZrxBalance.plus( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ); + expect(finalTakerBalanceA).to.be.bignumber.equal( + initialTakerBalanceA.plus( + makerAmounts[0].times( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalTakerBalanceB).to.be.bignumber.equal( + initialTakerBalanceB.plus( + makerAmounts[1].times( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalTakerZrxBalance).to.be.bignumber.equal( + initialTakerZrxBalance.minus( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ); + }); + it('should allow an order buying multiple assets to be partially filled', async () => { + const takerAmounts = [new BigNumber(10), new BigNumber(20)]; + const takerNestedAssetData = [ + assetDataUtils.encodeERC20AssetData(erc20TokenA.address), + assetDataUtils.encodeERC20AssetData(erc20TokenB.address), + ]; + const takerAssetData = assetDataUtils.encodeMultiAssetData(takerAmounts, takerNestedAssetData); + const takerAssetAmount = new BigNumber(30); + const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + const makerAssetAmount = new BigNumber(10); + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData, + takerAssetData, + makerAssetAmount, + takerAssetAmount, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + }); + + const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + const takerAssetFillAmount = takerAssetAmount.dividedToIntegerBy(2); + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount, + }); + + const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); + const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); + const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); + const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); + const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); + const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); + + expect(finalMakerBalanceA).to.be.bignumber.equal( + initialMakerBalanceA.plus( + takerAmounts[0].times( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalMakerBalanceB).to.be.bignumber.equal( + initialMakerBalanceB.plus( + takerAmounts[1].times( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalMakerZrxBalance).to.be.bignumber.equal( + initialMakerZrxBalance.minus( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ); + expect(finalTakerBalanceA).to.be.bignumber.equal( + initialTakerBalanceA.minus( + takerAmounts[0].times( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalTakerBalanceB).to.be.bignumber.equal( + initialTakerBalanceB.minus( + takerAmounts[1].times( + takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ), + ); + expect(finalTakerZrxBalance).to.be.bignumber.equal( + initialTakerZrxBalance.plus( + makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), + ), + ); + }); + }); + + describe('getOrderInfo', () => { + beforeEach(async () => { + signedOrder = await orderFactory.newSignedOrderAsync(); + }); + it('should return the correct orderInfo for an unfilled valid order', async () => { + const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = new BigNumber(0); + 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); + }); + it('should return the correct orderInfo for a fully filled order', async () => { + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); + const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount; + const expectedOrderStatus = OrderStatus.FullyFilled; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + it('should return the correct orderInfo for a partially filled order', async () => { + const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); + 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); + }); + it('should return the correct orderInfo for a cancelled and unfilled order', async () => { + await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); + const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = new BigNumber(0); + const expectedOrderStatus = OrderStatus.Cancelled; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + it('should return the correct orderInfo for a cancelled and partially filled order', async () => { + const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); + await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); + const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = takerAssetFillAmount; + const expectedOrderStatus = OrderStatus.Cancelled; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + it('should return the correct orderInfo for an expired and unfilled order', async () => { + const currentTimestamp = await getLatestBlockTimestampAsync(); + const timeUntilExpiration = signedOrder.expirationTimeSeconds.minus(currentTimestamp).toNumber(); + await increaseTimeAndMineBlockAsync(timeUntilExpiration); + const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = new BigNumber(0); + const expectedOrderStatus = OrderStatus.Expired; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + it('should return the correct orderInfo for an expired and partially filled order', async () => { + const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); + const currentTimestamp = await getLatestBlockTimestampAsync(); + const timeUntilExpiration = signedOrder.expirationTimeSeconds.minus(currentTimestamp).toNumber(); + await increaseTimeAndMineBlockAsync(timeUntilExpiration); + const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = takerAssetFillAmount; + const expectedOrderStatus = OrderStatus.Expired; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + it('should return the correct orderInfo for an expired and fully filled order', async () => { + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); + const currentTimestamp = await getLatestBlockTimestampAsync(); + const timeUntilExpiration = signedOrder.expirationTimeSeconds.minus(currentTimestamp).toNumber(); + await increaseTimeAndMineBlockAsync(timeUntilExpiration); + const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount; + // FULLY_FILLED takes precedence over EXPIRED + const expectedOrderStatus = OrderStatus.FullyFilled; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + it('should return the correct orderInfo for an order with a makerAssetAmount of 0', async () => { + signedOrder = await orderFactory.newSignedOrderAsync({ makerAssetAmount: new BigNumber(0) }); + const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = new BigNumber(0); + const expectedOrderStatus = OrderStatus.InvalidMakerAssetAmount; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + it('should return the correct orderInfo for an order with a takerAssetAmount of 0', async () => { + signedOrder = await orderFactory.newSignedOrderAsync({ takerAssetAmount: new BigNumber(0) }); + const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = new BigNumber(0); + const expectedOrderStatus = OrderStatus.InvalidTakerAssetAmount; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + }); +}); +// tslint:disable:max-file-line-count +// tslint:enable:no-unnecessary-type-assertion diff --git a/contracts/exchange/test/dispatcher.ts b/contracts/exchange/test/dispatcher.ts new file mode 100644 index 000000000..36d263bf4 --- /dev/null +++ b/contracts/exchange/test/dispatcher.ts @@ -0,0 +1,284 @@ +import { artifacts as proxyArtifacts, ERC20ProxyContract, ERC721ProxyContract } from '@0x/contracts-asset-proxy'; +import { + chaiSetup, + constants, + expectTransactionFailedAsync, + LogDecoder, + provider, + txDefaults, + web3Wrapper, +} from '@0x/contracts-test-utils'; +import { DummyERC20TokenContract } from '@0x/contracts-tokens'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils } from '@0x/order-utils'; +import { AssetProxyId, RevertReason } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; +import { LogWithDecodedArgs } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { + TestAssetProxyDispatcherAssetProxyRegisteredEventArgs, + TestAssetProxyDispatcherContract, +} from '../generated-wrappers/test_asset_proxy_dispatcher'; +import { artifacts } from '../src/artifacts'; + +import { ERC20Wrapper } from './utils/erc20_wrapper'; +import { ERC721Wrapper } from './utils/erc721_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +// tslint:disable:no-unnecessary-type-assertion +describe('AssetProxyDispatcher', () => { + let owner: string; + let notOwner: string; + let makerAddress: string; + let takerAddress: string; + + let zrxToken: DummyERC20TokenContract; + let erc20Proxy: ERC20ProxyContract; + let erc721Proxy: ERC721ProxyContract; + let assetProxyDispatcher: TestAssetProxyDispatcherContract; + + let erc20Wrapper: ERC20Wrapper; + let erc721Wrapper: ERC721Wrapper; + + before(async () => { + await blockchainLifecycle.startAsync(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + before(async () => { + // Setup accounts & addresses + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const usedAddresses = ([owner, notOwner, makerAddress, takerAddress] = _.slice(accounts, 0, 4)); + + erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); + + const numDummyErc20ToDeploy = 1; + [zrxToken] = await erc20Wrapper.deployDummyTokensAsync(numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS); + erc20Proxy = await erc20Wrapper.deployProxyAsync(); + await erc20Wrapper.setBalancesAndAllowancesAsync(); + + erc721Proxy = await erc721Wrapper.deployProxyAsync(); + + assetProxyDispatcher = await TestAssetProxyDispatcherContract.deployFrom0xArtifactAsync( + artifacts.TestAssetProxyDispatcher, + provider, + txDefaults, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('registerAssetProxy', () => { + it('should record proxy upon registration', async () => { + await web3Wrapper.awaitTransactionSuccessAsync( + await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); + expect(proxyAddress).to.be.equal(erc20Proxy.address); + }); + + it('should be able to record multiple proxies', async () => { + // Record first proxy + await web3Wrapper.awaitTransactionSuccessAsync( + await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + let proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); + expect(proxyAddress).to.be.equal(erc20Proxy.address); + // Record another proxy + await web3Wrapper.awaitTransactionSuccessAsync( + await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc721Proxy.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC721); + expect(proxyAddress).to.be.equal(erc721Proxy.address); + }); + + it('should throw if a proxy with the same id is already registered', async () => { + // Initial registration + await web3Wrapper.awaitTransactionSuccessAsync( + await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); + expect(proxyAddress).to.be.equal(erc20Proxy.address); + // Deploy a new version of the ERC20 Transfer Proxy contract + const newErc20TransferProxy = await ERC20ProxyContract.deployFrom0xArtifactAsync( + proxyArtifacts.ERC20Proxy, + provider, + txDefaults, + ); + // Register new ERC20 Transfer Proxy contract + return expectTransactionFailedAsync( + assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(newErc20TransferProxy.address, { + from: owner, + }), + RevertReason.AssetProxyAlreadyExists, + ); + }); + + it('should throw if requesting address is not owner', async () => { + return expectTransactionFailedAsync( + assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: notOwner }), + RevertReason.OnlyContractOwner, + ); + }); + + it('should log an event with correct arguments when an asset proxy is registered', async () => { + const logDecoder = new LogDecoder(web3Wrapper, artifacts); + const txReceipt = await logDecoder.getTxWithDecodedLogsAsync( + await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), + ); + const logs = txReceipt.logs; + const log = logs[0] as LogWithDecodedArgs; + expect(log.args.id).to.equal(AssetProxyId.ERC20); + expect(log.args.assetProxy).to.equal(erc20Proxy.address); + }); + }); + + describe('getAssetProxy', () => { + it('should return correct address of registered proxy', async () => { + await web3Wrapper.awaitTransactionSuccessAsync( + await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); + expect(proxyAddress).to.be.equal(erc20Proxy.address); + }); + + it('should return NULL address if requesting non-existent proxy', async () => { + const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); + expect(proxyAddress).to.be.equal(constants.NULL_ADDRESS); + }); + }); + + describe('dispatchTransferFrom', () => { + it('should dispatch transfer to registered proxy', async () => { + // Register ERC20 proxy + await web3Wrapper.awaitTransactionSuccessAsync( + await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Construct metadata for ERC20 proxy + const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + + // Perform a transfer from makerAddress to takerAddress + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const amount = new BigNumber(10); + await web3Wrapper.awaitTransactionSuccessAsync( + await assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( + encodedAssetData, + makerAddress, + takerAddress, + amount, + { from: owner }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Verify transfer was successful + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address].minus(amount), + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].plus(amount), + ); + }); + + it('should not dispatch a transfer if amount == 0', async () => { + // Register ERC20 proxy + await web3Wrapper.awaitTransactionSuccessAsync( + await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Construct metadata for ERC20 proxy + const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + + // Perform a transfer from makerAddress to takerAddress + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const amount = constants.ZERO_AMOUNT; + const txReceipt = await web3Wrapper.awaitTransactionSuccessAsync( + await assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( + encodedAssetData, + makerAddress, + takerAddress, + amount, + { from: owner }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + expect(txReceipt.logs.length).to.be.equal(0); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.deep.equal(erc20Balances); + }); + + it('should not dispatch a transfer if from == to', async () => { + // Register ERC20 proxy + await web3Wrapper.awaitTransactionSuccessAsync( + await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Construct metadata for ERC20 proxy + const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + + // Perform a transfer from makerAddress to takerAddress + const erc20Balances = await erc20Wrapper.getBalancesAsync(); + const amount = new BigNumber(10); + const txReceipt = await web3Wrapper.awaitTransactionSuccessAsync( + await assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( + encodedAssetData, + makerAddress, + makerAddress, + amount, + { from: owner }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + expect(txReceipt.logs.length).to.be.equal(0); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.deep.equal(erc20Balances); + }); + + it('should throw if dispatching to unregistered proxy', async () => { + // Construct metadata for ERC20 proxy + const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + // Perform a transfer from makerAddress to takerAddress + const amount = new BigNumber(10); + return expectTransactionFailedAsync( + assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( + encodedAssetData, + makerAddress, + takerAddress, + amount, + { from: owner }, + ), + RevertReason.AssetProxyDoesNotExist, + ); + }); + }); +}); +// tslint:enable:no-unnecessary-type-assertion diff --git a/contracts/exchange/test/fill_order.ts b/contracts/exchange/test/fill_order.ts new file mode 100644 index 000000000..fee39db53 --- /dev/null +++ b/contracts/exchange/test/fill_order.ts @@ -0,0 +1,314 @@ +import { + AllowanceAmountScenario, + AssetDataScenario, + BalanceAmountScenario, + chaiSetup, + ExpirationTimeSecondsScenario, + FeeRecipientAddressScenario, + FillScenario, + OrderAssetAmountScenario, + provider, + TakerAssetFillAmountScenario, + TakerScenario, + txDefaults, + web3Wrapper, +} from '@0x/contracts-test-utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import * as _ from 'lodash'; + +import { + FillOrderCombinatorialUtils, + fillOrderCombinatorialUtilsFactoryAsync, +} from './utils/fill_order_combinatorial_utils'; + +chaiSetup.configure(); +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +const defaultFillScenario = { + orderScenario: { + takerScenario: TakerScenario.Unspecified, + feeRecipientScenario: FeeRecipientAddressScenario.EthUserAddress, + makerAssetAmountScenario: OrderAssetAmountScenario.Large, + takerAssetAmountScenario: OrderAssetAmountScenario.Large, + makerFeeScenario: OrderAssetAmountScenario.Large, + takerFeeScenario: OrderAssetAmountScenario.Large, + expirationTimeSecondsScenario: ExpirationTimeSecondsScenario.InFuture, + makerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, + takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount, + makerStateScenario: { + traderAssetBalance: BalanceAmountScenario.Higher, + traderAssetAllowance: AllowanceAmountScenario.Higher, + zrxFeeBalance: BalanceAmountScenario.Higher, + zrxFeeAllowance: AllowanceAmountScenario.Higher, + }, + takerStateScenario: { + traderAssetBalance: BalanceAmountScenario.Higher, + traderAssetAllowance: AllowanceAmountScenario.Higher, + zrxFeeBalance: BalanceAmountScenario.Higher, + zrxFeeAllowance: AllowanceAmountScenario.Higher, + }, +}; + +describe('FillOrder Tests', () => { + let fillOrderCombinatorialUtils: FillOrderCombinatorialUtils; + + before(async () => { + await blockchainLifecycle.startAsync(); + fillOrderCombinatorialUtils = await fillOrderCombinatorialUtilsFactoryAsync(web3Wrapper, txDefaults); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('fillOrder', () => { + const test = (fillScenarios: FillScenario[]) => { + _.forEach(fillScenarios, fillScenario => { + const description = `Combinatorial OrderFill: ${JSON.stringify(fillScenario)}`; + it(description, async () => { + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + }); + }; + + const allFillScenarios = FillOrderCombinatorialUtils.generateFillOrderCombinations(); + describe('Combinatorially generated fills orders', () => test(allFillScenarios)); + + it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => { + const fillScenario = { + ...defaultFillScenario, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerAssetAmountScenario: OrderAssetAmountScenario.Small, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetAmountScenario: OrderAssetAmountScenario.Small, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount with zero decimals', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetAmountScenario: OrderAssetAmountScenario.Small, + makerAssetDataScenario: AssetDataScenario.ERC20ZeroDecimals, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + it('should transfer the correct amounts when taker is specified and order is claimed by taker', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerScenario: TakerScenario.CorrectlySpecified, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + it('should fill remaining value if takerAssetFillAmount > remaining takerAssetAmount', async () => { + const fillScenario = { + ...defaultFillScenario, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + it('should throw when taker is specified and order is claimed by other', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerScenario: TakerScenario.IncorrectlySpecified, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if makerAssetAmount is 0', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetAmountScenario: OrderAssetAmountScenario.Zero, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if takerAssetAmount is 0', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerAssetAmountScenario: OrderAssetAmountScenario.Zero, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if takerAssetFillAmount is 0', async () => { + const fillScenario = { + ...defaultFillScenario, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.Zero, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if an order is expired', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + expirationTimeSecondsScenario: ExpirationTimeSecondsScenario.InPast, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if maker erc20Balances are too low to fill order', async () => { + const fillScenario = { + ...defaultFillScenario, + makerStateScenario: { + ...defaultFillScenario.makerStateScenario, + traderAssetBalance: BalanceAmountScenario.TooLow, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if taker erc20Balances are too low to fill order', async () => { + const fillScenario = { + ...defaultFillScenario, + takerStateScenario: { + ...defaultFillScenario.makerStateScenario, + traderAssetBalance: BalanceAmountScenario.TooLow, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if maker allowances are too low to fill order', async () => { + const fillScenario = { + ...defaultFillScenario, + makerStateScenario: { + ...defaultFillScenario.makerStateScenario, + traderAssetAllowance: AllowanceAmountScenario.TooLow, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if taker allowances are too low to fill order', async () => { + const fillScenario = { + ...defaultFillScenario, + takerStateScenario: { + ...defaultFillScenario.makerStateScenario, + traderAssetAllowance: AllowanceAmountScenario.TooLow, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + }); + + describe('Testing exchange of ERC721 Tokens', () => { + it('should successfully exchange a single token between the maker and taker (via fillOrder)', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: AssetDataScenario.ERC721, + takerAssetDataScenario: AssetDataScenario.ERC721, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should successfully fill order when makerAsset is ERC721 and takerAsset is ERC20', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: AssetDataScenario.ERC721, + takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario, true); + }); + + it('should successfully fill order when makerAsset is ERC20 and takerAsset is ERC721', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, + takerAssetDataScenario: AssetDataScenario.ERC721, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should successfully fill order when makerAsset is ERC721 and approveAll is set for it', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: AssetDataScenario.ERC721, + takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, + makerStateScenario: { + ...defaultFillScenario.makerStateScenario, + traderAssetAllowance: AllowanceAmountScenario.Unlimited, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should successfully fill order when makerAsset and takerAsset are ERC721 and approveAll is set for them', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: AssetDataScenario.ERC721, + takerAssetDataScenario: AssetDataScenario.ERC721, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, + makerStateScenario: { + ...defaultFillScenario.makerStateScenario, + traderAssetAllowance: AllowanceAmountScenario.Unlimited, + }, + takerStateScenario: { + ...defaultFillScenario.takerStateScenario, + traderAssetAllowance: AllowanceAmountScenario.Unlimited, + }, + }; + await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + }); +}); diff --git a/contracts/exchange/test/global_hooks.ts b/contracts/exchange/test/global_hooks.ts new file mode 100644 index 000000000..f8ace376a --- /dev/null +++ b/contracts/exchange/test/global_hooks.ts @@ -0,0 +1,17 @@ +import { env, EnvVars } from '@0x/dev-utils'; + +import { coverage, profiler, provider } from '@0x/contracts-test-utils'; +before('start web3 provider', () => { + provider.start(); +}); +after('generate coverage report', async () => { + if (env.parseBoolean(EnvVars.SolidityCoverage)) { + const coverageSubprovider = coverage.getCoverageSubproviderSingleton(); + await coverageSubprovider.writeCoverageAsync(); + } + if (env.parseBoolean(EnvVars.SolidityProfiler)) { + const profilerSubprovider = profiler.getProfilerSubproviderSingleton(); + await profilerSubprovider.writeProfilerOutputAsync(); + } + provider.stop(); +}); diff --git a/contracts/exchange/test/internal.ts b/contracts/exchange/test/internal.ts new file mode 100644 index 000000000..3a7f0cff2 --- /dev/null +++ b/contracts/exchange/test/internal.ts @@ -0,0 +1,472 @@ +import { + bytes32Values, + chaiSetup, + constants, + FillResults, + getRevertReasonOrErrorMessageForSendTransactionAsync, + provider, + testCombinatoriallyWithReferenceFuncAsync, + txDefaults, + uint256Values, + web3Wrapper, +} from '@0x/contracts-test-utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { Order, RevertReason, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; +import * as _ from 'lodash'; + +import { TestExchangeInternalsContract } from '../generated-wrappers/test_exchange_internals'; +import { artifacts } from '../src/artifacts'; + +chaiSetup.configure(); +const expect = chai.expect; + +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +const MAX_UINT256 = new BigNumber(2).pow(256).minus(1); + +const emptyOrder: Order = { + senderAddress: constants.NULL_ADDRESS, + makerAddress: constants.NULL_ADDRESS, + takerAddress: constants.NULL_ADDRESS, + makerFee: new BigNumber(0), + takerFee: new BigNumber(0), + makerAssetAmount: new BigNumber(0), + takerAssetAmount: new BigNumber(0), + makerAssetData: '0x', + takerAssetData: '0x', + salt: new BigNumber(0), + exchangeAddress: constants.NULL_ADDRESS, + feeRecipientAddress: constants.NULL_ADDRESS, + expirationTimeSeconds: new BigNumber(0), +}; + +const emptySignedOrder: SignedOrder = { + ...emptyOrder, + signature: '', +}; + +const overflowErrorForCall = new Error(RevertReason.Uint256Overflow); + +describe('Exchange core internal functions', () => { + let testExchange: TestExchangeInternalsContract; + let overflowErrorForSendTransaction: Error | undefined; + let divisionByZeroErrorForCall: Error | undefined; + let roundingErrorForCall: Error | undefined; + + before(async () => { + await blockchainLifecycle.startAsync(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + before(async () => { + testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( + artifacts.TestExchangeInternals, + provider, + txDefaults, + ); + overflowErrorForSendTransaction = new Error( + await getRevertReasonOrErrorMessageForSendTransactionAsync(RevertReason.Uint256Overflow), + ); + divisionByZeroErrorForCall = new Error(RevertReason.DivisionByZero); + roundingErrorForCall = new Error(RevertReason.RoundingError); + }); + // Note(albrow): Don't forget to add beforeEach and afterEach calls to reset + // the blockchain state for any tests which modify it! + + async function referenceIsRoundingErrorFloorAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise { + if (denominator.eq(0)) { + throw divisionByZeroErrorForCall; + } + if (numerator.eq(0)) { + return false; + } + if (target.eq(0)) { + return false; + } + const product = numerator.multipliedBy(target); + const remainder = product.mod(denominator); + const remainderTimes1000 = remainder.multipliedBy('1000'); + const isError = remainderTimes1000.gte(product); + if (product.isGreaterThan(MAX_UINT256)) { + throw overflowErrorForCall; + } + if (remainderTimes1000.isGreaterThan(MAX_UINT256)) { + throw overflowErrorForCall; + } + return isError; + } + + async function referenceIsRoundingErrorCeilAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise { + if (denominator.eq(0)) { + throw divisionByZeroErrorForCall; + } + if (numerator.eq(0)) { + return false; + } + if (target.eq(0)) { + return false; + } + const product = numerator.multipliedBy(target); + const remainder = product.mod(denominator); + const error = denominator.minus(remainder).mod(denominator); + const errorTimes1000 = error.multipliedBy('1000'); + const isError = errorTimes1000.gte(product); + if (product.isGreaterThan(MAX_UINT256)) { + throw overflowErrorForCall; + } + if (errorTimes1000.isGreaterThan(MAX_UINT256)) { + throw overflowErrorForCall; + } + return isError; + } + + async function referenceSafeGetPartialAmountFloorAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise { + if (denominator.eq(0)) { + throw divisionByZeroErrorForCall; + } + const isRoundingError = await referenceIsRoundingErrorFloorAsync(numerator, denominator, target); + if (isRoundingError) { + throw roundingErrorForCall; + } + const product = numerator.multipliedBy(target); + if (product.isGreaterThan(MAX_UINT256)) { + throw overflowErrorForCall; + } + return product.dividedToIntegerBy(denominator); + } + + describe('addFillResults', async () => { + function makeFillResults(value: BigNumber): FillResults { + return { + makerAssetFilledAmount: value, + takerAssetFilledAmount: value, + makerFeePaid: value, + takerFeePaid: value, + }; + } + async function referenceAddFillResultsAsync( + totalValue: BigNumber, + singleValue: BigNumber, + ): Promise { + // Note(albrow): Here, each of totalFillResults and + // singleFillResults will consist of fields with the same values. + // This should be safe because none of the fields in a given + // FillResults are ever used together in a mathemetical operation. + // They are only used with the corresponding field from *the other* + // FillResults, which are different. + const totalFillResults = makeFillResults(totalValue); + const singleFillResults = makeFillResults(singleValue); + // HACK(albrow): _.mergeWith mutates the first argument! To + // workaround this we use _.cloneDeep. + return _.mergeWith( + _.cloneDeep(totalFillResults), + singleFillResults, + (totalVal: BigNumber, singleVal: BigNumber) => { + const newTotal = totalVal.plus(singleVal); + if (newTotal.isGreaterThan(MAX_UINT256)) { + throw overflowErrorForCall; + } + return newTotal; + }, + ); + } + async function testAddFillResultsAsync(totalValue: BigNumber, singleValue: BigNumber): Promise { + const totalFillResults = makeFillResults(totalValue); + const singleFillResults = makeFillResults(singleValue); + return testExchange.publicAddFillResults.callAsync(totalFillResults, singleFillResults); + } + await testCombinatoriallyWithReferenceFuncAsync( + 'addFillResults', + referenceAddFillResultsAsync, + testAddFillResultsAsync, + [uint256Values, uint256Values], + ); + }); + + describe('calculateFillResults', async () => { + function makeOrder( + makerAssetAmount: BigNumber, + takerAssetAmount: BigNumber, + makerFee: BigNumber, + takerFee: BigNumber, + ): Order { + return { + ...emptyOrder, + makerAssetAmount, + takerAssetAmount, + makerFee, + takerFee, + }; + } + async function referenceCalculateFillResultsAsync( + orderTakerAssetAmount: BigNumber, + takerAssetFilledAmount: BigNumber, + otherAmount: BigNumber, + ): Promise { + // Note(albrow): Here we are re-using the same value (otherAmount) + // for order.makerAssetAmount, order.makerFee, and order.takerFee. + // This should be safe because they are never used with each other + // in any mathematical operation in either the reference TypeScript + // implementation or the Solidity implementation of + // calculateFillResults. + const makerAssetFilledAmount = await referenceSafeGetPartialAmountFloorAsync( + takerAssetFilledAmount, + orderTakerAssetAmount, + otherAmount, + ); + const order = makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount); + const orderMakerAssetAmount = order.makerAssetAmount; + return { + makerAssetFilledAmount, + takerAssetFilledAmount, + makerFeePaid: await referenceSafeGetPartialAmountFloorAsync( + makerAssetFilledAmount, + orderMakerAssetAmount, + otherAmount, + ), + takerFeePaid: await referenceSafeGetPartialAmountFloorAsync( + takerAssetFilledAmount, + orderTakerAssetAmount, + otherAmount, + ), + }; + } + async function testCalculateFillResultsAsync( + orderTakerAssetAmount: BigNumber, + takerAssetFilledAmount: BigNumber, + otherAmount: BigNumber, + ): Promise { + const order = makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount); + return testExchange.publicCalculateFillResults.callAsync(order, takerAssetFilledAmount); + } + await testCombinatoriallyWithReferenceFuncAsync( + 'calculateFillResults', + referenceCalculateFillResultsAsync, + testCalculateFillResultsAsync, + [uint256Values, uint256Values, uint256Values], + ); + }); + + describe('getPartialAmountFloor', async () => { + async function referenceGetPartialAmountFloorAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise { + if (denominator.eq(0)) { + throw divisionByZeroErrorForCall; + } + const product = numerator.multipliedBy(target); + if (product.isGreaterThan(MAX_UINT256)) { + throw overflowErrorForCall; + } + return product.dividedToIntegerBy(denominator); + } + async function testGetPartialAmountFloorAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise { + return testExchange.publicGetPartialAmountFloor.callAsync(numerator, denominator, target); + } + await testCombinatoriallyWithReferenceFuncAsync( + 'getPartialAmountFloor', + referenceGetPartialAmountFloorAsync, + testGetPartialAmountFloorAsync, + [uint256Values, uint256Values, uint256Values], + ); + }); + + describe('getPartialAmountCeil', async () => { + async function referenceGetPartialAmountCeilAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise { + if (denominator.eq(0)) { + throw divisionByZeroErrorForCall; + } + const product = numerator.multipliedBy(target); + const offset = product.plus(denominator.minus(1)); + if (offset.isGreaterThan(MAX_UINT256)) { + throw overflowErrorForCall; + } + const result = offset.dividedToIntegerBy(denominator); + if (product.mod(denominator).eq(0)) { + expect(result.multipliedBy(denominator)).to.be.bignumber.eq(product); + } else { + expect(result.multipliedBy(denominator)).to.be.bignumber.gt(product); + } + return result; + } + async function testGetPartialAmountCeilAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise { + return testExchange.publicGetPartialAmountCeil.callAsync(numerator, denominator, target); + } + await testCombinatoriallyWithReferenceFuncAsync( + 'getPartialAmountCeil', + referenceGetPartialAmountCeilAsync, + testGetPartialAmountCeilAsync, + [uint256Values, uint256Values, uint256Values], + ); + }); + + describe('safeGetPartialAmountFloor', async () => { + async function testSafeGetPartialAmountFloorAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise { + return testExchange.publicSafeGetPartialAmountFloor.callAsync(numerator, denominator, target); + } + await testCombinatoriallyWithReferenceFuncAsync( + 'safeGetPartialAmountFloor', + referenceSafeGetPartialAmountFloorAsync, + testSafeGetPartialAmountFloorAsync, + [uint256Values, uint256Values, uint256Values], + ); + }); + + describe('safeGetPartialAmountCeil', async () => { + async function referenceSafeGetPartialAmountCeilAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise { + if (denominator.eq(0)) { + throw divisionByZeroErrorForCall; + } + const isRoundingError = await referenceIsRoundingErrorCeilAsync(numerator, denominator, target); + if (isRoundingError) { + throw roundingErrorForCall; + } + const product = numerator.multipliedBy(target); + const offset = product.plus(denominator.minus(1)); + if (offset.isGreaterThan(MAX_UINT256)) { + throw overflowErrorForCall; + } + const result = offset.dividedToIntegerBy(denominator); + if (product.mod(denominator).eq(0)) { + expect(result.multipliedBy(denominator)).to.be.bignumber.eq(product); + } else { + expect(result.multipliedBy(denominator)).to.be.bignumber.gt(product); + } + return result; + } + async function testSafeGetPartialAmountCeilAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise { + return testExchange.publicSafeGetPartialAmountCeil.callAsync(numerator, denominator, target); + } + await testCombinatoriallyWithReferenceFuncAsync( + 'safeGetPartialAmountCeil', + referenceSafeGetPartialAmountCeilAsync, + testSafeGetPartialAmountCeilAsync, + [uint256Values, uint256Values, uint256Values], + ); + }); + + describe('isRoundingErrorFloor', async () => { + async function testIsRoundingErrorFloorAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise { + return testExchange.publicIsRoundingErrorFloor.callAsync(numerator, denominator, target); + } + await testCombinatoriallyWithReferenceFuncAsync( + 'isRoundingErrorFloor', + referenceIsRoundingErrorFloorAsync, + testIsRoundingErrorFloorAsync, + [uint256Values, uint256Values, uint256Values], + ); + }); + + describe('isRoundingErrorCeil', async () => { + async function testIsRoundingErrorCeilAsync( + numerator: BigNumber, + denominator: BigNumber, + target: BigNumber, + ): Promise { + return testExchange.publicIsRoundingErrorCeil.callAsync(numerator, denominator, target); + } + await testCombinatoriallyWithReferenceFuncAsync( + 'isRoundingErrorCeil', + referenceIsRoundingErrorCeilAsync, + testIsRoundingErrorCeilAsync, + [uint256Values, uint256Values, uint256Values], + ); + }); + + describe('updateFilledState', async () => { + // Note(albrow): Since updateFilledState modifies the state by calling + // sendTransaction, we must reset the state after each test. + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + async function referenceUpdateFilledStateAsync( + takerAssetFilledAmount: BigNumber, + orderTakerAssetFilledAmount: BigNumber, + // tslint:disable-next-line:no-unused-variable + orderHash: string, + ): Promise { + const totalFilledAmount = takerAssetFilledAmount.plus(orderTakerAssetFilledAmount); + if (totalFilledAmount.isGreaterThan(MAX_UINT256)) { + throw overflowErrorForSendTransaction; + } + return totalFilledAmount; + } + async function testUpdateFilledStateAsync( + takerAssetFilledAmount: BigNumber, + orderTakerAssetFilledAmount: BigNumber, + orderHash: string, + ): Promise { + const fillResults = { + makerAssetFilledAmount: new BigNumber(0), + takerAssetFilledAmount, + makerFeePaid: new BigNumber(0), + takerFeePaid: new BigNumber(0), + }; + await web3Wrapper.awaitTransactionSuccessAsync( + await testExchange.publicUpdateFilledState.sendTransactionAsync( + emptySignedOrder, + constants.NULL_ADDRESS, + orderHash, + orderTakerAssetFilledAmount, + fillResults, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + return testExchange.filled.callAsync(orderHash); + } + await testCombinatoriallyWithReferenceFuncAsync( + 'updateFilledState', + referenceUpdateFilledStateAsync, + testUpdateFilledStateAsync, + [uint256Values, uint256Values, bytes32Values], + ); + }); +}); diff --git a/contracts/exchange/test/match_orders.ts b/contracts/exchange/test/match_orders.ts new file mode 100644 index 000000000..346ee42a9 --- /dev/null +++ b/contracts/exchange/test/match_orders.ts @@ -0,0 +1,1284 @@ +import { ERC20ProxyContract, ERC721ProxyContract } from '@0x/contracts-asset-proxy'; +import { + chaiSetup, + constants, + ERC20BalancesByOwner, + ERC721TokenIdsByOwner, + expectTransactionFailedAsync, + OrderFactory, + provider, + txDefaults, + web3Wrapper, +} from '@0x/contracts-test-utils'; +import { + artifacts as tokensArtifacts, + DummyERC20TokenContract, + DummyERC721TokenContract, + ReentrantERC20TokenContract, +} from '@0x/contracts-tokens'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils } from '@0x/order-utils'; +import { RevertReason } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import * as _ from 'lodash'; + +import { ExchangeContract } from '../generated-wrappers/exchange'; +import { TestExchangeInternalsContract } from '../generated-wrappers/test_exchange_internals'; +import { artifacts } from '../src/artifacts'; + +import { ERC20Wrapper } from './utils/erc20_wrapper'; +import { ERC721Wrapper } from './utils/erc721_wrapper'; +import { ExchangeWrapper } from './utils/exchange_wrapper'; +import { MatchOrderTester } from './utils/match_order_tester'; + +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +chaiSetup.configure(); +const expect = chai.expect; + +describe('matchOrders', () => { + let makerAddressLeft: string; + let makerAddressRight: string; + let owner: string; + let takerAddress: string; + let feeRecipientAddressLeft: string; + let feeRecipientAddressRight: string; + + let erc20TokenA: DummyERC20TokenContract; + let erc20TokenB: DummyERC20TokenContract; + let zrxToken: DummyERC20TokenContract; + let erc721Token: DummyERC721TokenContract; + let reentrantErc20Token: ReentrantERC20TokenContract; + let exchange: ExchangeContract; + let erc20Proxy: ERC20ProxyContract; + let erc721Proxy: ERC721ProxyContract; + + let erc20BalancesByOwner: ERC20BalancesByOwner; + let erc721TokenIdsByOwner: ERC721TokenIdsByOwner; + let exchangeWrapper: ExchangeWrapper; + let erc20Wrapper: ERC20Wrapper; + let erc721Wrapper: ERC721Wrapper; + let orderFactoryLeft: OrderFactory; + let orderFactoryRight: OrderFactory; + + let erc721LeftMakerAssetIds: BigNumber[]; + let erc721RightMakerAssetIds: BigNumber[]; + + let defaultERC20MakerAssetAddress: string; + let defaultERC20TakerAssetAddress: string; + let defaultERC721AssetAddress: string; + + let matchOrderTester: MatchOrderTester; + + let testExchange: TestExchangeInternalsContract; + + before(async () => { + await blockchainLifecycle.startAsync(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + before(async () => { + // Create accounts + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + // Hack(albrow): Both Prettier and TSLint insert a trailing comma below + // but that is invalid syntax as of TypeScript version >= 2.8. We don't + // have the right fine-grained configuration options in TSLint, + // Prettier, or TypeScript, to reconcile this, so we will just have to + // wait for them to sort it out. We disable TSLint and Prettier for + // this part of the code for now. This occurs several times in this + // file. See https://github.com/prettier/prettier/issues/4624. + // prettier-ignore + const usedAddresses = ([ + owner, + makerAddressLeft, + makerAddressRight, + takerAddress, + feeRecipientAddressLeft, + // tslint:disable-next-line:trailing-comma + feeRecipientAddressRight + ] = _.slice(accounts, 0, 6)); + // Create wrappers + erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); + // Deploy ERC20 token & ERC20 proxy + const numDummyErc20ToDeploy = 3; + [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + numDummyErc20ToDeploy, + constants.DUMMY_TOKEN_DECIMALS, + ); + erc20Proxy = await erc20Wrapper.deployProxyAsync(); + await erc20Wrapper.setBalancesAndAllowancesAsync(); + // Deploy ERC721 token and proxy + [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); + erc721Proxy = await erc721Wrapper.deployProxyAsync(); + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + erc721LeftMakerAssetIds = erc721Balances[makerAddressLeft][erc721Token.address]; + erc721RightMakerAssetIds = erc721Balances[makerAddressRight][erc721Token.address]; + // Depoy exchange + exchange = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + assetDataUtils.encodeERC20AssetData(zrxToken.address), + ); + exchangeWrapper = new ExchangeWrapper(exchange, provider); + await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); + await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); + // Authorize ERC20 and ERC721 trades by exchange + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + reentrantErc20Token = await ReentrantERC20TokenContract.deployFrom0xArtifactAsync( + tokensArtifacts.ReentrantERC20Token, + provider, + txDefaults, + exchange.address, + ); + + // Set default addresses + defaultERC20MakerAssetAddress = erc20TokenA.address; + defaultERC20TakerAssetAddress = erc20TokenB.address; + defaultERC721AssetAddress = erc721Token.address; + // Create default order parameters + const defaultOrderParamsLeft = { + ...constants.STATIC_ORDER_PARAMS, + makerAddress: makerAddressLeft, + exchangeAddress: exchange.address, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + feeRecipientAddress: feeRecipientAddressLeft, + }; + const defaultOrderParamsRight = { + ...constants.STATIC_ORDER_PARAMS, + makerAddress: makerAddressRight, + exchangeAddress: exchange.address, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + feeRecipientAddress: feeRecipientAddressRight, + }; + const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)]; + orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParamsLeft); + const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)]; + orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParamsRight); + // Set match order tester + matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper, zrxToken.address); + testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( + artifacts.TestExchangeInternals, + provider, + txDefaults, + ); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('matchOrders', () => { + beforeEach(async () => { + erc20BalancesByOwner = await erc20Wrapper.getBalancesAsync(); + erc721TokenIdsByOwner = await erc721Wrapper.getBalancesAsync(); + }); + + it('Should transfer correct amounts when right order is fully filled and values pass isRoundingErrorFloor but fail isRoundingErrorCeil', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(17), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(98), 0), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAddress: makerAddressRight, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Assert is rounding error ceil & not rounding error floor + // These assertions are taken from MixinMatchOrders::calculateMatchedFillResults + // The rounding error is derived computating how much the left maker will sell. + const numerator = signedOrderLeft.makerAssetAmount; + const denominator = signedOrderLeft.takerAssetAmount; + const target = signedOrderRight.makerAssetAmount; + const isRoundingErrorCeil = await testExchange.publicIsRoundingErrorCeil.callAsync( + numerator, + denominator, + target, + ); + expect(isRoundingErrorCeil).to.be.true(); + const isRoundingErrorFloor = await testExchange.publicIsRoundingErrorFloor.callAsync( + numerator, + denominator, + target, + ); + expect(isRoundingErrorFloor).to.be.false(); + // Match signedOrderLeft with signedOrderRight + // Note that the left maker received a slightly better sell price. + // This is intentional; see note in MixinMatchOrders.calculateMatchedFillResults. + // Because the left maker received a slightly more favorable sell price, the fee + // paid by the left taker is slightly higher than that paid by the left maker. + // Fees can be thought of as a tax paid by the seller, derived from the sale price. + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), // 76.47% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 0), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('Should transfer correct amounts when left order is fully filled and values pass isRoundingErrorCeil but fail isRoundingErrorFloor', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(15), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 0), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAddress: makerAddressRight, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(97), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(14), 0), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Assert is rounding error floor & not rounding error ceil + // These assertions are taken from MixinMatchOrders::calculateMatchedFillResults + // The rounding error is derived computating how much the right maker will buy. + const numerator = signedOrderRight.takerAssetAmount; + const denominator = signedOrderRight.makerAssetAmount; + const target = signedOrderLeft.takerAssetAmount; + const isRoundingErrorFloor = await testExchange.publicIsRoundingErrorFloor.callAsync( + numerator, + denominator, + target, + ); + expect(isRoundingErrorFloor).to.be.true(); + const isRoundingErrorCeil = await testExchange.publicIsRoundingErrorCeil.callAsync( + numerator, + denominator, + target, + ); + expect(isRoundingErrorCeil).to.be.false(); + // Match signedOrderLeft with signedOrderRight + // Note that the right maker received a slightly better purchase price. + // This is intentional; see note in MixinMatchOrders.calculateMatchedFillResults. + // Because the right maker received a slightly more favorable buy price, the fee + // paid by the right taker is slightly higher than that paid by the right maker. + // Fees can be thought of as a tax paid by the seller, derived from the sale price. + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(15), 0), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 0), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 0), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('92.7835051546391752'), 16), // 92.78% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 0), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber('92.8571428571428571'), 16), // 92.85% + }; + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('Should give right maker a better buy price when rounding', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(16), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAddress: makerAddressRight, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(83), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(49), 0), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Note: + // The correct price buy price for the right maker would yield (49/83) * 22 = 12.988 units + // of the left maker asset. This gets rounded up to 13, giving the right maker a better price. + // Note: + // The maker/taker fee percentage paid on the right order differs because + // they received different sale prices. The right maker pays a + // fee slightly lower than the right taker. + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(16), 0), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('26.5060240963855421'), 16), // 26.506% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 0), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber('26.5306122448979591'), 16), // 26.531% + }; + // Match signedOrderLeft with signedOrderRight + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('Should give left maker a better sell price when rounding', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(12), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(97), 0), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAddress: makerAddressRight, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Note: + // The maker/taker fee percentage paid on the left order differs because + // they received different sale prices. The left maker pays a fee + // slightly lower than the left taker. + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(11), 0), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.6666666666666666'), 16), // 91.6% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 0), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.7525773195876288'), 16), // 91.75% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + // Match signedOrderLeft with signedOrderRight + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('Should give right maker and right taker a favorable fee price when rounding', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(16), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAddress: makerAddressRight, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(83), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(49), 0), + feeRecipientAddress: feeRecipientAddressRight, + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 0), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 0), + }); + // Note: + // The maker/taker fee percentage paid on the right order differs because + // they received different sale prices. The right maker pays a + // fee slightly lower than the right taker. + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(16), 0), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2650), 0), // 2650.6 rounded down tro 2650 + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 0), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(2653), 0), // 2653.1 rounded down to 2653 + }; + // Match signedOrderLeft with signedOrderRight + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('Should give left maker and left taker a favorable fee price when rounding', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(12), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(97), 0), + feeRecipientAddress: feeRecipientAddressLeft, + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 0), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 0), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAddress: makerAddressRight, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), + feeRecipientAddress: feeRecipientAddressRight, + }); + // Note: + // The maker/taker fee percentage paid on the left order differs because + // they received different sale prices. The left maker pays a + // fee slightly lower than the left taker. + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(11), 0), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(9166), 0), // 9166.6 rounded down to 9166 + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 0), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(9175), 0), // 9175.2 rounded down to 9175 + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + // Match signedOrderLeft with signedOrderRight + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('Should transfer correct amounts when right order fill amount deviates from amount derived by `Exchange.fillOrder`', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1005), 0), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAddress: makerAddressRight, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2126), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1063), 0), + feeRecipientAddress: feeRecipientAddressRight, + }); + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 0), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1005), 0), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + // Notes: + // i. + // The left order is fully filled by the right order, so the right maker must sell 1005 units of their asset to the left maker. + // By selling 1005 units, the right maker should theoretically receive 502.5 units of the left maker's asset. + // Since the transfer amount must be an integer, this value must be rounded down to 502 or up to 503. + // ii. + // If the right order were filled via `Exchange.fillOrder` the respective fill amounts would be [1004, 502] or [1006, 503]. + // It follows that we cannot trigger a sale of 1005 units of the right maker's asset through `Exchange.fillOrder`. + // iii. + // For an optimal match, the algorithm must choose either [1005, 502] or [1005, 503] as fill amounts for the right order. + // The algorithm favors the right maker when the exchange rate must be rounded, so the final fill for the right order is [1005, 503]. + // iv. + // The right maker fee differs from the right taker fee because their exchange rate differs. + // The right maker always receives the better exchange and fee price. + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1005), 0), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(503), 0), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('47.2718720602069614'), 16), // 47.27% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(497), 0), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber('47.3189087488240827'), 16), // 47.31% + }; + // Match signedOrderLeft with signedOrderRight + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + const reentrancyTest = (functionNames: string[]) => { + _.forEach(functionNames, async (functionName: string, functionId: number) => { + const description = `should not allow matchOrders to reenter the Exchange contract via ${functionName}`; + it(description, async () => { + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAddress: makerAddressRight, + takerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + await web3Wrapper.awaitTransactionSuccessAsync( + await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await expectTransactionFailedAsync( + exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), + RevertReason.TransferFailed, + ); + }); + }); + }; + describe('matchOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); + + it('should transfer the correct amounts when orders completely fill each other', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + }); + // Match signedOrderLeft with signedOrderRight + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + }); + // Match signedOrderLeft with signedOrderRight + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + // Match signedOrderLeft with signedOrderRight + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('should transfer the correct amounts when left order is completely filled and right order is partially filled', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18), + }); + // Match signedOrderLeft with signedOrderRight + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 16), // 50% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 16), // 50% + }; + // Match signedOrderLeft with signedOrderRight + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('should transfer the correct amounts when right order is completely filled and left order is partially filled', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + }); + // Match signedOrderLeft with signedOrderRight + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + // Match signedOrderLeft with signedOrderRight + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('should transfer the correct amounts when consecutive calls are used to completely fill the left order', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + }); + // Match orders + let newERC20BalancesByOwner: ERC20BalancesByOwner; + let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner; + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + // prettier-ignore + [ + newERC20BalancesByOwner, + // tslint:disable-next-line:trailing-comma + newERC721TokenIdsByOwner + ] = await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + // Construct second right order + // Note: This order needs makerAssetAmount=90/takerAssetAmount=[anything <= 45] to fully fill the right order. + // However, we use 100/50 to ensure a partial fill as we want to go down the "left fill" + // branch in the contract twice for this test. + const signedOrderRight2 = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), + }); + // Match signedOrderLeft with signedOrderRight2 + const leftTakerAssetFilledAmount = signedOrderRight.makerAssetAmount; + const rightTakerAssetFilledAmount = new BigNumber(0); + const expectedTransferAmounts2 = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(45), 18), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 18), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90% (10% paid earlier) + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 18), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(45), 18), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90% (10% paid earlier) + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90% + }; + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight2, + takerAddress, + newERC20BalancesByOwner, + newERC721TokenIdsByOwner, + expectedTransferAmounts2, + leftTakerAssetFilledAmount, + rightTakerAssetFilledAmount, + ); + }); + + it('should transfer the correct amounts when consecutive calls are used to completely fill the right order', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + }); + + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), + }); + // Match orders + let newERC20BalancesByOwner: ERC20BalancesByOwner; + let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner; + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 16), // 4% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(6), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 16), // 4% + }; + // prettier-ignore + [ + newERC20BalancesByOwner, + // tslint:disable-next-line:trailing-comma + newERC721TokenIdsByOwner + ] = await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + + // Create second left order + // Note: This order needs makerAssetAmount=96/takerAssetAmount=48 to fully fill the right order. + // However, we use 100/50 to ensure a partial fill as we want to go down the "right fill" + // branch in the contract twice for this test. + const signedOrderLeft2 = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), + }); + // Match signedOrderLeft2 with signedOrderRight + const leftTakerAssetFilledAmount = new BigNumber(0); + const takerAmountReceived = newERC20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress].minus( + erc20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress], + ); + const rightTakerAssetFilledAmount = signedOrderLeft.makerAssetAmount.minus(takerAmountReceived); + const expectedTransferAmounts2 = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 18), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(48), 18), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(48), 18), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 18), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96% + }; + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft2, + signedOrderRight, + takerAddress, + newERC20BalancesByOwner, + newERC721TokenIdsByOwner, + expectedTransferAmounts2, + leftTakerAssetFilledAmount, + rightTakerAssetFilledAmount, + ); + }); + + it('should transfer the correct amounts if fee recipient is the same across both matched orders', async () => { + const feeRecipientAddress = feeRecipientAddressLeft; + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + feeRecipientAddress, + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + feeRecipientAddress, + }); + // Match orders + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('should transfer the correct amounts if taker is also the left order maker', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + }); + // Match orders + takerAddress = signedOrderLeft.makerAddress; + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('should transfer the correct amounts if taker is also the right order maker', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + }); + // Match orders + takerAddress = signedOrderRight.makerAddress; + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('should transfer the correct amounts if taker is also the left fee recipient', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + }); + // Match orders + takerAddress = feeRecipientAddressLeft; + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('should transfer the correct amounts if taker is also the right fee recipient', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + }); + // Match orders + takerAddress = feeRecipientAddressRight; + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('should transfer the correct amounts if left maker is the left fee recipient and right maker is the right fee recipient', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + }); + // Match orders + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('Should throw if left order is not fillable', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + }); + // Cancel left order + await exchangeWrapper.cancelOrderAsync(signedOrderLeft, signedOrderLeft.makerAddress); + // Match orders + return expectTransactionFailedAsync( + exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), + RevertReason.OrderUnfillable, + ); + }); + + it('Should throw if right order is not fillable', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + }); + // Cancel right order + await exchangeWrapper.cancelOrderAsync(signedOrderRight, signedOrderRight.makerAddress); + // Match orders + return expectTransactionFailedAsync( + exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), + RevertReason.OrderUnfillable, + ); + }); + + it('should throw if there is not a positive spread', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), + }); + // Match orders + return expectTransactionFailedAsync( + exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), + RevertReason.NegativeSpreadRequired, + ); + }); + + it('should throw if the left maker asset is not equal to the right taker asset ', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + }); + // Match orders + return expectTransactionFailedAsync( + exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), + // We are assuming assetData fields of the right order are the + // reverse of the left order, rather than checking equality. This + // saves a bunch of gas, but as a result if the assetData fields are + // off then the failure ends up happening at signature validation + RevertReason.InvalidOrderSignature, + ); + }); + + it('should throw if the right maker asset is not equal to the left taker asset', async () => { + // Create orders to match + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + }); + // Match orders + return expectTransactionFailedAsync( + exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), + RevertReason.InvalidOrderSignature, + ); + }); + + it('should transfer correct amounts when left order maker asset is an ERC721 token', async () => { + // Create orders to match + const erc721TokenToTransfer = erc721LeftMakerAssetIds[0]; + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), + makerAssetAmount: new BigNumber(1), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + takerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: new BigNumber(1), + }); + // Match orders + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 50% + }; + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + + it('should transfer correct amounts when right order maker asset is an ERC721 token', async () => { + // Create orders to match + const erc721TokenToTransfer = erc721RightMakerAssetIds[0]; + const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ + takerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + takerAssetAmount: new BigNumber(1), + }); + const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), + makerAssetAmount: new BigNumber(1), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), 18), + }); + // Match orders + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), 18), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + await matchOrderTester.matchOrdersAndAssertEffectsAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + expectedTransferAmounts, + ); + }); + }); +}); // tslint:disable-line:max-file-line-count diff --git a/contracts/exchange/test/signature_validator.ts b/contracts/exchange/test/signature_validator.ts new file mode 100644 index 000000000..c5f9a5683 --- /dev/null +++ b/contracts/exchange/test/signature_validator.ts @@ -0,0 +1,526 @@ +import { artifacts as examplesArtifacts, ValidatorContract, WalletContract } from '@0x/contracts-examples'; +import { + addressUtils, + chaiSetup, + constants, + expectContractCallFailedAsync, + LogDecoder, + OrderFactory, + provider, + txDefaults, + web3Wrapper, +} from '@0x/contracts-test-utils'; +import { artifacts as tokensArtifacts } from '@0x/contracts-tokens'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils, orderHashUtils, signatureUtils } from '@0x/order-utils'; +import { RevertReason, SignatureType, SignedOrder } from '@0x/types'; +import * as chai from 'chai'; +import { LogWithDecodedArgs } from 'ethereum-types'; +import ethUtil = require('ethereumjs-util'); + +import { + artifacts, + TestSignatureValidatorContract, + TestSignatureValidatorSignatureValidatorApprovalEventArgs, + TestStaticCallReceiverContract, +} from '../src'; + +chaiSetup.configure(); +const expect = chai.expect; + +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +// tslint:disable:no-unnecessary-type-assertion +describe('MixinSignatureValidator', () => { + let signedOrder: SignedOrder; + let orderFactory: OrderFactory; + let signatureValidator: TestSignatureValidatorContract; + let testWallet: WalletContract; + let testValidator: ValidatorContract; + let maliciousWallet: TestStaticCallReceiverContract; + let maliciousValidator: TestStaticCallReceiverContract; + let signerAddress: string; + let signerPrivateKey: Buffer; + let notSignerAddress: string; + let notSignerPrivateKey: Buffer; + let signatureValidatorLogDecoder: LogDecoder; + + before(async () => { + await blockchainLifecycle.startAsync(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + before(async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const makerAddress = accounts[0]; + signerAddress = makerAddress; + notSignerAddress = accounts[1]; + signatureValidator = await TestSignatureValidatorContract.deployFrom0xArtifactAsync( + artifacts.TestSignatureValidator, + provider, + txDefaults, + ); + testWallet = await WalletContract.deployFrom0xArtifactAsync( + examplesArtifacts.Wallet, + provider, + txDefaults, + signerAddress, + ); + testValidator = await ValidatorContract.deployFrom0xArtifactAsync( + examplesArtifacts.Validator, + provider, + txDefaults, + signerAddress, + ); + maliciousWallet = maliciousValidator = await TestStaticCallReceiverContract.deployFrom0xArtifactAsync( + artifacts.TestStaticCallReceiver, + provider, + txDefaults, + ); + signatureValidatorLogDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...tokensArtifacts }); + await web3Wrapper.awaitTransactionSuccessAsync( + await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync(testValidator.address, true, { + from: signerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( + maliciousValidator.address, + true, + { + from: signerAddress, + }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + const defaultOrderParams = { + ...constants.STATIC_ORDER_PARAMS, + exchangeAddress: signatureValidator.address, + makerAddress, + feeRecipientAddress: addressUtils.generatePseudoRandomAddress(), + makerAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), + takerAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), + }; + signerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; + notSignerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(notSignerAddress)]; + orderFactory = new OrderFactory(signerPrivateKey, defaultOrderParams); + }); + + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + + describe('isValidSignature', () => { + beforeEach(async () => { + signedOrder = await orderFactory.newSignedOrderAsync(); + }); + + it('should revert when signature is empty', async () => { + const emptySignature = '0x'; + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + return expectContractCallFailedAsync( + signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + signedOrder.makerAddress, + emptySignature, + ), + RevertReason.LengthGreaterThan0Required, + ); + }); + + it('should revert when signature type is unsupported', async () => { + const unsupportedSignatureType = SignatureType.NSignatureTypes; + const unsupportedSignatureHex = `0x${Buffer.from([unsupportedSignatureType]).toString('hex')}`; + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + return expectContractCallFailedAsync( + signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + signedOrder.makerAddress, + unsupportedSignatureHex, + ), + RevertReason.SignatureUnsupported, + ); + }); + + it('should revert when SignatureType=Illegal', async () => { + const unsupportedSignatureHex = `0x${Buffer.from([SignatureType.Illegal]).toString('hex')}`; + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + return expectContractCallFailedAsync( + signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + signedOrder.makerAddress, + unsupportedSignatureHex, + ), + RevertReason.SignatureIllegal, + ); + }); + + it('should return false when SignatureType=Invalid and signature has a length of zero', async () => { + const signatureHex = `0x${Buffer.from([SignatureType.Invalid]).toString('hex')}`; + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + signedOrder.makerAddress, + signatureHex, + ); + expect(isValidSignature).to.be.false(); + }); + + it('should revert when SignatureType=Invalid and signature length is non-zero', async () => { + const fillerData = ethUtil.toBuffer('0xdeadbeef'); + const signatureType = ethUtil.toBuffer(`0x${SignatureType.Invalid}`); + const signatureBuffer = Buffer.concat([fillerData, signatureType]); + const signatureHex = ethUtil.bufferToHex(signatureBuffer); + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + return expectContractCallFailedAsync( + signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + signedOrder.makerAddress, + signatureHex, + ), + RevertReason.Length0Required, + ); + }); + + it('should return true when SignatureType=EIP712 and signature is valid', async () => { + // Create EIP712 signature + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + const orderHashBuffer = ethUtil.toBuffer(orderHashHex); + const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey); + // Create 0x signature from EIP712 signature + const signature = Buffer.concat([ + ethUtil.toBuffer(ecSignature.v), + ecSignature.r, + ecSignature.s, + ethUtil.toBuffer(`0x${SignatureType.EIP712}`), + ]); + const signatureHex = ethUtil.bufferToHex(signature); + // Validate signature + const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + signerAddress, + signatureHex, + ); + expect(isValidSignature).to.be.true(); + }); + + it('should return false when SignatureType=EIP712 and signature is invalid', async () => { + // Create EIP712 signature + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + const orderHashBuffer = ethUtil.toBuffer(orderHashHex); + const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey); + // Create 0x signature from EIP712 signature + const signature = Buffer.concat([ + ethUtil.toBuffer(ecSignature.v), + ecSignature.r, + ecSignature.s, + ethUtil.toBuffer(`0x${SignatureType.EIP712}`), + ]); + const signatureHex = ethUtil.bufferToHex(signature); + // Validate signature. + // This will fail because `signerAddress` signed the message, but we're passing in `notSignerAddress` + const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + notSignerAddress, + signatureHex, + ); + expect(isValidSignature).to.be.false(); + }); + + it('should return true when SignatureType=EthSign and signature is valid', async () => { + // Create EthSign signature + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix(orderHashHex); + const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex); + const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey); + // Create 0x signature from EthSign signature + const signature = Buffer.concat([ + ethUtil.toBuffer(ecSignature.v), + ecSignature.r, + ecSignature.s, + ethUtil.toBuffer(`0x${SignatureType.EthSign}`), + ]); + const signatureHex = ethUtil.bufferToHex(signature); + // Validate signature + const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + signerAddress, + signatureHex, + ); + expect(isValidSignature).to.be.true(); + }); + + it('should return false when SignatureType=EthSign and signature is invalid', async () => { + // Create EthSign signature + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix(orderHashHex); + const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex); + const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey); + // Create 0x signature from EthSign signature + const signature = Buffer.concat([ + ethUtil.toBuffer(ecSignature.v), + ecSignature.r, + ecSignature.s, + ethUtil.toBuffer(`0x${SignatureType.EthSign}`), + ]); + const signatureHex = ethUtil.bufferToHex(signature); + // Validate signature. + // This will fail because `signerAddress` signed the message, but we're passing in `notSignerAddress` + const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + notSignerAddress, + signatureHex, + ); + expect(isValidSignature).to.be.false(); + }); + + it('should return true when SignatureType=Wallet and signature is valid', async () => { + // Create EIP712 signature + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + const orderHashBuffer = ethUtil.toBuffer(orderHashHex); + const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey); + // Create 0x signature from EIP712 signature + const signature = Buffer.concat([ + ethUtil.toBuffer(ecSignature.v), + ecSignature.r, + ecSignature.s, + ethUtil.toBuffer(`0x${SignatureType.Wallet}`), + ]); + const signatureHex = ethUtil.bufferToHex(signature); + // Validate signature + const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + testWallet.address, + signatureHex, + ); + expect(isValidSignature).to.be.true(); + }); + + it('should return false when SignatureType=Wallet and signature is invalid', async () => { + // Create EIP712 signature using a private key that does not belong to the wallet owner. + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + const orderHashBuffer = ethUtil.toBuffer(orderHashHex); + const notWalletOwnerPrivateKey = notSignerPrivateKey; + const ecSignature = ethUtil.ecsign(orderHashBuffer, notWalletOwnerPrivateKey); + // Create 0x signature from EIP712 signature + const signature = Buffer.concat([ + ethUtil.toBuffer(ecSignature.v), + ecSignature.r, + ecSignature.s, + ethUtil.toBuffer(`0x${SignatureType.Wallet}`), + ]); + const signatureHex = ethUtil.bufferToHex(signature); + // Validate signature + const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + testWallet.address, + signatureHex, + ); + expect(isValidSignature).to.be.false(); + }); + + it('should revert when `isValidSignature` attempts to update state and SignatureType=Wallet', async () => { + // Create EIP712 signature + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + const orderHashBuffer = ethUtil.toBuffer(orderHashHex); + const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey); + // Create 0x signature from EIP712 signature + const signature = Buffer.concat([ + ethUtil.toBuffer(ecSignature.v), + ecSignature.r, + ecSignature.s, + ethUtil.toBuffer(`0x${SignatureType.Wallet}`), + ]); + const signatureHex = ethUtil.bufferToHex(signature); + await expectContractCallFailedAsync( + signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + maliciousWallet.address, + signatureHex, + ), + RevertReason.WalletError, + ); + }); + + it('should return true when SignatureType=Validator, signature is valid and validator is approved', async () => { + const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`); + const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); + const signature = Buffer.concat([validatorAddress, signatureType]); + const signatureHex = ethUtil.bufferToHex(signature); + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + signerAddress, + signatureHex, + ); + expect(isValidSignature).to.be.true(); + }); + + it('should return false when SignatureType=Validator, signature is invalid and validator is approved', async () => { + const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`); + const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); + const signature = Buffer.concat([validatorAddress, signatureType]); + const signatureHex = ethUtil.bufferToHex(signature); + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + // This will return false because we signed the message with `signerAddress`, but + // are validating against `notSignerAddress` + const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + notSignerAddress, + signatureHex, + ); + expect(isValidSignature).to.be.false(); + }); + + it('should revert when `isValidSignature` attempts to update state and SignatureType=Validator', async () => { + const validatorAddress = ethUtil.toBuffer(`${maliciousValidator.address}`); + const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); + const signature = Buffer.concat([validatorAddress, signatureType]); + const signatureHex = ethUtil.bufferToHex(signature); + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + await expectContractCallFailedAsync( + signatureValidator.publicIsValidSignature.callAsync(orderHashHex, signerAddress, signatureHex), + RevertReason.ValidatorError, + ); + }); + it('should return false when SignatureType=Validator, signature is valid and validator is not approved', async () => { + // Set approval of signature validator to false + await web3Wrapper.awaitTransactionSuccessAsync( + await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( + testValidator.address, + false, + { from: signerAddress }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Validate signature + const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`); + const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); + const signature = Buffer.concat([validatorAddress, signatureType]); + const signatureHex = ethUtil.bufferToHex(signature); + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + signerAddress, + signatureHex, + ); + expect(isValidSignature).to.be.false(); + }); + + it('should return true when SignatureType=Presigned and signer has presigned hash', async () => { + // Presign hash + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + await web3Wrapper.awaitTransactionSuccessAsync( + await signatureValidator.preSign.sendTransactionAsync( + orderHashHex, + signedOrder.makerAddress, + signedOrder.signature, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + // Validate presigned signature + const signature = ethUtil.toBuffer(`0x${SignatureType.PreSigned}`); + const signatureHex = ethUtil.bufferToHex(signature); + const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + signedOrder.makerAddress, + signatureHex, + ); + expect(isValidSignature).to.be.true(); + }); + + it('should return false when SignatureType=Presigned and signer has not presigned hash', async () => { + const signature = ethUtil.toBuffer(`0x${SignatureType.PreSigned}`); + const signatureHex = ethUtil.bufferToHex(signature); + const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); + const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( + orderHashHex, + signedOrder.makerAddress, + signatureHex, + ); + expect(isValidSignature).to.be.false(); + }); + + it('should return true when message was signed by a Trezor One (firmware version 1.6.2)', async () => { + // messageHash translates to 0x2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b + const messageHash = ethUtil.bufferToHex(ethUtil.toBuffer('++++++++++++++++++++++++++++++++')); + const signer = '0xc28b145f10f0bcf0fc000e778615f8fd73490bad'; + const v = ethUtil.toBuffer('0x1c'); + const r = ethUtil.toBuffer('0x7b888b596ccf87f0bacab0dcb483124973f7420f169b4824d7a12534ac1e9832'); + const s = ethUtil.toBuffer('0x0c8e14f7edc01459e13965f1da56e0c23ed11e2cca932571eee1292178f90424'); + const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`); + const signature = Buffer.concat([v, r, s, trezorSignatureType]); + const signatureHex = ethUtil.bufferToHex(signature); + const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( + messageHash, + signer, + signatureHex, + ); + expect(isValidSignature).to.be.true(); + }); + + it('should return true when message was signed by a Trezor Model T (firmware version 2.0.7)', async () => { + // messageHash translates to 0x2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b + const messageHash = ethUtil.bufferToHex(ethUtil.toBuffer('++++++++++++++++++++++++++++++++')); + const signer = '0x98ce6d9345e8ffa7d99ee0822272fae9d2c0e895'; + const v = ethUtil.toBuffer('0x1c'); + const r = ethUtil.toBuffer('0x423b71062c327f0ec4fe199b8da0f34185e59b4c1cb4cc23df86cac4a601fb3f'); + const s = ethUtil.toBuffer('0x53810d6591b5348b7ee08ee812c874b0fdfb942c9849d59512c90e295221091f'); + const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`); + const signature = Buffer.concat([v, r, s, trezorSignatureType]); + const signatureHex = ethUtil.bufferToHex(signature); + const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( + messageHash, + signer, + signatureHex, + ); + expect(isValidSignature).to.be.true(); + }); + }); + + describe('setSignatureValidatorApproval', () => { + it('should emit a SignatureValidatorApprovalSet with correct args when a validator is approved', async () => { + const approval = true; + const res = await signatureValidatorLogDecoder.getTxWithDecodedLogsAsync( + await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( + testValidator.address, + approval, + { + from: signerAddress, + }, + ), + ); + expect(res.logs.length).to.equal(1); + const log = res.logs[0] as LogWithDecodedArgs; + const logArgs = log.args; + expect(logArgs.signerAddress).to.equal(signerAddress); + expect(logArgs.validatorAddress).to.equal(testValidator.address); + expect(logArgs.approved).to.equal(approval); + }); + it('should emit a SignatureValidatorApprovalSet with correct args when a validator is disapproved', async () => { + const approval = false; + const res = await signatureValidatorLogDecoder.getTxWithDecodedLogsAsync( + await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( + testValidator.address, + approval, + { + from: signerAddress, + }, + ), + ); + expect(res.logs.length).to.equal(1); + const log = res.logs[0] as LogWithDecodedArgs; + const logArgs = log.args; + expect(logArgs.signerAddress).to.equal(signerAddress); + expect(logArgs.validatorAddress).to.equal(testValidator.address); + expect(logArgs.approved).to.equal(approval); + }); + }); +}); +// tslint:disable:max-file-line-count +// tslint:enable:no-unnecessary-type-assertion diff --git a/contracts/exchange/test/transactions.ts b/contracts/exchange/test/transactions.ts new file mode 100644 index 000000000..737944600 --- /dev/null +++ b/contracts/exchange/test/transactions.ts @@ -0,0 +1,467 @@ +import { ERC20ProxyContract } from '@0x/contracts-asset-proxy'; +import { artifacts as examplesArtifacts, ExchangeWrapperContract, WhitelistContract } from '@0x/contracts-examples'; +import { + chaiSetup, + constants, + ERC20BalancesByOwner, + expectTransactionFailedAsync, + OrderFactory, + orderUtils, + provider, + SignedTransaction, + TransactionFactory, + txDefaults, + web3Wrapper, +} from '@0x/contracts-test-utils'; +import { DummyERC20TokenContract } from '@0x/contracts-tokens'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils'; +import { OrderWithoutExchangeAddress, RevertReason, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; +import * as _ from 'lodash'; + +import { ExchangeContract } from '../generated-wrappers/exchange'; +import { artifacts } from '../src/artifacts'; + +import { ERC20Wrapper } from './utils/erc20_wrapper'; +import { ExchangeWrapper } from './utils/exchange_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('Exchange transactions', () => { + let senderAddress: string; + let owner: string; + let makerAddress: string; + let takerAddress: string; + let feeRecipientAddress: string; + + let erc20TokenA: DummyERC20TokenContract; + let erc20TokenB: DummyERC20TokenContract; + let zrxToken: DummyERC20TokenContract; + let exchange: ExchangeContract; + let erc20Proxy: ERC20ProxyContract; + + let erc20Balances: ERC20BalancesByOwner; + let signedOrder: SignedOrder; + let signedTx: SignedTransaction; + let orderWithoutExchangeAddress: OrderWithoutExchangeAddress; + let orderFactory: OrderFactory; + let makerTransactionFactory: TransactionFactory; + let takerTransactionFactory: TransactionFactory; + let exchangeWrapper: ExchangeWrapper; + let erc20Wrapper: ERC20Wrapper; + + let defaultMakerTokenAddress: string; + let defaultTakerTokenAddress: string; + let makerPrivateKey: Buffer; + let takerPrivateKey: Buffer; + + before(async () => { + await blockchainLifecycle.startAsync(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + before(async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const usedAddresses = ([owner, senderAddress, makerAddress, takerAddress, feeRecipientAddress] = _.slice( + accounts, + 0, + 5, + )); + + erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + + const numDummyErc20ToDeploy = 3; + [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + numDummyErc20ToDeploy, + constants.DUMMY_TOKEN_DECIMALS, + ); + erc20Proxy = await erc20Wrapper.deployProxyAsync(); + await erc20Wrapper.setBalancesAndAllowancesAsync(); + + exchange = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + assetDataUtils.encodeERC20AssetData(zrxToken.address), + ); + exchangeWrapper = new ExchangeWrapper(exchange, provider); + await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); + + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + defaultMakerTokenAddress = erc20TokenA.address; + defaultTakerTokenAddress = erc20TokenB.address; + + const defaultOrderParams = { + ...constants.STATIC_ORDER_PARAMS, + senderAddress, + exchangeAddress: exchange.address, + makerAddress, + feeRecipientAddress, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerTokenAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerTokenAddress), + }; + makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; + takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(takerAddress)]; + orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams); + makerTransactionFactory = new TransactionFactory(makerPrivateKey, exchange.address); + takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchange.address); + }); + describe('executeTransaction', () => { + describe('fillOrder', () => { + let takerAssetFillAmount: BigNumber; + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + signedOrder = await orderFactory.newSignedOrderAsync(); + orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); + + takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); + const data = exchange.fillOrder.getABIEncodedTransactionData( + orderWithoutExchangeAddress, + takerAssetFillAmount, + signedOrder.signature, + ); + signedTx = takerTransactionFactory.newSignedTransaction(data); + }); + + it('should throw if not called by specified sender', async () => { + return expectTransactionFailedAsync( + exchangeWrapper.executeTransactionAsync(signedTx, takerAddress), + RevertReason.FailedExecution, + ); + }); + + it('should transfer the correct amounts when signed by taker and called by sender', async () => { + await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(signedOrder.makerAssetAmount) + .dividedToIntegerBy(signedOrder.takerAssetAmount); + const makerFeePaid = signedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + const takerFeePaid = signedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + expect(newBalances[makerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultMakerTokenAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[makerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultTakerTokenAddress].plus(takerAssetFillAmount), + ); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[takerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultTakerTokenAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[takerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultMakerTokenAddress].plus(makerAssetFillAmount), + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFeePaid.plus(takerFeePaid)), + ); + }); + + it('should throw if the a 0x transaction with the same transactionHash has already been executed', async () => { + await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); + return expectTransactionFailedAsync( + exchangeWrapper.executeTransactionAsync(signedTx, senderAddress), + RevertReason.InvalidTxHash, + ); + }); + + it('should reset the currentContextAddress', async () => { + await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); + const currentContextAddress = await exchange.currentContextAddress.callAsync(); + expect(currentContextAddress).to.equal(constants.NULL_ADDRESS); + }); + }); + + describe('cancelOrder', () => { + beforeEach(async () => { + const data = exchange.cancelOrder.getABIEncodedTransactionData(orderWithoutExchangeAddress); + signedTx = makerTransactionFactory.newSignedTransaction(data); + }); + + it('should throw if not called by specified sender', async () => { + return expectTransactionFailedAsync( + exchangeWrapper.executeTransactionAsync(signedTx, makerAddress), + RevertReason.FailedExecution, + ); + }); + + it('should cancel the order when signed by maker and called by sender', async () => { + await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); + return expectTransactionFailedAsync( + exchangeWrapper.fillOrderAsync(signedOrder, senderAddress), + RevertReason.OrderUnfillable, + ); + }); + }); + + describe('cancelOrdersUpTo', () => { + let exchangeWrapperContract: ExchangeWrapperContract; + + before(async () => { + exchangeWrapperContract = await ExchangeWrapperContract.deployFrom0xArtifactAsync( + examplesArtifacts.ExchangeWrapper, + provider, + txDefaults, + exchange.address, + ); + }); + + it("should cancel an order if called from the order's sender", async () => { + const orderSalt = new BigNumber(0); + signedOrder = await orderFactory.newSignedOrderAsync({ + senderAddress: exchangeWrapperContract.address, + salt: orderSalt, + }); + const targetOrderEpoch = orderSalt.plus(1); + const cancelData = exchange.cancelOrdersUpTo.getABIEncodedTransactionData(targetOrderEpoch); + const signedCancelTx = makerTransactionFactory.newSignedTransaction(cancelData); + await exchangeWrapperContract.cancelOrdersUpTo.sendTransactionAsync( + targetOrderEpoch, + signedCancelTx.salt, + signedCancelTx.signature, + { + from: makerAddress, + }, + ); + + const takerAssetFillAmount = signedOrder.takerAssetAmount; + orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); + const fillData = exchange.fillOrder.getABIEncodedTransactionData( + orderWithoutExchangeAddress, + takerAssetFillAmount, + signedOrder.signature, + ); + const signedFillTx = takerTransactionFactory.newSignedTransaction(fillData); + return expectTransactionFailedAsync( + exchangeWrapperContract.fillOrder.sendTransactionAsync( + orderWithoutExchangeAddress, + takerAssetFillAmount, + signedFillTx.salt, + signedOrder.signature, + signedFillTx.signature, + { from: takerAddress }, + ), + RevertReason.FailedExecution, + ); + }); + + it("should not cancel an order if not called from the order's sender", async () => { + const orderSalt = new BigNumber(0); + signedOrder = await orderFactory.newSignedOrderAsync({ + senderAddress: exchangeWrapperContract.address, + salt: orderSalt, + }); + const targetOrderEpoch = orderSalt.plus(1); + await exchangeWrapper.cancelOrdersUpToAsync(targetOrderEpoch, makerAddress); + + erc20Balances = await erc20Wrapper.getBalancesAsync(); + const takerAssetFillAmount = signedOrder.takerAssetAmount; + orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); + const data = exchange.fillOrder.getABIEncodedTransactionData( + orderWithoutExchangeAddress, + takerAssetFillAmount, + signedOrder.signature, + ); + signedTx = takerTransactionFactory.newSignedTransaction(data); + await exchangeWrapperContract.fillOrder.sendTransactionAsync( + orderWithoutExchangeAddress, + takerAssetFillAmount, + signedTx.salt, + signedOrder.signature, + signedTx.signature, + { from: takerAddress }, + ); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(signedOrder.makerAssetAmount) + .dividedToIntegerBy(signedOrder.takerAssetAmount); + const makerFeePaid = signedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + const takerFeePaid = signedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + expect(newBalances[makerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultMakerTokenAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[makerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultTakerTokenAddress].plus(takerAssetFillAmount), + ); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[takerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultTakerTokenAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[takerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultMakerTokenAddress].plus(makerAssetFillAmount), + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFeePaid.plus(takerFeePaid)), + ); + }); + }); + }); + + describe('Whitelist', () => { + let whitelist: WhitelistContract; + let whitelistOrderFactory: OrderFactory; + + before(async () => { + whitelist = await WhitelistContract.deployFrom0xArtifactAsync( + examplesArtifacts.Whitelist, + provider, + txDefaults, + exchange.address, + ); + const isApproved = true; + await web3Wrapper.awaitTransactionSuccessAsync( + await exchange.setSignatureValidatorApproval.sendTransactionAsync(whitelist.address, isApproved, { + from: takerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + const defaultOrderParams = { + ...constants.STATIC_ORDER_PARAMS, + senderAddress: whitelist.address, + exchangeAddress: exchange.address, + makerAddress, + feeRecipientAddress, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerTokenAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerTokenAddress), + }; + whitelistOrderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams); + }); + + beforeEach(async () => { + signedOrder = await whitelistOrderFactory.newSignedOrderAsync(); + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + + it('should revert if maker has not been whitelisted', async () => { + const isApproved = true; + await web3Wrapper.awaitTransactionSuccessAsync( + await whitelist.updateWhitelistStatus.sendTransactionAsync(takerAddress, isApproved, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); + const takerAssetFillAmount = signedOrder.takerAssetAmount; + const salt = generatePseudoRandomSalt(); + return expectTransactionFailedAsync( + whitelist.fillOrderIfWhitelisted.sendTransactionAsync( + orderWithoutExchangeAddress, + takerAssetFillAmount, + salt, + signedOrder.signature, + { from: takerAddress }, + ), + RevertReason.MakerNotWhitelisted, + ); + }); + + it('should revert if taker has not been whitelisted', async () => { + const isApproved = true; + await web3Wrapper.awaitTransactionSuccessAsync( + await whitelist.updateWhitelistStatus.sendTransactionAsync(makerAddress, isApproved, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); + const takerAssetFillAmount = signedOrder.takerAssetAmount; + const salt = generatePseudoRandomSalt(); + return expectTransactionFailedAsync( + whitelist.fillOrderIfWhitelisted.sendTransactionAsync( + orderWithoutExchangeAddress, + takerAssetFillAmount, + salt, + signedOrder.signature, + { from: takerAddress }, + ), + RevertReason.TakerNotWhitelisted, + ); + }); + + it('should fill the order if maker and taker have been whitelisted', async () => { + const isApproved = true; + await web3Wrapper.awaitTransactionSuccessAsync( + await whitelist.updateWhitelistStatus.sendTransactionAsync(makerAddress, isApproved, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + await web3Wrapper.awaitTransactionSuccessAsync( + await whitelist.updateWhitelistStatus.sendTransactionAsync(takerAddress, isApproved, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); + const takerAssetFillAmount = signedOrder.takerAssetAmount; + const salt = generatePseudoRandomSalt(); + await web3Wrapper.awaitTransactionSuccessAsync( + await whitelist.fillOrderIfWhitelisted.sendTransactionAsync( + orderWithoutExchangeAddress, + takerAssetFillAmount, + salt, + signedOrder.signature, + { from: takerAddress }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + + const makerAssetFillAmount = signedOrder.makerAssetAmount; + const makerFeePaid = signedOrder.makerFee; + const takerFeePaid = signedOrder.takerFee; + + expect(newBalances[makerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultMakerTokenAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[makerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultTakerTokenAddress].plus(takerAssetFillAmount), + ); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[takerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultTakerTokenAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[takerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultMakerTokenAddress].plus(makerAssetFillAmount), + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFeePaid.plus(takerFeePaid)), + ); + }); + }); +}); diff --git a/contracts/exchange/test/utils/asset_wrapper.ts b/contracts/exchange/test/utils/asset_wrapper.ts new file mode 100644 index 000000000..e4090ad74 --- /dev/null +++ b/contracts/exchange/test/utils/asset_wrapper.ts @@ -0,0 +1,222 @@ +import { AbstractAssetWrapper, constants } from '@0x/contracts-test-utils'; +import { assetDataUtils } from '@0x/order-utils'; +import { AssetProxyId } from '@0x/types'; +import { BigNumber, errorUtils } from '@0x/utils'; +import * as _ from 'lodash'; + +import { ERC20Wrapper } from './erc20_wrapper'; +import { ERC721Wrapper } from './erc721_wrapper'; + +interface ProxyIdToAssetWrappers { + [proxyId: string]: AbstractAssetWrapper; +} + +/** + * This class abstracts away the differences between ERC20 and ERC721 tokens so that + * the logic that uses it does not need to care what standard a token belongs to. + */ +export class AssetWrapper { + private readonly _proxyIdToAssetWrappers: ProxyIdToAssetWrappers; + constructor(assetWrappers: AbstractAssetWrapper[]) { + this._proxyIdToAssetWrappers = {}; + _.each(assetWrappers, assetWrapper => { + const proxyId = assetWrapper.getProxyId(); + this._proxyIdToAssetWrappers[proxyId] = assetWrapper; + }); + } + public async getBalanceAsync(userAddress: string, assetData: string): Promise { + const proxyId = assetDataUtils.decodeAssetProxyId(assetData); + switch (proxyId) { + case AssetProxyId.ERC20: { + const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper; + const balance = await erc20Wrapper.getBalanceAsync(userAddress, assetData); + return balance; + } + case AssetProxyId.ERC721: { + const assetWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper; + const assetProxyData = assetDataUtils.decodeERC721AssetData(assetData); + const isOwner = await assetWrapper.isOwnerAsync( + userAddress, + assetProxyData.tokenAddress, + assetProxyData.tokenId, + ); + const balance = isOwner ? new BigNumber(1) : new BigNumber(0); + return balance; + } + default: + throw errorUtils.spawnSwitchErr('proxyId', proxyId); + } + } + public async setBalanceAsync(userAddress: string, assetData: string, desiredBalance: BigNumber): Promise { + const proxyId = assetDataUtils.decodeAssetProxyId(assetData); + switch (proxyId) { + case AssetProxyId.ERC20: { + const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper; + await erc20Wrapper.setBalanceAsync(userAddress, assetData, desiredBalance); + return; + } + case AssetProxyId.ERC721: { + if (!desiredBalance.eq(0) && !desiredBalance.eq(1)) { + throw new Error(`Balance for ERC721 token can only be set to 0 or 1. Got: ${desiredBalance}`); + } + const erc721Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper; + const assetProxyData = assetDataUtils.decodeERC721AssetData(assetData); + const doesTokenExist = erc721Wrapper.doesTokenExistAsync( + assetProxyData.tokenAddress, + assetProxyData.tokenId, + ); + if (!doesTokenExist && desiredBalance.eq(1)) { + await erc721Wrapper.mintAsync(assetProxyData.tokenAddress, assetProxyData.tokenId, userAddress); + return; + } else if (!doesTokenExist && desiredBalance.eq(0)) { + return; // noop + } + const tokenOwner = await erc721Wrapper.ownerOfAsync( + assetProxyData.tokenAddress, + assetProxyData.tokenId, + ); + if (userAddress !== tokenOwner && desiredBalance.eq(1)) { + await erc721Wrapper.transferFromAsync( + assetProxyData.tokenAddress, + assetProxyData.tokenId, + tokenOwner, + userAddress, + ); + } else if (tokenOwner === userAddress && desiredBalance.eq(0)) { + // Transfer token to someone else + const userAddresses = await (erc721Wrapper as any)._web3Wrapper.getAvailableAddressesAsync(); + const nonOwner = _.find(userAddresses, a => a !== userAddress); + await erc721Wrapper.transferFromAsync( + assetProxyData.tokenAddress, + assetProxyData.tokenId, + tokenOwner, + nonOwner, + ); + return; + } else if ( + (userAddress !== tokenOwner && desiredBalance.eq(0)) || + (tokenOwner === userAddress && desiredBalance.eq(1)) + ) { + return; // noop + } + break; + } + default: + throw errorUtils.spawnSwitchErr('proxyId', proxyId); + } + } + public async getProxyAllowanceAsync(userAddress: string, assetData: string): Promise { + const proxyId = assetDataUtils.decodeAssetProxyId(assetData); + switch (proxyId) { + case AssetProxyId.ERC20: { + const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper; + const allowance = await erc20Wrapper.getProxyAllowanceAsync(userAddress, assetData); + return allowance; + } + case AssetProxyId.ERC721: { + const assetWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper; + const erc721ProxyData = assetDataUtils.decodeERC721AssetData(assetData); + const isProxyApprovedForAll = await assetWrapper.isProxyApprovedForAllAsync( + userAddress, + erc721ProxyData.tokenAddress, + ); + if (isProxyApprovedForAll) { + return constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; + } + + const isProxyApproved = await assetWrapper.isProxyApprovedAsync( + erc721ProxyData.tokenAddress, + erc721ProxyData.tokenId, + ); + const allowance = isProxyApproved ? new BigNumber(1) : new BigNumber(0); + return allowance; + } + default: + throw errorUtils.spawnSwitchErr('proxyId', proxyId); + } + } + public async setProxyAllowanceAsync( + userAddress: string, + assetData: string, + desiredAllowance: BigNumber, + ): Promise { + const proxyId = assetDataUtils.decodeAssetProxyId(assetData); + switch (proxyId) { + case AssetProxyId.ERC20: { + const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper; + await erc20Wrapper.setAllowanceAsync(userAddress, assetData, desiredAllowance); + return; + } + case AssetProxyId.ERC721: { + if ( + !desiredAllowance.eq(0) && + !desiredAllowance.eq(1) && + !desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS) + ) { + throw new Error( + `Allowance for ERC721 token can only be set to 0, 1 or 2^256-1. Got: ${desiredAllowance}`, + ); + } + const erc721Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper; + const assetProxyData = assetDataUtils.decodeERC721AssetData(assetData); + + const doesTokenExist = await erc721Wrapper.doesTokenExistAsync( + assetProxyData.tokenAddress, + assetProxyData.tokenId, + ); + if (!doesTokenExist) { + throw new Error( + `Cannot setProxyAllowance on non-existent token: ${assetProxyData.tokenAddress} ${ + assetProxyData.tokenId + }`, + ); + } + const isProxyApprovedForAll = await erc721Wrapper.isProxyApprovedForAllAsync( + userAddress, + assetProxyData.tokenAddress, + ); + if (!isProxyApprovedForAll && desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) { + const isApproved = true; + await erc721Wrapper.approveProxyForAllAsync( + assetProxyData.tokenAddress, + assetProxyData.tokenId, + isApproved, + ); + } else if (isProxyApprovedForAll && desiredAllowance.eq(0)) { + const isApproved = false; + await erc721Wrapper.approveProxyForAllAsync( + assetProxyData.tokenAddress, + assetProxyData.tokenId, + isApproved, + ); + } else if (isProxyApprovedForAll && desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) { + return; // Noop + } + + const isProxyApproved = await erc721Wrapper.isProxyApprovedAsync( + assetProxyData.tokenAddress, + assetProxyData.tokenId, + ); + if (!isProxyApproved && desiredAllowance.eq(1)) { + await erc721Wrapper.approveProxyAsync(assetProxyData.tokenAddress, assetProxyData.tokenId); + } else if (isProxyApproved && desiredAllowance.eq(0)) { + // Remove approval + await erc721Wrapper.approveAsync( + constants.NULL_ADDRESS, + assetProxyData.tokenAddress, + assetProxyData.tokenId, + ); + } else if ( + (!isProxyApproved && desiredAllowance.eq(0)) || + (isProxyApproved && desiredAllowance.eq(1)) + ) { + return; // noop + } + + break; + } + default: + throw errorUtils.spawnSwitchErr('proxyId', proxyId); + } + } +} diff --git a/contracts/exchange/test/utils/erc20_wrapper.ts b/contracts/exchange/test/utils/erc20_wrapper.ts new file mode 100644 index 000000000..3fe21ff96 --- /dev/null +++ b/contracts/exchange/test/utils/erc20_wrapper.ts @@ -0,0 +1,177 @@ +import { artifacts as proxyArtifacts, ERC20ProxyContract } from '@0x/contracts-asset-proxy'; +import { constants, ERC20BalancesByOwner, txDefaults } from '@0x/contracts-test-utils'; +import { artifacts as tokensArtifacts, DummyERC20TokenContract } from '@0x/contracts-tokens'; +import { assetDataUtils } from '@0x/order-utils'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import { Provider } from 'ethereum-types'; +import * as _ from 'lodash'; + +export class ERC20Wrapper { + private readonly _tokenOwnerAddresses: string[]; + private readonly _contractOwnerAddress: string; + private readonly _web3Wrapper: Web3Wrapper; + private readonly _provider: Provider; + private readonly _dummyTokenContracts: DummyERC20TokenContract[]; + private _proxyContract?: ERC20ProxyContract; + private _proxyIdIfExists?: string; + /** + * Instanitates an ERC20Wrapper + * @param provider Web3 provider to use for all JSON RPC requests + * @param tokenOwnerAddresses Addresses that we want to endow as owners for dummy ERC20 tokens + * @param contractOwnerAddress Desired owner of the contract + * Instance of ERC20Wrapper + */ + constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) { + this._dummyTokenContracts = []; + this._web3Wrapper = new Web3Wrapper(provider); + this._provider = provider; + this._tokenOwnerAddresses = tokenOwnerAddresses; + this._contractOwnerAddress = contractOwnerAddress; + } + public async deployDummyTokensAsync( + numberToDeploy: number, + decimals: BigNumber, + ): Promise { + for (let i = 0; i < numberToDeploy; i++) { + this._dummyTokenContracts.push( + await DummyERC20TokenContract.deployFrom0xArtifactAsync( + tokensArtifacts.DummyERC20Token, + this._provider, + txDefaults, + constants.DUMMY_TOKEN_NAME, + constants.DUMMY_TOKEN_SYMBOL, + decimals, + constants.DUMMY_TOKEN_TOTAL_SUPPLY, + ), + ); + } + return this._dummyTokenContracts; + } + public async deployProxyAsync(): Promise { + this._proxyContract = await ERC20ProxyContract.deployFrom0xArtifactAsync( + proxyArtifacts.ERC20Proxy, + this._provider, + txDefaults, + ); + this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync(); + return this._proxyContract; + } + public getProxyId(): string { + this._validateProxyContractExistsOrThrow(); + return this._proxyIdIfExists as string; + } + public async setBalancesAndAllowancesAsync(): Promise { + this._validateDummyTokenContractsExistOrThrow(); + this._validateProxyContractExistsOrThrow(); + for (const dummyTokenContract of this._dummyTokenContracts) { + for (const tokenOwnerAddress of this._tokenOwnerAddresses) { + await this._web3Wrapper.awaitTransactionSuccessAsync( + await dummyTokenContract.setBalance.sendTransactionAsync( + tokenOwnerAddress, + constants.INITIAL_ERC20_BALANCE, + { from: this._contractOwnerAddress }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await dummyTokenContract.approve.sendTransactionAsync( + (this._proxyContract as ERC20ProxyContract).address, + constants.INITIAL_ERC20_ALLOWANCE, + { from: tokenOwnerAddress }, + ), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + } + } + public async getBalanceAsync(userAddress: string, assetData: string): Promise { + const tokenContract = this._getTokenContractFromAssetData(assetData); + const balance = new BigNumber(await tokenContract.balanceOf.callAsync(userAddress)); + return balance; + } + public async setBalanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise { + const tokenContract = this._getTokenContractFromAssetData(assetData); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.setBalance.sendTransactionAsync(userAddress, amount, { + from: this._contractOwnerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async getProxyAllowanceAsync(userAddress: string, assetData: string): Promise { + const tokenContract = this._getTokenContractFromAssetData(assetData); + const proxyAddress = (this._proxyContract as ERC20ProxyContract).address; + const allowance = new BigNumber(await tokenContract.allowance.callAsync(userAddress, proxyAddress)); + return allowance; + } + public async setAllowanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise { + const tokenContract = this._getTokenContractFromAssetData(assetData); + const proxyAddress = (this._proxyContract as ERC20ProxyContract).address; + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.approve.sendTransactionAsync(proxyAddress, amount, { + from: userAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async getBalancesAsync(): Promise { + this._validateDummyTokenContractsExistOrThrow(); + const balancesByOwner: ERC20BalancesByOwner = {}; + const balances: BigNumber[] = []; + const balanceInfo: Array<{ tokenOwnerAddress: string; tokenAddress: string }> = []; + for (const dummyTokenContract of this._dummyTokenContracts) { + for (const tokenOwnerAddress of this._tokenOwnerAddresses) { + balances.push(await dummyTokenContract.balanceOf.callAsync(tokenOwnerAddress)); + balanceInfo.push({ + tokenOwnerAddress, + tokenAddress: dummyTokenContract.address, + }); + } + } + _.forEach(balances, (balance, balanceIndex) => { + const tokenAddress = balanceInfo[balanceIndex].tokenAddress; + const tokenOwnerAddress = balanceInfo[balanceIndex].tokenOwnerAddress; + if (_.isUndefined(balancesByOwner[tokenOwnerAddress])) { + balancesByOwner[tokenOwnerAddress] = {}; + } + const wrappedBalance = new BigNumber(balance); + balancesByOwner[tokenOwnerAddress][tokenAddress] = wrappedBalance; + }); + return balancesByOwner; + } + public addDummyTokenContract(dummy: DummyERC20TokenContract): void { + if (!_.isUndefined(this._dummyTokenContracts)) { + this._dummyTokenContracts.push(dummy); + } + } + public addTokenOwnerAddress(address: string): void { + this._tokenOwnerAddresses.push(address); + } + public getTokenOwnerAddresses(): string[] { + return this._tokenOwnerAddresses; + } + public getTokenAddresses(): string[] { + const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address); + return tokenAddresses; + } + private _getTokenContractFromAssetData(assetData: string): DummyERC20TokenContract { + const erc20ProxyData = assetDataUtils.decodeERC20AssetData(assetData); + const tokenAddress = erc20ProxyData.tokenAddress; + const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress); + if (_.isUndefined(tokenContractIfExists)) { + throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`); + } + return tokenContractIfExists; + } + private _validateDummyTokenContractsExistOrThrow(): void { + if (_.isUndefined(this._dummyTokenContracts)) { + throw new Error('Dummy ERC20 tokens not yet deployed, please call "deployDummyTokensAsync"'); + } + } + private _validateProxyContractExistsOrThrow(): void { + if (_.isUndefined(this._proxyContract)) { + throw new Error('ERC20 proxy contract not yet deployed, please call "deployProxyAsync"'); + } + } +} diff --git a/contracts/exchange/test/utils/erc721_wrapper.ts b/contracts/exchange/test/utils/erc721_wrapper.ts new file mode 100644 index 000000000..021c75ef2 --- /dev/null +++ b/contracts/exchange/test/utils/erc721_wrapper.ts @@ -0,0 +1,234 @@ +import { artifacts as proxyArtifacts, ERC721ProxyContract } from '@0x/contracts-asset-proxy'; +import { constants, ERC721TokenIdsByOwner, txDefaults } from '@0x/contracts-test-utils'; +import { artifacts as tokensArtifacts, DummyERC721TokenContract } from '@0x/contracts-tokens'; +import { generatePseudoRandomSalt } from '@0x/order-utils'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import { Provider } from 'ethereum-types'; +import * as _ from 'lodash'; + +export class ERC721Wrapper { + private readonly _tokenOwnerAddresses: string[]; + private readonly _contractOwnerAddress: string; + private readonly _web3Wrapper: Web3Wrapper; + private readonly _provider: Provider; + private readonly _dummyTokenContracts: DummyERC721TokenContract[]; + private _proxyContract?: ERC721ProxyContract; + private _proxyIdIfExists?: string; + private _initialTokenIdsByOwner: ERC721TokenIdsByOwner = {}; + constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) { + this._web3Wrapper = new Web3Wrapper(provider); + this._provider = provider; + this._dummyTokenContracts = []; + this._tokenOwnerAddresses = tokenOwnerAddresses; + this._contractOwnerAddress = contractOwnerAddress; + } + public async deployDummyTokensAsync(): Promise { + // tslint:disable-next-line:no-unused-variable + for (const i of _.times(constants.NUM_DUMMY_ERC721_TO_DEPLOY)) { + this._dummyTokenContracts.push( + await DummyERC721TokenContract.deployFrom0xArtifactAsync( + tokensArtifacts.DummyERC721Token, + this._provider, + txDefaults, + constants.DUMMY_TOKEN_NAME, + constants.DUMMY_TOKEN_SYMBOL, + ), + ); + } + return this._dummyTokenContracts; + } + public async deployProxyAsync(): Promise { + this._proxyContract = await ERC721ProxyContract.deployFrom0xArtifactAsync( + proxyArtifacts.ERC721Proxy, + this._provider, + txDefaults, + ); + this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync(); + return this._proxyContract; + } + public getProxyId(): string { + this._validateProxyContractExistsOrThrow(); + return this._proxyIdIfExists as string; + } + public async setBalancesAndAllowancesAsync(): Promise { + this._validateDummyTokenContractsExistOrThrow(); + this._validateProxyContractExistsOrThrow(); + this._initialTokenIdsByOwner = {}; + for (const dummyTokenContract of this._dummyTokenContracts) { + for (const tokenOwnerAddress of this._tokenOwnerAddresses) { + // tslint:disable-next-line:no-unused-variable + for (const i of _.times(constants.NUM_ERC721_TOKENS_TO_MINT)) { + const tokenId = generatePseudoRandomSalt(); + await this.mintAsync(dummyTokenContract.address, tokenId, tokenOwnerAddress); + if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress])) { + this._initialTokenIdsByOwner[tokenOwnerAddress] = { + [dummyTokenContract.address]: [], + }; + } + if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address])) { + this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address] = []; + } + this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address].push(tokenId); + + await this.approveProxyAsync(dummyTokenContract.address, tokenId); + } + } + } + } + public async doesTokenExistAsync(tokenAddress: string, tokenId: BigNumber): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const owner = await tokenContract.ownerOf.callAsync(tokenId); + const doesExist = owner !== constants.NULL_ADDRESS; + return doesExist; + } + public async approveProxyAsync(tokenAddress: string, tokenId: BigNumber): Promise { + const proxyAddress = (this._proxyContract as ERC721ProxyContract).address; + await this.approveAsync(proxyAddress, tokenAddress, tokenId); + } + public async approveProxyForAllAsync(tokenAddress: string, tokenId: BigNumber, isApproved: boolean): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId); + const proxyAddress = (this._proxyContract as ERC721ProxyContract).address; + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.setApprovalForAll.sendTransactionAsync(proxyAddress, isApproved, { + from: tokenOwner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async approveAsync(to: string, tokenAddress: string, tokenId: BigNumber): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.approve.sendTransactionAsync(to, tokenId, { + from: tokenOwner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async transferFromAsync( + tokenAddress: string, + tokenId: BigNumber, + currentOwner: string, + userAddress: string, + ): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.transferFrom.sendTransactionAsync(currentOwner, userAddress, tokenId, { + from: currentOwner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async mintAsync(tokenAddress: string, tokenId: BigNumber, userAddress: string): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.mint.sendTransactionAsync(userAddress, tokenId, { + from: this._contractOwnerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async burnAsync(tokenAddress: string, tokenId: BigNumber, owner: string): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.burn.sendTransactionAsync(owner, tokenId, { + from: this._contractOwnerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async ownerOfAsync(tokenAddress: string, tokenId: BigNumber): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const owner = await tokenContract.ownerOf.callAsync(tokenId); + return owner; + } + public async isOwnerAsync(userAddress: string, tokenAddress: string, tokenId: BigNumber): Promise { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const tokenOwner = await tokenContract.ownerOf.callAsync(tokenId); + const isOwner = tokenOwner === userAddress; + return isOwner; + } + public async isProxyApprovedForAllAsync(userAddress: string, tokenAddress: string): Promise { + this._validateProxyContractExistsOrThrow(); + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const operator = (this._proxyContract as ERC721ProxyContract).address; + const didApproveAll = await tokenContract.isApprovedForAll.callAsync(userAddress, operator); + return didApproveAll; + } + public async isProxyApprovedAsync(tokenAddress: string, tokenId: BigNumber): Promise { + this._validateProxyContractExistsOrThrow(); + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const approvedAddress = await tokenContract.getApproved.callAsync(tokenId); + const proxyAddress = (this._proxyContract as ERC721ProxyContract).address; + const isProxyAnApprovedOperator = approvedAddress === proxyAddress; + return isProxyAnApprovedOperator; + } + public async getBalancesAsync(): Promise { + this._validateDummyTokenContractsExistOrThrow(); + this._validateBalancesAndAllowancesSetOrThrow(); + const tokenIdsByOwner: ERC721TokenIdsByOwner = {}; + const tokenOwnerAddresses: string[] = []; + const tokenInfo: Array<{ tokenId: BigNumber; tokenAddress: string }> = []; + for (const dummyTokenContract of this._dummyTokenContracts) { + for (const tokenOwnerAddress of this._tokenOwnerAddresses) { + const initialTokenOwnerIds = this._initialTokenIdsByOwner[tokenOwnerAddress][ + dummyTokenContract.address + ]; + for (const tokenId of initialTokenOwnerIds) { + tokenOwnerAddresses.push(await dummyTokenContract.ownerOf.callAsync(tokenId)); + tokenInfo.push({ + tokenId, + tokenAddress: dummyTokenContract.address, + }); + } + } + } + _.forEach(tokenOwnerAddresses, (tokenOwnerAddress, ownerIndex) => { + const tokenAddress = tokenInfo[ownerIndex].tokenAddress; + const tokenId = tokenInfo[ownerIndex].tokenId; + if (_.isUndefined(tokenIdsByOwner[tokenOwnerAddress])) { + tokenIdsByOwner[tokenOwnerAddress] = { + [tokenAddress]: [], + }; + } + if (_.isUndefined(tokenIdsByOwner[tokenOwnerAddress][tokenAddress])) { + tokenIdsByOwner[tokenOwnerAddress][tokenAddress] = []; + } + tokenIdsByOwner[tokenOwnerAddress][tokenAddress].push(tokenId); + }); + return tokenIdsByOwner; + } + public getTokenOwnerAddresses(): string[] { + return this._tokenOwnerAddresses; + } + public getTokenAddresses(): string[] { + const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address); + return tokenAddresses; + } + private _getTokenContractFromAssetData(tokenAddress: string): DummyERC721TokenContract { + const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress); + if (_.isUndefined(tokenContractIfExists)) { + throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`); + } + return tokenContractIfExists; + } + private _validateDummyTokenContractsExistOrThrow(): void { + if (_.isUndefined(this._dummyTokenContracts)) { + throw new Error('Dummy ERC721 tokens not yet deployed, please call "deployDummyTokensAsync"'); + } + } + private _validateProxyContractExistsOrThrow(): void { + if (_.isUndefined(this._proxyContract)) { + throw new Error('ERC721 proxy contract not yet deployed, please call "deployProxyAsync"'); + } + } + private _validateBalancesAndAllowancesSetOrThrow(): void { + if (_.keys(this._initialTokenIdsByOwner).length === 0) { + throw new Error( + 'Dummy ERC721 balances and allowances not yet set, please call "setBalancesAndAllowancesAsync"', + ); + } + } +} diff --git a/contracts/exchange/test/utils/exchange_wrapper.ts b/contracts/exchange/test/utils/exchange_wrapper.ts new file mode 100644 index 000000000..437882b41 --- /dev/null +++ b/contracts/exchange/test/utils/exchange_wrapper.ts @@ -0,0 +1,292 @@ +import { + FillResults, + formatters, + LogDecoder, + OrderInfo, + orderUtils, + SignedTransaction, +} from '@0x/contracts-test-utils'; +import { artifacts as tokensArtifacts } from '@0x/contracts-tokens'; +import { SignedOrder } from '@0x/types'; +import { AbiEncoder, BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import { MethodAbi, Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { ExchangeContract } from '../../generated-wrappers/exchange'; +import { artifacts } from '../../src/artifacts'; + +import { AbiDecodedFillOrderData } from './types'; + +export class ExchangeWrapper { + private readonly _exchange: ExchangeContract; + private readonly _web3Wrapper: Web3Wrapper; + private readonly _logDecoder: LogDecoder; + constructor(exchangeContract: ExchangeContract, provider: Provider) { + this._exchange = exchangeContract; + this._web3Wrapper = new Web3Wrapper(provider); + this._logDecoder = new LogDecoder(this._web3Wrapper, { ...artifacts, ...tokensArtifacts }); + } + public async fillOrderAsync( + signedOrder: SignedOrder, + from: string, + opts: { takerAssetFillAmount?: BigNumber } = {}, + ): Promise { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const txHash = await this._exchange.fillOrder.sendTransactionAsync( + params.order, + params.takerAssetFillAmount, + params.signature, + { from }, + ); + const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return txReceipt; + } + public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise { + const params = orderUtils.createCancel(signedOrder); + const txHash = await this._exchange.cancelOrder.sendTransactionAsync(params.order, { from }); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async fillOrKillOrderAsync( + signedOrder: SignedOrder, + from: string, + opts: { takerAssetFillAmount?: BigNumber } = {}, + ): Promise { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const txHash = await this._exchange.fillOrKillOrder.sendTransactionAsync( + params.order, + params.takerAssetFillAmount, + params.signature, + { from }, + ); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async fillOrderNoThrowAsync( + signedOrder: SignedOrder, + from: string, + opts: { takerAssetFillAmount?: BigNumber; gas?: number } = {}, + ): Promise { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const txHash = await this._exchange.fillOrderNoThrow.sendTransactionAsync( + params.order, + params.takerAssetFillAmount, + params.signature, + { from, gas: opts.gas }, + ); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async batchFillOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmounts?: BigNumber[] } = {}, + ): Promise { + const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); + const txHash = await this._exchange.batchFillOrders.sendTransactionAsync( + params.orders, + params.takerAssetFillAmounts, + params.signatures, + { from }, + ); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async batchFillOrKillOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmounts?: BigNumber[] } = {}, + ): Promise { + const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); + const txHash = await this._exchange.batchFillOrKillOrders.sendTransactionAsync( + params.orders, + params.takerAssetFillAmounts, + params.signatures, + { from }, + ); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async batchFillOrdersNoThrowAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number } = {}, + ): Promise { + const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); + const txHash = await this._exchange.batchFillOrdersNoThrow.sendTransactionAsync( + params.orders, + params.takerAssetFillAmounts, + params.signatures, + { from, gas: opts.gas }, + ); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async marketSellOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmount: BigNumber }, + ): Promise { + const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); + const txHash = await this._exchange.marketSellOrders.sendTransactionAsync( + params.orders, + params.takerAssetFillAmount, + params.signatures, + { from }, + ); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async marketSellOrdersNoThrowAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmount: BigNumber; gas?: number }, + ): Promise { + const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); + const txHash = await this._exchange.marketSellOrdersNoThrow.sendTransactionAsync( + params.orders, + params.takerAssetFillAmount, + params.signatures, + { from, gas: opts.gas }, + ); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async marketBuyOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { makerAssetFillAmount: BigNumber }, + ): Promise { + const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); + const txHash = await this._exchange.marketBuyOrders.sendTransactionAsync( + params.orders, + params.makerAssetFillAmount, + params.signatures, + { from }, + ); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async marketBuyOrdersNoThrowAsync( + orders: SignedOrder[], + from: string, + opts: { makerAssetFillAmount: BigNumber; gas?: number }, + ): Promise { + const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); + const txHash = await this._exchange.marketBuyOrdersNoThrow.sendTransactionAsync( + params.orders, + params.makerAssetFillAmount, + params.signatures, + { from, gas: opts.gas }, + ); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async batchCancelOrdersAsync( + orders: SignedOrder[], + from: string, + ): Promise { + const params = formatters.createBatchCancel(orders); + const txHash = await this._exchange.batchCancelOrders.sendTransactionAsync(params.orders, { from }); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async cancelOrdersUpToAsync(salt: BigNumber, from: string): Promise { + const txHash = await this._exchange.cancelOrdersUpTo.sendTransactionAsync(salt, { from }); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async registerAssetProxyAsync( + assetProxyAddress: string, + from: string, + ): Promise { + const txHash = await this._exchange.registerAssetProxy.sendTransactionAsync(assetProxyAddress, { from }); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async executeTransactionAsync( + signedTx: SignedTransaction, + from: string, + ): Promise { + const txHash = await this._exchange.executeTransaction.sendTransactionAsync( + signedTx.salt, + signedTx.signerAddress, + signedTx.data, + signedTx.signature, + { from }, + ); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise { + const filledAmount = await this._exchange.filled.callAsync(orderHashHex); + return filledAmount; + } + public async isCancelledAsync(orderHashHex: string): Promise { + const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex); + return isCancelled; + } + public async getOrderEpochAsync(makerAddress: string, senderAddress: string): Promise { + const orderEpoch = await this._exchange.orderEpoch.callAsync(makerAddress, senderAddress); + return orderEpoch; + } + public async getOrderInfoAsync(signedOrder: SignedOrder): Promise { + const orderInfo = await this._exchange.getOrderInfo.callAsync(signedOrder); + return orderInfo; + } + public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise { + const ordersInfo = (await this._exchange.getOrdersInfo.callAsync(signedOrders)) as OrderInfo[]; + return ordersInfo; + } + public async matchOrdersAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + from: string, + ): Promise { + const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight); + const txHash = await this._exchange.matchOrders.sendTransactionAsync( + params.left, + params.right, + params.leftSignature, + params.rightSignature, + { from }, + ); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async getFillOrderResultsAsync( + signedOrder: SignedOrder, + from: string, + opts: { takerAssetFillAmount?: BigNumber } = {}, + ): Promise { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const fillResults = await this._exchange.fillOrder.callAsync( + params.order, + params.takerAssetFillAmount, + params.signature, + { from }, + ); + return fillResults; + } + public abiEncodeFillOrder(signedOrder: SignedOrder, opts: { takerAssetFillAmount?: BigNumber } = {}): string { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const data = this._exchange.fillOrder.getABIEncodedTransactionData( + params.order, + params.takerAssetFillAmount, + params.signature, + ); + return data; + } + public abiDecodeFillOrder(data: string): AbiDecodedFillOrderData { + // Lookup fillOrder ABI in exchange abi + const fillOrderAbi = _.find(this._exchange.abi, { name: 'fillOrder' }) as MethodAbi; + // Decode input data + const abiEncoder = new AbiEncoder.Method(fillOrderAbi); + const decodedData = abiEncoder.decode(data) as AbiDecodedFillOrderData; + return decodedData; + } + public getExchangeAddress(): string { + return this._exchange.address; + } +} diff --git a/contracts/exchange/test/utils/fill_order_combinatorial_utils.ts b/contracts/exchange/test/utils/fill_order_combinatorial_utils.ts new file mode 100644 index 000000000..c2f530e5b --- /dev/null +++ b/contracts/exchange/test/utils/fill_order_combinatorial_utils.ts @@ -0,0 +1,928 @@ +import { artifacts as libsArtifacts, TestLibsContract } from '@0x/contracts-libs'; +import { + AllowanceAmountScenario, + AssetDataScenario, + BalanceAmountScenario, + chaiSetup, + constants, + expectTransactionFailedAsync, + ExpirationTimeSecondsScenario, + FeeRecipientAddressScenario, + FillScenario, + OrderAssetAmountScenario, + orderUtils, + signingUtils, + TakerAssetFillAmountScenario, + TakerScenario, + TraderStateScenario, +} from '@0x/contracts-test-utils'; +import { + assetDataUtils, + BalanceAndProxyAllowanceLazyStore, + ExchangeTransferSimulator, + orderHashUtils, + OrderStateUtils, + OrderValidationUtils, +} from '@0x/order-utils'; +import { AssetProxyId, RevertReason, SignatureType, SignedOrder } from '@0x/types'; +import { BigNumber, errorUtils, logUtils } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import { LogWithDecodedArgs, Provider, TxData } from 'ethereum-types'; +import * as _ from 'lodash'; +import 'make-promises-safe'; + +import { ExchangeContract, ExchangeFillEventArgs } from '../../generated-wrappers/exchange'; +import { artifacts } from '../../src/artifacts'; + +import { AssetWrapper } from './asset_wrapper'; +import { ERC20Wrapper } from './erc20_wrapper'; +import { ERC721Wrapper } from './erc721_wrapper'; +import { ExchangeWrapper } from './exchange_wrapper'; +import { OrderFactoryFromScenario } from './order_factory_from_scenario'; +import { SimpleAssetBalanceAndProxyAllowanceFetcher } from './simple_asset_balance_and_proxy_allowance_fetcher'; +import { SimpleOrderFilledCancelledFetcher } from './simple_order_filled_cancelled_fetcher'; + +chaiSetup.configure(); +const expect = chai.expect; + +/** + * Instantiates a new instance of FillOrderCombinatorialUtils. Since this method has some + * required async setup, a factory method is required. + * @param web3Wrapper Web3Wrapper instance + * @param txDefaults Default Ethereum tx options + * @return FillOrderCombinatorialUtils instance + */ +export async function fillOrderCombinatorialUtilsFactoryAsync( + web3Wrapper: Web3Wrapper, + txDefaults: Partial, +): Promise { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const userAddresses = _.slice(accounts, 0, 5); + const [ownerAddress, makerAddress, takerAddress] = userAddresses; + const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)]; + + const provider = web3Wrapper.getProvider(); + const erc20Wrapper = new ERC20Wrapper(provider, userAddresses, ownerAddress); + const erc721Wrapper = new ERC721Wrapper(provider, userAddresses, ownerAddress); + + const erc20EighteenDecimalTokenCount = 3; + const eighteenDecimals = new BigNumber(18); + const [ + erc20EighteenDecimalTokenA, + erc20EighteenDecimalTokenB, + zrxToken, + ] = await erc20Wrapper.deployDummyTokensAsync(erc20EighteenDecimalTokenCount, eighteenDecimals); + const zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + + const erc20FiveDecimalTokenCount = 2; + const fiveDecimals = new BigNumber(5); + const [erc20FiveDecimalTokenA, erc20FiveDecimalTokenB] = await erc20Wrapper.deployDummyTokensAsync( + erc20FiveDecimalTokenCount, + fiveDecimals, + ); + const zeroDecimals = new BigNumber(0); + const erc20ZeroDecimalTokenCount = 2; + const [erc20ZeroDecimalTokenA, erc20ZeroDecimalTokenB] = await erc20Wrapper.deployDummyTokensAsync( + erc20ZeroDecimalTokenCount, + zeroDecimals, + ); + const erc20Proxy = await erc20Wrapper.deployProxyAsync(); + await erc20Wrapper.setBalancesAndAllowancesAsync(); + + const [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); + const erc721Proxy = await erc721Wrapper.deployProxyAsync(); + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + + const assetWrapper = new AssetWrapper([erc20Wrapper, erc721Wrapper]); + + const exchangeContract = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + zrxAssetData, + ); + const exchangeWrapper = new ExchangeWrapper(exchangeContract, provider); + await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, ownerAddress); + await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, ownerAddress); + + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, { + from: ownerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, { + from: ownerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + const orderFactory = new OrderFactoryFromScenario( + userAddresses, + zrxToken.address, + [erc20EighteenDecimalTokenA.address, erc20EighteenDecimalTokenB.address], + [erc20FiveDecimalTokenA.address, erc20FiveDecimalTokenB.address], + [erc20ZeroDecimalTokenA.address, erc20ZeroDecimalTokenB.address], + erc721Token, + erc721Balances, + exchangeContract.address, + ); + + const testLibsContract = await TestLibsContract.deployFrom0xArtifactAsync( + libsArtifacts.TestLibs, + provider, + txDefaults, + ); + + const fillOrderCombinatorialUtils = new FillOrderCombinatorialUtils( + orderFactory, + ownerAddress, + makerAddress, + makerPrivateKey, + takerAddress, + zrxAssetData, + exchangeWrapper, + assetWrapper, + testLibsContract, + ); + return fillOrderCombinatorialUtils; +} + +export class FillOrderCombinatorialUtils { + public orderFactory: OrderFactoryFromScenario; + public ownerAddress: string; + public makerAddress: string; + public makerPrivateKey: Buffer; + public takerAddress: string; + public zrxAssetData: string; + public exchangeWrapper: ExchangeWrapper; + public assetWrapper: AssetWrapper; + public testLibsContract: TestLibsContract; + public static generateFillOrderCombinations(): FillScenario[] { + const takerScenarios = [ + TakerScenario.Unspecified, + // TakerScenario.CorrectlySpecified, + // TakerScenario.IncorrectlySpecified, + ]; + const feeRecipientScenarios = [ + FeeRecipientAddressScenario.EthUserAddress, + // FeeRecipientAddressScenario.BurnAddress, + ]; + const makerAssetAmountScenario = [ + OrderAssetAmountScenario.Large, + // OrderAssetAmountScenario.Zero, + // OrderAssetAmountScenario.Small, + ]; + const takerAssetAmountScenario = [ + OrderAssetAmountScenario.Large, + // OrderAssetAmountScenario.Zero, + // OrderAssetAmountScenario.Small, + ]; + const makerFeeScenario = [ + OrderAssetAmountScenario.Large, + // OrderAssetAmountScenario.Small, + // OrderAssetAmountScenario.Zero, + ]; + const takerFeeScenario = [ + OrderAssetAmountScenario.Large, + // OrderAssetAmountScenario.Small, + // OrderAssetAmountScenario.Zero, + ]; + const expirationTimeSecondsScenario = [ + ExpirationTimeSecondsScenario.InFuture, + ExpirationTimeSecondsScenario.InPast, + ]; + const makerAssetDataScenario = [ + AssetDataScenario.ERC20FiveDecimals, + AssetDataScenario.ERC20NonZRXEighteenDecimals, + AssetDataScenario.ERC721, + AssetDataScenario.ZRXFeeToken, + ]; + const takerAssetDataScenario = [ + AssetDataScenario.ERC20FiveDecimals, + AssetDataScenario.ERC20NonZRXEighteenDecimals, + AssetDataScenario.ERC721, + AssetDataScenario.ZRXFeeToken, + ]; + const takerAssetFillAmountScenario = [ + TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, + // TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount, + // TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount, + ]; + const makerAssetBalanceScenario = [ + BalanceAmountScenario.Higher, + // BalanceAmountScenario.Exact, + // BalanceAmountScenario.TooLow, + ]; + const makerAssetAllowanceScenario = [ + AllowanceAmountScenario.Higher, + // AllowanceAmountScenario.Exact, + // AllowanceAmountScenario.TooLow, + // AllowanceAmountScenario.Unlimited, + ]; + const makerZRXBalanceScenario = [ + BalanceAmountScenario.Higher, + // BalanceAmountScenario.Exact, + // BalanceAmountScenario.TooLow, + ]; + const makerZRXAllowanceScenario = [ + AllowanceAmountScenario.Higher, + // AllowanceAmountScenario.Exact, + // AllowanceAmountScenario.TooLow, + // AllowanceAmountScenario.Unlimited, + ]; + const takerAssetBalanceScenario = [ + BalanceAmountScenario.Higher, + // BalanceAmountScenario.Exact, + // BalanceAmountScenario.TooLow, + ]; + const takerAssetAllowanceScenario = [ + AllowanceAmountScenario.Higher, + // AllowanceAmountScenario.Exact, + // AllowanceAmountScenario.TooLow, + // AllowanceAmountScenario.Unlimited, + ]; + const takerZRXBalanceScenario = [ + BalanceAmountScenario.Higher, + // BalanceAmountScenario.Exact, + // BalanceAmountScenario.TooLow, + ]; + const takerZRXAllowanceScenario = [ + AllowanceAmountScenario.Higher, + // AllowanceAmountScenario.Exact, + // AllowanceAmountScenario.TooLow, + // AllowanceAmountScenario.Unlimited, + ]; + const fillScenarioArrays = FillOrderCombinatorialUtils._getAllCombinations([ + takerScenarios, + feeRecipientScenarios, + makerAssetAmountScenario, + takerAssetAmountScenario, + makerFeeScenario, + takerFeeScenario, + expirationTimeSecondsScenario, + makerAssetDataScenario, + takerAssetDataScenario, + takerAssetFillAmountScenario, + makerAssetBalanceScenario, + makerAssetAllowanceScenario, + makerZRXBalanceScenario, + makerZRXAllowanceScenario, + takerAssetBalanceScenario, + takerAssetAllowanceScenario, + takerZRXBalanceScenario, + takerZRXAllowanceScenario, + ]); + + const fillScenarios = _.map(fillScenarioArrays, fillScenarioArray => { + // tslint:disable:custom-no-magic-numbers + const fillScenario: FillScenario = { + orderScenario: { + takerScenario: fillScenarioArray[0] as TakerScenario, + feeRecipientScenario: fillScenarioArray[1] as FeeRecipientAddressScenario, + makerAssetAmountScenario: fillScenarioArray[2] as OrderAssetAmountScenario, + takerAssetAmountScenario: fillScenarioArray[3] as OrderAssetAmountScenario, + makerFeeScenario: fillScenarioArray[4] as OrderAssetAmountScenario, + takerFeeScenario: fillScenarioArray[5] as OrderAssetAmountScenario, + expirationTimeSecondsScenario: fillScenarioArray[6] as ExpirationTimeSecondsScenario, + makerAssetDataScenario: fillScenarioArray[7] as AssetDataScenario, + takerAssetDataScenario: fillScenarioArray[8] as AssetDataScenario, + }, + takerAssetFillAmountScenario: fillScenarioArray[9] as TakerAssetFillAmountScenario, + makerStateScenario: { + traderAssetBalance: fillScenarioArray[10] as BalanceAmountScenario, + traderAssetAllowance: fillScenarioArray[11] as AllowanceAmountScenario, + zrxFeeBalance: fillScenarioArray[12] as BalanceAmountScenario, + zrxFeeAllowance: fillScenarioArray[13] as AllowanceAmountScenario, + }, + takerStateScenario: { + traderAssetBalance: fillScenarioArray[14] as BalanceAmountScenario, + traderAssetAllowance: fillScenarioArray[15] as AllowanceAmountScenario, + zrxFeeBalance: fillScenarioArray[16] as BalanceAmountScenario, + zrxFeeAllowance: fillScenarioArray[17] as AllowanceAmountScenario, + }, + }; + // tslint:enable:custom-no-magic-numbers + return fillScenario; + }); + + return fillScenarios; + } + /** + * Recursive implementation of generating all combinations of the supplied + * string-containing arrays. + */ + private static _getAllCombinations(arrays: string[][]): string[][] { + // Base case + if (arrays.length === 1) { + const remainingValues = _.map(arrays[0], val => { + return [val]; + }); + return remainingValues; + } else { + const result = []; + const restOfArrays = arrays.slice(1); + const allCombinationsOfRemaining = FillOrderCombinatorialUtils._getAllCombinations(restOfArrays); // recur with the rest of array + // tslint:disable:prefer-for-of + for (let i = 0; i < allCombinationsOfRemaining.length; i++) { + for (let j = 0; j < arrays[0].length; j++) { + result.push([arrays[0][j], ...allCombinationsOfRemaining[i]]); + } + } + // tslint:enable:prefer-for-of + return result; + } + } + constructor( + orderFactory: OrderFactoryFromScenario, + ownerAddress: string, + makerAddress: string, + makerPrivateKey: Buffer, + takerAddress: string, + zrxAssetData: string, + exchangeWrapper: ExchangeWrapper, + assetWrapper: AssetWrapper, + testLibsContract: TestLibsContract, + ) { + this.orderFactory = orderFactory; + this.ownerAddress = ownerAddress; + this.makerAddress = makerAddress; + this.makerPrivateKey = makerPrivateKey; + this.takerAddress = takerAddress; + this.zrxAssetData = zrxAssetData; + this.exchangeWrapper = exchangeWrapper; + this.assetWrapper = assetWrapper; + this.testLibsContract = testLibsContract; + } + public async testFillOrderScenarioAsync( + provider: Provider, + fillScenario: FillScenario, + isVerbose: boolean = false, + ): Promise { + // 1. Generate order + const order = this.orderFactory.generateOrder(fillScenario.orderScenario); + + // 2. Sign order + const orderHashBuff = orderHashUtils.getOrderHashBuffer(order); + const signature = signingUtils.signMessage(orderHashBuff, this.makerPrivateKey, SignatureType.EthSign); + const signedOrder = { + ...order, + signature: `0x${signature.toString('hex')}`, + }; + + const balanceAndProxyAllowanceFetcher = new SimpleAssetBalanceAndProxyAllowanceFetcher(this.assetWrapper); + const orderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher( + this.exchangeWrapper, + this.zrxAssetData, + ); + + // 3. Figure out fill amount + const takerAssetFillAmount = await this._getTakerAssetFillAmountAsync( + signedOrder, + fillScenario.takerAssetFillAmountScenario, + balanceAndProxyAllowanceFetcher, + orderFilledCancelledFetcher, + ); + + // 4. Permutate the maker and taker balance/allowance scenarios + await this._modifyTraderStateAsync( + fillScenario.makerStateScenario, + fillScenario.takerStateScenario, + signedOrder, + takerAssetFillAmount, + ); + + // 5. If I fill it by X, what are the resulting balances/allowances/filled amounts expected? + const orderValidationUtils = new OrderValidationUtils(orderFilledCancelledFetcher, provider); + const lazyStore = new BalanceAndProxyAllowanceLazyStore(balanceAndProxyAllowanceFetcher); + const exchangeTransferSimulator = new ExchangeTransferSimulator(lazyStore); + + let fillRevertReasonIfExists; + try { + await orderValidationUtils.validateFillOrderThrowIfInvalidAsync( + exchangeTransferSimulator, + provider, + signedOrder, + takerAssetFillAmount, + this.takerAddress, + this.zrxAssetData, + ); + if (isVerbose) { + logUtils.log(`Expecting fillOrder to succeed.`); + } + } catch (err) { + fillRevertReasonIfExists = err.message; + if (isVerbose) { + logUtils.log(`Expecting fillOrder to fail with:`); + logUtils.log(err); + } + } + + // 6. Fill the order + await this._fillOrderAndAssertOutcomeAsync( + signedOrder, + takerAssetFillAmount, + lazyStore, + fillRevertReasonIfExists, + ); + + await this._abiEncodeFillOrderAndAssertOutcomeAsync(signedOrder, takerAssetFillAmount); + } + private async _fillOrderAndAssertOutcomeAsync( + signedOrder: SignedOrder, + takerAssetFillAmount: BigNumber, + lazyStore: BalanceAndProxyAllowanceLazyStore, + fillRevertReasonIfExists: RevertReason | undefined, + ): Promise { + if (!_.isUndefined(fillRevertReasonIfExists)) { + return expectTransactionFailedAsync( + this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, { takerAssetFillAmount }), + fillRevertReasonIfExists, + ); + } + + const makerAddress = signedOrder.makerAddress; + const makerAssetData = signedOrder.makerAssetData; + const takerAssetData = signedOrder.takerAssetData; + const feeRecipient = signedOrder.feeRecipientAddress; + + const expMakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(makerAssetData, makerAddress); + const expMakerAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync(makerAssetData, makerAddress); + const expTakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(takerAssetData, makerAddress); + const expZRXAssetBalanceOfMaker = await lazyStore.getBalanceAsync(this.zrxAssetData, makerAddress); + const expZRXAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync(this.zrxAssetData, makerAddress); + const expTakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(takerAssetData, this.takerAddress); + const expTakerAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync(takerAssetData, this.takerAddress); + const expMakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(makerAssetData, this.takerAddress); + const expZRXAssetBalanceOfTaker = await lazyStore.getBalanceAsync(this.zrxAssetData, this.takerAddress); + const expZRXAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync( + this.zrxAssetData, + this.takerAddress, + ); + const expZRXAssetBalanceOfFeeRecipient = await lazyStore.getBalanceAsync(this.zrxAssetData, feeRecipient); + + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + const alreadyFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash); + const remainingTakerAmountToFill = signedOrder.takerAssetAmount.minus(alreadyFilledTakerAmount); + const expFilledTakerAmount = takerAssetFillAmount.gt(remainingTakerAmountToFill) + ? remainingTakerAmountToFill + : alreadyFilledTakerAmount.plus(takerAssetFillAmount); + + const expFilledMakerAmount = orderUtils.getPartialAmountFloor( + expFilledTakerAmount, + signedOrder.takerAssetAmount, + signedOrder.makerAssetAmount, + ); + const expMakerFeePaid = orderUtils.getPartialAmountFloor( + expFilledTakerAmount, + signedOrder.takerAssetAmount, + signedOrder.makerFee, + ); + const expTakerFeePaid = orderUtils.getPartialAmountFloor( + expFilledTakerAmount, + signedOrder.takerAssetAmount, + signedOrder.takerFee, + ); + const fillResults = await this.exchangeWrapper.getFillOrderResultsAsync(signedOrder, this.takerAddress, { + takerAssetFillAmount, + }); + expect(fillResults.takerAssetFilledAmount).to.be.bignumber.equal( + expFilledTakerAmount, + 'takerAssetFilledAmount', + ); + expect(fillResults.makerAssetFilledAmount).to.be.bignumber.equal( + expFilledMakerAmount, + 'makerAssetFilledAmount', + ); + expect(fillResults.takerFeePaid).to.be.bignumber.equal(expTakerFeePaid, 'takerFeePaid'); + expect(fillResults.makerFeePaid).to.be.bignumber.equal(expMakerFeePaid, 'makerFeePaid'); + + // - Let's fill the order! + const txReceipt = await this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, { + takerAssetFillAmount, + }); + + const actFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash); + expect(actFilledTakerAmount).to.be.bignumber.equal(expFilledTakerAmount, 'filledTakerAmount'); + + const exchangeLogs = _.filter( + txReceipt.logs, + txLog => txLog.address === this.exchangeWrapper.getExchangeAddress(), + ); + expect(exchangeLogs.length).to.be.equal(1, 'logs length'); + // tslint:disable-next-line:no-unnecessary-type-assertion + const log = txReceipt.logs[0] as LogWithDecodedArgs; + expect(log.args.makerAddress).to.be.equal(makerAddress, 'log.args.makerAddress'); + expect(log.args.takerAddress).to.be.equal(this.takerAddress, 'log.args.this.takerAddress'); + expect(log.args.feeRecipientAddress).to.be.equal(feeRecipient, 'log.args.feeRecipientAddress'); + expect(log.args.makerAssetFilledAmount).to.be.bignumber.equal( + expFilledMakerAmount, + 'log.args.makerAssetFilledAmount', + ); + expect(log.args.takerAssetFilledAmount).to.be.bignumber.equal( + expFilledTakerAmount, + 'log.args.takerAssetFilledAmount', + ); + expect(log.args.makerFeePaid).to.be.bignumber.equal(expMakerFeePaid, 'log.args.makerFeePaid'); + expect(log.args.takerFeePaid).to.be.bignumber.equal(expTakerFeePaid, 'logs.args.takerFeePaid'); + expect(log.args.orderHash).to.be.equal(orderHash, 'log.args.orderHash'); + expect(log.args.makerAssetData).to.be.equal(makerAssetData, 'log.args.makerAssetData'); + expect(log.args.takerAssetData).to.be.equal(takerAssetData, 'log.args.takerAssetData'); + + const actMakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, makerAssetData); + expect(actMakerAssetBalanceOfMaker).to.be.bignumber.equal( + expMakerAssetBalanceOfMaker, + 'makerAssetBalanceOfMaker', + ); + + const actMakerAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync( + makerAddress, + makerAssetData, + ); + expect(actMakerAssetAllowanceOfMaker).to.be.bignumber.equal( + expMakerAssetAllowanceOfMaker, + 'makerAssetAllowanceOfMaker', + ); + + const actTakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, takerAssetData); + expect(actTakerAssetBalanceOfMaker).to.be.bignumber.equal( + expTakerAssetBalanceOfMaker, + 'takerAssetBalanceOfMaker', + ); + + const actZRXAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, this.zrxAssetData); + expect(actZRXAssetBalanceOfMaker).to.be.bignumber.equal(expZRXAssetBalanceOfMaker, 'ZRXAssetBalanceOfMaker'); + + const actZRXAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync( + makerAddress, + this.zrxAssetData, + ); + expect(actZRXAssetAllowanceOfMaker).to.be.bignumber.equal( + expZRXAssetAllowanceOfMaker, + 'ZRXAssetAllowanceOfMaker', + ); + + const actTakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, takerAssetData); + expect(actTakerAssetBalanceOfTaker).to.be.bignumber.equal( + expTakerAssetBalanceOfTaker, + 'TakerAssetBalanceOfTaker', + ); + + const actTakerAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync( + this.takerAddress, + takerAssetData, + ); + + expect(actTakerAssetAllowanceOfTaker).to.be.bignumber.equal( + expTakerAssetAllowanceOfTaker, + 'TakerAssetAllowanceOfTaker', + ); + + const actMakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, makerAssetData); + expect(actMakerAssetBalanceOfTaker).to.be.bignumber.equal( + expMakerAssetBalanceOfTaker, + 'MakerAssetBalanceOfTaker', + ); + + const actZRXAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, this.zrxAssetData); + expect(actZRXAssetBalanceOfTaker).to.be.bignumber.equal(expZRXAssetBalanceOfTaker, 'ZRXAssetBalanceOfTaker'); + + const actZRXAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync( + this.takerAddress, + this.zrxAssetData, + ); + expect(actZRXAssetAllowanceOfTaker).to.be.bignumber.equal( + expZRXAssetAllowanceOfTaker, + 'ZRXAssetAllowanceOfTaker', + ); + + const actZRXAssetBalanceOfFeeRecipient = await this.assetWrapper.getBalanceAsync( + feeRecipient, + this.zrxAssetData, + ); + expect(actZRXAssetBalanceOfFeeRecipient).to.be.bignumber.equal( + expZRXAssetBalanceOfFeeRecipient, + 'ZRXAssetBalanceOfFeeRecipient', + ); + } + private async _abiEncodeFillOrderAndAssertOutcomeAsync( + signedOrder: SignedOrder, + takerAssetFillAmount: BigNumber, + ): Promise { + const params = orderUtils.createFill(signedOrder, takerAssetFillAmount); + const abiDataEncodedByContract = await this.testLibsContract.publicAbiEncodeFillOrder.callAsync( + params.order, + params.takerAssetFillAmount, + params.signature, + ); + const paramsDecodedByClient = this.exchangeWrapper.abiDecodeFillOrder(abiDataEncodedByContract); + expect(paramsDecodedByClient).to.be.deep.equal(params, 'ABIEncodedFillOrderData'); + } + private async _getTakerAssetFillAmountAsync( + signedOrder: SignedOrder, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario, + balanceAndProxyAllowanceFetcher: SimpleAssetBalanceAndProxyAllowanceFetcher, + orderFilledCancelledFetcher: SimpleOrderFilledCancelledFetcher, + ): Promise { + const orderStateUtils = new OrderStateUtils(balanceAndProxyAllowanceFetcher, orderFilledCancelledFetcher); + const fillableTakerAssetAmount = await orderStateUtils.getMaxFillableTakerAssetAmountAsync( + signedOrder, + this.takerAddress, + ); + + let takerAssetFillAmount; + switch (takerAssetFillAmountScenario) { + case TakerAssetFillAmountScenario.Zero: + takerAssetFillAmount = new BigNumber(0); + break; + + case TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount: + takerAssetFillAmount = fillableTakerAssetAmount; + break; + + case TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount: + takerAssetFillAmount = fillableTakerAssetAmount.plus(1); + break; + + case TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount: + const takerAssetProxyId = assetDataUtils.decodeAssetProxyId(signedOrder.takerAssetData); + const makerAssetProxyId = assetDataUtils.decodeAssetProxyId(signedOrder.makerAssetData); + const isEitherAssetERC721 = + takerAssetProxyId === AssetProxyId.ERC721 || makerAssetProxyId === AssetProxyId.ERC721; + if (isEitherAssetERC721) { + throw new Error( + 'Cannot test `TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount` together with ERC721 assets since orders involving ERC721 must always be filled exactly.', + ); + } + takerAssetFillAmount = fillableTakerAssetAmount.div(2).integerValue(BigNumber.ROUND_FLOOR); + break; + + default: + throw errorUtils.spawnSwitchErr('TakerAssetFillAmountScenario', takerAssetFillAmountScenario); + } + + return takerAssetFillAmount; + } + private async _modifyTraderStateAsync( + makerStateScenario: TraderStateScenario, + takerStateScenario: TraderStateScenario, + signedOrder: SignedOrder, + takerAssetFillAmount: BigNumber, + ): Promise { + const makerAssetFillAmount = orderUtils.getPartialAmountFloor( + takerAssetFillAmount, + signedOrder.takerAssetAmount, + signedOrder.makerAssetAmount, + ); + switch (makerStateScenario.traderAssetBalance) { + case BalanceAmountScenario.Higher: + break; // Noop since this is already the default + + case BalanceAmountScenario.TooLow: + if (makerAssetFillAmount.eq(0)) { + throw new Error(`Cannot set makerAssetBalanceOfMaker TooLow if makerAssetFillAmount is 0`); + } + const tooLowBalance = makerAssetFillAmount.minus(1); + await this.assetWrapper.setBalanceAsync( + signedOrder.makerAddress, + signedOrder.makerAssetData, + tooLowBalance, + ); + break; + + case BalanceAmountScenario.Exact: + const exactBalance = makerAssetFillAmount; + await this.assetWrapper.setBalanceAsync( + signedOrder.makerAddress, + signedOrder.makerAssetData, + exactBalance, + ); + break; + + default: + throw errorUtils.spawnSwitchErr( + 'makerStateScenario.traderAssetBalance', + makerStateScenario.traderAssetBalance, + ); + } + + const makerFee = orderUtils.getPartialAmountFloor( + takerAssetFillAmount, + signedOrder.takerAssetAmount, + signedOrder.makerFee, + ); + switch (makerStateScenario.zrxFeeBalance) { + case BalanceAmountScenario.Higher: + break; // Noop since this is already the default + + case BalanceAmountScenario.TooLow: + if (makerFee.eq(0)) { + throw new Error(`Cannot set zrxAsserBalanceOfMaker TooLow if makerFee is 0`); + } + const tooLowBalance = makerFee.minus(1); + await this.assetWrapper.setBalanceAsync(signedOrder.makerAddress, this.zrxAssetData, tooLowBalance); + break; + + case BalanceAmountScenario.Exact: + const exactBalance = makerFee; + await this.assetWrapper.setBalanceAsync(signedOrder.makerAddress, this.zrxAssetData, exactBalance); + break; + + default: + throw errorUtils.spawnSwitchErr('makerStateScenario.zrxFeeBalance', makerStateScenario.zrxFeeBalance); + } + + switch (makerStateScenario.traderAssetAllowance) { + case AllowanceAmountScenario.Higher: + break; // Noop since this is already the default + + case AllowanceAmountScenario.TooLow: + const tooLowAllowance = makerAssetFillAmount.minus(1); + await this.assetWrapper.setProxyAllowanceAsync( + signedOrder.makerAddress, + signedOrder.makerAssetData, + tooLowAllowance, + ); + break; + + case AllowanceAmountScenario.Exact: + const exactAllowance = makerAssetFillAmount; + await this.assetWrapper.setProxyAllowanceAsync( + signedOrder.makerAddress, + signedOrder.makerAssetData, + exactAllowance, + ); + break; + + case AllowanceAmountScenario.Unlimited: + await this.assetWrapper.setProxyAllowanceAsync( + signedOrder.makerAddress, + signedOrder.makerAssetData, + constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, + ); + break; + + default: + throw errorUtils.spawnSwitchErr( + 'makerStateScenario.traderAssetAllowance', + makerStateScenario.traderAssetAllowance, + ); + } + + switch (makerStateScenario.zrxFeeAllowance) { + case AllowanceAmountScenario.Higher: + break; // Noop since this is already the default + + case AllowanceAmountScenario.TooLow: + const tooLowAllowance = makerFee.minus(1); + await this.assetWrapper.setProxyAllowanceAsync( + signedOrder.makerAddress, + this.zrxAssetData, + tooLowAllowance, + ); + break; + + case AllowanceAmountScenario.Exact: + const exactAllowance = makerFee; + await this.assetWrapper.setProxyAllowanceAsync( + signedOrder.makerAddress, + this.zrxAssetData, + exactAllowance, + ); + break; + + case AllowanceAmountScenario.Unlimited: + await this.assetWrapper.setProxyAllowanceAsync( + signedOrder.makerAddress, + this.zrxAssetData, + constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, + ); + break; + + default: + throw errorUtils.spawnSwitchErr( + 'makerStateScenario.zrxFeeAllowance', + makerStateScenario.zrxFeeAllowance, + ); + } + + switch (takerStateScenario.traderAssetBalance) { + case BalanceAmountScenario.Higher: + break; // Noop since this is already the default + + case BalanceAmountScenario.TooLow: + if (takerAssetFillAmount.eq(0)) { + throw new Error(`Cannot set takerAssetBalanceOfTaker TooLow if takerAssetFillAmount is 0`); + } + const tooLowBalance = takerAssetFillAmount.minus(1); + await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerAssetData, tooLowBalance); + break; + + case BalanceAmountScenario.Exact: + const exactBalance = takerAssetFillAmount; + await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerAssetData, exactBalance); + break; + + default: + throw errorUtils.spawnSwitchErr( + 'takerStateScenario.traderAssetBalance', + takerStateScenario.traderAssetBalance, + ); + } + + const takerFee = orderUtils.getPartialAmountFloor( + takerAssetFillAmount, + signedOrder.takerAssetAmount, + signedOrder.takerFee, + ); + switch (takerStateScenario.zrxFeeBalance) { + case BalanceAmountScenario.Higher: + break; // Noop since this is already the default + + case BalanceAmountScenario.TooLow: + if (takerFee.eq(0)) { + throw new Error(`Cannot set zrxAssetBalanceOfTaker TooLow if takerFee is 0`); + } + const tooLowBalance = takerFee.minus(1); + await this.assetWrapper.setBalanceAsync(this.takerAddress, this.zrxAssetData, tooLowBalance); + break; + + case BalanceAmountScenario.Exact: + const exactBalance = takerFee; + await this.assetWrapper.setBalanceAsync(this.takerAddress, this.zrxAssetData, exactBalance); + break; + + default: + throw errorUtils.spawnSwitchErr('takerStateScenario.zrxFeeBalance', takerStateScenario.zrxFeeBalance); + } + + switch (takerStateScenario.traderAssetAllowance) { + case AllowanceAmountScenario.Higher: + break; // Noop since this is already the default + + case AllowanceAmountScenario.TooLow: + const tooLowAllowance = takerAssetFillAmount.minus(1); + await this.assetWrapper.setProxyAllowanceAsync( + this.takerAddress, + signedOrder.takerAssetData, + tooLowAllowance, + ); + break; + + case AllowanceAmountScenario.Exact: + const exactAllowance = takerAssetFillAmount; + await this.assetWrapper.setProxyAllowanceAsync( + this.takerAddress, + signedOrder.takerAssetData, + exactAllowance, + ); + break; + + case AllowanceAmountScenario.Unlimited: + await this.assetWrapper.setProxyAllowanceAsync( + this.takerAddress, + signedOrder.takerAssetData, + constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, + ); + break; + + default: + throw errorUtils.spawnSwitchErr( + 'takerStateScenario.traderAssetAllowance', + takerStateScenario.traderAssetAllowance, + ); + } + + switch (takerStateScenario.zrxFeeAllowance) { + case AllowanceAmountScenario.Higher: + break; // Noop since this is already the default + + case AllowanceAmountScenario.TooLow: + const tooLowAllowance = takerFee.minus(1); + await this.assetWrapper.setProxyAllowanceAsync(this.takerAddress, this.zrxAssetData, tooLowAllowance); + break; + + case AllowanceAmountScenario.Exact: + const exactAllowance = takerFee; + await this.assetWrapper.setProxyAllowanceAsync(this.takerAddress, this.zrxAssetData, exactAllowance); + break; + + case AllowanceAmountScenario.Unlimited: + await this.assetWrapper.setProxyAllowanceAsync( + this.takerAddress, + this.zrxAssetData, + constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, + ); + break; + + default: + throw errorUtils.spawnSwitchErr( + 'takerStateScenario.zrxFeeAllowance', + takerStateScenario.zrxFeeAllowance, + ); + } + } +} // tslint:disable:max-file-line-count diff --git a/contracts/exchange/test/utils/index.ts b/contracts/exchange/test/utils/index.ts new file mode 100644 index 000000000..75cd88666 --- /dev/null +++ b/contracts/exchange/test/utils/index.ts @@ -0,0 +1,3 @@ +export * from './exchange_wrapper'; +export * from './erc20_wrapper'; +export * from './erc721_wrapper'; diff --git a/contracts/exchange/test/utils/match_order_tester.ts b/contracts/exchange/test/utils/match_order_tester.ts new file mode 100644 index 000000000..0ba8017c5 --- /dev/null +++ b/contracts/exchange/test/utils/match_order_tester.ts @@ -0,0 +1,564 @@ +import { + chaiSetup, + ERC20BalancesByOwner, + ERC721TokenIdsByOwner, + OrderInfo, + OrderStatus, + TransferAmountsByMatchOrders as TransferAmounts, + TransferAmountsLoggedByMatchOrders as LoggedTransferAmounts, +} from '@0x/contracts-test-utils'; +import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; +import { AssetProxyId, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; +import * as _ from 'lodash'; + +import { TransactionReceiptWithDecodedLogs } from '../../../../node_modules/ethereum-types'; + +import { ERC20Wrapper } from './erc20_wrapper'; +import { ERC721Wrapper } from './erc721_wrapper'; +import { ExchangeWrapper } from './exchange_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; + +export class MatchOrderTester { + private readonly _exchangeWrapper: ExchangeWrapper; + private readonly _erc20Wrapper: ERC20Wrapper; + private readonly _erc721Wrapper: ERC721Wrapper; + private readonly _feeTokenAddress: string; + /// @dev Checks values from the logs produced by Exchange.matchOrders against the expected transfer amounts. + /// Values include the amounts transferred from the left/right makers and taker, along with + /// the fees paid on each matched order. These are also the return values of MatchOrders. + /// @param signedOrderLeft First matched order. + /// @param signedOrderRight Second matched order. + /// @param transactionReceipt Transaction receipt and logs produced by Exchange.matchOrders. + /// @param takerAddress Address of taker (account that called Exchange.matchOrders) + /// @param expectedTransferAmounts Expected amounts transferred as a result of order matching. + private static async _assertLogsAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + transactionReceipt: TransactionReceiptWithDecodedLogs, + takerAddress: string, + expectedTransferAmounts: TransferAmounts, + ): Promise { + // Should have two fill event logs -- one for each order. + const transactionFillLogs = _.filter(transactionReceipt.logs, ['event', 'Fill']); + expect(transactionFillLogs.length, 'Checking number of logs').to.be.equal(2); + // First log is for left fill + const leftLog = (transactionFillLogs[0] as any).args as LoggedTransferAmounts; + expect(leftLog.makerAddress, 'Checking logged maker address of left order').to.be.equal( + signedOrderLeft.makerAddress, + ); + expect(leftLog.takerAddress, 'Checking logged taker address of right order').to.be.equal(takerAddress); + const amountBoughtByLeftMaker = new BigNumber(leftLog.takerAssetFilledAmount); + const amountSoldByLeftMaker = new BigNumber(leftLog.makerAssetFilledAmount); + const feePaidByLeftMaker = new BigNumber(leftLog.makerFeePaid); + const feePaidByTakerLeft = new BigNumber(leftLog.takerFeePaid); + // Second log is for right fill + const rightLog = (transactionFillLogs[1] as any).args as LoggedTransferAmounts; + expect(rightLog.makerAddress, 'Checking logged maker address of right order').to.be.equal( + signedOrderRight.makerAddress, + ); + expect(rightLog.takerAddress, 'Checking loggerd taker address of right order').to.be.equal(takerAddress); + const amountBoughtByRightMaker = new BigNumber(rightLog.takerAssetFilledAmount); + const amountSoldByRightMaker = new BigNumber(rightLog.makerAssetFilledAmount); + const feePaidByRightMaker = new BigNumber(rightLog.makerFeePaid); + const feePaidByTakerRight = new BigNumber(rightLog.takerFeePaid); + // Derive amount received by taker + const amountReceivedByTaker = amountSoldByLeftMaker.minus(amountBoughtByRightMaker); + // Assert log values - left order + expect(amountBoughtByLeftMaker, 'Checking logged amount bought by left maker').to.be.bignumber.equal( + expectedTransferAmounts.amountBoughtByLeftMaker, + ); + expect(amountSoldByLeftMaker, 'Checking logged amount sold by left maker').to.be.bignumber.equal( + expectedTransferAmounts.amountSoldByLeftMaker, + ); + expect(feePaidByLeftMaker, 'Checking logged fee paid by left maker').to.be.bignumber.equal( + expectedTransferAmounts.feePaidByLeftMaker, + ); + expect(feePaidByTakerLeft, 'Checking logged fee paid on left order by taker').to.be.bignumber.equal( + expectedTransferAmounts.feePaidByTakerLeft, + ); + // Assert log values - right order + expect(amountBoughtByRightMaker, 'Checking logged amount bought by right maker').to.be.bignumber.equal( + expectedTransferAmounts.amountBoughtByRightMaker, + ); + expect(amountSoldByRightMaker, 'Checking logged amount sold by right maker').to.be.bignumber.equal( + expectedTransferAmounts.amountSoldByRightMaker, + ); + expect(feePaidByRightMaker, 'Checking logged fee paid by right maker').to.be.bignumber.equal( + expectedTransferAmounts.feePaidByRightMaker, + ); + expect(feePaidByTakerRight, 'Checking logged fee paid on right order by taker').to.be.bignumber.equal( + expectedTransferAmounts.feePaidByTakerRight, + ); + // Assert derived amount received by taker + expect(amountReceivedByTaker, 'Checking logged amount received by taker').to.be.bignumber.equal( + expectedTransferAmounts.amountReceivedByTaker, + ); + } + /// @dev Asserts all expected ERC20 and ERC721 account holdings match the real holdings. + /// @param expectedERC20BalancesByOwner Expected ERC20 balances. + /// @param realERC20BalancesByOwner Real ERC20 balances. + /// @param expectedERC721TokenIdsByOwner Expected ERC721 token owners. + /// @param realERC721TokenIdsByOwner Real ERC20 token owners. + private static async _assertAllKnownBalancesAsync( + expectedERC20BalancesByOwner: ERC20BalancesByOwner, + realERC20BalancesByOwner: ERC20BalancesByOwner, + expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner, + realERC721TokenIdsByOwner: ERC721TokenIdsByOwner, + ): Promise { + // ERC20 Balances + const areERC20BalancesEqual = _.isEqual(expectedERC20BalancesByOwner, realERC20BalancesByOwner); + expect(areERC20BalancesEqual, 'Checking all known ERC20 account balances').to.be.true(); + // ERC721 Token Ids + const sortedExpectedNewERC721TokenIdsByOwner = _.mapValues(expectedERC721TokenIdsByOwner, tokenIdsByOwner => { + _.mapValues(tokenIdsByOwner, tokenIds => { + _.sortBy(tokenIds); + }); + }); + const sortedNewERC721TokenIdsByOwner = _.mapValues(realERC721TokenIdsByOwner, tokenIdsByOwner => { + _.mapValues(tokenIdsByOwner, tokenIds => { + _.sortBy(tokenIds); + }); + }); + const areERC721TokenIdsEqual = _.isEqual( + sortedExpectedNewERC721TokenIdsByOwner, + sortedNewERC721TokenIdsByOwner, + ); + expect(areERC721TokenIdsEqual, 'Checking all known ERC721 account balances').to.be.true(); + } + /// @dev Constructs new MatchOrderTester. + /// @param exchangeWrapper Used to call to the Exchange. + /// @param erc20Wrapper Used to fetch ERC20 balances. + /// @param erc721Wrapper Used to fetch ERC721 token owners. + /// @param feeTokenAddress Address of ERC20 fee token. + constructor( + exchangeWrapper: ExchangeWrapper, + erc20Wrapper: ERC20Wrapper, + erc721Wrapper: ERC721Wrapper, + feeTokenAddress: string, + ) { + this._exchangeWrapper = exchangeWrapper; + this._erc20Wrapper = erc20Wrapper; + this._erc721Wrapper = erc721Wrapper; + this._feeTokenAddress = feeTokenAddress; + } + /// @dev Matches two complementary orders and asserts results. + /// @param signedOrderLeft First matched order. + /// @param signedOrderRight Second matched order. + /// @param takerAddress Address of taker (the address who matched the two orders) + /// @param erc20BalancesByOwner Current ERC20 balances. + /// @param erc721TokenIdsByOwner Current ERC721 token owners. + /// @param expectedTransferAmounts Expected amounts transferred as a result of order matching. + /// @param initialLeftOrderFilledAmount How much left order has been filled, prior to matching orders. + /// @param initialRightOrderFilledAmount How much the right order has been filled, prior to matching orders. + /// @return New ERC20 balances & ERC721 token owners. + public async matchOrdersAndAssertEffectsAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + takerAddress: string, + erc20BalancesByOwner: ERC20BalancesByOwner, + erc721TokenIdsByOwner: ERC721TokenIdsByOwner, + expectedTransferAmounts: TransferAmounts, + initialLeftOrderFilledAmount: BigNumber = new BigNumber(0), + initialRightOrderFilledAmount: BigNumber = new BigNumber(0), + ): Promise<[ERC20BalancesByOwner, ERC721TokenIdsByOwner]> { + // Assert initial order states + await this._assertInitialOrderStatesAsync( + signedOrderLeft, + signedOrderRight, + initialLeftOrderFilledAmount, + initialRightOrderFilledAmount, + ); + // Match left & right orders + const transactionReceipt = await this._exchangeWrapper.matchOrdersAsync( + signedOrderLeft, + signedOrderRight, + takerAddress, + ); + const newERC20BalancesByOwner = await this._erc20Wrapper.getBalancesAsync(); + const newERC721TokenIdsByOwner = await this._erc721Wrapper.getBalancesAsync(); + // Assert logs + await MatchOrderTester._assertLogsAsync( + signedOrderLeft, + signedOrderRight, + transactionReceipt, + takerAddress, + expectedTransferAmounts, + ); + // Assert exchange state + await this._assertExchangeStateAsync( + signedOrderLeft, + signedOrderRight, + initialLeftOrderFilledAmount, + initialRightOrderFilledAmount, + expectedTransferAmounts, + ); + // Assert balances of makers, taker, and fee recipients + await this._assertBalancesAsync( + signedOrderLeft, + signedOrderRight, + erc20BalancesByOwner, + erc721TokenIdsByOwner, + newERC20BalancesByOwner, + newERC721TokenIdsByOwner, + expectedTransferAmounts, + takerAddress, + ); + return [newERC20BalancesByOwner, newERC721TokenIdsByOwner]; + } + /// @dev Asserts initial exchange state for the left and right orders. + /// @param signedOrderLeft First matched order. + /// @param signedOrderRight Second matched order. + /// @param expectedOrderFilledAmountLeft How much left order has been filled, prior to matching orders. + /// @param expectedOrderFilledAmountRight How much the right order has been filled, prior to matching orders. + private async _assertInitialOrderStatesAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + expectedOrderFilledAmountLeft: BigNumber, + expectedOrderFilledAmountRight: BigNumber, + ): Promise { + // Assert left order initial state + const orderTakerAssetFilledAmountLeft = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + orderHashUtils.getOrderHashHex(signedOrderLeft), + ); + expect(orderTakerAssetFilledAmountLeft, 'Checking inital state of left order').to.be.bignumber.equal( + expectedOrderFilledAmountLeft, + ); + // Assert right order initial state + const orderTakerAssetFilledAmountRight = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + orderHashUtils.getOrderHashHex(signedOrderRight), + ); + expect(orderTakerAssetFilledAmountRight, 'Checking inital state of right order').to.be.bignumber.equal( + expectedOrderFilledAmountRight, + ); + } + /// @dev Asserts the exchange state against the expected amounts transferred by from matching orders. + /// @param signedOrderLeft First matched order. + /// @param signedOrderRight Second matched order. + /// @param initialLeftOrderFilledAmount How much left order has been filled, prior to matching orders. + /// @param initialRightOrderFilledAmount How much the right order has been filled, prior to matching orders. + /// @return TransferAmounts A struct containing the expected transfer amounts. + private async _assertExchangeStateAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + initialLeftOrderFilledAmount: BigNumber, + initialRightOrderFilledAmount: BigNumber, + expectedTransferAmounts: TransferAmounts, + ): Promise { + // Assert state for left order: amount bought by left maker + let amountBoughtByLeftMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + orderHashUtils.getOrderHashHex(signedOrderLeft), + ); + amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(initialLeftOrderFilledAmount); + expect(amountBoughtByLeftMaker, 'Checking exchange state for left order').to.be.bignumber.equal( + expectedTransferAmounts.amountBoughtByLeftMaker, + ); + // Assert state for right order: amount bought by right maker + let amountBoughtByRightMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( + orderHashUtils.getOrderHashHex(signedOrderRight), + ); + amountBoughtByRightMaker = amountBoughtByRightMaker.minus(initialRightOrderFilledAmount); + expect(amountBoughtByRightMaker, 'Checking exchange state for right order').to.be.bignumber.equal( + expectedTransferAmounts.amountBoughtByRightMaker, + ); + // Assert left order status + const maxAmountBoughtByLeftMaker = signedOrderLeft.takerAssetAmount.minus(initialLeftOrderFilledAmount); + const leftOrderInfo: OrderInfo = await this._exchangeWrapper.getOrderInfoAsync(signedOrderLeft); + const leftExpectedStatus = expectedTransferAmounts.amountBoughtByLeftMaker.isEqualTo(maxAmountBoughtByLeftMaker) + ? OrderStatus.FullyFilled + : OrderStatus.Fillable; + expect(leftOrderInfo.orderStatus, 'Checking exchange status for left order').to.be.equal(leftExpectedStatus); + // Assert right order status + const maxAmountBoughtByRightMaker = signedOrderRight.takerAssetAmount.minus(initialRightOrderFilledAmount); + const rightOrderInfo: OrderInfo = await this._exchangeWrapper.getOrderInfoAsync(signedOrderRight); + const rightExpectedStatus = expectedTransferAmounts.amountBoughtByRightMaker.isEqualTo( + maxAmountBoughtByRightMaker, + ) + ? OrderStatus.FullyFilled + : OrderStatus.Fillable; + expect(rightOrderInfo.orderStatus, 'Checking exchange status for right order').to.be.equal(rightExpectedStatus); + } + /// @dev Asserts account balances after matching orders. + /// @param signedOrderLeft First matched order. + /// @param signedOrderRight Second matched order. + /// @param initialERC20BalancesByOwner ERC20 balances prior to order matching. + /// @param initialERC721TokenIdsByOwner ERC721 token owners prior to order matching. + /// @param finalERC20BalancesByOwner ERC20 balances after order matching. + /// @param finalERC721TokenIdsByOwner ERC721 token owners after order matching. + /// @param expectedTransferAmounts Expected amounts transferred as a result of order matching. + /// @param takerAddress Address of taker (account that called Exchange.matchOrders). + private async _assertBalancesAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + initialERC20BalancesByOwner: ERC20BalancesByOwner, + initialERC721TokenIdsByOwner: ERC721TokenIdsByOwner, + finalERC20BalancesByOwner: ERC20BalancesByOwner, + finalERC721TokenIdsByOwner: ERC721TokenIdsByOwner, + expectedTransferAmounts: TransferAmounts, + takerAddress: string, + ): Promise { + let expectedERC20BalancesByOwner: ERC20BalancesByOwner; + let expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner; + [expectedERC20BalancesByOwner, expectedERC721TokenIdsByOwner] = this._calculateExpectedBalances( + signedOrderLeft, + signedOrderRight, + takerAddress, + initialERC20BalancesByOwner, + initialERC721TokenIdsByOwner, + expectedTransferAmounts, + ); + // Assert balances of makers, taker, and fee recipients + await this._assertMakerTakerAndFeeRecipientBalancesAsync( + signedOrderLeft, + signedOrderRight, + expectedERC20BalancesByOwner, + finalERC20BalancesByOwner, + expectedERC721TokenIdsByOwner, + finalERC721TokenIdsByOwner, + takerAddress, + ); + // Assert balances for all known accounts + await MatchOrderTester._assertAllKnownBalancesAsync( + expectedERC20BalancesByOwner, + finalERC20BalancesByOwner, + expectedERC721TokenIdsByOwner, + finalERC721TokenIdsByOwner, + ); + } + /// @dev Calculates the expected balances of order makers, fee recipients, and the taker, + /// as a result of matching two orders. + /// @param signedOrderRight First matched order. + /// @param signedOrderRight Second matched order. + /// @param takerAddress Address of taker (the address who matched the two orders) + /// @param erc20BalancesByOwner Current ERC20 balances. + /// @param erc721TokenIdsByOwner Current ERC721 token owners. + /// @param expectedTransferAmounts Expected amounts transferred as a result of order matching. + /// @return Expected ERC20 balances & ERC721 token owners after orders have been matched. + private _calculateExpectedBalances( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + takerAddress: string, + erc20BalancesByOwner: ERC20BalancesByOwner, + erc721TokenIdsByOwner: ERC721TokenIdsByOwner, + expectedTransferAmounts: TransferAmounts, + ): [ERC20BalancesByOwner, ERC721TokenIdsByOwner] { + const makerAddressLeft = signedOrderLeft.makerAddress; + const makerAddressRight = signedOrderRight.makerAddress; + const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress; + const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress; + // Operations are performed on copies of the balances + const expectedNewERC20BalancesByOwner = _.cloneDeep(erc20BalancesByOwner); + const expectedNewERC721TokenIdsByOwner = _.cloneDeep(erc721TokenIdsByOwner); + // Left Maker Asset (Right Taker Asset) + const makerAssetProxyIdLeft = assetDataUtils.decodeAssetProxyId(signedOrderLeft.makerAssetData); + if (makerAssetProxyIdLeft === AssetProxyId.ERC20) { + // Decode asset data + const erc20AssetData = assetDataUtils.decodeERC20AssetData(signedOrderLeft.makerAssetData); + const makerAssetAddressLeft = erc20AssetData.tokenAddress; + const takerAssetAddressRight = makerAssetAddressLeft; + // Left Maker + expectedNewERC20BalancesByOwner[makerAddressLeft][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ + makerAddressLeft + ][makerAssetAddressLeft].minus(expectedTransferAmounts.amountSoldByLeftMaker); + // Right Maker + expectedNewERC20BalancesByOwner[makerAddressRight][ + takerAssetAddressRight + ] = expectedNewERC20BalancesByOwner[makerAddressRight][takerAssetAddressRight].plus( + expectedTransferAmounts.amountBoughtByRightMaker, + ); + // Taker + expectedNewERC20BalancesByOwner[takerAddress][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ + takerAddress + ][makerAssetAddressLeft].plus(expectedTransferAmounts.amountReceivedByTaker); + } else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) { + // Decode asset data + const erc721AssetData = assetDataUtils.decodeERC721AssetData(signedOrderLeft.makerAssetData); + const makerAssetAddressLeft = erc721AssetData.tokenAddress; + const makerAssetIdLeft = erc721AssetData.tokenId; + const takerAssetAddressRight = makerAssetAddressLeft; + const takerAssetIdRight = makerAssetIdLeft; + // Left Maker + _.remove(expectedNewERC721TokenIdsByOwner[makerAddressLeft][makerAssetAddressLeft], makerAssetIdLeft); + // Right Maker + expectedNewERC721TokenIdsByOwner[makerAddressRight][takerAssetAddressRight].push(takerAssetIdRight); + // Taker: Since there is only 1 asset transferred, the taker does not receive any of the left maker asset. + } + // Left Taker Asset (Right Maker Asset) + // Note: This exchange is only between the order makers: the Taker does not receive any of the left taker asset. + const takerAssetProxyIdLeft = assetDataUtils.decodeAssetProxyId(signedOrderLeft.takerAssetData); + if (takerAssetProxyIdLeft === AssetProxyId.ERC20) { + // Decode asset data + const erc20AssetData = assetDataUtils.decodeERC20AssetData(signedOrderLeft.takerAssetData); + const takerAssetAddressLeft = erc20AssetData.tokenAddress; + const makerAssetAddressRight = takerAssetAddressLeft; + // Left Maker + expectedNewERC20BalancesByOwner[makerAddressLeft][takerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ + makerAddressLeft + ][takerAssetAddressLeft].plus(expectedTransferAmounts.amountBoughtByLeftMaker); + // Right Maker + expectedNewERC20BalancesByOwner[makerAddressRight][ + makerAssetAddressRight + ] = expectedNewERC20BalancesByOwner[makerAddressRight][makerAssetAddressRight].minus( + expectedTransferAmounts.amountSoldByRightMaker, + ); + } else if (takerAssetProxyIdLeft === AssetProxyId.ERC721) { + // Decode asset data + const erc721AssetData = assetDataUtils.decodeERC721AssetData(signedOrderRight.makerAssetData); + const makerAssetAddressRight = erc721AssetData.tokenAddress; + const makerAssetIdRight = erc721AssetData.tokenId; + const takerAssetAddressLeft = makerAssetAddressRight; + const takerAssetIdLeft = makerAssetIdRight; + // Right Maker + _.remove(expectedNewERC721TokenIdsByOwner[makerAddressRight][makerAssetAddressRight], makerAssetIdRight); + // Left Maker + expectedNewERC721TokenIdsByOwner[makerAddressLeft][takerAssetAddressLeft].push(takerAssetIdLeft); + } + // Left Maker Fees + expectedNewERC20BalancesByOwner[makerAddressLeft][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[ + makerAddressLeft + ][this._feeTokenAddress].minus(expectedTransferAmounts.feePaidByLeftMaker); + // Right Maker Fees + expectedNewERC20BalancesByOwner[makerAddressRight][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[ + makerAddressRight + ][this._feeTokenAddress].minus(expectedTransferAmounts.feePaidByRightMaker); + // Taker Fees + expectedNewERC20BalancesByOwner[takerAddress][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[ + takerAddress + ][this._feeTokenAddress].minus( + expectedTransferAmounts.feePaidByTakerLeft.plus(expectedTransferAmounts.feePaidByTakerRight), + ); + // Left Fee Recipient Fees + expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][ + this._feeTokenAddress + ] = expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][this._feeTokenAddress].plus( + expectedTransferAmounts.feePaidByLeftMaker.plus(expectedTransferAmounts.feePaidByTakerLeft), + ); + // Right Fee Recipient Fees + expectedNewERC20BalancesByOwner[feeRecipientAddressRight][ + this._feeTokenAddress + ] = expectedNewERC20BalancesByOwner[feeRecipientAddressRight][this._feeTokenAddress].plus( + expectedTransferAmounts.feePaidByRightMaker.plus(expectedTransferAmounts.feePaidByTakerRight), + ); + + return [expectedNewERC20BalancesByOwner, expectedNewERC721TokenIdsByOwner]; + } + /// @dev Asserts ERC20 account balances and ERC721 token holdings that result from order matching. + /// Specifically checks balances of makers, taker and fee recipients. + /// @param signedOrderLeft First matched order. + /// @param signedOrderRight Second matched order. + /// @param expectedERC20BalancesByOwner Expected ERC20 balances. + /// @param realERC20BalancesByOwner Real ERC20 balances. + /// @param expectedERC721TokenIdsByOwner Expected ERC721 token owners. + /// @param realERC721TokenIdsByOwner Real ERC20 token owners. + /// @param takerAddress Address of taker (account that called Exchange.matchOrders). + private async _assertMakerTakerAndFeeRecipientBalancesAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + expectedERC20BalancesByOwner: ERC20BalancesByOwner, + realERC20BalancesByOwner: ERC20BalancesByOwner, + expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner, + realERC721TokenIdsByOwner: ERC721TokenIdsByOwner, + takerAddress: string, + ): Promise { + // Individual balance comparisons + const makerAssetProxyIdLeft = assetDataUtils.decodeAssetProxyId(signedOrderLeft.makerAssetData); + const makerERC20AssetDataLeft = + makerAssetProxyIdLeft === AssetProxyId.ERC20 + ? assetDataUtils.decodeERC20AssetData(signedOrderLeft.makerAssetData) + : assetDataUtils.decodeERC721AssetData(signedOrderLeft.makerAssetData); + const makerAssetAddressLeft = makerERC20AssetDataLeft.tokenAddress; + const makerAssetProxyIdRight = assetDataUtils.decodeAssetProxyId(signedOrderRight.makerAssetData); + const makerERC20AssetDataRight = + makerAssetProxyIdRight === AssetProxyId.ERC20 + ? assetDataUtils.decodeERC20AssetData(signedOrderRight.makerAssetData) + : assetDataUtils.decodeERC721AssetData(signedOrderRight.makerAssetData); + const makerAssetAddressRight = makerERC20AssetDataRight.tokenAddress; + if (makerAssetProxyIdLeft === AssetProxyId.ERC20) { + expect( + realERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft], + 'Checking left maker egress ERC20 account balance', + ).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft]); + expect( + realERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft], + 'Checking right maker ingress ERC20 account balance', + ).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft]); + expect( + realERC20BalancesByOwner[takerAddress][makerAssetAddressLeft], + 'Checking taker ingress ERC20 account balance', + ).to.be.bignumber.equal(expectedERC20BalancesByOwner[takerAddress][makerAssetAddressLeft]); + } else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) { + expect( + realERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft].sort(), + 'Checking left maker egress ERC721 account holdings', + ).to.be.deep.equal( + expectedERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft].sort(), + ); + expect( + realERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft].sort(), + 'Checking right maker ERC721 account holdings', + ).to.be.deep.equal( + expectedERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft].sort(), + ); + expect( + realERC721TokenIdsByOwner[takerAddress][makerAssetAddressLeft].sort(), + 'Checking taker ingress ERC721 account holdings', + ).to.be.deep.equal(expectedERC721TokenIdsByOwner[takerAddress][makerAssetAddressLeft].sort()); + } else { + throw new Error(`Unhandled Asset Proxy ID: ${makerAssetProxyIdLeft}`); + } + if (makerAssetProxyIdRight === AssetProxyId.ERC20) { + expect( + realERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight], + 'Checking left maker ingress ERC20 account balance', + ).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight]); + expect( + realERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressRight], + 'Checking right maker egress ERC20 account balance', + ).to.be.bignumber.equal( + expectedERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressRight], + ); + } else if (makerAssetProxyIdRight === AssetProxyId.ERC721) { + expect( + realERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight].sort(), + 'Checking left maker ingress ERC721 account holdings', + ).to.be.deep.equal( + expectedERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight].sort(), + ); + expect( + realERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressRight], + 'Checking right maker agress ERC721 account holdings', + ).to.be.deep.equal(expectedERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressRight]); + } else { + throw new Error(`Unhandled Asset Proxy ID: ${makerAssetProxyIdRight}`); + } + // Paid fees + expect( + realERC20BalancesByOwner[signedOrderLeft.makerAddress][this._feeTokenAddress], + 'Checking left maker egress ERC20 account fees', + ).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderLeft.makerAddress][this._feeTokenAddress]); + expect( + realERC20BalancesByOwner[signedOrderRight.makerAddress][this._feeTokenAddress], + 'Checking right maker egress ERC20 account fees', + ).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderRight.makerAddress][this._feeTokenAddress]); + expect( + realERC20BalancesByOwner[takerAddress][this._feeTokenAddress], + 'Checking taker egress ERC20 account fees', + ).to.be.bignumber.equal(expectedERC20BalancesByOwner[takerAddress][this._feeTokenAddress]); + // Received fees + expect( + realERC20BalancesByOwner[signedOrderLeft.feeRecipientAddress][this._feeTokenAddress], + 'Checking left fee recipient ingress ERC20 account fees', + ).to.be.bignumber.equal( + expectedERC20BalancesByOwner[signedOrderLeft.feeRecipientAddress][this._feeTokenAddress], + ); + expect( + realERC20BalancesByOwner[signedOrderRight.feeRecipientAddress][this._feeTokenAddress], + 'Checking right fee receipient ingress ERC20 account fees', + ).to.be.bignumber.equal( + expectedERC20BalancesByOwner[signedOrderRight.feeRecipientAddress][this._feeTokenAddress], + ); + } +} // tslint:disable-line:max-file-line-count diff --git a/contracts/exchange/test/utils/order_factory_from_scenario.ts b/contracts/exchange/test/utils/order_factory_from_scenario.ts new file mode 100644 index 000000000..ecb4b2e28 --- /dev/null +++ b/contracts/exchange/test/utils/order_factory_from_scenario.ts @@ -0,0 +1,294 @@ +import { + AssetDataScenario, + constants, + ERC721TokenIdsByOwner, + ExpirationTimeSecondsScenario, + FeeRecipientAddressScenario, + OrderAssetAmountScenario, + OrderScenario, + TakerScenario, +} from '@0x/contracts-test-utils'; +import { DummyERC721TokenContract } from '@0x/contracts-tokens'; +import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils'; +import { Order } from '@0x/types'; +import { BigNumber, errorUtils } from '@0x/utils'; + +const TEN_UNITS_EIGHTEEN_DECIMALS = new BigNumber(10_000_000_000_000_000_000); +const FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(5_000_000_000_000_000_000); +const POINT_ONE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(100_000_000_000_000_000); +const POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(50_000_000_000_000_000); +const TEN_UNITS_FIVE_DECIMALS = new BigNumber(1_000_000); +const FIVE_UNITS_FIVE_DECIMALS = new BigNumber(500_000); +const TEN_UNITS_ZERO_DECIMALS = new BigNumber(10); +const ONE_THOUSAND_UNITS_ZERO_DECIMALS = new BigNumber(1000); +const ONE_NFT_UNIT = new BigNumber(1); + +export class OrderFactoryFromScenario { + private readonly _userAddresses: string[]; + private readonly _zrxAddress: string; + private readonly _nonZrxERC20EighteenDecimalTokenAddresses: string[]; + private readonly _erc20FiveDecimalTokenAddresses: string[]; + private readonly _erc20ZeroDecimalTokenAddresses: string[]; + private readonly _erc721Token: DummyERC721TokenContract; + private readonly _erc721Balances: ERC721TokenIdsByOwner; + private readonly _exchangeAddress: string; + constructor( + userAddresses: string[], + zrxAddress: string, + nonZrxERC20EighteenDecimalTokenAddresses: string[], + erc20FiveDecimalTokenAddresses: string[], + erc20ZeroDecimalTokenAddresses: string[], + erc721Token: DummyERC721TokenContract, + erc721Balances: ERC721TokenIdsByOwner, + exchangeAddress: string, + ) { + this._userAddresses = userAddresses; + this._zrxAddress = zrxAddress; + this._nonZrxERC20EighteenDecimalTokenAddresses = nonZrxERC20EighteenDecimalTokenAddresses; + this._erc20FiveDecimalTokenAddresses = erc20FiveDecimalTokenAddresses; + this._erc20ZeroDecimalTokenAddresses = erc20ZeroDecimalTokenAddresses; + this._erc721Token = erc721Token; + this._erc721Balances = erc721Balances; + this._exchangeAddress = exchangeAddress; + } + public generateOrder(orderScenario: OrderScenario): Order { + const makerAddress = this._userAddresses[1]; + let takerAddress = this._userAddresses[2]; + const erc721MakerAssetIds = this._erc721Balances[makerAddress][this._erc721Token.address]; + const erc721TakerAssetIds = this._erc721Balances[takerAddress][this._erc721Token.address]; + let feeRecipientAddress; + let makerAssetAmount; + let takerAssetAmount; + let makerFee; + let takerFee; + let expirationTimeSeconds; + let makerAssetData; + let takerAssetData; + + switch (orderScenario.feeRecipientScenario) { + case FeeRecipientAddressScenario.BurnAddress: + feeRecipientAddress = constants.NULL_ADDRESS; + break; + case FeeRecipientAddressScenario.EthUserAddress: + feeRecipientAddress = this._userAddresses[4]; + break; + default: + throw errorUtils.spawnSwitchErr('FeeRecipientAddressScenario', orderScenario.feeRecipientScenario); + } + + switch (orderScenario.makerAssetDataScenario) { + case AssetDataScenario.ZRXFeeToken: + makerAssetData = assetDataUtils.encodeERC20AssetData(this._zrxAddress); + break; + case AssetDataScenario.ERC20NonZRXEighteenDecimals: + makerAssetData = assetDataUtils.encodeERC20AssetData(this._nonZrxERC20EighteenDecimalTokenAddresses[0]); + break; + case AssetDataScenario.ERC20FiveDecimals: + makerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[0]); + break; + case AssetDataScenario.ERC721: + makerAssetData = assetDataUtils.encodeERC721AssetData( + this._erc721Token.address, + erc721MakerAssetIds[0], + ); + break; + case AssetDataScenario.ERC20ZeroDecimals: + makerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20ZeroDecimalTokenAddresses[0]); + break; + default: + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); + } + + switch (orderScenario.takerAssetDataScenario) { + case AssetDataScenario.ZRXFeeToken: + takerAssetData = assetDataUtils.encodeERC20AssetData(this._zrxAddress); + break; + case AssetDataScenario.ERC20NonZRXEighteenDecimals: + takerAssetData = assetDataUtils.encodeERC20AssetData(this._nonZrxERC20EighteenDecimalTokenAddresses[1]); + break; + case AssetDataScenario.ERC20FiveDecimals: + takerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[1]); + break; + case AssetDataScenario.ERC721: + takerAssetData = assetDataUtils.encodeERC721AssetData( + this._erc721Token.address, + erc721TakerAssetIds[0], + ); + break; + case AssetDataScenario.ERC20ZeroDecimals: + takerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20ZeroDecimalTokenAddresses[1]); + break; + default: + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); + } + + switch (orderScenario.makerAssetAmountScenario) { + case OrderAssetAmountScenario.Large: + switch (orderScenario.makerAssetDataScenario) { + case AssetDataScenario.ZRXFeeToken: + case AssetDataScenario.ERC20NonZRXEighteenDecimals: + makerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS; + break; + case AssetDataScenario.ERC20FiveDecimals: + makerAssetAmount = TEN_UNITS_FIVE_DECIMALS; + break; + case AssetDataScenario.ERC721: + makerAssetAmount = ONE_NFT_UNIT; + break; + case AssetDataScenario.ERC20ZeroDecimals: + makerAssetAmount = ONE_THOUSAND_UNITS_ZERO_DECIMALS; + break; + default: + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); + } + break; + case OrderAssetAmountScenario.Small: + switch (orderScenario.makerAssetDataScenario) { + case AssetDataScenario.ZRXFeeToken: + case AssetDataScenario.ERC20NonZRXEighteenDecimals: + makerAssetAmount = FIVE_UNITS_EIGHTEEN_DECIMALS; + break; + case AssetDataScenario.ERC20FiveDecimals: + makerAssetAmount = FIVE_UNITS_FIVE_DECIMALS; + break; + case AssetDataScenario.ERC721: + makerAssetAmount = ONE_NFT_UNIT; + break; + case AssetDataScenario.ERC20ZeroDecimals: + makerAssetAmount = TEN_UNITS_ZERO_DECIMALS; + break; + default: + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); + } + break; + case OrderAssetAmountScenario.Zero: + makerAssetAmount = new BigNumber(0); + break; + default: + throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.makerAssetAmountScenario); + } + + switch (orderScenario.takerAssetAmountScenario) { + case OrderAssetAmountScenario.Large: + switch (orderScenario.takerAssetDataScenario) { + case AssetDataScenario.ERC20NonZRXEighteenDecimals: + case AssetDataScenario.ZRXFeeToken: + takerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS; + break; + case AssetDataScenario.ERC20FiveDecimals: + takerAssetAmount = TEN_UNITS_FIVE_DECIMALS; + break; + case AssetDataScenario.ERC721: + takerAssetAmount = ONE_NFT_UNIT; + break; + case AssetDataScenario.ERC20ZeroDecimals: + takerAssetAmount = ONE_THOUSAND_UNITS_ZERO_DECIMALS; + break; + default: + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); + } + break; + case OrderAssetAmountScenario.Small: + switch (orderScenario.takerAssetDataScenario) { + case AssetDataScenario.ERC20NonZRXEighteenDecimals: + case AssetDataScenario.ZRXFeeToken: + takerAssetAmount = FIVE_UNITS_EIGHTEEN_DECIMALS; + break; + case AssetDataScenario.ERC20FiveDecimals: + takerAssetAmount = FIVE_UNITS_FIVE_DECIMALS; + break; + case AssetDataScenario.ERC721: + takerAssetAmount = ONE_NFT_UNIT; + break; + case AssetDataScenario.ERC20ZeroDecimals: + takerAssetAmount = TEN_UNITS_ZERO_DECIMALS; + break; + default: + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); + } + break; + case OrderAssetAmountScenario.Zero: + takerAssetAmount = new BigNumber(0); + break; + default: + throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.takerAssetAmountScenario); + } + + switch (orderScenario.makerFeeScenario) { + case OrderAssetAmountScenario.Large: + makerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS; + break; + case OrderAssetAmountScenario.Small: + makerFee = POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS; + break; + case OrderAssetAmountScenario.Zero: + makerFee = new BigNumber(0); + break; + default: + throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.makerFeeScenario); + } + + switch (orderScenario.takerFeeScenario) { + case OrderAssetAmountScenario.Large: + takerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS; + break; + case OrderAssetAmountScenario.Small: + takerFee = POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS; + break; + case OrderAssetAmountScenario.Zero: + takerFee = new BigNumber(0); + break; + default: + throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.takerFeeScenario); + } + + switch (orderScenario.expirationTimeSecondsScenario) { + case ExpirationTimeSecondsScenario.InFuture: + expirationTimeSeconds = new BigNumber(2524604400); // Close to infinite + break; + case ExpirationTimeSecondsScenario.InPast: + expirationTimeSeconds = new BigNumber(0); // Jan 1, 1970 + break; + default: + throw errorUtils.spawnSwitchErr( + 'ExpirationTimeSecondsScenario', + orderScenario.expirationTimeSecondsScenario, + ); + } + + switch (orderScenario.takerScenario) { + case TakerScenario.CorrectlySpecified: + break; // noop since takerAddress is already specified + + case TakerScenario.IncorrectlySpecified: + const notTaker = this._userAddresses[3]; + takerAddress = notTaker; + break; + + case TakerScenario.Unspecified: + takerAddress = constants.NULL_ADDRESS; + break; + + default: + throw errorUtils.spawnSwitchErr('TakerScenario', orderScenario.takerScenario); + } + + const order = { + senderAddress: constants.NULL_ADDRESS, + makerAddress, + takerAddress, + makerFee, + takerFee, + makerAssetAmount, + takerAssetAmount, + makerAssetData, + takerAssetData, + salt: generatePseudoRandomSalt(), + exchangeAddress: this._exchangeAddress, + feeRecipientAddress, + expirationTimeSeconds, + }; + + return order; + } +} diff --git a/contracts/exchange/test/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts b/contracts/exchange/test/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts new file mode 100644 index 000000000..64b7dedbe --- /dev/null +++ b/contracts/exchange/test/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts @@ -0,0 +1,19 @@ +import { AbstractBalanceAndProxyAllowanceFetcher } from '@0x/order-utils'; +import { BigNumber } from '@0x/utils'; + +import { AssetWrapper } from './asset_wrapper'; + +export class SimpleAssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher { + private readonly _assetWrapper: AssetWrapper; + constructor(assetWrapper: AssetWrapper) { + this._assetWrapper = assetWrapper; + } + public async getBalanceAsync(assetData: string, userAddress: string): Promise { + const balance = await this._assetWrapper.getBalanceAsync(userAddress, assetData); + return balance; + } + public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise { + const proxyAllowance = await this._assetWrapper.getProxyAllowanceAsync(userAddress, assetData); + return proxyAllowance; + } +} diff --git a/contracts/exchange/test/utils/simple_order_filled_cancelled_fetcher.ts b/contracts/exchange/test/utils/simple_order_filled_cancelled_fetcher.ts new file mode 100644 index 000000000..af959e00e --- /dev/null +++ b/contracts/exchange/test/utils/simple_order_filled_cancelled_fetcher.ts @@ -0,0 +1,31 @@ +import { AbstractOrderFilledCancelledFetcher, orderHashUtils } from '@0x/order-utils'; +import { SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; + +import { ExchangeWrapper } from './exchange_wrapper'; + +export class SimpleOrderFilledCancelledFetcher implements AbstractOrderFilledCancelledFetcher { + private readonly _exchangeWrapper: ExchangeWrapper; + private readonly _zrxAssetData: string; + constructor(exchange: ExchangeWrapper, zrxAssetData: string) { + this._exchangeWrapper = exchange; + this._zrxAssetData = zrxAssetData; + } + public async getFilledTakerAmountAsync(orderHash: string): Promise { + const filledTakerAmount = new BigNumber(await this._exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash)); + return filledTakerAmount; + } + public async isOrderCancelledAsync(signedOrder: SignedOrder): Promise { + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + const isCancelled = await this._exchangeWrapper.isCancelledAsync(orderHash); + const orderEpoch = await this._exchangeWrapper.getOrderEpochAsync( + signedOrder.makerAddress, + signedOrder.senderAddress, + ); + const isCancelledByOrderEpoch = orderEpoch > signedOrder.salt; + return isCancelled || isCancelledByOrderEpoch; + } + public getZRXAssetData(): string { + return this._zrxAssetData; + } +} diff --git a/contracts/exchange/test/utils/types.ts b/contracts/exchange/test/utils/types.ts new file mode 100644 index 000000000..5f12635a7 --- /dev/null +++ b/contracts/exchange/test/utils/types.ts @@ -0,0 +1,8 @@ +import { SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; + +export interface AbiDecodedFillOrderData { + order: SignedOrder; + takerAssetFillAmount: BigNumber; + signature: string; +} diff --git a/contracts/exchange/test/wrapper.ts b/contracts/exchange/test/wrapper.ts new file mode 100644 index 000000000..6211682b1 --- /dev/null +++ b/contracts/exchange/test/wrapper.ts @@ -0,0 +1,1461 @@ +import { ERC20ProxyContract, ERC721ProxyContract } from '@0x/contracts-asset-proxy'; +import { + chaiSetup, + constants, + ERC20BalancesByOwner, + expectTransactionFailedAsync, + getLatestBlockTimestampAsync, + increaseTimeAndMineBlockAsync, + OrderFactory, + OrderStatus, + provider, + txDefaults, + web3Wrapper, +} from '@0x/contracts-test-utils'; +import { + artifacts as tokensArtifacts, + DummyERC20TokenContract, + DummyERC721TokenContract, + ReentrantERC20TokenContract, +} from '@0x/contracts-tokens'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; +import { RevertReason, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import * as _ from 'lodash'; + +import { ExchangeContract } from '../generated-wrappers/exchange'; +import { artifacts } from '../src/artifacts'; + +import { ERC20Wrapper } from './utils/erc20_wrapper'; +import { ERC721Wrapper } from './utils/erc721_wrapper'; +import { ExchangeWrapper } from './utils/exchange_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('Exchange wrappers', () => { + let makerAddress: string; + let owner: string; + let takerAddress: string; + let feeRecipientAddress: string; + + let erc20TokenA: DummyERC20TokenContract; + let erc20TokenB: DummyERC20TokenContract; + let zrxToken: DummyERC20TokenContract; + let erc721Token: DummyERC721TokenContract; + let exchange: ExchangeContract; + let erc20Proxy: ERC20ProxyContract; + let erc721Proxy: ERC721ProxyContract; + let reentrantErc20Token: ReentrantERC20TokenContract; + + let exchangeWrapper: ExchangeWrapper; + let erc20Wrapper: ERC20Wrapper; + let erc721Wrapper: ERC721Wrapper; + let erc20Balances: ERC20BalancesByOwner; + let orderFactory: OrderFactory; + + let erc721MakerAssetId: BigNumber; + let erc721TakerAssetId: BigNumber; + + let defaultMakerAssetAddress: string; + let defaultTakerAssetAddress: string; + + before(async () => { + await blockchainLifecycle.startAsync(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + before(async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = _.slice(accounts, 0, 4)); + + erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); + + const numDummyErc20ToDeploy = 3; + [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + numDummyErc20ToDeploy, + constants.DUMMY_TOKEN_DECIMALS, + ); + erc20Proxy = await erc20Wrapper.deployProxyAsync(); + await erc20Wrapper.setBalancesAndAllowancesAsync(); + + [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); + erc721Proxy = await erc721Wrapper.deployProxyAsync(); + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + erc721MakerAssetId = erc721Balances[makerAddress][erc721Token.address][0]; + erc721TakerAssetId = erc721Balances[takerAddress][erc721Token.address][0]; + + exchange = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + assetDataUtils.encodeERC20AssetData(zrxToken.address), + ); + exchangeWrapper = new ExchangeWrapper(exchange, provider); + await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); + await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); + + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { + from: owner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + reentrantErc20Token = await ReentrantERC20TokenContract.deployFrom0xArtifactAsync( + tokensArtifacts.ReentrantERC20Token, + provider, + txDefaults, + exchange.address, + ); + + defaultMakerAssetAddress = erc20TokenA.address; + defaultTakerAssetAddress = erc20TokenB.address; + + const defaultOrderParams = { + ...constants.STATIC_ORDER_PARAMS, + exchangeAddress: exchange.address, + makerAddress, + feeRecipientAddress, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), + }; + const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; + orderFactory = new OrderFactory(privateKey, defaultOrderParams); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('fillOrKillOrder', () => { + const reentrancyTest = (functionNames: string[]) => { + _.forEach(functionNames, async (functionName: string, functionId: number) => { + const description = `should not allow fillOrKillOrder to reenter the Exchange contract via ${functionName}`; + it(description, async () => { + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), + }); + await web3Wrapper.awaitTransactionSuccessAsync( + await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await expectTransactionFailedAsync( + exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress), + RevertReason.TransferFailed, + ); + }); + }); + }; + describe('fillOrKillOrder reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); + + it('should transfer the correct amounts', async () => { + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), + }); + const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); + await exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + + const makerAssetFilledAmount = takerAssetFillAmount + .times(signedOrder.makerAssetAmount) + .dividedToIntegerBy(signedOrder.takerAssetAmount); + const makerFee = signedOrder.makerFee + .times(makerAssetFilledAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + const takerFee = signedOrder.takerFee + .times(makerAssetFilledAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), + ); + expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultTakerAssetAddress].plus(takerAssetFillAmount), + ); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address].minus(makerFee), + ); + expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFilledAmount), + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].minus(takerFee), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), + ); + }); + + it('should throw if a signedOrder is expired', async () => { + const currentTimestamp = await getLatestBlockTimestampAsync(); + const signedOrder = await orderFactory.newSignedOrderAsync({ + expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10), + }); + + return expectTransactionFailedAsync( + exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress), + RevertReason.OrderUnfillable, + ); + }); + + it('should throw if entire takerAssetFillAmount not filled', async () => { + const signedOrder = await orderFactory.newSignedOrderAsync(); + + await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { + takerAssetFillAmount: signedOrder.takerAssetAmount.div(2), + }); + + return expectTransactionFailedAsync( + exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress), + RevertReason.CompleteFillFailed, + ); + }); + }); + + describe('fillOrderNoThrow', () => { + const reentrancyTest = (functionNames: string[]) => { + _.forEach(functionNames, async (functionName: string, functionId: number) => { + const description = `should not allow fillOrderNoThrow to reenter the Exchange contract via ${functionName}`; + it(description, async () => { + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), + }); + await web3Wrapper.awaitTransactionSuccessAsync( + await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(erc20Balances).to.deep.equal(newBalances); + }); + }); + }; + describe('fillOrderNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); + + it('should transfer the correct amounts', async () => { + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), + }); + const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); + + await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress, { + takerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 250000, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFilledAmount = takerAssetFillAmount + .times(signedOrder.makerAssetAmount) + .dividedToIntegerBy(signedOrder.takerAssetAmount); + const makerFee = signedOrder.makerFee + .times(makerAssetFilledAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + const takerFee = signedOrder.takerFee + .times(makerAssetFilledAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + + expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), + ); + expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultTakerAssetAddress].plus(takerAssetFillAmount), + ); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address].minus(makerFee), + ); + expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFilledAmount), + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].minus(takerFee), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), + ); + }); + + it('should not change erc20Balances if maker erc20Balances are too low to fill order', async () => { + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18), + }); + + await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + + it('should not change erc20Balances if taker erc20Balances are too low to fill order', async () => { + const signedOrder = await orderFactory.newSignedOrderAsync({ + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18), + }); + + await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + + it('should not change erc20Balances if maker allowances are too low to fill order', async () => { + const signedOrder = await orderFactory.newSignedOrderAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), { + from: makerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, constants.INITIAL_ERC20_ALLOWANCE, { + from: makerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + + it('should not change erc20Balances if taker allowances are too low to fill order', async () => { + const signedOrder = await orderFactory.newSignedOrderAsync(); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), { + from: takerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, constants.INITIAL_ERC20_ALLOWANCE, { + from: takerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + + it('should not change erc20Balances if makerAssetAddress is ZRX, makerAssetAmount + makerFee > maker balance', async () => { + const makerZRXBalance = new BigNumber(erc20Balances[makerAddress][zrxToken.address]); + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: makerZRXBalance, + makerFee: new BigNumber(1), + makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), + }); + await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + + it('should not change erc20Balances if makerAssetAddress is ZRX, makerAssetAmount + makerFee > maker allowance', async () => { + const makerZRXAllowance = await zrxToken.allowance.callAsync(makerAddress, erc20Proxy.address); + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: new BigNumber(makerZRXAllowance), + makerFee: new BigNumber(1), + makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), + }); + await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + + it('should not change erc20Balances if takerAssetAddress is ZRX, takerAssetAmount + takerFee > taker balance', async () => { + const takerZRXBalance = new BigNumber(erc20Balances[takerAddress][zrxToken.address]); + const signedOrder = await orderFactory.newSignedOrderAsync({ + takerAssetAmount: takerZRXBalance, + takerFee: new BigNumber(1), + takerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), + }); + await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + + it('should not change erc20Balances if takerAssetAddress is ZRX, takerAssetAmount + takerFee > taker allowance', async () => { + const takerZRXAllowance = await zrxToken.allowance.callAsync(takerAddress, erc20Proxy.address); + const signedOrder = await orderFactory.newSignedOrderAsync({ + takerAssetAmount: new BigNumber(takerZRXAllowance), + takerFee: new BigNumber(1), + takerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), + }); + await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + + it('should successfully exchange ERC721 tokens', async () => { + // Construct Exchange parameters + const makerAssetId = erc721MakerAssetId; + const takerAssetId = erc721TakerAssetId; + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: new BigNumber(1), + takerAssetAmount: new BigNumber(1), + makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), + takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), + }); + // Verify pre-conditions + const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); + expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress); + const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); + expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); + // Call Exchange + const takerAssetFillAmount = signedOrder.takerAssetAmount; + await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress, { + takerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 280000, + }); + // Verify post-conditions + const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); + expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress); + const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); + expect(newOwnerTakerAsset).to.be.bignumber.equal(makerAddress); + }); + }); + + describe('batch functions', () => { + let signedOrders: SignedOrder[]; + beforeEach(async () => { + signedOrders = [ + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync(), + ]; + }); + + describe('batchFillOrders', () => { + const reentrancyTest = (functionNames: string[]) => { + _.forEach(functionNames, async (functionName: string, functionId: number) => { + const description = `should not allow batchFillOrders to reenter the Exchange contract via ${functionName}`; + it(description, async () => { + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), + }); + await web3Wrapper.awaitTransactionSuccessAsync( + await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await expectTransactionFailedAsync( + exchangeWrapper.batchFillOrdersAsync([signedOrder], takerAddress), + RevertReason.TransferFailed, + ); + }); + }); + }; + describe('batchFillOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); + + it('should transfer the correct amounts', async () => { + const takerAssetFillAmounts: BigNumber[] = []; + const makerAssetAddress = erc20TokenA.address; + const takerAssetAddress = erc20TokenB.address; + _.forEach(signedOrders, signedOrder => { + const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); + const makerAssetFilledAmount = takerAssetFillAmount + .times(signedOrder.makerAssetAmount) + .dividedToIntegerBy(signedOrder.takerAssetAmount); + const makerFee = signedOrder.makerFee + .times(makerAssetFilledAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + const takerFee = signedOrder.takerFee + .times(makerAssetFilledAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + takerAssetFillAmounts.push(takerAssetFillAmount); + erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ + makerAssetAddress + ].minus(makerAssetFilledAmount); + erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][ + takerAssetAddress + ].plus(takerAssetFillAmount); + erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( + makerFee, + ); + erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][ + makerAssetAddress + ].plus(makerAssetFilledAmount); + erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][ + takerAssetAddress + ].minus(takerAssetFillAmount); + erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( + takerFee, + ); + erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ + zrxToken.address + ].plus(makerFee.plus(takerFee)); + }); + + await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { + takerAssetFillAmounts, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + }); + + describe('batchFillOrKillOrders', () => { + const reentrancyTest = (functionNames: string[]) => { + _.forEach(functionNames, async (functionName: string, functionId: number) => { + const description = `should not allow batchFillOrKillOrders to reenter the Exchange contract via ${functionName}`; + it(description, async () => { + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), + }); + await web3Wrapper.awaitTransactionSuccessAsync( + await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await expectTransactionFailedAsync( + exchangeWrapper.batchFillOrKillOrdersAsync([signedOrder], takerAddress), + RevertReason.TransferFailed, + ); + }); + }); + }; + describe('batchFillOrKillOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); + + it('should transfer the correct amounts', async () => { + const takerAssetFillAmounts: BigNumber[] = []; + const makerAssetAddress = erc20TokenA.address; + const takerAssetAddress = erc20TokenB.address; + _.forEach(signedOrders, signedOrder => { + const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); + const makerAssetFilledAmount = takerAssetFillAmount + .times(signedOrder.makerAssetAmount) + .dividedToIntegerBy(signedOrder.takerAssetAmount); + const makerFee = signedOrder.makerFee + .times(makerAssetFilledAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + const takerFee = signedOrder.takerFee + .times(makerAssetFilledAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + takerAssetFillAmounts.push(takerAssetFillAmount); + erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ + makerAssetAddress + ].minus(makerAssetFilledAmount); + erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][ + takerAssetAddress + ].plus(takerAssetFillAmount); + erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( + makerFee, + ); + erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][ + makerAssetAddress + ].plus(makerAssetFilledAmount); + erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][ + takerAssetAddress + ].minus(takerAssetFillAmount); + erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( + takerFee, + ); + erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ + zrxToken.address + ].plus(makerFee.plus(takerFee)); + }); + + await exchangeWrapper.batchFillOrKillOrdersAsync(signedOrders, takerAddress, { + takerAssetFillAmounts, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + + it('should throw if a single signedOrder does not fill the expected amount', async () => { + const takerAssetFillAmounts: BigNumber[] = []; + _.forEach(signedOrders, signedOrder => { + const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); + takerAssetFillAmounts.push(takerAssetFillAmount); + }); + + await exchangeWrapper.fillOrKillOrderAsync(signedOrders[0], takerAddress); + + return expectTransactionFailedAsync( + exchangeWrapper.batchFillOrKillOrdersAsync(signedOrders, takerAddress, { + takerAssetFillAmounts, + }), + RevertReason.OrderUnfillable, + ); + }); + }); + + describe('batchFillOrdersNoThrow', async () => { + const reentrancyTest = (functionNames: string[]) => { + _.forEach(functionNames, async (functionName: string, functionId: number) => { + const description = `should not allow batchFillOrdersNoThrow to reenter the Exchange contract via ${functionName}`; + it(description, async () => { + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), + }); + await web3Wrapper.awaitTransactionSuccessAsync( + await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await exchangeWrapper.batchFillOrdersNoThrowAsync([signedOrder], takerAddress); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(erc20Balances).to.deep.equal(newBalances); + }); + }); + }; + describe('batchFillOrdersNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); + + it('should transfer the correct amounts', async () => { + const takerAssetFillAmounts: BigNumber[] = []; + const makerAssetAddress = erc20TokenA.address; + const takerAssetAddress = erc20TokenB.address; + _.forEach(signedOrders, signedOrder => { + const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); + const makerAssetFilledAmount = takerAssetFillAmount + .times(signedOrder.makerAssetAmount) + .dividedToIntegerBy(signedOrder.takerAssetAmount); + const makerFee = signedOrder.makerFee + .times(makerAssetFilledAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + const takerFee = signedOrder.takerFee + .times(makerAssetFilledAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + takerAssetFillAmounts.push(takerAssetFillAmount); + erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ + makerAssetAddress + ].minus(makerAssetFilledAmount); + erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][ + takerAssetAddress + ].plus(takerAssetFillAmount); + erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( + makerFee, + ); + erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][ + makerAssetAddress + ].plus(makerAssetFilledAmount); + erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][ + takerAssetAddress + ].minus(takerAssetFillAmount); + erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( + takerFee, + ); + erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ + zrxToken.address + ].plus(makerFee.plus(takerFee)); + }); + + await exchangeWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress, { + takerAssetFillAmounts, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + + it('should not throw if an order is invalid and fill the remaining orders', async () => { + const takerAssetFillAmounts: BigNumber[] = []; + const makerAssetAddress = erc20TokenA.address; + const takerAssetAddress = erc20TokenB.address; + + const invalidOrder = { + ...signedOrders[0], + signature: '0x00', + }; + const validOrders = signedOrders.slice(1); + + takerAssetFillAmounts.push(invalidOrder.takerAssetAmount.div(2)); + _.forEach(validOrders, signedOrder => { + const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); + const makerAssetFilledAmount = takerAssetFillAmount + .times(signedOrder.makerAssetAmount) + .dividedToIntegerBy(signedOrder.takerAssetAmount); + const makerFee = signedOrder.makerFee + .times(makerAssetFilledAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + const takerFee = signedOrder.takerFee + .times(makerAssetFilledAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount); + takerAssetFillAmounts.push(takerAssetFillAmount); + erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ + makerAssetAddress + ].minus(makerAssetFilledAmount); + erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][ + takerAssetAddress + ].plus(takerAssetFillAmount); + erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( + makerFee, + ); + erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][ + makerAssetAddress + ].plus(makerAssetFilledAmount); + erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][ + takerAssetAddress + ].minus(takerAssetFillAmount); + erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( + takerFee, + ); + erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ + zrxToken.address + ].plus(makerFee.plus(takerFee)); + }); + + const newOrders = [invalidOrder, ...validOrders]; + await exchangeWrapper.batchFillOrdersNoThrowAsync(newOrders, takerAddress, { + takerAssetFillAmounts, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 450000, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + }); + + describe('marketSellOrders', () => { + const reentrancyTest = (functionNames: string[]) => { + _.forEach(functionNames, async (functionName: string, functionId: number) => { + const description = `should not allow marketSellOrders to reenter the Exchange contract via ${functionName}`; + it(description, async () => { + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), + }); + await web3Wrapper.awaitTransactionSuccessAsync( + await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await expectTransactionFailedAsync( + exchangeWrapper.marketSellOrdersAsync([signedOrder], takerAddress, { + takerAssetFillAmount: signedOrder.takerAssetAmount, + }), + RevertReason.TransferFailed, + ); + }); + }); + }; + describe('marketSellOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); + + it('should stop when the entire takerAssetFillAmount is filled', async () => { + const takerAssetFillAmount = signedOrders[0].takerAssetAmount.plus( + signedOrders[1].takerAssetAmount.div(2), + ); + await exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, { + takerAssetFillAmount, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + + const makerAssetFilledAmount = signedOrders[0].makerAssetAmount.plus( + signedOrders[1].makerAssetAmount.dividedToIntegerBy(2), + ); + const makerFee = signedOrders[0].makerFee.plus(signedOrders[1].makerFee.dividedToIntegerBy(2)); + const takerFee = signedOrders[0].takerFee.plus(signedOrders[1].takerFee.dividedToIntegerBy(2)); + expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), + ); + expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultTakerAssetAddress].plus(takerAssetFillAmount), + ); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address].minus(makerFee), + ); + expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFilledAmount), + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].minus(takerFee), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), + ); + }); + + it('should fill all signedOrders if cannot fill entire takerAssetFillAmount', async () => { + const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); + _.forEach(signedOrders, signedOrder => { + erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ + defaultMakerAssetAddress + ].minus(signedOrder.makerAssetAmount); + erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ + defaultTakerAssetAddress + ].plus(signedOrder.takerAssetAmount); + erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( + signedOrder.makerFee, + ); + erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ + defaultMakerAssetAddress + ].plus(signedOrder.makerAssetAmount); + erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ + defaultTakerAssetAddress + ].minus(signedOrder.takerAssetAmount); + erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( + signedOrder.takerFee, + ); + erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ + zrxToken.address + ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); + }); + await exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, { + takerAssetFillAmount, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + + it('should throw when a signedOrder does not use the same takerAssetAddress', async () => { + signedOrders = [ + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync({ + takerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), + }), + await orderFactory.newSignedOrderAsync(), + ]; + + return expectTransactionFailedAsync( + exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, { + takerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18), + }), + // We simply use the takerAssetData from the first order for all orders. + // If they are not the same, the contract throws when validating the order signature + RevertReason.InvalidOrderSignature, + ); + }); + }); + + describe('marketSellOrdersNoThrow', () => { + const reentrancyTest = (functionNames: string[]) => { + _.forEach(functionNames, async (functionName: string, functionId: number) => { + const description = `should not allow marketSellOrdersNoThrow to reenter the Exchange contract via ${functionName}`; + it(description, async () => { + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), + }); + await web3Wrapper.awaitTransactionSuccessAsync( + await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await exchangeWrapper.marketSellOrdersNoThrowAsync([signedOrder], takerAddress, { + takerAssetFillAmount: signedOrder.takerAssetAmount, + }); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(erc20Balances).to.deep.equal(newBalances); + }); + }); + }; + describe('marketSellOrdersNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); + + it('should stop when the entire takerAssetFillAmount is filled', async () => { + const takerAssetFillAmount = signedOrders[0].takerAssetAmount.plus( + signedOrders[1].takerAssetAmount.div(2), + ); + await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, { + takerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 6000000, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + + const makerAssetFilledAmount = signedOrders[0].makerAssetAmount.plus( + signedOrders[1].makerAssetAmount.dividedToIntegerBy(2), + ); + const makerFee = signedOrders[0].makerFee.plus(signedOrders[1].makerFee.dividedToIntegerBy(2)); + const takerFee = signedOrders[0].takerFee.plus(signedOrders[1].takerFee.dividedToIntegerBy(2)); + expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), + ); + expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultTakerAssetAddress].plus(takerAssetFillAmount), + ); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address].minus(makerFee), + ); + expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFilledAmount), + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].minus(takerFee), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), + ); + }); + + it('should fill all signedOrders if cannot fill entire takerAssetFillAmount', async () => { + const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); + _.forEach(signedOrders, signedOrder => { + erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ + defaultMakerAssetAddress + ].minus(signedOrder.makerAssetAmount); + erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ + defaultTakerAssetAddress + ].plus(signedOrder.takerAssetAmount); + erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( + signedOrder.makerFee, + ); + erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ + defaultMakerAssetAddress + ].plus(signedOrder.makerAssetAmount); + erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ + defaultTakerAssetAddress + ].minus(signedOrder.takerAssetAmount); + erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( + signedOrder.takerFee, + ); + erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ + zrxToken.address + ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); + }); + await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, { + takerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + + it('should not fill a signedOrder that does not use the same takerAssetAddress', async () => { + signedOrders = [ + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync({ + takerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), + }), + ]; + const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); + const filledSignedOrders = signedOrders.slice(0, -1); + _.forEach(filledSignedOrders, signedOrder => { + erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ + defaultMakerAssetAddress + ].minus(signedOrder.makerAssetAmount); + erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ + defaultTakerAssetAddress + ].plus(signedOrder.takerAssetAmount); + erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( + signedOrder.makerFee, + ); + erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ + defaultMakerAssetAddress + ].plus(signedOrder.makerAssetAmount); + erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ + defaultTakerAssetAddress + ].minus(signedOrder.takerAssetAmount); + erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( + signedOrder.takerFee, + ); + erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ + zrxToken.address + ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); + }); + await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, { + takerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + }); + + describe('marketBuyOrders', () => { + const reentrancyTest = (functionNames: string[]) => { + _.forEach(functionNames, async (functionName: string, functionId: number) => { + const description = `should not allow marketBuyOrders to reenter the Exchange contract via ${functionName}`; + it(description, async () => { + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), + }); + await web3Wrapper.awaitTransactionSuccessAsync( + await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await expectTransactionFailedAsync( + exchangeWrapper.marketBuyOrdersAsync([signedOrder], takerAddress, { + makerAssetFillAmount: signedOrder.makerAssetAmount, + }), + RevertReason.TransferFailed, + ); + }); + }); + }; + describe('marketBuyOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); + + it('should stop when the entire makerAssetFillAmount is filled', async () => { + const makerAssetFillAmount = signedOrders[0].makerAssetAmount.plus( + signedOrders[1].makerAssetAmount.div(2), + ); + await exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, { + makerAssetFillAmount, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + + const makerAmountBought = signedOrders[0].takerAssetAmount.plus( + signedOrders[1].takerAssetAmount.dividedToIntegerBy(2), + ); + const makerFee = signedOrders[0].makerFee.plus(signedOrders[1].makerFee.dividedToIntegerBy(2)); + const takerFee = signedOrders[0].takerFee.plus(signedOrders[1].takerFee.dividedToIntegerBy(2)); + expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultTakerAssetAddress].plus(makerAmountBought), + ); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address].minus(makerFee), + ); + expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultTakerAssetAddress].minus(makerAmountBought), + ); + expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].minus(takerFee), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), + ); + }); + + it('should fill all signedOrders if cannot fill entire makerAssetFillAmount', async () => { + const makerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); + _.forEach(signedOrders, signedOrder => { + erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ + defaultMakerAssetAddress + ].minus(signedOrder.makerAssetAmount); + erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ + defaultTakerAssetAddress + ].plus(signedOrder.takerAssetAmount); + erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( + signedOrder.makerFee, + ); + erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ + defaultMakerAssetAddress + ].plus(signedOrder.makerAssetAmount); + erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ + defaultTakerAssetAddress + ].minus(signedOrder.takerAssetAmount); + erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( + signedOrder.takerFee, + ); + erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ + zrxToken.address + ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); + }); + await exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, { + makerAssetFillAmount, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + + it('should throw when a signedOrder does not use the same makerAssetAddress', async () => { + signedOrders = [ + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), + }), + await orderFactory.newSignedOrderAsync(), + ]; + + return expectTransactionFailedAsync( + exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, { + makerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18), + }), + RevertReason.InvalidOrderSignature, + ); + }); + }); + + describe('marketBuyOrdersNoThrow', () => { + const reentrancyTest = (functionNames: string[]) => { + _.forEach(functionNames, async (functionName: string, functionId: number) => { + const description = `should not allow marketBuyOrdersNoThrow to reenter the Exchange contract via ${functionName}`; + it(description, async () => { + const signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), + }); + await web3Wrapper.awaitTransactionSuccessAsync( + await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await exchangeWrapper.marketBuyOrdersNoThrowAsync([signedOrder], takerAddress, { + makerAssetFillAmount: signedOrder.makerAssetAmount, + }); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(erc20Balances).to.deep.equal(newBalances); + }); + }); + }; + describe('marketBuyOrdersNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); + + it('should stop when the entire makerAssetFillAmount is filled', async () => { + const makerAssetFillAmount = signedOrders[0].makerAssetAmount.plus( + signedOrders[1].makerAssetAmount.div(2), + ); + await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, { + makerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + + const makerAmountBought = signedOrders[0].takerAssetAmount.plus( + signedOrders[1].takerAssetAmount.dividedToIntegerBy(2), + ); + const makerFee = signedOrders[0].makerFee.plus(signedOrders[1].makerFee.dividedToIntegerBy(2)); + const takerFee = signedOrders[0].takerFee.plus(signedOrders[1].takerFee.dividedToIntegerBy(2)); + expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[makerAddress][defaultTakerAssetAddress].plus(makerAmountBought), + ); + expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[makerAddress][zrxToken.address].minus(makerFee), + ); + expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultTakerAssetAddress].minus(makerAmountBought), + ); + expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), + ); + expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[takerAddress][zrxToken.address].minus(takerFee), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), + ); + }); + + it('should fill all signedOrders if cannot fill entire makerAssetFillAmount', async () => { + const makerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); + _.forEach(signedOrders, signedOrder => { + erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ + defaultMakerAssetAddress + ].minus(signedOrder.makerAssetAmount); + erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ + defaultTakerAssetAddress + ].plus(signedOrder.takerAssetAmount); + erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( + signedOrder.makerFee, + ); + erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ + defaultMakerAssetAddress + ].plus(signedOrder.makerAssetAmount); + erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ + defaultTakerAssetAddress + ].minus(signedOrder.takerAssetAmount); + erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( + signedOrder.takerFee, + ); + erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ + zrxToken.address + ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); + }); + await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, { + makerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + + it('should not fill a signedOrder that does not use the same makerAssetAddress', async () => { + signedOrders = [ + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), + }), + ]; + + const makerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); + const filledSignedOrders = signedOrders.slice(0, -1); + _.forEach(filledSignedOrders, signedOrder => { + erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ + defaultMakerAssetAddress + ].minus(signedOrder.makerAssetAmount); + erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ + defaultTakerAssetAddress + ].plus(signedOrder.takerAssetAmount); + erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( + signedOrder.makerFee, + ); + erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ + defaultMakerAssetAddress + ].plus(signedOrder.makerAssetAmount); + erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ + defaultTakerAssetAddress + ].minus(signedOrder.takerAssetAmount); + erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( + signedOrder.takerFee, + ); + erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ + zrxToken.address + ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); + }); + await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, { + makerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); + }); + }); + + describe('batchCancelOrders', () => { + it('should be able to cancel multiple signedOrders', async () => { + const takerAssetCancelAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount); + await exchangeWrapper.batchCancelOrdersAsync(signedOrders, makerAddress); + + await exchangeWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress, { + takerAssetFillAmounts: takerAssetCancelAmounts, + }); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(erc20Balances).to.be.deep.equal(newBalances); + }); + }); + + describe('getOrdersInfo', () => { + beforeEach(async () => { + signedOrders = [ + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync(), + ]; + }); + it('should get the correct information for multiple unfilled orders', async () => { + const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); + expect(ordersInfo.length).to.be.equal(3); + _.forEach(signedOrders, (signedOrder, index) => { + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = new BigNumber(0); + const expectedOrderStatus = OrderStatus.Fillable; + const orderInfo = ordersInfo[index]; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + }); + it('should get the correct information for multiple partially filled orders', async () => { + const takerAssetFillAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount.div(2)); + await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmounts }); + const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); + expect(ordersInfo.length).to.be.equal(3); + _.forEach(signedOrders, (signedOrder, index) => { + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount.div(2); + const expectedOrderStatus = OrderStatus.Fillable; + const orderInfo = ordersInfo[index]; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + }); + it('should get the correct information for multiple fully filled orders', async () => { + await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress); + const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); + expect(ordersInfo.length).to.be.equal(3); + _.forEach(signedOrders, (signedOrder, index) => { + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount; + const expectedOrderStatus = OrderStatus.FullyFilled; + const orderInfo = ordersInfo[index]; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + }); + it('should get the correct information for multiple cancelled and unfilled orders', async () => { + await exchangeWrapper.batchCancelOrdersAsync(signedOrders, makerAddress); + const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); + expect(ordersInfo.length).to.be.equal(3); + _.forEach(signedOrders, (signedOrder, index) => { + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = new BigNumber(0); + const expectedOrderStatus = OrderStatus.Cancelled; + const orderInfo = ordersInfo[index]; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + }); + it('should get the correct information for multiple cancelled and partially filled orders', async () => { + const takerAssetFillAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount.div(2)); + await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmounts }); + await exchangeWrapper.batchCancelOrdersAsync(signedOrders, makerAddress); + const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); + expect(ordersInfo.length).to.be.equal(3); + _.forEach(signedOrders, (signedOrder, index) => { + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount.div(2); + const expectedOrderStatus = OrderStatus.Cancelled; + const orderInfo = ordersInfo[index]; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + }); + it('should get the correct information for multiple expired and unfilled orders', async () => { + const currentTimestamp = await getLatestBlockTimestampAsync(); + const timeUntilExpiration = signedOrders[0].expirationTimeSeconds.minus(currentTimestamp).toNumber(); + await increaseTimeAndMineBlockAsync(timeUntilExpiration); + const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); + expect(ordersInfo.length).to.be.equal(3); + _.forEach(signedOrders, (signedOrder, index) => { + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = new BigNumber(0); + const expectedOrderStatus = OrderStatus.Expired; + const orderInfo = ordersInfo[index]; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + }); + it('should get the correct information for multiple expired and partially filled orders', async () => { + const takerAssetFillAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount.div(2)); + await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmounts }); + const currentTimestamp = await getLatestBlockTimestampAsync(); + const timeUntilExpiration = signedOrders[0].expirationTimeSeconds.minus(currentTimestamp).toNumber(); + await increaseTimeAndMineBlockAsync(timeUntilExpiration); + const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); + expect(ordersInfo.length).to.be.equal(3); + _.forEach(signedOrders, (signedOrder, index) => { + const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); + const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount.div(2); + const expectedOrderStatus = OrderStatus.Expired; + const orderInfo = ordersInfo[index]; + expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); + expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); + expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); + }); + }); + it('should get the correct information for a mix of unfilled, partially filled, fully filled, cancelled, and expired orders', async () => { + const unfilledOrder = await orderFactory.newSignedOrderAsync(); + const partiallyFilledOrder = await orderFactory.newSignedOrderAsync(); + await exchangeWrapper.fillOrderAsync(partiallyFilledOrder, takerAddress, { + takerAssetFillAmount: partiallyFilledOrder.takerAssetAmount.div(2), + }); + const fullyFilledOrder = await orderFactory.newSignedOrderAsync(); + await exchangeWrapper.fillOrderAsync(fullyFilledOrder, takerAddress); + const cancelledOrder = await orderFactory.newSignedOrderAsync(); + await exchangeWrapper.cancelOrderAsync(cancelledOrder, makerAddress); + const currentTimestamp = await getLatestBlockTimestampAsync(); + const expiredOrder = await orderFactory.newSignedOrderAsync({ + expirationTimeSeconds: new BigNumber(currentTimestamp), + }); + signedOrders = [unfilledOrder, partiallyFilledOrder, fullyFilledOrder, cancelledOrder, expiredOrder]; + const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); + expect(ordersInfo.length).to.be.equal(5); + + const expectedUnfilledOrderHash = orderHashUtils.getOrderHashHex(unfilledOrder); + const expectedUnfilledTakerAssetFilledAmount = new BigNumber(0); + const expectedUnfilledOrderStatus = OrderStatus.Fillable; + const unfilledOrderInfo = ordersInfo[0]; + expect(unfilledOrderInfo.orderHash).to.be.equal(expectedUnfilledOrderHash); + expect(unfilledOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal( + expectedUnfilledTakerAssetFilledAmount, + ); + expect(unfilledOrderInfo.orderStatus).to.be.equal(expectedUnfilledOrderStatus); + + const expectedPartialOrderHash = orderHashUtils.getOrderHashHex(partiallyFilledOrder); + const expectedPartialTakerAssetFilledAmount = partiallyFilledOrder.takerAssetAmount.div(2); + const expectedPartialOrderStatus = OrderStatus.Fillable; + const partialOrderInfo = ordersInfo[1]; + expect(partialOrderInfo.orderHash).to.be.equal(expectedPartialOrderHash); + expect(partialOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal( + expectedPartialTakerAssetFilledAmount, + ); + expect(partialOrderInfo.orderStatus).to.be.equal(expectedPartialOrderStatus); + + const expectedFilledOrderHash = orderHashUtils.getOrderHashHex(fullyFilledOrder); + const expectedFilledTakerAssetFilledAmount = fullyFilledOrder.takerAssetAmount; + const expectedFilledOrderStatus = OrderStatus.FullyFilled; + const filledOrderInfo = ordersInfo[2]; + expect(filledOrderInfo.orderHash).to.be.equal(expectedFilledOrderHash); + expect(filledOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal( + expectedFilledTakerAssetFilledAmount, + ); + expect(filledOrderInfo.orderStatus).to.be.equal(expectedFilledOrderStatus); + + const expectedCancelledOrderHash = orderHashUtils.getOrderHashHex(cancelledOrder); + const expectedCancelledTakerAssetFilledAmount = new BigNumber(0); + const expectedCancelledOrderStatus = OrderStatus.Cancelled; + const cancelledOrderInfo = ordersInfo[3]; + expect(cancelledOrderInfo.orderHash).to.be.equal(expectedCancelledOrderHash); + expect(cancelledOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal( + expectedCancelledTakerAssetFilledAmount, + ); + expect(cancelledOrderInfo.orderStatus).to.be.equal(expectedCancelledOrderStatus); + + const expectedExpiredOrderHash = orderHashUtils.getOrderHashHex(expiredOrder); + const expectedExpiredTakerAssetFilledAmount = new BigNumber(0); + const expectedExpiredOrderStatus = OrderStatus.Expired; + const expiredOrderInfo = ordersInfo[4]; + expect(expiredOrderInfo.orderHash).to.be.equal(expectedExpiredOrderHash); + expect(expiredOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal( + expectedExpiredTakerAssetFilledAmount, + ); + expect(expiredOrderInfo.orderStatus).to.be.equal(expectedExpiredOrderStatus); + }); + }); + }); +}); // tslint:disable-line:max-file-line-count diff --git a/contracts/exchange/tsconfig.json b/contracts/exchange/tsconfig.json new file mode 100644 index 000000000..bd3ecbf1e --- /dev/null +++ b/contracts/exchange/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "lib", + "rootDir": ".", + "resolveJsonModule": true + }, + "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], + "files": [ + "./generated-artifacts/Exchange.json", + "./generated-artifacts/TestAssetProxyDispatcher.json", + "./generated-artifacts/TestExchangeInternals.json", + "./generated-artifacts/TestSignatureValidator.json", + "./generated-artifacts/TestStaticCallReceiver.json" + ], + "exclude": ["./deploy/solc/solc_bin"] +} diff --git a/contracts/exchange/tslint.json b/contracts/exchange/tslint.json new file mode 100644 index 000000000..1bb3ac2a2 --- /dev/null +++ b/contracts/exchange/tslint.json @@ -0,0 +1,6 @@ +{ + "extends": ["@0x/tslint-config"], + "rules": { + "custom-no-magic-numbers": false + } +} diff --git a/contracts/multisig/compiler.json b/contracts/multisig/compiler.json index 5a1f689e2..772665775 100644 --- a/contracts/multisig/compiler.json +++ b/contracts/multisig/compiler.json @@ -18,5 +18,11 @@ } } }, - "contracts": ["MultiSigWallet", "MultiSigWalletWithTimeLock", "TestRejectEther"] + "contracts": [ + "AssetProxyOwner", + "MultiSigWallet", + "MultiSigWalletWithTimeLock", + "TestAssetProxyOwner", + "TestRejectEther" + ] } diff --git a/contracts/multisig/contracts/multisig/AssetProxyOwner.sol b/contracts/multisig/contracts/multisig/AssetProxyOwner.sol new file mode 100644 index 000000000..e72918bf9 --- /dev/null +++ b/contracts/multisig/contracts/multisig/AssetProxyOwner.sol @@ -0,0 +1,108 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "@0x/contracts-multisig/contracts/multisig/MultiSigWalletWithTimeLock.sol"; +import "@0x/contracts-utils/contracts/utils/LibBytes.sol"; + + +contract AssetProxyOwner is + MultiSigWalletWithTimeLock +{ + using LibBytes for bytes; + + event AssetProxyRegistration(address assetProxyContract, bool isRegistered); + + // Mapping of AssetProxy contract address => + // if this contract is allowed to call the AssetProxy's `removeAuthorizedAddressAtIndex` method without a time lock. + mapping (address => bool) public isAssetProxyRegistered; + + bytes4 constant internal REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR = bytes4(keccak256("removeAuthorizedAddressAtIndex(address,uint256)")); + + /// @dev Function will revert if the transaction does not call `removeAuthorizedAddressAtIndex` + /// on an approved AssetProxy contract. + modifier validRemoveAuthorizedAddressAtIndexTx(uint256 transactionId) { + Transaction storage txn = transactions[transactionId]; + require( + isAssetProxyRegistered[txn.destination], + "UNREGISTERED_ASSET_PROXY" + ); + require( + txn.data.readBytes4(0) == REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR, + "INVALID_FUNCTION_SELECTOR" + ); + _; + } + + /// @dev Contract constructor sets initial owners, required number of confirmations, + /// time lock, and list of AssetProxy addresses. + /// @param _owners List of initial owners. + /// @param _assetProxyContracts Array of AssetProxy contract addresses. + /// @param _required Number of required confirmations. + /// @param _secondsTimeLocked Duration needed after a transaction is confirmed and before it becomes executable, in seconds. + constructor ( + address[] memory _owners, + address[] memory _assetProxyContracts, + uint256 _required, + uint256 _secondsTimeLocked + ) + public + MultiSigWalletWithTimeLock(_owners, _required, _secondsTimeLocked) + { + for (uint256 i = 0; i < _assetProxyContracts.length; i++) { + address assetProxy = _assetProxyContracts[i]; + require( + assetProxy != address(0), + "INVALID_ASSET_PROXY" + ); + isAssetProxyRegistered[assetProxy] = true; + } + } + + /// @dev Registers or deregisters an AssetProxy to be able to execute + /// `removeAuthorizedAddressAtIndex` without a timelock. + /// @param assetProxyContract Address of AssetProxy contract. + /// @param isRegistered Status of approval for AssetProxy contract. + function registerAssetProxy(address assetProxyContract, bool isRegistered) + public + onlyWallet + notNull(assetProxyContract) + { + isAssetProxyRegistered[assetProxyContract] = isRegistered; + emit AssetProxyRegistration(assetProxyContract, isRegistered); + } + + /// @dev Allows execution of `removeAuthorizedAddressAtIndex` without time lock. + /// @param transactionId Transaction ID. + function executeRemoveAuthorizedAddressAtIndex(uint256 transactionId) + public + notExecuted(transactionId) + fullyConfirmed(transactionId) + validRemoveAuthorizedAddressAtIndexTx(transactionId) + { + Transaction storage txn = transactions[transactionId]; + txn.executed = true; + if (external_call(txn.destination, txn.value, txn.data.length, txn.data)) { + emit Execution(transactionId); + } else { + emit ExecutionFailure(transactionId); + txn.executed = false; + } + } +} diff --git a/contracts/multisig/contracts/test/TestAssetProxyOwner.sol b/contracts/multisig/contracts/test/TestAssetProxyOwner.sol new file mode 100644 index 000000000..eb4c6d908 --- /dev/null +++ b/contracts/multisig/contracts/test/TestAssetProxyOwner.sol @@ -0,0 +1,58 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +import "../multisig/AssetProxyOwner.sol"; + + +// solhint-disable no-empty-blocks +contract TestAssetProxyOwner is + AssetProxyOwner +{ + constructor ( + address[] memory _owners, + address[] memory _assetProxyContracts, + uint256 _required, + uint256 _secondsTimeLocked + ) + public + AssetProxyOwner(_owners, _assetProxyContracts, _required, _secondsTimeLocked) + {} + + function testValidRemoveAuthorizedAddressAtIndexTx(uint256 id) + public + view + validRemoveAuthorizedAddressAtIndexTx(id) + returns (bool) + { + // Do nothing. We expect reverts through the modifier + return true; + } + + /// @dev Compares first 4 bytes of byte array to `removeAuthorizedAddressAtIndex` function selector. + /// @param data Transaction data. + /// @return Successful if data is a call to `removeAuthorizedAddressAtIndex`. + function isFunctionRemoveAuthorizedAddressAtIndex(bytes memory data) + public + pure + returns (bool) + { + return data.readBytes4(0) == REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR; + } +} diff --git a/contracts/multisig/contracts/test/TestRejectEther.sol b/contracts/multisig/contracts/test/TestRejectEther.sol new file mode 100644 index 000000000..e523f591d --- /dev/null +++ b/contracts/multisig/contracts/test/TestRejectEther.sol @@ -0,0 +1,23 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + + +// solhint-disable no-empty-blocks +contract TestRejectEther {} diff --git a/contracts/multisig/contracts/test/TestRejectEther/TestRejectEther.sol b/contracts/multisig/contracts/test/TestRejectEther/TestRejectEther.sol deleted file mode 100644 index e523f591d..000000000 --- a/contracts/multisig/contracts/test/TestRejectEther/TestRejectEther.sol +++ /dev/null @@ -1,23 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; - - -// solhint-disable no-empty-blocks -contract TestRejectEther {} diff --git a/contracts/multisig/package.json b/contracts/multisig/package.json index 1c3911cbc..ec6bf5367 100644 --- a/contracts/multisig/package.json +++ b/contracts/multisig/package.json @@ -32,7 +32,7 @@ "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis": "generated-artifacts/@(MultiSigWallet|MultiSigWalletWithTimeLock|TestRejectEther).json" + "abis": "generated-artifacts/@(AssetProxyOwner|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetProxyOwner|TestRejectEther).json" }, "repository": { "type": "git", @@ -70,6 +70,8 @@ }, "dependencies": { "@0x/base-contract": "^3.0.13", + "@0x/contracts-asset-proxy": "^2.2.3", + "@0x/contracts-tokens": "^1.0.6", "@0x/order-utils": "^3.1.2", "@0x/types": "^1.5.2", "@0x/typescript-typings": "^3.0.8", diff --git a/contracts/multisig/src/artifacts/index.ts b/contracts/multisig/src/artifacts/index.ts index 7cf47be01..326d79000 100644 --- a/contracts/multisig/src/artifacts/index.ts +++ b/contracts/multisig/src/artifacts/index.ts @@ -1,11 +1,15 @@ import { ContractArtifact } from 'ethereum-types'; +import * as AssetProxyOwner from '../../generated-artifacts/AssetProxyOwner.json'; import * as MultiSigWallet from '../../generated-artifacts/MultiSigWallet.json'; import * as MultiSigWalletWithTimeLock from '../../generated-artifacts/MultiSigWalletWithTimeLock.json'; +import * as TestAssetProxyOwner from '../../generated-artifacts/TestAssetProxyOwner.json'; import * as TestRejectEther from '../../generated-artifacts/TestRejectEther.json'; export const artifacts = { - TestRejectEther: TestRejectEther as ContractArtifact, + AssetProxyOwner: AssetProxyOwner as ContractArtifact, MultiSigWallet: MultiSigWallet as ContractArtifact, MultiSigWalletWithTimeLock: MultiSigWalletWithTimeLock as ContractArtifact, + TestAssetProxyOwner: TestAssetProxyOwner as ContractArtifact, + TestRejectEther: TestRejectEther as ContractArtifact, }; diff --git a/contracts/multisig/test/asset_proxy_owner.ts b/contracts/multisig/test/asset_proxy_owner.ts new file mode 100644 index 000000000..c7b7d997b --- /dev/null +++ b/contracts/multisig/test/asset_proxy_owner.ts @@ -0,0 +1,507 @@ +import { artifacts as proxyArtifacts, MixinAuthorizableContract } from '@0x/contracts-asset-proxy'; +import { + chaiSetup, + constants, + expectContractCallFailedAsync, + expectContractCreationFailedAsync, + expectTransactionFailedAsync, + expectTransactionFailedWithoutReasonAsync, + increaseTimeAndMineBlockAsync, + provider, + sendTransactionResult, + txDefaults, + web3Wrapper, +} from '@0x/contracts-test-utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { RevertReason } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; +import { LogWithDecodedArgs } from 'ethereum-types'; + +import { + AssetProxyOwnerAssetProxyRegistrationEventArgs, + AssetProxyOwnerContract, + AssetProxyOwnerExecutionEventArgs, + AssetProxyOwnerExecutionFailureEventArgs, + AssetProxyOwnerSubmissionEventArgs, +} from '../generated-wrappers/asset_proxy_owner'; +import { TestAssetProxyOwnerContract } from '../generated-wrappers/test_asset_proxy_owner'; +import { artifacts } from '../src/artifacts'; + +import { AssetProxyOwnerWrapper } from './utils/asset_proxy_owner_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +// tslint:disable:no-unnecessary-type-assertion +describe('AssetProxyOwner', () => { + let owners: string[]; + let authorized: string; + let notOwner: string; + const REQUIRED_APPROVALS = new BigNumber(2); + const SECONDS_TIME_LOCKED = new BigNumber(1000000); + + let erc20Proxy: MixinAuthorizableContract; + let erc721Proxy: MixinAuthorizableContract; + let testAssetProxyOwner: TestAssetProxyOwnerContract; + let assetProxyOwnerWrapper: AssetProxyOwnerWrapper; + + before(async () => { + await blockchainLifecycle.startAsync(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + before(async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + owners = [accounts[0], accounts[1]]; + authorized = accounts[2]; + notOwner = accounts[3]; + const initialOwner = accounts[0]; + erc20Proxy = await MixinAuthorizableContract.deployFrom0xArtifactAsync( + proxyArtifacts.MixinAuthorizable, + provider, + txDefaults, + ); + erc721Proxy = await MixinAuthorizableContract.deployFrom0xArtifactAsync( + proxyArtifacts.MixinAuthorizable, + provider, + txDefaults, + ); + const defaultAssetProxyContractAddresses: string[] = []; + testAssetProxyOwner = await TestAssetProxyOwnerContract.deployFrom0xArtifactAsync( + artifacts.TestAssetProxyOwner, + provider, + txDefaults, + owners, + defaultAssetProxyContractAddresses, + REQUIRED_APPROVALS, + SECONDS_TIME_LOCKED, + ); + assetProxyOwnerWrapper = new AssetProxyOwnerWrapper(testAssetProxyOwner, provider); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20Proxy.transferOwnership.sendTransactionAsync(testAssetProxyOwner.address, { + from: initialOwner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721Proxy.transferOwnership.sendTransactionAsync(testAssetProxyOwner.address, { + from: initialOwner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + + describe('constructor', () => { + it('should register passed in assetProxyContracts', async () => { + const assetProxyContractAddresses = [erc20Proxy.address, erc721Proxy.address]; + const newMultiSig = await AssetProxyOwnerContract.deployFrom0xArtifactAsync( + artifacts.AssetProxyOwner, + provider, + txDefaults, + owners, + assetProxyContractAddresses, + REQUIRED_APPROVALS, + SECONDS_TIME_LOCKED, + ); + const isErc20ProxyRegistered = await newMultiSig.isAssetProxyRegistered.callAsync(erc20Proxy.address); + const isErc721ProxyRegistered = await newMultiSig.isAssetProxyRegistered.callAsync(erc721Proxy.address); + expect(isErc20ProxyRegistered).to.equal(true); + expect(isErc721ProxyRegistered).to.equal(true); + }); + it('should throw if a null address is included in assetProxyContracts', async () => { + const assetProxyContractAddresses = [erc20Proxy.address, constants.NULL_ADDRESS]; + return expectContractCreationFailedAsync( + (AssetProxyOwnerContract.deployFrom0xArtifactAsync( + artifacts.AssetProxyOwner, + provider, + txDefaults, + owners, + assetProxyContractAddresses, + REQUIRED_APPROVALS, + SECONDS_TIME_LOCKED, + ) as any) as sendTransactionResult, + RevertReason.InvalidAssetProxy, + ); + }); + }); + + describe('isFunctionRemoveAuthorizedAddressAtIndex', () => { + it('should return false if data is not for removeAuthorizedAddressAtIndex', async () => { + const notRemoveAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData( + owners[0], + ); + + const isFunctionRemoveAuthorizedAddressAtIndex = await testAssetProxyOwner.isFunctionRemoveAuthorizedAddressAtIndex.callAsync( + notRemoveAuthorizedAddressData, + ); + expect(isFunctionRemoveAuthorizedAddressAtIndex).to.be.false(); + }); + + it('should return true if data is for removeAuthorizedAddressAtIndex', async () => { + const index = new BigNumber(0); + const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( + owners[0], + index, + ); + const isFunctionRemoveAuthorizedAddressAtIndex = await testAssetProxyOwner.isFunctionRemoveAuthorizedAddressAtIndex.callAsync( + removeAuthorizedAddressAtIndexData, + ); + expect(isFunctionRemoveAuthorizedAddressAtIndex).to.be.true(); + }); + }); + + describe('registerAssetProxy', () => { + it('should throw if not called by multisig', async () => { + const isRegistered = true; + return expectTransactionFailedWithoutReasonAsync( + testAssetProxyOwner.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, isRegistered, { + from: owners[0], + }), + ); + }); + + it('should register an address if called by multisig after timelock', async () => { + const addressToRegister = erc20Proxy.address; + const isRegistered = true; + const registerAssetProxyData = testAssetProxyOwner.registerAssetProxy.getABIEncodedTransactionData( + addressToRegister, + isRegistered, + ); + const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync( + testAssetProxyOwner.address, + registerAssetProxyData, + owners[0], + ); + + const log = submitTxRes.logs[0] as LogWithDecodedArgs; + const txId = log.args.transactionId; + + await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); + await increaseTimeAndMineBlockAsync(SECONDS_TIME_LOCKED.toNumber()); + + const executeTxRes = await assetProxyOwnerWrapper.executeTransactionAsync(txId, owners[0]); + const registerLog = executeTxRes.logs[0] as LogWithDecodedArgs< + AssetProxyOwnerAssetProxyRegistrationEventArgs + >; + expect(registerLog.args.assetProxyContract).to.equal(addressToRegister); + expect(registerLog.args.isRegistered).to.equal(isRegistered); + + const isAssetProxyRegistered = await testAssetProxyOwner.isAssetProxyRegistered.callAsync( + addressToRegister, + ); + expect(isAssetProxyRegistered).to.equal(isRegistered); + }); + + it('should fail if registering a null address', async () => { + const addressToRegister = constants.NULL_ADDRESS; + const isRegistered = true; + const registerAssetProxyData = testAssetProxyOwner.registerAssetProxy.getABIEncodedTransactionData( + addressToRegister, + isRegistered, + ); + const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync( + testAssetProxyOwner.address, + registerAssetProxyData, + owners[0], + ); + const log = submitTxRes.logs[0] as LogWithDecodedArgs; + const txId = log.args.transactionId; + + await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); + await increaseTimeAndMineBlockAsync(SECONDS_TIME_LOCKED.toNumber()); + + const executeTxRes = await assetProxyOwnerWrapper.executeTransactionAsync(txId, owners[0]); + const failureLog = executeTxRes.logs[0] as LogWithDecodedArgs; + expect(failureLog.args.transactionId).to.be.bignumber.equal(txId); + + const isAssetProxyRegistered = await testAssetProxyOwner.isAssetProxyRegistered.callAsync( + addressToRegister, + ); + expect(isAssetProxyRegistered).to.equal(false); + }); + }); + + describe('Calling removeAuthorizedAddressAtIndex', () => { + const erc20Index = new BigNumber(0); + const erc721Index = new BigNumber(1); + before('authorize both proxies and register erc20 proxy', async () => { + // Only register ERC20 proxy + const addressToRegister = erc20Proxy.address; + const isRegistered = true; + const registerAssetProxyData = testAssetProxyOwner.registerAssetProxy.getABIEncodedTransactionData( + addressToRegister, + isRegistered, + ); + const registerAssetProxySubmitRes = await assetProxyOwnerWrapper.submitTransactionAsync( + testAssetProxyOwner.address, + registerAssetProxyData, + owners[0], + ); + const registerAssetProxySubmitLog = registerAssetProxySubmitRes.logs[0] as LogWithDecodedArgs< + AssetProxyOwnerSubmissionEventArgs + >; + + const addAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData(authorized); + const erc20AddAuthorizedAddressSubmitRes = await assetProxyOwnerWrapper.submitTransactionAsync( + erc20Proxy.address, + addAuthorizedAddressData, + owners[0], + ); + const erc721AddAuthorizedAddressSubmitRes = await assetProxyOwnerWrapper.submitTransactionAsync( + erc721Proxy.address, + addAuthorizedAddressData, + owners[0], + ); + const erc20AddAuthorizedAddressSubmitLog = erc20AddAuthorizedAddressSubmitRes.logs[0] as LogWithDecodedArgs< + AssetProxyOwnerSubmissionEventArgs + >; + const erc721AddAuthorizedAddressSubmitLog = erc721AddAuthorizedAddressSubmitRes + .logs[0] as LogWithDecodedArgs; + + const registerAssetProxyTxId = registerAssetProxySubmitLog.args.transactionId; + const erc20AddAuthorizedAddressTxId = erc20AddAuthorizedAddressSubmitLog.args.transactionId; + const erc721AddAuthorizedAddressTxId = erc721AddAuthorizedAddressSubmitLog.args.transactionId; + + await assetProxyOwnerWrapper.confirmTransactionAsync(registerAssetProxyTxId, owners[1]); + await assetProxyOwnerWrapper.confirmTransactionAsync(erc20AddAuthorizedAddressTxId, owners[1]); + await assetProxyOwnerWrapper.confirmTransactionAsync(erc721AddAuthorizedAddressTxId, owners[1]); + await increaseTimeAndMineBlockAsync(SECONDS_TIME_LOCKED.toNumber()); + await assetProxyOwnerWrapper.executeTransactionAsync(registerAssetProxyTxId, owners[0]); + await assetProxyOwnerWrapper.executeTransactionAsync(erc20AddAuthorizedAddressTxId, owners[0], { + gas: constants.MAX_EXECUTE_TRANSACTION_GAS, + }); + await assetProxyOwnerWrapper.executeTransactionAsync(erc721AddAuthorizedAddressTxId, owners[0], { + gas: constants.MAX_EXECUTE_TRANSACTION_GAS, + }); + }); + + describe('validRemoveAuthorizedAddressAtIndexTx', () => { + it('should revert if data is not for removeAuthorizedAddressAtIndex and proxy is registered', async () => { + const notRemoveAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData( + authorized, + ); + const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync( + erc20Proxy.address, + notRemoveAuthorizedAddressData, + owners[0], + ); + const log = submitTxRes.logs[0] as LogWithDecodedArgs; + const txId = log.args.transactionId; + return expectContractCallFailedAsync( + testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync(txId), + RevertReason.InvalidFunctionSelector, + ); + }); + + it('should return true if data is for removeAuthorizedAddressAtIndex and proxy is registered', async () => { + const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( + authorized, + erc20Index, + ); + const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync( + erc20Proxy.address, + removeAuthorizedAddressAtIndexData, + owners[0], + ); + const log = submitTxRes.logs[0] as LogWithDecodedArgs; + const txId = log.args.transactionId; + const isValidRemoveAuthorizedAddressAtIndexTx = await testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync( + txId, + ); + expect(isValidRemoveAuthorizedAddressAtIndexTx).to.be.true(); + }); + + it('should revert if data is for removeAuthorizedAddressAtIndex and proxy is not registered', async () => { + const removeAuthorizedAddressAtIndexData = erc721Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( + authorized, + erc721Index, + ); + const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync( + erc721Proxy.address, + removeAuthorizedAddressAtIndexData, + owners[0], + ); + const log = submitTxRes.logs[0] as LogWithDecodedArgs; + const txId = log.args.transactionId; + return expectContractCallFailedAsync( + testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync(txId), + RevertReason.UnregisteredAssetProxy, + ); + }); + }); + + describe('executeRemoveAuthorizedAddressAtIndex', () => { + it('should throw without the required confirmations', async () => { + const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( + authorized, + erc20Index, + ); + const res = await assetProxyOwnerWrapper.submitTransactionAsync( + erc20Proxy.address, + removeAuthorizedAddressAtIndexData, + owners[0], + ); + const log = res.logs[0] as LogWithDecodedArgs; + const txId = log.args.transactionId; + + return expectTransactionFailedAsync( + testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { + from: owners[1], + }), + RevertReason.TxNotFullyConfirmed, + ); + }); + + it('should throw if tx destination is not registered', async () => { + const removeAuthorizedAddressAtIndexData = erc721Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( + authorized, + erc721Index, + ); + const res = await assetProxyOwnerWrapper.submitTransactionAsync( + erc721Proxy.address, + removeAuthorizedAddressAtIndexData, + owners[0], + ); + const log = res.logs[0] as LogWithDecodedArgs; + const txId = log.args.transactionId; + + await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); + + return expectTransactionFailedAsync( + testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { + from: owners[1], + }), + RevertReason.UnregisteredAssetProxy, + ); + }); + + it('should throw if tx data is not for removeAuthorizedAddressAtIndex', async () => { + const newAuthorized = owners[1]; + const addAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData( + newAuthorized, + ); + const res = await assetProxyOwnerWrapper.submitTransactionAsync( + erc20Proxy.address, + addAuthorizedAddressData, + owners[0], + ); + const log = res.logs[0] as LogWithDecodedArgs; + const txId = log.args.transactionId; + + await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); + + return expectTransactionFailedAsync( + testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { + from: owners[1], + }), + RevertReason.InvalidFunctionSelector, + ); + }); + + it('should execute removeAuthorizedAddressAtIndex for registered address if fully confirmed and called by owner', async () => { + const isAuthorizedBefore = await erc20Proxy.authorized.callAsync(authorized); + expect(isAuthorizedBefore).to.equal(true); + + const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( + authorized, + erc20Index, + ); + const submitRes = await assetProxyOwnerWrapper.submitTransactionAsync( + erc20Proxy.address, + removeAuthorizedAddressAtIndexData, + owners[0], + ); + const submitLog = submitRes.logs[0] as LogWithDecodedArgs; + const txId = submitLog.args.transactionId; + + await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); + + const execRes = await assetProxyOwnerWrapper.executeRemoveAuthorizedAddressAtIndexAsync( + txId, + owners[0], + ); + const execLog = execRes.logs[1] as LogWithDecodedArgs; + expect(execLog.args.transactionId).to.be.bignumber.equal(txId); + + const tx = await testAssetProxyOwner.transactions.callAsync(txId); + const isExecuted = tx[3]; + expect(isExecuted).to.equal(true); + + const isAuthorizedAfter = await erc20Proxy.authorized.callAsync(authorized); + expect(isAuthorizedAfter).to.equal(false); + }); + + it('should execute removeAuthorizedAddressAtIndex for registered address if fully confirmed and called by non-owner', async () => { + const isAuthorizedBefore = await erc20Proxy.authorized.callAsync(authorized); + expect(isAuthorizedBefore).to.equal(true); + + const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( + authorized, + erc20Index, + ); + const submitRes = await assetProxyOwnerWrapper.submitTransactionAsync( + erc20Proxy.address, + removeAuthorizedAddressAtIndexData, + owners[0], + ); + const submitLog = submitRes.logs[0] as LogWithDecodedArgs; + const txId = submitLog.args.transactionId; + + await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); + + const execRes = await assetProxyOwnerWrapper.executeRemoveAuthorizedAddressAtIndexAsync(txId, notOwner); + const execLog = execRes.logs[1] as LogWithDecodedArgs; + expect(execLog.args.transactionId).to.be.bignumber.equal(txId); + + const tx = await testAssetProxyOwner.transactions.callAsync(txId); + const isExecuted = tx[3]; + expect(isExecuted).to.equal(true); + + const isAuthorizedAfter = await erc20Proxy.authorized.callAsync(authorized); + expect(isAuthorizedAfter).to.equal(false); + }); + + it('should throw if already executed', async () => { + const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( + authorized, + erc20Index, + ); + const submitRes = await assetProxyOwnerWrapper.submitTransactionAsync( + erc20Proxy.address, + removeAuthorizedAddressAtIndexData, + owners[0], + ); + const submitLog = submitRes.logs[0] as LogWithDecodedArgs; + const txId = submitLog.args.transactionId; + + await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); + + const execRes = await assetProxyOwnerWrapper.executeRemoveAuthorizedAddressAtIndexAsync( + txId, + owners[0], + ); + const execLog = execRes.logs[1] as LogWithDecodedArgs; + expect(execLog.args.transactionId).to.be.bignumber.equal(txId); + + const tx = await testAssetProxyOwner.transactions.callAsync(txId); + const isExecuted = tx[3]; + expect(isExecuted).to.equal(true); + + return expectTransactionFailedWithoutReasonAsync( + testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { + from: owners[1], + }), + ); + }); + }); + }); +}); +// tslint:disable-line max-file-line-count diff --git a/contracts/multisig/test/utils/asset_proxy_owner_wrapper.ts b/contracts/multisig/test/utils/asset_proxy_owner_wrapper.ts new file mode 100644 index 000000000..924a215db --- /dev/null +++ b/contracts/multisig/test/utils/asset_proxy_owner_wrapper.ts @@ -0,0 +1,71 @@ +import { artifacts as proxyArtifacts } from '@0x/contracts-asset-proxy'; +import { LogDecoder } from '@0x/contracts-test-utils'; +import { artifacts as tokensArtifacts } from '@0x/contracts-tokens'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { AssetProxyOwnerContract } from '../../generated-wrappers/asset_proxy_owner'; +import { artifacts } from '../../src/artifacts'; + +export class AssetProxyOwnerWrapper { + private readonly _assetProxyOwner: AssetProxyOwnerContract; + private readonly _web3Wrapper: Web3Wrapper; + private readonly _logDecoder: LogDecoder; + constructor(assetproxyOwnerContract: AssetProxyOwnerContract, provider: Provider) { + this._assetProxyOwner = assetproxyOwnerContract; + this._web3Wrapper = new Web3Wrapper(provider); + this._logDecoder = new LogDecoder(this._web3Wrapper, { ...artifacts, ...tokensArtifacts, ...proxyArtifacts }); + } + public async submitTransactionAsync( + destination: string, + data: string, + from: string, + opts: { value?: BigNumber } = {}, + ): Promise { + const value = _.isUndefined(opts.value) ? new BigNumber(0) : opts.value; + const txHash = await this._assetProxyOwner.submitTransaction.sendTransactionAsync(destination, value, data, { + from, + }); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async confirmTransactionAsync(txId: BigNumber, from: string): Promise { + const txHash = await this._assetProxyOwner.confirmTransaction.sendTransactionAsync(txId, { from }); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async revokeConfirmationAsync(txId: BigNumber, from: string): Promise { + const txHash = await this._assetProxyOwner.revokeConfirmation.sendTransactionAsync(txId, { from }); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async executeTransactionAsync( + txId: BigNumber, + from: string, + opts: { gas?: number } = {}, + ): Promise { + const txHash = await this._assetProxyOwner.executeTransaction.sendTransactionAsync(txId, { + from, + gas: opts.gas, + }); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } + public async executeRemoveAuthorizedAddressAtIndexAsync( + txId: BigNumber, + from: string, + ): Promise { + // tslint:disable-next-line:no-unnecessary-type-assertion + const txHash = await (this + ._assetProxyOwner as AssetProxyOwnerContract).executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync( + txId, + { + from, + }, + ); + const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return tx; + } +} diff --git a/contracts/multisig/tsconfig.json b/contracts/multisig/tsconfig.json index 6f381620e..ad1707e43 100644 --- a/contracts/multisig/tsconfig.json +++ b/contracts/multisig/tsconfig.json @@ -7,8 +7,10 @@ }, "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], "files": [ + "./generated-artifacts/AssetProxyOwner.json", "./generated-artifacts/MultiSigWallet.json", "./generated-artifacts/MultiSigWalletWithTimeLock.json", + "./generated-artifacts/TestAssetProxyOwner.json", "./generated-artifacts/TestRejectEther.json" ], "exclude": ["./deploy/solc/solc_bin"] diff --git a/contracts/protocol/CHANGELOG.json b/contracts/protocol/CHANGELOG.json deleted file mode 100644 index 0d513cc17..000000000 --- a/contracts/protocol/CHANGELOG.json +++ /dev/null @@ -1,65 +0,0 @@ -[ - { - "version": "3.0.0", - "changes": [ - { - "note": "Upgrade the bignumber.js to v8.0.2", - "pr": 1517 - } - ] - }, - { - "timestamp": 1547747677, - "version": "2.2.3", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1547561734, - "version": "2.2.2", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "timestamp": 1547225310, - "version": "2.2.1", - "changes": [ - { - "note": "Dependencies updated" - } - ] - }, - { - "version": "2.2.0", - "changes": [ - { - "note": "Added LibAddressArray", - "pr": 1383 - }, - { - "note": "Add validation and comments to MultiAssetProxy", - "pr": 1455 - }, - { - "note": "Move OrderValidator to extensions", - "pr": 1464 - } - ], - "timestamp": 1547040760 - }, - { - "timestamp": 1544741676, - "version": "2.1.59", - "changes": [ - { - "note": "Dependencies updated" - } - ] - } -] diff --git a/contracts/protocol/CHANGELOG.md b/contracts/protocol/CHANGELOG.md deleted file mode 100644 index 779eb68a7..000000000 --- a/contracts/protocol/CHANGELOG.md +++ /dev/null @@ -1,28 +0,0 @@ - - -CHANGELOG - -## v2.2.3 - _January 17, 2019_ - - * Dependencies updated - -## v2.2.2 - _January 15, 2019_ - - * Dependencies updated - -## v2.2.1 - _January 11, 2019_ - - * Dependencies updated - -## v2.2.0 - _January 9, 2019_ - - * Added LibAddressArray (#1383) - * Add validation and comments to MultiAssetProxy (#1455) - * Move OrderValidator to extensions (#1464) - -## v2.1.59 - _December 13, 2018_ - - * Dependencies updated diff --git a/contracts/protocol/DEPLOYS.json b/contracts/protocol/DEPLOYS.json deleted file mode 100644 index 5c24ae59c..000000000 --- a/contracts/protocol/DEPLOYS.json +++ /dev/null @@ -1,92 +0,0 @@ -[ - { - "name": "MultiAssetProxy", - "version": "1.0.0", - "changes": [ - { - "note": "Add MultiAssetProxy implementation", - "pr": 1224 - } - ] - }, - { - "name": "OrderValidator", - "version": "1.0.0", - "changes": [ - { - "note": "remove `getApproved` check from ERC721 approval query", - "pr": 1149 - } - ] - }, - { - "name": "OrderValidator", - "version": "1.0.0", - "changes": [ - { - "note": "protocol v2 deploy", - "networks": { - "1": "0x9463e518dea6810309563c81d5266c1b1d149138", - "3": "0x90431a90516ab49af23a0530e04e8c7836e7122f", - "42": "0xb389da3d204b412df2f75c6afb3d0a7ce0bc283d" - } - } - ] - }, - { - "name": "Exchange", - "version": "2.0.0", - "changes": [ - { - "note": "protocol v2 deploy", - "networks": { - "1": "0x4f833a24e1f95d70f028921e27040ca56e09ab0b", - "3": "0x4530c0483a1633c7a1c97d2c53721caff2caaaaf", - "42": "0x35dd2932454449b14cee11a94d3674a936d5d7b2" - } - } - ] - }, - { - "name": "ERC20Proxy", - "version": "1.0.0", - "changes": [ - { - "note": "protocol v2 deploy", - "networks": { - "1": "0x2240dab907db71e64d3e0dba4800c83b5c502d4e", - "3": "0xb1408f4c245a23c31b98d2c626777d4c0d766caa", - "42": "0xf1ec01d6236d3cd881a0bf0130ea25fe4234003e" - } - } - ] - }, - { - "name": "ERC721Proxy", - "version": "1.0.0", - "changes": [ - { - "note": "protocol v2 deploy", - "networks": { - "1": "0x208e41fb445f1bb1b6780d58356e81405f3e6127", - "3": "0xe654aac058bfbf9f83fcaee7793311dd82f6ddb4", - "42": "0x2a9127c745688a165106c11cd4d647d2220af821" - } - } - ] - }, - { - "name": "AssetProxyOwner", - "version": "1.0.0", - "changes": [ - { - "note": "protocol v2 deploy", - "networks": { - "1": "0x17992e4ffb22730138e4b62aaa6367fa9d3699a6", - "3": "0xf5fa5b5fed2727a0e44ac67f6772e97977aa358b", - "42": "0x2c824d2882baa668e0d5202b1e7f2922278703f8" - } - } - ] - } -] diff --git a/contracts/protocol/README.md b/contracts/protocol/README.md deleted file mode 100644 index dc1f32c98..000000000 --- a/contracts/protocol/README.md +++ /dev/null @@ -1,74 +0,0 @@ -## Contracts - -Smart contracts that implement the 0x protocol. Addresses of the deployed contracts can be found in the 0x [wiki](https://0xproject.com/wiki#Deployed-Addresses) or the [CHANGELOG](./CHANGELOG.json) of this package. - -## Usage - -Contracts that make up and interact with version 2.0.0 of the protocol can be found in the [contracts](./contracts) directory. The contents of this directory are broken down into the following subdirectories: - -- [protocol](./contracts/protocol) - - This directory contains the contracts that make up version 2.0.0. A full specification can be found [here](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md). -- [test](./contracts/test) - - This directory contains mocks and other contracts that are used solely for testing contracts within the other directories. - -## Bug bounty - -A bug bounty for the 2.0.0 contracts is ongoing! Instructions can be found [here](https://0xproject.com/wiki#Bug-Bounty). - -## Contributing - -We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. - -For proposals regarding the 0x protocol's smart contract architecture, message format, or additional functionality, go to the [0x Improvement Proposals (ZEIPs)](https://github.com/0xProject/ZEIPs) repository and follow the contribution guidelines provided therein. - -Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. - -### Install Dependencies - -If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them: - -```bash -yarn config set workspaces-experimental true -``` - -Then install dependencies - -```bash -yarn install -``` - -### Build - -To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: - -```bash -PKG=@0x/contracts-protocol yarn build -``` - -Or continuously rebuild on change: - -```bash -PKG=@0x/contracts-protocol yarn watch -``` - -### Clean - -```bash -yarn clean -``` - -### Lint - -```bash -yarn lint -``` - -### Run Tests - -```bash -yarn test -``` - -#### Testing options - -Contracts testing options like coverage, profiling, revert traces or backing node choosing - are described [here](../TESTING.md). diff --git a/contracts/protocol/compiler.json b/contracts/protocol/compiler.json deleted file mode 100644 index 10e5bb0a1..000000000 --- a/contracts/protocol/compiler.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "artifactsDir": "./generated-artifacts", - "contractsDir": "./contracts", - "compilerSettings": { - "optimizer": { - "enabled": true, - "runs": 1000000 - }, - "outputSelection": { - "*": { - "*": [ - "abi", - "evm.bytecode.object", - "evm.bytecode.sourceMap", - "evm.deployedBytecode.object", - "evm.deployedBytecode.sourceMap" - ] - } - } - }, - "contracts": [ - "AssetProxyOwner", - "ERC20Proxy", - "ERC721Proxy", - "Exchange", - "MixinAuthorizable", - "MultiAssetProxy", - "TestAssetProxyOwner", - "TestAssetProxyDispatcher", - "TestExchangeInternals", - "TestSignatureValidator", - "TestStaticCallReceiver" - ] -} diff --git a/contracts/protocol/contracts/protocol/AssetProxy/ERC20Proxy.sol b/contracts/protocol/contracts/protocol/AssetProxy/ERC20Proxy.sol deleted file mode 100644 index 258443bca..000000000 --- a/contracts/protocol/contracts/protocol/AssetProxy/ERC20Proxy.sol +++ /dev/null @@ -1,184 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; - -import "./MixinAuthorizable.sol"; - - -contract ERC20Proxy is - MixinAuthorizable -{ - // Id of this proxy. - bytes4 constant internal PROXY_ID = bytes4(keccak256("ERC20Token(address)")); - - // solhint-disable-next-line payable-fallback - function () - external - { - assembly { - // The first 4 bytes of calldata holds the function selector - let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000) - - // `transferFrom` will be called with the following parameters: - // assetData Encoded byte array. - // from Address to transfer asset from. - // to Address to transfer asset to. - // amount Amount of asset to transfer. - // bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4 - if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) { - - // To lookup a value in a mapping, we load from the storage location keccak256(k, p), - // where k is the key left padded to 32 bytes and p is the storage slot - let start := mload(64) - mstore(start, and(caller, 0xffffffffffffffffffffffffffffffffffffffff)) - mstore(add(start, 32), authorized_slot) - - // Revert if authorized[msg.sender] == false - if iszero(sload(keccak256(start, 64))) { - // Revert with `Error("SENDER_NOT_AUTHORIZED")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000001553454e4445525f4e4f545f415554484f52495a454400000000000000) - mstore(96, 0) - revert(0, 100) - } - - // `transferFrom`. - // The function is marked `external`, so no abi decodeding is done for - // us. Instead, we expect the `calldata` memory to contain the - // following: - // - // | Area | Offset | Length | Contents | - // |----------|--------|---------|-------------------------------------| - // | Header | 0 | 4 | function selector | - // | Params | | 4 * 32 | function parameters: | - // | | 4 | | 1. offset to assetData (*) | - // | | 36 | | 2. from | - // | | 68 | | 3. to | - // | | 100 | | 4. amount | - // | Data | | | assetData: | - // | | 132 | 32 | assetData Length | - // | | 164 | ** | assetData Contents | - // - // (*): offset is computed from start of function parameters, so offset - // by an additional 4 bytes in the calldata. - // - // (**): see table below to compute length of assetData Contents - // - // WARNING: The ABIv2 specification allows additional padding between - // the Params and Data section. This will result in a larger - // offset to assetData. - - // Asset data itself is encoded as follows: - // - // | Area | Offset | Length | Contents | - // |----------|--------|---------|-------------------------------------| - // | Header | 0 | 4 | function selector | - // | Params | | 1 * 32 | function parameters: | - // | | 4 | 12 + 20 | 1. token address | - - // We construct calldata for the `token.transferFrom` ABI. - // The layout of this calldata is in the table below. - // - // | Area | Offset | Length | Contents | - // |----------|--------|---------|-------------------------------------| - // | Header | 0 | 4 | function selector | - // | Params | | 3 * 32 | function parameters: | - // | | 4 | | 1. from | - // | | 36 | | 2. to | - // | | 68 | | 3. amount | - - /////// Read token address from calldata /////// - // * The token address is stored in `assetData`. - // - // * The "offset to assetData" is stored at offset 4 in the calldata (table 1). - // [assetDataOffsetFromParams = calldataload(4)] - // - // * Notes that the "offset to assetData" is relative to the "Params" area of calldata; - // add 4 bytes to account for the length of the "Header" area (table 1). - // [assetDataOffsetFromHeader = assetDataOffsetFromParams + 4] - // - // * The "token address" is offset 32+4=36 bytes into "assetData" (tables 1 & 2). - // [tokenOffset = assetDataOffsetFromHeader + 36 = calldataload(4) + 4 + 36] - let token := calldataload(add(calldataload(4), 40)) - - /////// Setup Header Area /////// - // This area holds the 4-byte `transferFrom` selector. - // Any trailing data in transferFromSelector will be - // overwritten in the next `mstore` call. - mstore(0, 0x23b872dd00000000000000000000000000000000000000000000000000000000) - - /////// Setup Params Area /////// - // We copy the fields `from`, `to` and `amount` in bulk - // from our own calldata to the new calldata. - calldatacopy(4, 36, 96) - - /////// Call `token.transferFrom` using the calldata /////// - let success := call( - gas, // forward all gas - token, // call address of token contract - 0, // don't send any ETH - 0, // pointer to start of input - 100, // length of input - 0, // write output over input - 32 // output size should be 32 bytes - ) - - /////// Check return data. /////// - // If there is no return data, we assume the token incorrectly - // does not return a bool. In this case we expect it to revert - // on failure, which was handled above. - // If the token does return data, we require that it is a single - // nonzero 32 bytes value. - // So the transfer succeeded if the call succeeded and either - // returned nothing, or returned a non-zero 32 byte value. - success := and(success, or( - iszero(returndatasize), - and( - eq(returndatasize, 32), - gt(mload(0), 0) - ) - )) - if success { - return(0, 0) - } - - // Revert with `Error("TRANSFER_FAILED")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000000f5452414e534645525f4641494c454400000000000000000000000000) - mstore(96, 0) - revert(0, 100) - } - - // Revert if undefined function is called - revert(0, 0) - } - } - - /// @dev Gets the proxy id associated with the proxy address. - /// @return Proxy id. - function getProxyId() - external - pure - returns (bytes4) - { - return PROXY_ID; - } -} diff --git a/contracts/protocol/contracts/protocol/AssetProxy/ERC721Proxy.sol b/contracts/protocol/contracts/protocol/AssetProxy/ERC721Proxy.sol deleted file mode 100644 index 65b664b8b..000000000 --- a/contracts/protocol/contracts/protocol/AssetProxy/ERC721Proxy.sol +++ /dev/null @@ -1,171 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; - -import "./MixinAuthorizable.sol"; - - -contract ERC721Proxy is - MixinAuthorizable -{ - // Id of this proxy. - bytes4 constant internal PROXY_ID = bytes4(keccak256("ERC721Token(address,uint256)")); - - // solhint-disable-next-line payable-fallback - function () - external - { - assembly { - // The first 4 bytes of calldata holds the function selector - let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000) - - // `transferFrom` will be called with the following parameters: - // assetData Encoded byte array. - // from Address to transfer asset from. - // to Address to transfer asset to. - // amount Amount of asset to transfer. - // bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4 - if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) { - - // To lookup a value in a mapping, we load from the storage location keccak256(k, p), - // where k is the key left padded to 32 bytes and p is the storage slot - let start := mload(64) - mstore(start, and(caller, 0xffffffffffffffffffffffffffffffffffffffff)) - mstore(add(start, 32), authorized_slot) - - // Revert if authorized[msg.sender] == false - if iszero(sload(keccak256(start, 64))) { - // Revert with `Error("SENDER_NOT_AUTHORIZED")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000001553454e4445525f4e4f545f415554484f52495a454400000000000000) - mstore(96, 0) - revert(0, 100) - } - - // `transferFrom`. - // The function is marked `external`, so no abi decodeding is done for - // us. Instead, we expect the `calldata` memory to contain the - // following: - // - // | Area | Offset | Length | Contents | - // |----------|--------|---------|-------------------------------------| - // | Header | 0 | 4 | function selector | - // | Params | | 4 * 32 | function parameters: | - // | | 4 | | 1. offset to assetData (*) | - // | | 36 | | 2. from | - // | | 68 | | 3. to | - // | | 100 | | 4. amount | - // | Data | | | assetData: | - // | | 132 | 32 | assetData Length | - // | | 164 | ** | assetData Contents | - // - // (*): offset is computed from start of function parameters, so offset - // by an additional 4 bytes in the calldata. - // - // (**): see table below to compute length of assetData Contents - // - // WARNING: The ABIv2 specification allows additional padding between - // the Params and Data section. This will result in a larger - // offset to assetData. - - // Asset data itself is encoded as follows: - // - // | Area | Offset | Length | Contents | - // |----------|--------|---------|-------------------------------------| - // | Header | 0 | 4 | function selector | - // | Params | | 2 * 32 | function parameters: | - // | | 4 | 12 + 20 | 1. token address | - // | | 36 | | 2. tokenId | - - // We construct calldata for the `token.transferFrom` ABI. - // The layout of this calldata is in the table below. - // - // | Area | Offset | Length | Contents | - // |----------|--------|---------|-------------------------------------| - // | Header | 0 | 4 | function selector | - // | Params | | 3 * 32 | function parameters: | - // | | 4 | | 1. from | - // | | 36 | | 2. to | - // | | 68 | | 3. tokenId | - - // There exists only 1 of each token. - // require(amount == 1, "INVALID_AMOUNT") - if sub(calldataload(100), 1) { - // Revert with `Error("INVALID_AMOUNT")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000000e494e56414c49445f414d4f554e540000000000000000000000000000) - mstore(96, 0) - revert(0, 100) - } - - /////// Setup Header Area /////// - // This area holds the 4-byte `transferFrom` selector. - // Any trailing data in transferFromSelector will be - // overwritten in the next `mstore` call. - mstore(0, 0x23b872dd00000000000000000000000000000000000000000000000000000000) - - /////// Setup Params Area /////// - // We copy the fields `from` and `to` in bulk - // from our own calldata to the new calldata. - calldatacopy(4, 36, 64) - - // Copy `tokenId` field from our own calldata to the new calldata. - let assetDataOffset := calldataload(4) - calldatacopy(68, add(assetDataOffset, 72), 32) - - /////// Call `token.transferFrom` using the calldata /////// - let token := calldataload(add(assetDataOffset, 40)) - let success := call( - gas, // forward all gas - token, // call address of token contract - 0, // don't send any ETH - 0, // pointer to start of input - 100, // length of input - 0, // write output to null - 0 // output size is 0 bytes - ) - if success { - return(0, 0) - } - - // Revert with `Error("TRANSFER_FAILED")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000000f5452414e534645525f4641494c454400000000000000000000000000) - mstore(96, 0) - revert(0, 100) - } - - // Revert if undefined function is called - revert(0, 0) - } - } - - /// @dev Gets the proxy id associated with the proxy address. - /// @return Proxy id. - function getProxyId() - external - pure - returns (bytes4) - { - return PROXY_ID; - } -} diff --git a/contracts/protocol/contracts/protocol/AssetProxy/MixinAuthorizable.sol b/contracts/protocol/contracts/protocol/AssetProxy/MixinAuthorizable.sol deleted file mode 100644 index b610ef709..000000000 --- a/contracts/protocol/contracts/protocol/AssetProxy/MixinAuthorizable.sol +++ /dev/null @@ -1,117 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity ^0.4.24; - -import "@0x/contracts-utils/contracts/utils/Ownable/Ownable.sol"; -import "./mixins/MAuthorizable.sol"; - - -contract MixinAuthorizable is - Ownable, - MAuthorizable -{ - /// @dev Only authorized addresses can invoke functions with this modifier. - modifier onlyAuthorized { - require( - authorized[msg.sender], - "SENDER_NOT_AUTHORIZED" - ); - _; - } - - mapping (address => bool) public authorized; - address[] public authorities; - - /// @dev Authorizes an address. - /// @param target Address to authorize. - function addAuthorizedAddress(address target) - external - onlyOwner - { - require( - !authorized[target], - "TARGET_ALREADY_AUTHORIZED" - ); - - authorized[target] = true; - authorities.push(target); - emit AuthorizedAddressAdded(target, msg.sender); - } - - /// @dev Removes authorizion of an address. - /// @param target Address to remove authorization from. - function removeAuthorizedAddress(address target) - external - onlyOwner - { - require( - authorized[target], - "TARGET_NOT_AUTHORIZED" - ); - - delete authorized[target]; - for (uint256 i = 0; i < authorities.length; i++) { - if (authorities[i] == target) { - authorities[i] = authorities[authorities.length - 1]; - authorities.length -= 1; - break; - } - } - emit AuthorizedAddressRemoved(target, msg.sender); - } - - /// @dev Removes authorizion of an address. - /// @param target Address to remove authorization from. - /// @param index Index of target in authorities array. - function removeAuthorizedAddressAtIndex( - address target, - uint256 index - ) - external - onlyOwner - { - require( - authorized[target], - "TARGET_NOT_AUTHORIZED" - ); - require( - index < authorities.length, - "INDEX_OUT_OF_BOUNDS" - ); - require( - authorities[index] == target, - "AUTHORIZED_ADDRESS_MISMATCH" - ); - - delete authorized[target]; - authorities[index] = authorities[authorities.length - 1]; - authorities.length -= 1; - emit AuthorizedAddressRemoved(target, msg.sender); - } - - /// @dev Gets all authorized addresses. - /// @return Array of authorized addresses. - function getAuthorizedAddresses() - external - view - returns (address[] memory) - { - return authorities; - } -} diff --git a/contracts/protocol/contracts/protocol/AssetProxy/MultiAssetProxy.sol b/contracts/protocol/contracts/protocol/AssetProxy/MultiAssetProxy.sol deleted file mode 100644 index 5f559163c..000000000 --- a/contracts/protocol/contracts/protocol/AssetProxy/MultiAssetProxy.sol +++ /dev/null @@ -1,306 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; - -import "../Exchange/MixinAssetProxyDispatcher.sol"; -import "./MixinAuthorizable.sol"; - - -contract MultiAssetProxy is - MixinAssetProxyDispatcher, - MixinAuthorizable -{ - // Id of this proxy. - bytes4 constant internal PROXY_ID = bytes4(keccak256("MultiAsset(uint256[],bytes[])")); - - // solhint-disable-next-line payable-fallback - function () - external - { - // NOTE: The below assembly assumes that clients do some input validation and that the input is properly encoded according to the AbiV2 specification. - // It is technically possible for inputs with very large lengths and offsets to cause overflows. However, this would make the calldata prohibitively - // expensive and we therefore do not check for overflows in these scenarios. - assembly { - // The first 4 bytes of calldata holds the function selector - let selector := and(calldataload(0), 0xffffffff00000000000000000000000000000000000000000000000000000000) - - // `transferFrom` will be called with the following parameters: - // assetData Encoded byte array. - // from Address to transfer asset from. - // to Address to transfer asset to. - // amount Amount of asset to transfer. - // bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4 - if eq(selector, 0xa85e59e400000000000000000000000000000000000000000000000000000000) { - - // To lookup a value in a mapping, we load from the storage location keccak256(k, p), - // where k is the key left padded to 32 bytes and p is the storage slot - mstore(0, caller) - mstore(32, authorized_slot) - - // Revert if authorized[msg.sender] == false - if iszero(sload(keccak256(0, 64))) { - // Revert with `Error("SENDER_NOT_AUTHORIZED")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000001553454e4445525f4e4f545f415554484f52495a454400000000000000) - mstore(96, 0) - revert(0, 100) - } - - // `transferFrom`. - // The function is marked `external`, so no abi decoding is done for - // us. Instead, we expect the `calldata` memory to contain the - // following: - // - // | Area | Offset | Length | Contents | - // |----------|--------|---------|-------------------------------------| - // | Header | 0 | 4 | function selector | - // | Params | | 4 * 32 | function parameters: | - // | | 4 | | 1. offset to assetData (*) | - // | | 36 | | 2. from | - // | | 68 | | 3. to | - // | | 100 | | 4. amount | - // | Data | | | assetData: | - // | | 132 | 32 | assetData Length | - // | | 164 | ** | assetData Contents | - // - // (*): offset is computed from start of function parameters, so offset - // by an additional 4 bytes in the calldata. - // - // (**): see table below to compute length of assetData Contents - // - // WARNING: The ABIv2 specification allows additional padding between - // the Params and Data section. This will result in a larger - // offset to assetData. - - // Load offset to `assetData` - let assetDataOffset := calldataload(4) - - // Asset data itself is encoded as follows: - // - // | Area | Offset | Length | Contents | - // |----------|-------------|---------|-------------------------------------| - // | Header | 0 | 4 | assetProxyId | - // | Params | | 2 * 32 | function parameters: | - // | | 4 | | 1. offset to amounts (*) | - // | | 36 | | 2. offset to nestedAssetData (*) | - // | Data | | | amounts: | - // | | 68 | 32 | amounts Length | - // | | 100 | a | amounts Contents | - // | | | | nestedAssetData: | - // | | 100 + a | 32 | nestedAssetData Length | - // | | 132 + a | b | nestedAssetData Contents (offsets) | - // | | 132 + a + b | | nestedAssetData[0, ..., len] | - - // In order to find the offset to `amounts`, we must add: - // 4 (function selector) - // + assetDataOffset - // + 32 (assetData len) - // + 4 (assetProxyId) - let amountsOffset := calldataload(add(assetDataOffset, 40)) - - // In order to find the offset to `nestedAssetData`, we must add: - // 4 (function selector) - // + assetDataOffset - // + 32 (assetData len) - // + 4 (assetProxyId) - // + 32 (amounts offset) - let nestedAssetDataOffset := calldataload(add(assetDataOffset, 72)) - - // In order to find the start of the `amounts` contents, we must add: - // 4 (function selector) - // + assetDataOffset - // + 32 (assetData len) - // + 4 (assetProxyId) - // + amountsOffset - // + 32 (amounts len) - let amountsContentsStart := add(assetDataOffset, add(amountsOffset, 72)) - - // Load number of elements in `amounts` - let amountsLen := calldataload(sub(amountsContentsStart, 32)) - - // In order to find the start of the `nestedAssetData` contents, we must add: - // 4 (function selector) - // + assetDataOffset - // + 32 (assetData len) - // + 4 (assetProxyId) - // + nestedAssetDataOffset - // + 32 (nestedAssetData len) - let nestedAssetDataContentsStart := add(assetDataOffset, add(nestedAssetDataOffset, 72)) - - // Load number of elements in `nestedAssetData` - let nestedAssetDataLen := calldataload(sub(nestedAssetDataContentsStart, 32)) - - // Revert if number of elements in `amounts` differs from number of elements in `nestedAssetData` - if sub(amountsLen, nestedAssetDataLen) { - // Revert with `Error("LENGTH_MISMATCH")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000000f4c454e4754485f4d49534d4154434800000000000000000000000000) - mstore(96, 0) - revert(0, 100) - } - - // Copy `transferFrom` selector, offset to `assetData`, `from`, and `to` from calldata to memory - calldatacopy( - 0, // memory can safely be overwritten from beginning - 0, // start of calldata - 100 // length of selector (4) and 3 params (32 * 3) - ) - - // Overwrite existing offset to `assetData` with our own - mstore(4, 128) - - // Load `amount` - let amount := calldataload(100) - - // Calculate number of bytes in `amounts` contents - let amountsByteLen := mul(amountsLen, 32) - - // Initialize `assetProxyId` and `assetProxy` to 0 - let assetProxyId := 0 - let assetProxy := 0 - - // Loop through `amounts` and `nestedAssetData`, calling `transferFrom` for each respective element - for {let i := 0} lt(i, amountsByteLen) {i := add(i, 32)} { - - // Calculate the total amount - let amountsElement := calldataload(add(amountsContentsStart, i)) - let totalAmount := mul(amountsElement, amount) - - // Revert if `amount` != 0 and multiplication resulted in an overflow - if iszero(or( - iszero(amount), - eq(div(totalAmount, amount), amountsElement) - )) { - // Revert with `Error("UINT256_OVERFLOW")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000001055494e543235365f4f564552464c4f57000000000000000000000000) - mstore(96, 0) - revert(0, 100) - } - - // Write `totalAmount` to memory - mstore(100, totalAmount) - - // Load offset to `nestedAssetData[i]` - let nestedAssetDataElementOffset := calldataload(add(nestedAssetDataContentsStart, i)) - - // In order to find the start of the `nestedAssetData[i]` contents, we must add: - // 4 (function selector) - // + assetDataOffset - // + 32 (assetData len) - // + 4 (assetProxyId) - // + nestedAssetDataOffset - // + 32 (nestedAssetData len) - // + nestedAssetDataElementOffset - // + 32 (nestedAssetDataElement len) - let nestedAssetDataElementContentsStart := add(assetDataOffset, add(nestedAssetDataOffset, add(nestedAssetDataElementOffset, 104))) - - // Load length of `nestedAssetData[i]` - let nestedAssetDataElementLenStart := sub(nestedAssetDataElementContentsStart, 32) - let nestedAssetDataElementLen := calldataload(nestedAssetDataElementLenStart) - - // Revert if the `nestedAssetData` does not contain a 4 byte `assetProxyId` - if lt(nestedAssetDataElementLen, 4) { - // Revert with `Error("LENGTH_GREATER_THAN_3_REQUIRED")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000001e4c454e4754485f475245415445525f5448414e5f335f524551554952) - mstore(96, 0x4544000000000000000000000000000000000000000000000000000000000000) - revert(0, 100) - } - - // Load AssetProxy id - let currentAssetProxyId := and( - calldataload(nestedAssetDataElementContentsStart), - 0xffffffff00000000000000000000000000000000000000000000000000000000 - ) - - // Only load `assetProxy` if `currentAssetProxyId` does not equal `assetProxyId` - // We do not need to check if `currentAssetProxyId` is 0 since `assetProxy` is also initialized to 0 - if sub(currentAssetProxyId, assetProxyId) { - // Update `assetProxyId` - assetProxyId := currentAssetProxyId - // To lookup a value in a mapping, we load from the storage location keccak256(k, p), - // where k is the key left padded to 32 bytes and p is the storage slot - mstore(132, assetProxyId) - mstore(164, assetProxies_slot) - assetProxy := sload(keccak256(132, 64)) - } - - // Revert if AssetProxy with given id does not exist - if iszero(assetProxy) { - // Revert with `Error("ASSET_PROXY_DOES_NOT_EXIST")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000001a41535345545f50524f58595f444f45535f4e4f545f45584953540000) - mstore(96, 0) - revert(0, 100) - } - - // Copy `nestedAssetData[i]` from calldata to memory - calldatacopy( - 132, // memory slot after `amounts[i]` - nestedAssetDataElementLenStart, // location of `nestedAssetData[i]` in calldata - add(nestedAssetDataElementLen, 32) // `nestedAssetData[i].length` plus 32 byte length - ) - - // call `assetProxy.transferFrom` - let success := call( - gas, // forward all gas - assetProxy, // call address of asset proxy - 0, // don't send any ETH - 0, // pointer to start of input - add(164, nestedAssetDataElementLen), // length of input - 0, // write output over memory that won't be reused - 0 // don't copy output to memory - ) - - // Revert with reason given by AssetProxy if `transferFrom` call failed - if iszero(success) { - returndatacopy( - 0, // copy to memory at 0 - 0, // copy from return data at 0 - returndatasize() // copy all return data - ) - revert(0, returndatasize()) - } - } - - // Return if no `transferFrom` calls reverted - return(0, 0) - } - - // Revert if undefined function is called - revert(0, 0) - } - } - - /// @dev Gets the proxy id associated with the proxy address. - /// @return Proxy id. - function getProxyId() - external - pure - returns (bytes4) - { - return PROXY_ID; - } -} diff --git a/contracts/protocol/contracts/protocol/AssetProxy/mixins/MAuthorizable.sol b/contracts/protocol/contracts/protocol/AssetProxy/mixins/MAuthorizable.sol deleted file mode 100644 index 6c3f70500..000000000 --- a/contracts/protocol/contracts/protocol/AssetProxy/mixins/MAuthorizable.sol +++ /dev/null @@ -1,41 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity ^0.4.24; - -import "@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAuthorizable.sol"; - - -contract MAuthorizable is - IAuthorizable -{ - // Event logged when a new address is authorized. - event AuthorizedAddressAdded( - address indexed target, - address indexed caller - ); - - // Event logged when a currently authorized address is unauthorized. - event AuthorizedAddressRemoved( - address indexed target, - address indexed caller - ); - - /// @dev Only authorized addresses can invoke functions with this modifier. - modifier onlyAuthorized { revert(); _; } -} diff --git a/contracts/protocol/contracts/protocol/AssetProxyOwner/AssetProxyOwner.sol b/contracts/protocol/contracts/protocol/AssetProxyOwner/AssetProxyOwner.sol deleted file mode 100644 index bfc7b5a66..000000000 --- a/contracts/protocol/contracts/protocol/AssetProxyOwner/AssetProxyOwner.sol +++ /dev/null @@ -1,108 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; - -import "@0x/contracts-multisig/contracts/multisig/MultiSigWalletWithTimeLock.sol"; -import "@0x/contracts-utils/contracts/utils/LibBytes/LibBytes.sol"; - - -contract AssetProxyOwner is - MultiSigWalletWithTimeLock -{ - using LibBytes for bytes; - - event AssetProxyRegistration(address assetProxyContract, bool isRegistered); - - // Mapping of AssetProxy contract address => - // if this contract is allowed to call the AssetProxy's `removeAuthorizedAddressAtIndex` method without a time lock. - mapping (address => bool) public isAssetProxyRegistered; - - bytes4 constant internal REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR = bytes4(keccak256("removeAuthorizedAddressAtIndex(address,uint256)")); - - /// @dev Function will revert if the transaction does not call `removeAuthorizedAddressAtIndex` - /// on an approved AssetProxy contract. - modifier validRemoveAuthorizedAddressAtIndexTx(uint256 transactionId) { - Transaction storage txn = transactions[transactionId]; - require( - isAssetProxyRegistered[txn.destination], - "UNREGISTERED_ASSET_PROXY" - ); - require( - txn.data.readBytes4(0) == REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR, - "INVALID_FUNCTION_SELECTOR" - ); - _; - } - - /// @dev Contract constructor sets initial owners, required number of confirmations, - /// time lock, and list of AssetProxy addresses. - /// @param _owners List of initial owners. - /// @param _assetProxyContracts Array of AssetProxy contract addresses. - /// @param _required Number of required confirmations. - /// @param _secondsTimeLocked Duration needed after a transaction is confirmed and before it becomes executable, in seconds. - constructor ( - address[] memory _owners, - address[] memory _assetProxyContracts, - uint256 _required, - uint256 _secondsTimeLocked - ) - public - MultiSigWalletWithTimeLock(_owners, _required, _secondsTimeLocked) - { - for (uint256 i = 0; i < _assetProxyContracts.length; i++) { - address assetProxy = _assetProxyContracts[i]; - require( - assetProxy != address(0), - "INVALID_ASSET_PROXY" - ); - isAssetProxyRegistered[assetProxy] = true; - } - } - - /// @dev Registers or deregisters an AssetProxy to be able to execute - /// `removeAuthorizedAddressAtIndex` without a timelock. - /// @param assetProxyContract Address of AssetProxy contract. - /// @param isRegistered Status of approval for AssetProxy contract. - function registerAssetProxy(address assetProxyContract, bool isRegistered) - public - onlyWallet - notNull(assetProxyContract) - { - isAssetProxyRegistered[assetProxyContract] = isRegistered; - emit AssetProxyRegistration(assetProxyContract, isRegistered); - } - - /// @dev Allows execution of `removeAuthorizedAddressAtIndex` without time lock. - /// @param transactionId Transaction ID. - function executeRemoveAuthorizedAddressAtIndex(uint256 transactionId) - public - notExecuted(transactionId) - fullyConfirmed(transactionId) - validRemoveAuthorizedAddressAtIndexTx(transactionId) - { - Transaction storage txn = transactions[transactionId]; - txn.executed = true; - if (external_call(txn.destination, txn.value, txn.data.length, txn.data)) { - emit Execution(transactionId); - } else { - emit ExecutionFailure(transactionId); - txn.executed = false; - } - } -} diff --git a/contracts/protocol/contracts/protocol/Exchange/Exchange.sol b/contracts/protocol/contracts/protocol/Exchange/Exchange.sol deleted file mode 100644 index 65ca742ea..000000000 --- a/contracts/protocol/contracts/protocol/Exchange/Exchange.sol +++ /dev/null @@ -1,53 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; - -import "@0x/contracts-libs/contracts/libs/LibConstants.sol"; -import "./MixinExchangeCore.sol"; -import "./MixinSignatureValidator.sol"; -import "./MixinWrapperFunctions.sol"; -import "./MixinAssetProxyDispatcher.sol"; -import "./MixinTransactions.sol"; -import "./MixinMatchOrders.sol"; - - -// solhint-disable no-empty-blocks -contract Exchange is - MixinExchangeCore, - MixinMatchOrders, - MixinSignatureValidator, - MixinTransactions, - MixinAssetProxyDispatcher, - MixinWrapperFunctions -{ - string constant public VERSION = "2.0.1-alpha"; - - // Mixins are instantiated in the order they are inherited - constructor (bytes memory _zrxAssetData) - public - LibConstants(_zrxAssetData) // @TODO: Remove when we deploy. - MixinExchangeCore() - MixinMatchOrders() - MixinSignatureValidator() - MixinTransactions() - MixinAssetProxyDispatcher() - MixinWrapperFunctions() - {} -} diff --git a/contracts/protocol/contracts/protocol/Exchange/MixinAssetProxyDispatcher.sol b/contracts/protocol/contracts/protocol/Exchange/MixinAssetProxyDispatcher.sol deleted file mode 100644 index 2922a8c1a..000000000 --- a/contracts/protocol/contracts/protocol/Exchange/MixinAssetProxyDispatcher.sol +++ /dev/null @@ -1,174 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity ^0.4.24; - -import "@0x/contracts-utils/contracts/utils/Ownable/Ownable.sol"; -import "./mixins/MAssetProxyDispatcher.sol"; -import "@0x/contracts-interfaces/contracts/protocol/AssetProxy/IAssetProxy.sol"; - - -contract MixinAssetProxyDispatcher is - Ownable, - MAssetProxyDispatcher -{ - // Mapping from Asset Proxy Id's to their respective Asset Proxy - mapping (bytes4 => IAssetProxy) public assetProxies; - - /// @dev Registers an asset proxy to its asset proxy id. - /// Once an asset proxy is registered, it cannot be unregistered. - /// @param assetProxy Address of new asset proxy to register. - function registerAssetProxy(address assetProxy) - external - onlyOwner - { - IAssetProxy assetProxyContract = IAssetProxy(assetProxy); - - // Ensure that no asset proxy exists with current id. - bytes4 assetProxyId = assetProxyContract.getProxyId(); - address currentAssetProxy = assetProxies[assetProxyId]; - require( - currentAssetProxy == address(0), - "ASSET_PROXY_ALREADY_EXISTS" - ); - - // Add asset proxy and log registration. - assetProxies[assetProxyId] = assetProxyContract; - emit AssetProxyRegistered( - assetProxyId, - assetProxy - ); - } - - /// @dev Gets an asset proxy. - /// @param assetProxyId Id of the asset proxy. - /// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered. - function getAssetProxy(bytes4 assetProxyId) - external - view - returns (address) - { - return assetProxies[assetProxyId]; - } - - /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws. - /// @param assetData Byte array encoded for the asset. - /// @param from Address to transfer token from. - /// @param to Address to transfer token to. - /// @param amount Amount of token to transfer. - function dispatchTransferFrom( - bytes memory assetData, - address from, - address to, - uint256 amount - ) - internal - { - // Do nothing if no amount should be transferred. - if (amount > 0 && from != to) { - // Ensure assetData length is valid - require( - assetData.length > 3, - "LENGTH_GREATER_THAN_3_REQUIRED" - ); - - // Lookup assetProxy. We do not use `LibBytes.readBytes4` for gas efficiency reasons. - bytes4 assetProxyId; - assembly { - assetProxyId := and(mload( - add(assetData, 32)), - 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 - ) - } - address assetProxy = assetProxies[assetProxyId]; - - // Ensure that assetProxy exists - require( - assetProxy != address(0), - "ASSET_PROXY_DOES_NOT_EXIST" - ); - - // We construct calldata for the `assetProxy.transferFrom` ABI. - // The layout of this calldata is in the table below. - // - // | Area | Offset | Length | Contents | - // | -------- |--------|---------|-------------------------------------------- | - // | Header | 0 | 4 | function selector | - // | Params | | 4 * 32 | function parameters: | - // | | 4 | | 1. offset to assetData (*) | - // | | 36 | | 2. from | - // | | 68 | | 3. to | - // | | 100 | | 4. amount | - // | Data | | | assetData: | - // | | 132 | 32 | assetData Length | - // | | 164 | ** | assetData Contents | - - assembly { - /////// Setup State /////// - // `cdStart` is the start of the calldata for `assetProxy.transferFrom` (equal to free memory ptr). - let cdStart := mload(64) - // `dataAreaLength` is the total number of words needed to store `assetData` - // As-per the ABI spec, this value is padded up to the nearest multiple of 32, - // and includes 32-bytes for length. - let dataAreaLength := and(add(mload(assetData), 63), 0xFFFFFFFFFFFE0) - // `cdEnd` is the end of the calldata for `assetProxy.transferFrom`. - let cdEnd := add(cdStart, add(132, dataAreaLength)) - - - /////// Setup Header Area /////// - // This area holds the 4-byte `transferFromSelector`. - // bytes4(keccak256("transferFrom(bytes,address,address,uint256)")) = 0xa85e59e4 - mstore(cdStart, 0xa85e59e400000000000000000000000000000000000000000000000000000000) - - /////// Setup Params Area /////// - // Each parameter is padded to 32-bytes. The entire Params Area is 128 bytes. - // Notes: - // 1. The offset to `assetData` is the length of the Params Area (128 bytes). - // 2. A 20-byte mask is applied to addresses to zero-out the unused bytes. - mstore(add(cdStart, 4), 128) - mstore(add(cdStart, 36), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) - mstore(add(cdStart, 68), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) - mstore(add(cdStart, 100), amount) - - /////// Setup Data Area /////// - // This area holds `assetData`. - let dataArea := add(cdStart, 132) - // solhint-disable-next-line no-empty-blocks - for {} lt(dataArea, cdEnd) {} { - mstore(dataArea, mload(assetData)) - dataArea := add(dataArea, 32) - assetData := add(assetData, 32) - } - - /////// Call `assetProxy.transferFrom` using the constructed calldata /////// - let success := call( - gas, // forward all gas - assetProxy, // call address of asset proxy - 0, // don't send any ETH - cdStart, // pointer to start of input - sub(cdEnd, cdStart), // length of input - cdStart, // write output over input - 512 // reserve 512 bytes for output - ) - if iszero(success) { - revert(cdStart, returndatasize()) - } - } - } - } -} diff --git a/contracts/protocol/contracts/protocol/Exchange/MixinExchangeCore.sol b/contracts/protocol/contracts/protocol/Exchange/MixinExchangeCore.sol deleted file mode 100644 index 72bcebc62..000000000 --- a/contracts/protocol/contracts/protocol/Exchange/MixinExchangeCore.sol +++ /dev/null @@ -1,529 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity ^0.4.24; -pragma experimental ABIEncoderV2; - -import "@0x/contracts-utils/contracts/utils/ReentrancyGuard/ReentrancyGuard.sol"; -import "@0x/contracts-libs/contracts/libs/LibConstants.sol"; -import "@0x/contracts-libs/contracts/libs/LibFillResults.sol"; -import "@0x/contracts-libs/contracts/libs/LibOrder.sol"; -import "@0x/contracts-libs/contracts/libs/LibMath.sol"; -import "./mixins/MExchangeCore.sol"; -import "./mixins/MSignatureValidator.sol"; -import "./mixins/MTransactions.sol"; -import "./mixins/MAssetProxyDispatcher.sol"; - - -contract MixinExchangeCore is - ReentrancyGuard, - LibConstants, - LibMath, - LibOrder, - LibFillResults, - MAssetProxyDispatcher, - MExchangeCore, - MSignatureValidator, - MTransactions -{ - // Mapping of orderHash => amount of takerAsset already bought by maker - mapping (bytes32 => uint256) public filled; - - // Mapping of orderHash => cancelled - mapping (bytes32 => bool) public cancelled; - - // Mapping of makerAddress => senderAddress => lowest salt an order can have in order to be fillable - // Orders with specified senderAddress and with a salt less than their epoch are considered cancelled - mapping (address => mapping (address => uint256)) public orderEpoch; - - /// @dev Cancels all orders created by makerAddress with a salt less than or equal to the targetOrderEpoch - /// and senderAddress equal to msg.sender (or null address if msg.sender == makerAddress). - /// @param targetOrderEpoch Orders created with a salt less or equal to this value will be cancelled. - function cancelOrdersUpTo(uint256 targetOrderEpoch) - external - nonReentrant - { - address makerAddress = getCurrentContextAddress(); - // If this function is called via `executeTransaction`, we only update the orderEpoch for the makerAddress/msg.sender combination. - // This allows external filter contracts to add rules to how orders are cancelled via this function. - address senderAddress = makerAddress == msg.sender ? address(0) : msg.sender; - - // orderEpoch is initialized to 0, so to cancelUpTo we need salt + 1 - uint256 newOrderEpoch = targetOrderEpoch + 1; - uint256 oldOrderEpoch = orderEpoch[makerAddress][senderAddress]; - - // Ensure orderEpoch is monotonically increasing - require( - newOrderEpoch > oldOrderEpoch, - "INVALID_NEW_ORDER_EPOCH" - ); - - // Update orderEpoch - orderEpoch[makerAddress][senderAddress] = newOrderEpoch; - emit CancelUpTo( - makerAddress, - senderAddress, - newOrderEpoch - ); - } - - /// @dev Fills the input order. - /// @param order Order struct containing order specifications. - /// @param takerAssetFillAmount Desired amount of takerAsset to sell. - /// @param signature Proof that order has been created by maker. - /// @return Amounts filled and fees paid by maker and taker. - function fillOrder( - Order memory order, - uint256 takerAssetFillAmount, - bytes memory signature - ) - public - nonReentrant - returns (FillResults memory fillResults) - { - fillResults = fillOrderInternal( - order, - takerAssetFillAmount, - signature - ); - return fillResults; - } - - /// @dev After calling, the order can not be filled anymore. - /// Throws if order is invalid or sender does not have permission to cancel. - /// @param order Order to cancel. Order must be OrderStatus.FILLABLE. - function cancelOrder(Order memory order) - public - nonReentrant - { - cancelOrderInternal(order); - } - - /// @dev Gets information about an order: status, hash, and amount filled. - /// @param order Order to gather information on. - /// @return OrderInfo Information about the order and its state. - /// See LibOrder.OrderInfo for a complete description. - function getOrderInfo(Order memory order) - public - view - returns (OrderInfo memory orderInfo) - { - // Compute the order hash - orderInfo.orderHash = getOrderHash(order); - - // Fetch filled amount - orderInfo.orderTakerAssetFilledAmount = filled[orderInfo.orderHash]; - - // If order.makerAssetAmount is zero, we also reject the order. - // While the Exchange contract handles them correctly, they create - // edge cases in the supporting infrastructure because they have - // an 'infinite' price when computed by a simple division. - if (order.makerAssetAmount == 0) { - orderInfo.orderStatus = uint8(OrderStatus.INVALID_MAKER_ASSET_AMOUNT); - return orderInfo; - } - - // If order.takerAssetAmount is zero, then the order will always - // be considered filled because 0 == takerAssetAmount == orderTakerAssetFilledAmount - // Instead of distinguishing between unfilled and filled zero taker - // amount orders, we choose not to support them. - if (order.takerAssetAmount == 0) { - orderInfo.orderStatus = uint8(OrderStatus.INVALID_TAKER_ASSET_AMOUNT); - return orderInfo; - } - - // Validate order availability - if (orderInfo.orderTakerAssetFilledAmount >= order.takerAssetAmount) { - orderInfo.orderStatus = uint8(OrderStatus.FULLY_FILLED); - return orderInfo; - } - - // Validate order expiration - // solhint-disable-next-line not-rely-on-time - if (block.timestamp >= order.expirationTimeSeconds) { - orderInfo.orderStatus = uint8(OrderStatus.EXPIRED); - return orderInfo; - } - - // Check if order has been cancelled - if (cancelled[orderInfo.orderHash]) { - orderInfo.orderStatus = uint8(OrderStatus.CANCELLED); - return orderInfo; - } - if (orderEpoch[order.makerAddress][order.senderAddress] > order.salt) { - orderInfo.orderStatus = uint8(OrderStatus.CANCELLED); - return orderInfo; - } - - // All other statuses are ruled out: order is Fillable - orderInfo.orderStatus = uint8(OrderStatus.FILLABLE); - return orderInfo; - } - - /// @dev Fills the input order. - /// @param order Order struct containing order specifications. - /// @param takerAssetFillAmount Desired amount of takerAsset to sell. - /// @param signature Proof that order has been created by maker. - /// @return Amounts filled and fees paid by maker and taker. - function fillOrderInternal( - Order memory order, - uint256 takerAssetFillAmount, - bytes memory signature - ) - internal - returns (FillResults memory fillResults) - { - // Fetch order info - OrderInfo memory orderInfo = getOrderInfo(order); - - // Fetch taker address - address takerAddress = getCurrentContextAddress(); - - // Assert that the order is fillable by taker - assertFillableOrder( - order, - orderInfo, - takerAddress, - signature - ); - - // Get amount of takerAsset to fill - uint256 remainingTakerAssetAmount = safeSub(order.takerAssetAmount, orderInfo.orderTakerAssetFilledAmount); - uint256 takerAssetFilledAmount = min256(takerAssetFillAmount, remainingTakerAssetAmount); - - // Validate context - assertValidFill( - order, - orderInfo, - takerAssetFillAmount, - takerAssetFilledAmount, - fillResults.makerAssetFilledAmount - ); - - // Compute proportional fill amounts - fillResults = calculateFillResults(order, takerAssetFilledAmount); - - // Update exchange internal state - updateFilledState( - order, - takerAddress, - orderInfo.orderHash, - orderInfo.orderTakerAssetFilledAmount, - fillResults - ); - - // Settle order - settleOrder( - order, - takerAddress, - fillResults - ); - - return fillResults; - } - - /// @dev After calling, the order can not be filled anymore. - /// Throws if order is invalid or sender does not have permission to cancel. - /// @param order Order to cancel. Order must be OrderStatus.FILLABLE. - function cancelOrderInternal(Order memory order) - internal - { - // Fetch current order status - OrderInfo memory orderInfo = getOrderInfo(order); - - // Validate context - assertValidCancel(order, orderInfo); - - // Perform cancel - updateCancelledState(order, orderInfo.orderHash); - } - - /// @dev Updates state with results of a fill order. - /// @param order that was filled. - /// @param takerAddress Address of taker who filled the order. - /// @param orderTakerAssetFilledAmount Amount of order already filled. - function updateFilledState( - Order memory order, - address takerAddress, - bytes32 orderHash, - uint256 orderTakerAssetFilledAmount, - FillResults memory fillResults - ) - internal - { - // Update state - filled[orderHash] = safeAdd(orderTakerAssetFilledAmount, fillResults.takerAssetFilledAmount); - - // Log order - emit Fill( - order.makerAddress, - order.feeRecipientAddress, - takerAddress, - msg.sender, - fillResults.makerAssetFilledAmount, - fillResults.takerAssetFilledAmount, - fillResults.makerFeePaid, - fillResults.takerFeePaid, - orderHash, - order.makerAssetData, - order.takerAssetData - ); - } - - /// @dev Updates state with results of cancelling an order. - /// State is only updated if the order is currently fillable. - /// Otherwise, updating state would have no effect. - /// @param order that was cancelled. - /// @param orderHash Hash of order that was cancelled. - function updateCancelledState( - Order memory order, - bytes32 orderHash - ) - internal - { - // Perform cancel - cancelled[orderHash] = true; - - // Log cancel - emit Cancel( - order.makerAddress, - order.feeRecipientAddress, - msg.sender, - orderHash, - order.makerAssetData, - order.takerAssetData - ); - } - - /// @dev Validates context for fillOrder. Succeeds or throws. - /// @param order to be filled. - /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. - /// @param takerAddress Address of order taker. - /// @param signature Proof that the orders was created by its maker. - function assertFillableOrder( - Order memory order, - OrderInfo memory orderInfo, - address takerAddress, - bytes memory signature - ) - internal - view - { - // An order can only be filled if its status is FILLABLE. - require( - orderInfo.orderStatus == uint8(OrderStatus.FILLABLE), - "ORDER_UNFILLABLE" - ); - - // Validate sender is allowed to fill this order - if (order.senderAddress != address(0)) { - require( - order.senderAddress == msg.sender, - "INVALID_SENDER" - ); - } - - // Validate taker is allowed to fill this order - if (order.takerAddress != address(0)) { - require( - order.takerAddress == takerAddress, - "INVALID_TAKER" - ); - } - - // Validate Maker signature (check only if first time seen) - if (orderInfo.orderTakerAssetFilledAmount == 0) { - require( - isValidSignature( - orderInfo.orderHash, - order.makerAddress, - signature - ), - "INVALID_ORDER_SIGNATURE" - ); - } - } - - /// @dev Validates context for fillOrder. Succeeds or throws. - /// @param order to be filled. - /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. - /// @param takerAssetFillAmount Desired amount of order to fill by taker. - /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. - /// @param makerAssetFilledAmount Amount of makerAsset that will be transfered. - function assertValidFill( - Order memory order, - OrderInfo memory orderInfo, - uint256 takerAssetFillAmount, // TODO: use FillResults - uint256 takerAssetFilledAmount, - uint256 makerAssetFilledAmount - ) - internal - view - { - // Revert if fill amount is invalid - // TODO: reconsider necessity for v2.1 - require( - takerAssetFillAmount != 0, - "INVALID_TAKER_AMOUNT" - ); - - // Make sure taker does not pay more than desired amount - // NOTE: This assertion should never fail, it is here - // as an extra defence against potential bugs. - require( - takerAssetFilledAmount <= takerAssetFillAmount, - "TAKER_OVERPAY" - ); - - // Make sure order is not overfilled - // NOTE: This assertion should never fail, it is here - // as an extra defence against potential bugs. - require( - safeAdd(orderInfo.orderTakerAssetFilledAmount, takerAssetFilledAmount) <= order.takerAssetAmount, - "ORDER_OVERFILL" - ); - - // Make sure order is filled at acceptable price. - // The order has an implied price from the makers perspective: - // order price = order.makerAssetAmount / order.takerAssetAmount - // i.e. the number of makerAsset maker is paying per takerAsset. The - // maker is guaranteed to get this price or a better (lower) one. The - // actual price maker is getting in this fill is: - // fill price = makerAssetFilledAmount / takerAssetFilledAmount - // We need `fill price <= order price` for the fill to be fair to maker. - // This amounts to: - // makerAssetFilledAmount order.makerAssetAmount - // ------------------------ <= ----------------------- - // takerAssetFilledAmount order.takerAssetAmount - // or, equivalently: - // makerAssetFilledAmount * order.takerAssetAmount <= - // order.makerAssetAmount * takerAssetFilledAmount - // NOTE: This assertion should never fail, it is here - // as an extra defence against potential bugs. - require( - safeMul(makerAssetFilledAmount, order.takerAssetAmount) - <= - safeMul(order.makerAssetAmount, takerAssetFilledAmount), - "INVALID_FILL_PRICE" - ); - } - - /// @dev Validates context for cancelOrder. Succeeds or throws. - /// @param order to be cancelled. - /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. - function assertValidCancel( - Order memory order, - OrderInfo memory orderInfo - ) - internal - view - { - // Ensure order is valid - // An order can only be cancelled if its status is FILLABLE. - require( - orderInfo.orderStatus == uint8(OrderStatus.FILLABLE), - "ORDER_UNFILLABLE" - ); - - // Validate sender is allowed to cancel this order - if (order.senderAddress != address(0)) { - require( - order.senderAddress == msg.sender, - "INVALID_SENDER" - ); - } - - // Validate transaction signed by maker - address makerAddress = getCurrentContextAddress(); - require( - order.makerAddress == makerAddress, - "INVALID_MAKER" - ); - } - - /// @dev Calculates amounts filled and fees paid by maker and taker. - /// @param order to be filled. - /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. - /// @return fillResults Amounts filled and fees paid by maker and taker. - function calculateFillResults( - Order memory order, - uint256 takerAssetFilledAmount - ) - internal - pure - returns (FillResults memory fillResults) - { - // Compute proportional transfer amounts - fillResults.takerAssetFilledAmount = takerAssetFilledAmount; - fillResults.makerAssetFilledAmount = safeGetPartialAmountFloor( - takerAssetFilledAmount, - order.takerAssetAmount, - order.makerAssetAmount - ); - fillResults.makerFeePaid = safeGetPartialAmountFloor( - fillResults.makerAssetFilledAmount, - order.makerAssetAmount, - order.makerFee - ); - fillResults.takerFeePaid = safeGetPartialAmountFloor( - takerAssetFilledAmount, - order.takerAssetAmount, - order.takerFee - ); - - return fillResults; - } - - /// @dev Settles an order by transferring assets between counterparties. - /// @param order Order struct containing order specifications. - /// @param takerAddress Address selling takerAsset and buying makerAsset. - /// @param fillResults Amounts to be filled and fees paid by maker and taker. - function settleOrder( - LibOrder.Order memory order, - address takerAddress, - LibFillResults.FillResults memory fillResults - ) - private - { - bytes memory zrxAssetData = ZRX_ASSET_DATA; - dispatchTransferFrom( - order.makerAssetData, - order.makerAddress, - takerAddress, - fillResults.makerAssetFilledAmount - ); - dispatchTransferFrom( - order.takerAssetData, - takerAddress, - order.makerAddress, - fillResults.takerAssetFilledAmount - ); - dispatchTransferFrom( - zrxAssetData, - order.makerAddress, - order.feeRecipientAddress, - fillResults.makerFeePaid - ); - dispatchTransferFrom( - zrxAssetData, - takerAddress, - order.feeRecipientAddress, - fillResults.takerFeePaid - ); - } -} diff --git a/contracts/protocol/contracts/protocol/Exchange/MixinMatchOrders.sol b/contracts/protocol/contracts/protocol/Exchange/MixinMatchOrders.sol deleted file mode 100644 index 2627b82fd..000000000 --- a/contracts/protocol/contracts/protocol/Exchange/MixinMatchOrders.sol +++ /dev/null @@ -1,335 +0,0 @@ -/* - Copyright 2018 ZeroEx Intl. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -pragma solidity ^0.4.24; -pragma experimental ABIEncoderV2; - -import "@0x/contracts-utils/contracts/utils/ReentrancyGuard/ReentrancyGuard.sol"; -import "@0x/contracts-libs/contracts/libs/LibConstants.sol"; -import "@0x/contracts-libs/contracts/libs/LibMath.sol"; -import "@0x/contracts-libs/contracts/libs/LibOrder.sol"; -import "@0x/contracts-libs/contracts/libs/LibFillResults.sol"; -import "./mixins/MExchangeCore.sol"; -import "./mixins/MMatchOrders.sol"; -import "./mixins/MTransactions.sol"; -import "./mixins/MAssetProxyDispatcher.sol"; - - -contract MixinMatchOrders is - ReentrancyGuard, - LibConstants, - LibMath, - MAssetProxyDispatcher, - MExchangeCore, - MMatchOrders, - MTransactions -{ - /// @dev Match two complementary orders that have a profitable spread. - /// Each order is filled at their respective price point. However, the calculations are - /// carried out as though the orders are both being filled at the right order's price point. - /// The profit made by the left order goes to the taker (who matched the two orders). - /// @param leftOrder First order to match. - /// @param rightOrder Second order to match. - /// @param leftSignature Proof that order was created by the left maker. - /// @param rightSignature Proof that order was created by the right maker. - /// @return matchedFillResults Amounts filled and fees paid by maker and taker of matched orders. - function matchOrders( - LibOrder.Order memory leftOrder, - LibOrder.Order memory rightOrder, - bytes memory leftSignature, - bytes memory rightSignature - ) - public - nonReentrant - returns (LibFillResults.MatchedFillResults memory matchedFillResults) - { - // We assume that rightOrder.takerAssetData == leftOrder.makerAssetData and rightOrder.makerAssetData == leftOrder.takerAssetData. - // If this assumption isn't true, the match will fail at signature validation. - rightOrder.makerAssetData = leftOrder.takerAssetData; - rightOrder.takerAssetData = leftOrder.makerAssetData; - - // Get left & right order info - LibOrder.OrderInfo memory leftOrderInfo = getOrderInfo(leftOrder); - LibOrder.OrderInfo memory rightOrderInfo = getOrderInfo(rightOrder); - - // Fetch taker address - address takerAddress = getCurrentContextAddress(); - - // Either our context is valid or we revert - assertFillableOrder( - leftOrder, - leftOrderInfo, - takerAddress, - leftSignature - ); - assertFillableOrder( - rightOrder, - rightOrderInfo, - takerAddress, - rightSignature - ); - assertValidMatch(leftOrder, rightOrder); - - // Compute proportional fill amounts - matchedFillResults = calculateMatchedFillResults( - leftOrder, - rightOrder, - leftOrderInfo.orderTakerAssetFilledAmount, - rightOrderInfo.orderTakerAssetFilledAmount - ); - - // Validate fill contexts - assertValidFill( - leftOrder, - leftOrderInfo, - matchedFillResults.left.takerAssetFilledAmount, - matchedFillResults.left.takerAssetFilledAmount, - matchedFillResults.left.makerAssetFilledAmount - ); - assertValidFill( - rightOrder, - rightOrderInfo, - matchedFillResults.right.takerAssetFilledAmount, - matchedFillResults.right.takerAssetFilledAmount, - matchedFillResults.right.makerAssetFilledAmount - ); - - // Update exchange state - updateFilledState( - leftOrder, - takerAddress, - leftOrderInfo.orderHash, - leftOrderInfo.orderTakerAssetFilledAmount, - matchedFillResults.left - ); - updateFilledState( - rightOrder, - takerAddress, - rightOrderInfo.orderHash, - rightOrderInfo.orderTakerAssetFilledAmount, - matchedFillResults.right - ); - - // Settle matched orders. Succeeds or throws. - settleMatchedOrders( - leftOrder, - rightOrder, - takerAddress, - matchedFillResults - ); - - return matchedFillResults; - } - - /// @dev Validates context for matchOrders. Succeeds or throws. - /// @param leftOrder First order to match. - /// @param rightOrder Second order to match. - function assertValidMatch( - LibOrder.Order memory leftOrder, - LibOrder.Order memory rightOrder - ) - internal - pure - { - // Make sure there is a profitable spread. - // There is a profitable spread iff the cost per unit bought (OrderA.MakerAmount/OrderA.TakerAmount) for each order is greater - // than the profit per unit sold of the matched order (OrderB.TakerAmount/OrderB.MakerAmount). - // This is satisfied by the equations below: - // / >= / - // AND - // / >= / - // These equations can be combined to get the following: - require( - safeMul(leftOrder.makerAssetAmount, rightOrder.makerAssetAmount) >= - safeMul(leftOrder.takerAssetAmount, rightOrder.takerAssetAmount), - "NEGATIVE_SPREAD_REQUIRED" - ); - } - - /// @dev Calculates fill amounts for the matched orders. - /// Each order is filled at their respective price point. However, the calculations are - /// carried out as though the orders are both being filled at the right order's price point. - /// The profit made by the leftOrder order goes to the taker (who matched the two orders). - /// @param leftOrder First order to match. - /// @param rightOrder Second order to match. - /// @param leftOrderTakerAssetFilledAmount Amount of left order already filled. - /// @param rightOrderTakerAssetFilledAmount Amount of right order already filled. - /// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders. - function calculateMatchedFillResults( - LibOrder.Order memory leftOrder, - LibOrder.Order memory rightOrder, - uint256 leftOrderTakerAssetFilledAmount, - uint256 rightOrderTakerAssetFilledAmount - ) - internal - pure - returns (LibFillResults.MatchedFillResults memory matchedFillResults) - { - // Derive maker asset amounts for left & right orders, given store taker assert amounts - uint256 leftTakerAssetAmountRemaining = safeSub(leftOrder.takerAssetAmount, leftOrderTakerAssetFilledAmount); - uint256 leftMakerAssetAmountRemaining = safeGetPartialAmountFloor( - leftOrder.makerAssetAmount, - leftOrder.takerAssetAmount, - leftTakerAssetAmountRemaining - ); - uint256 rightTakerAssetAmountRemaining = safeSub(rightOrder.takerAssetAmount, rightOrderTakerAssetFilledAmount); - uint256 rightMakerAssetAmountRemaining = safeGetPartialAmountFloor( - rightOrder.makerAssetAmount, - rightOrder.takerAssetAmount, - rightTakerAssetAmountRemaining - ); - - // Calculate fill results for maker and taker assets: at least one order will be fully filled. - // The maximum amount the left maker can buy is `leftTakerAssetAmountRemaining` - // The maximum amount the right maker can sell is `rightMakerAssetAmountRemaining` - // We have two distinct cases for calculating the fill results: - // Case 1. - // If the left maker can buy more than the right maker can sell, then only the right order is fully filled. - // If the left maker can buy exactly what the right maker can sell, then both orders are fully filled. - // Case 2. - // If the left maker cannot buy more than the right maker can sell, then only the left order is fully filled. - if (leftTakerAssetAmountRemaining >= rightMakerAssetAmountRemaining) { - // Case 1: Right order is fully filled - matchedFillResults.right.makerAssetFilledAmount = rightMakerAssetAmountRemaining; - matchedFillResults.right.takerAssetFilledAmount = rightTakerAssetAmountRemaining; - matchedFillResults.left.takerAssetFilledAmount = matchedFillResults.right.makerAssetFilledAmount; - // Round down to ensure the maker's exchange rate does not exceed the price specified by the order. - // We favor the maker when the exchange rate must be rounded. - matchedFillResults.left.makerAssetFilledAmount = safeGetPartialAmountFloor( - leftOrder.makerAssetAmount, - leftOrder.takerAssetAmount, - matchedFillResults.left.takerAssetFilledAmount - ); - } else { - // Case 2: Left order is fully filled - matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining; - matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining; - matchedFillResults.right.makerAssetFilledAmount = matchedFillResults.left.takerAssetFilledAmount; - // Round up to ensure the maker's exchange rate does not exceed the price specified by the order. - // We favor the maker when the exchange rate must be rounded. - matchedFillResults.right.takerAssetFilledAmount = safeGetPartialAmountCeil( - rightOrder.takerAssetAmount, - rightOrder.makerAssetAmount, - matchedFillResults.right.makerAssetFilledAmount - ); - } - - // Calculate amount given to taker - matchedFillResults.leftMakerAssetSpreadAmount = safeSub( - matchedFillResults.left.makerAssetFilledAmount, - matchedFillResults.right.takerAssetFilledAmount - ); - - // Compute fees for left order - matchedFillResults.left.makerFeePaid = safeGetPartialAmountFloor( - matchedFillResults.left.makerAssetFilledAmount, - leftOrder.makerAssetAmount, - leftOrder.makerFee - ); - matchedFillResults.left.takerFeePaid = safeGetPartialAmountFloor( - matchedFillResults.left.takerAssetFilledAmount, - leftOrder.takerAssetAmount, - leftOrder.takerFee - ); - - // Compute fees for right order - matchedFillResults.right.makerFeePaid = safeGetPartialAmountFloor( - matchedFillResults.right.makerAssetFilledAmount, - rightOrder.makerAssetAmount, - rightOrder.makerFee - ); - matchedFillResults.right.takerFeePaid = safeGetPartialAmountFloor( - matchedFillResults.right.takerAssetFilledAmount, - rightOrder.takerAssetAmount, - rightOrder.takerFee - ); - - // Return fill results - return matchedFillResults; - } - - /// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient. - /// @param leftOrder First matched order. - /// @param rightOrder Second matched order. - /// @param takerAddress Address that matched the orders. The taker receives the spread between orders as profit. - /// @param matchedFillResults Struct holding amounts to transfer between makers, taker, and fee recipients. - function settleMatchedOrders( - LibOrder.Order memory leftOrder, - LibOrder.Order memory rightOrder, - address takerAddress, - LibFillResults.MatchedFillResults memory matchedFillResults - ) - private - { - bytes memory zrxAssetData = ZRX_ASSET_DATA; - // Order makers and taker - dispatchTransferFrom( - leftOrder.makerAssetData, - leftOrder.makerAddress, - rightOrder.makerAddress, - matchedFillResults.right.takerAssetFilledAmount - ); - dispatchTransferFrom( - rightOrder.makerAssetData, - rightOrder.makerAddress, - leftOrder.makerAddress, - matchedFillResults.left.takerAssetFilledAmount - ); - dispatchTransferFrom( - leftOrder.makerAssetData, - leftOrder.makerAddress, - takerAddress, - matchedFillResults.leftMakerAssetSpreadAmount - ); - - // Maker fees - dispatchTransferFrom( - zrxAssetData, - leftOrder.makerAddress, - leftOrder.feeRecipientAddress, - matchedFillResults.left.makerFeePaid - ); - dispatchTransferFrom( - zrxAssetData, - rightOrder.makerAddress, - rightOrder.feeRecipientAddress, - matchedFillResults.right.makerFeePaid - ); - - // Taker fees - if (leftOrder.feeRecipientAddress == rightOrder.feeRecipientAddress) { - dispatchTransferFrom( - zrxAssetData, - takerAddress, - leftOrder.feeRecipientAddress, - safeAdd( - matchedFillResults.left.takerFeePaid, - matchedFillResults.right.takerFeePaid - ) - ); - } else { - dispatchTransferFrom( - zrxAssetData, - takerAddress, - leftOrder.feeRecipientAddress, - matchedFillResults.left.takerFeePaid - ); - dispatchTransferFrom( - zrxAssetData, - takerAddress, - rightOrder.feeRecipientAddress, - matchedFillResults.right.takerFeePaid - ); - } - } -} diff --git a/contracts/protocol/contracts/protocol/Exchange/MixinSignatureValidator.sol b/contracts/protocol/contracts/protocol/Exchange/MixinSignatureValidator.sol deleted file mode 100644 index b40aa1412..000000000 --- a/contracts/protocol/contracts/protocol/Exchange/MixinSignatureValidator.sol +++ /dev/null @@ -1,324 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity ^0.4.24; - -import "@0x/contracts-utils/contracts/utils/LibBytes/LibBytes.sol"; -import "@0x/contracts-utils/contracts/utils/ReentrancyGuard/ReentrancyGuard.sol"; -import "./mixins/MSignatureValidator.sol"; -import "./mixins/MTransactions.sol"; -import "@0x/contracts-interfaces/contracts/protocol/Exchange/IWallet.sol"; -import "@0x/contracts-interfaces/contracts/protocol/Exchange/IValidator.sol"; - - -contract MixinSignatureValidator is - ReentrancyGuard, - MSignatureValidator, - MTransactions -{ - using LibBytes for bytes; - - // Mapping of hash => signer => signed - mapping (bytes32 => mapping (address => bool)) public preSigned; - - // Mapping of signer => validator => approved - mapping (address => mapping (address => bool)) public allowedValidators; - - /// @dev Approves a hash on-chain using any valid signature type. - /// After presigning a hash, the preSign signature type will become valid for that hash and signer. - /// @param signerAddress Address that should have signed the given hash. - /// @param signature Proof that the hash has been signed by signer. - function preSign( - bytes32 hash, - address signerAddress, - bytes signature - ) - external - { - if (signerAddress != msg.sender) { - require( - isValidSignature( - hash, - signerAddress, - signature - ), - "INVALID_SIGNATURE" - ); - } - preSigned[hash][signerAddress] = true; - } - - /// @dev Approves/unnapproves a Validator contract to verify signatures on signer's behalf. - /// @param validatorAddress Address of Validator contract. - /// @param approval Approval or disapproval of Validator contract. - function setSignatureValidatorApproval( - address validatorAddress, - bool approval - ) - external - nonReentrant - { - address signerAddress = getCurrentContextAddress(); - allowedValidators[signerAddress][validatorAddress] = approval; - emit SignatureValidatorApproval( - signerAddress, - validatorAddress, - approval - ); - } - - /// @dev Verifies that a hash has been signed by the given signer. - /// @param hash Any 32 byte hash. - /// @param signerAddress Address that should have signed the given hash. - /// @param signature Proof that the hash has been signed by signer. - /// @return True if the address recovered from the provided signature matches the input signer address. - function isValidSignature( - bytes32 hash, - address signerAddress, - bytes memory signature - ) - public - view - returns (bool isValid) - { - require( - signature.length > 0, - "LENGTH_GREATER_THAN_0_REQUIRED" - ); - - // Pop last byte off of signature byte array. - uint8 signatureTypeRaw = uint8(signature.popLastByte()); - - // Ensure signature is supported - require( - signatureTypeRaw < uint8(SignatureType.NSignatureTypes), - "SIGNATURE_UNSUPPORTED" - ); - - SignatureType signatureType = SignatureType(signatureTypeRaw); - - // Variables are not scoped in Solidity. - uint8 v; - bytes32 r; - bytes32 s; - address recovered; - - // Always illegal signature. - // This is always an implicit option since a signer can create a - // signature array with invalid type or length. We may as well make - // it an explicit option. This aids testing and analysis. It is - // also the initialization value for the enum type. - if (signatureType == SignatureType.Illegal) { - revert("SIGNATURE_ILLEGAL"); - - // Always invalid signature. - // Like Illegal, this is always implicitly available and therefore - // offered explicitly. It can be implicitly created by providing - // a correctly formatted but incorrect signature. - } else if (signatureType == SignatureType.Invalid) { - require( - signature.length == 0, - "LENGTH_0_REQUIRED" - ); - isValid = false; - return isValid; - - // Signature using EIP712 - } else if (signatureType == SignatureType.EIP712) { - require( - signature.length == 65, - "LENGTH_65_REQUIRED" - ); - v = uint8(signature[0]); - r = signature.readBytes32(1); - s = signature.readBytes32(33); - recovered = ecrecover( - hash, - v, - r, - s - ); - isValid = signerAddress == recovered; - return isValid; - - // Signed using web3.eth_sign - } else if (signatureType == SignatureType.EthSign) { - require( - signature.length == 65, - "LENGTH_65_REQUIRED" - ); - v = uint8(signature[0]); - r = signature.readBytes32(1); - s = signature.readBytes32(33); - recovered = ecrecover( - keccak256(abi.encodePacked( - "\x19Ethereum Signed Message:\n32", - hash - )), - v, - r, - s - ); - isValid = signerAddress == recovered; - return isValid; - - // Signature verified by wallet contract. - // If used with an order, the maker of the order is the wallet contract. - } else if (signatureType == SignatureType.Wallet) { - isValid = isValidWalletSignature( - hash, - signerAddress, - signature - ); - return isValid; - - // Signature verified by validator contract. - // If used with an order, the maker of the order can still be an EOA. - // A signature using this type should be encoded as: - // | Offset | Length | Contents | - // | 0x00 | x | Signature to validate | - // | 0x00 + x | 20 | Address of validator contract | - // | 0x14 + x | 1 | Signature type is always "\x06" | - } else if (signatureType == SignatureType.Validator) { - // Pop last 20 bytes off of signature byte array. - address validatorAddress = signature.popLast20Bytes(); - - // Ensure signer has approved validator. - if (!allowedValidators[signerAddress][validatorAddress]) { - return false; - } - isValid = isValidValidatorSignature( - validatorAddress, - hash, - signerAddress, - signature - ); - return isValid; - - // Signer signed hash previously using the preSign function. - } else if (signatureType == SignatureType.PreSigned) { - isValid = preSigned[hash][signerAddress]; - return isValid; - } - - // Anything else is illegal (We do not return false because - // the signature may actually be valid, just not in a format - // that we currently support. In this case returning false - // may lead the caller to incorrectly believe that the - // signature was invalid.) - revert("SIGNATURE_UNSUPPORTED"); - } - - /// @dev Verifies signature using logic defined by Wallet contract. - /// @param hash Any 32 byte hash. - /// @param walletAddress Address that should have signed the given hash - /// and defines its own signature verification method. - /// @param signature Proof that the hash has been signed by signer. - /// @return True if signature is valid for given wallet.. - function isValidWalletSignature( - bytes32 hash, - address walletAddress, - bytes signature - ) - internal - view - returns (bool isValid) - { - bytes memory callData = abi.encodeWithSelector( - IWallet(walletAddress).isValidSignature.selector, - hash, - signature - ); - assembly { - let cdStart := add(callData, 32) - let success := staticcall( - gas, // forward all gas - walletAddress, // address of Wallet contract - cdStart, // pointer to start of input - mload(callData), // length of input - cdStart, // write output over input - 32 // output size is 32 bytes - ) - - switch success - case 0 { - // Revert with `Error("WALLET_ERROR")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000000c57414c4c45545f4552524f5200000000000000000000000000000000) - mstore(96, 0) - revert(0, 100) - } - case 1 { - // Signature is valid if call did not revert and returned true - isValid := mload(cdStart) - } - } - return isValid; - } - - /// @dev Verifies signature using logic defined by Validator contract. - /// @param validatorAddress Address of validator contract. - /// @param hash Any 32 byte hash. - /// @param signerAddress Address that should have signed the given hash. - /// @param signature Proof that the hash has been signed by signer. - /// @return True if the address recovered from the provided signature matches the input signer address. - function isValidValidatorSignature( - address validatorAddress, - bytes32 hash, - address signerAddress, - bytes signature - ) - internal - view - returns (bool isValid) - { - bytes memory callData = abi.encodeWithSelector( - IValidator(signerAddress).isValidSignature.selector, - hash, - signerAddress, - signature - ); - assembly { - let cdStart := add(callData, 32) - let success := staticcall( - gas, // forward all gas - validatorAddress, // address of Validator contract - cdStart, // pointer to start of input - mload(callData), // length of input - cdStart, // write output over input - 32 // output size is 32 bytes - ) - - switch success - case 0 { - // Revert with `Error("VALIDATOR_ERROR")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000000f56414c494441544f525f4552524f5200000000000000000000000000) - mstore(96, 0) - revert(0, 100) - } - case 1 { - // Signature is valid if call did not revert and returned true - isValid := mload(cdStart) - } - } - return isValid; - } -} diff --git a/contracts/protocol/contracts/protocol/Exchange/MixinTransactions.sol b/contracts/protocol/contracts/protocol/Exchange/MixinTransactions.sol deleted file mode 100644 index 1ac5b1a5e..000000000 --- a/contracts/protocol/contracts/protocol/Exchange/MixinTransactions.sol +++ /dev/null @@ -1,152 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ -pragma solidity ^0.4.24; - -import "@0x/contracts-libs/contracts/libs/LibExchangeErrors.sol"; -import "./mixins/MSignatureValidator.sol"; -import "./mixins/MTransactions.sol"; -import "@0x/contracts-libs/contracts/libs/LibEIP712.sol"; - - -contract MixinTransactions is - LibEIP712, - MSignatureValidator, - MTransactions -{ - // Mapping of transaction hash => executed - // This prevents transactions from being executed more than once. - mapping (bytes32 => bool) public transactions; - - // Address of current transaction signer - address public currentContextAddress; - - /// @dev Executes an exchange method call in the context of signer. - /// @param salt Arbitrary number to ensure uniqueness of transaction hash. - /// @param signerAddress Address of transaction signer. - /// @param data AbiV2 encoded calldata. - /// @param signature Proof of signer transaction by signer. - function executeTransaction( - uint256 salt, - address signerAddress, - bytes data, - bytes signature - ) - external - { - // Prevent reentrancy - require( - currentContextAddress == address(0), - "REENTRANCY_ILLEGAL" - ); - - bytes32 transactionHash = hashEIP712Message(hashZeroExTransaction( - salt, - signerAddress, - data - )); - - // Validate transaction has not been executed - require( - !transactions[transactionHash], - "INVALID_TX_HASH" - ); - - // Transaction always valid if signer is sender of transaction - if (signerAddress != msg.sender) { - // Validate signature - require( - isValidSignature( - transactionHash, - signerAddress, - signature - ), - "INVALID_TX_SIGNATURE" - ); - - // Set the current transaction signer - currentContextAddress = signerAddress; - } - - // Execute transaction - transactions[transactionHash] = true; - require( - address(this).delegatecall(data), - "FAILED_EXECUTION" - ); - - // Reset current transaction signer if it was previously updated - if (signerAddress != msg.sender) { - currentContextAddress = address(0); - } - } - - /// @dev Calculates EIP712 hash of the Transaction. - /// @param salt Arbitrary number to ensure uniqueness of transaction hash. - /// @param signerAddress Address of transaction signer. - /// @param data AbiV2 encoded calldata. - /// @return EIP712 hash of the Transaction. - function hashZeroExTransaction( - uint256 salt, - address signerAddress, - bytes memory data - ) - internal - pure - returns (bytes32 result) - { - bytes32 schemaHash = EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH; - bytes32 dataHash = keccak256(data); - - // Assembly for more efficiently computing: - // keccak256(abi.encodePacked( - // EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH, - // salt, - // bytes32(signerAddress), - // keccak256(data) - // )); - - assembly { - // Load free memory pointer - let memPtr := mload(64) - - mstore(memPtr, schemaHash) // hash of schema - mstore(add(memPtr, 32), salt) // salt - mstore(add(memPtr, 64), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff)) // signerAddress - mstore(add(memPtr, 96), dataHash) // hash of data - - // Compute hash - result := keccak256(memPtr, 128) - } - return result; - } - - /// @dev The current function will be called in the context of this address (either 0x transaction signer or `msg.sender`). - /// If calling a fill function, this address will represent the taker. - /// If calling a cancel function, this address will represent the maker. - /// @return Signer of 0x transaction if entry point is `executeTransaction`. - /// `msg.sender` if entry point is any other function. - function getCurrentContextAddress() - internal - view - returns (address) - { - address currentContextAddress_ = currentContextAddress; - address contextAddress = currentContextAddress_ == address(0) ? msg.sender : currentContextAddress_; - return contextAddress; - } -} diff --git a/contracts/protocol/contracts/protocol/Exchange/MixinWrapperFunctions.sol b/contracts/protocol/contracts/protocol/Exchange/MixinWrapperFunctions.sol deleted file mode 100644 index 6fc6ee999..000000000 --- a/contracts/protocol/contracts/protocol/Exchange/MixinWrapperFunctions.sol +++ /dev/null @@ -1,426 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity ^0.4.24; -pragma experimental ABIEncoderV2; - -import "@0x/contracts-utils/contracts/utils/ReentrancyGuard/ReentrancyGuard.sol"; -import "@0x/contracts-libs/contracts/libs/LibMath.sol"; -import "@0x/contracts-libs/contracts/libs/LibOrder.sol"; -import "@0x/contracts-libs/contracts/libs/LibFillResults.sol"; -import "@0x/contracts-libs/contracts/libs/LibAbiEncoder.sol"; -import "./mixins/MExchangeCore.sol"; -import "./mixins/MWrapperFunctions.sol"; - - -contract MixinWrapperFunctions is - ReentrancyGuard, - LibMath, - LibFillResults, - LibAbiEncoder, - MExchangeCore, - MWrapperFunctions -{ - /// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled. - /// @param order Order struct containing order specifications. - /// @param takerAssetFillAmount Desired amount of takerAsset to sell. - /// @param signature Proof that order has been created by maker. - function fillOrKillOrder( - LibOrder.Order memory order, - uint256 takerAssetFillAmount, - bytes memory signature - ) - public - nonReentrant - returns (FillResults memory fillResults) - { - fillResults = fillOrKillOrderInternal( - order, - takerAssetFillAmount, - signature - ); - return fillResults; - } - - /// @dev Fills the input order. - /// Returns false if the transaction would otherwise revert. - /// @param order Order struct containing order specifications. - /// @param takerAssetFillAmount Desired amount of takerAsset to sell. - /// @param signature Proof that order has been created by maker. - /// @return Amounts filled and fees paid by maker and taker. - function fillOrderNoThrow( - LibOrder.Order memory order, - uint256 takerAssetFillAmount, - bytes memory signature - ) - public - returns (FillResults memory fillResults) - { - // ABI encode calldata for `fillOrder` - bytes memory fillOrderCalldata = abiEncodeFillOrder( - order, - takerAssetFillAmount, - signature - ); - - // Delegate to `fillOrder` and handle any exceptions gracefully - assembly { - let success := delegatecall( - gas, // forward all gas - address, // call address of this contract - add(fillOrderCalldata, 32), // pointer to start of input (skip array length in first 32 bytes) - mload(fillOrderCalldata), // length of input - fillOrderCalldata, // write output over input - 128 // output size is 128 bytes - ) - if success { - mstore(fillResults, mload(fillOrderCalldata)) - mstore(add(fillResults, 32), mload(add(fillOrderCalldata, 32))) - mstore(add(fillResults, 64), mload(add(fillOrderCalldata, 64))) - mstore(add(fillResults, 96), mload(add(fillOrderCalldata, 96))) - } - } - // fillResults values will be 0 by default if call was unsuccessful - return fillResults; - } - - /// @dev Synchronously executes multiple calls of fillOrder. - /// @param orders Array of order specifications. - /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders. - /// @param signatures Proofs that orders have been created by makers. - /// @return Amounts filled and fees paid by makers and taker. - /// NOTE: makerAssetFilledAmount and takerAssetFilledAmount may include amounts filled of different assets. - function batchFillOrders( - LibOrder.Order[] memory orders, - uint256[] memory takerAssetFillAmounts, - bytes[] memory signatures - ) - public - nonReentrant - returns (FillResults memory totalFillResults) - { - uint256 ordersLength = orders.length; - for (uint256 i = 0; i != ordersLength; i++) { - FillResults memory singleFillResults = fillOrderInternal( - orders[i], - takerAssetFillAmounts[i], - signatures[i] - ); - addFillResults(totalFillResults, singleFillResults); - } - return totalFillResults; - } - - /// @dev Synchronously executes multiple calls of fillOrKill. - /// @param orders Array of order specifications. - /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders. - /// @param signatures Proofs that orders have been created by makers. - /// @return Amounts filled and fees paid by makers and taker. - /// NOTE: makerAssetFilledAmount and takerAssetFilledAmount may include amounts filled of different assets. - function batchFillOrKillOrders( - LibOrder.Order[] memory orders, - uint256[] memory takerAssetFillAmounts, - bytes[] memory signatures - ) - public - nonReentrant - returns (FillResults memory totalFillResults) - { - uint256 ordersLength = orders.length; - for (uint256 i = 0; i != ordersLength; i++) { - FillResults memory singleFillResults = fillOrKillOrderInternal( - orders[i], - takerAssetFillAmounts[i], - signatures[i] - ); - addFillResults(totalFillResults, singleFillResults); - } - return totalFillResults; - } - - /// @dev Fills an order with specified parameters and ECDSA signature. - /// Returns false if the transaction would otherwise revert. - /// @param orders Array of order specifications. - /// @param takerAssetFillAmounts Array of desired amounts of takerAsset to sell in orders. - /// @param signatures Proofs that orders have been created by makers. - /// @return Amounts filled and fees paid by makers and taker. - /// NOTE: makerAssetFilledAmount and takerAssetFilledAmount may include amounts filled of different assets. - function batchFillOrdersNoThrow( - LibOrder.Order[] memory orders, - uint256[] memory takerAssetFillAmounts, - bytes[] memory signatures - ) - public - returns (FillResults memory totalFillResults) - { - uint256 ordersLength = orders.length; - for (uint256 i = 0; i != ordersLength; i++) { - FillResults memory singleFillResults = fillOrderNoThrow( - orders[i], - takerAssetFillAmounts[i], - signatures[i] - ); - addFillResults(totalFillResults, singleFillResults); - } - return totalFillResults; - } - - /// @dev Synchronously executes multiple calls of fillOrder until total amount of takerAsset is sold by taker. - /// @param orders Array of order specifications. - /// @param takerAssetFillAmount Desired amount of takerAsset to sell. - /// @param signatures Proofs that orders have been created by makers. - /// @return Amounts filled and fees paid by makers and taker. - function marketSellOrders( - LibOrder.Order[] memory orders, - uint256 takerAssetFillAmount, - bytes[] memory signatures - ) - public - nonReentrant - returns (FillResults memory totalFillResults) - { - bytes memory takerAssetData = orders[0].takerAssetData; - - uint256 ordersLength = orders.length; - for (uint256 i = 0; i != ordersLength; i++) { - - // We assume that asset being sold by taker is the same for each order. - // Rather than passing this in as calldata, we use the takerAssetData from the first order in all later orders. - orders[i].takerAssetData = takerAssetData; - - // Calculate the remaining amount of takerAsset to sell - uint256 remainingTakerAssetFillAmount = safeSub(takerAssetFillAmount, totalFillResults.takerAssetFilledAmount); - - // Attempt to sell the remaining amount of takerAsset - FillResults memory singleFillResults = fillOrderInternal( - orders[i], - remainingTakerAssetFillAmount, - signatures[i] - ); - - // Update amounts filled and fees paid by maker and taker - addFillResults(totalFillResults, singleFillResults); - - // Stop execution if the entire amount of takerAsset has been sold - if (totalFillResults.takerAssetFilledAmount >= takerAssetFillAmount) { - break; - } - } - return totalFillResults; - } - - /// @dev Synchronously executes multiple calls of fillOrder until total amount of takerAsset is sold by taker. - /// Returns false if the transaction would otherwise revert. - /// @param orders Array of order specifications. - /// @param takerAssetFillAmount Desired amount of takerAsset to sell. - /// @param signatures Proofs that orders have been signed by makers. - /// @return Amounts filled and fees paid by makers and taker. - function marketSellOrdersNoThrow( - LibOrder.Order[] memory orders, - uint256 takerAssetFillAmount, - bytes[] memory signatures - ) - public - returns (FillResults memory totalFillResults) - { - bytes memory takerAssetData = orders[0].takerAssetData; - - uint256 ordersLength = orders.length; - for (uint256 i = 0; i != ordersLength; i++) { - - // We assume that asset being sold by taker is the same for each order. - // Rather than passing this in as calldata, we use the takerAssetData from the first order in all later orders. - orders[i].takerAssetData = takerAssetData; - - // Calculate the remaining amount of takerAsset to sell - uint256 remainingTakerAssetFillAmount = safeSub(takerAssetFillAmount, totalFillResults.takerAssetFilledAmount); - - // Attempt to sell the remaining amount of takerAsset - FillResults memory singleFillResults = fillOrderNoThrow( - orders[i], - remainingTakerAssetFillAmount, - signatures[i] - ); - - // Update amounts filled and fees paid by maker and taker - addFillResults(totalFillResults, singleFillResults); - - // Stop execution if the entire amount of takerAsset has been sold - if (totalFillResults.takerAssetFilledAmount >= takerAssetFillAmount) { - break; - } - } - return totalFillResults; - } - - /// @dev Synchronously executes multiple calls of fillOrder until total amount of makerAsset is bought by taker. - /// @param orders Array of order specifications. - /// @param makerAssetFillAmount Desired amount of makerAsset to buy. - /// @param signatures Proofs that orders have been signed by makers. - /// @return Amounts filled and fees paid by makers and taker. - function marketBuyOrders( - LibOrder.Order[] memory orders, - uint256 makerAssetFillAmount, - bytes[] memory signatures - ) - public - nonReentrant - returns (FillResults memory totalFillResults) - { - bytes memory makerAssetData = orders[0].makerAssetData; - - uint256 ordersLength = orders.length; - for (uint256 i = 0; i != ordersLength; i++) { - - // We assume that asset being bought by taker is the same for each order. - // Rather than passing this in as calldata, we copy the makerAssetData from the first order onto all later orders. - orders[i].makerAssetData = makerAssetData; - - // Calculate the remaining amount of makerAsset to buy - uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount); - - // Convert the remaining amount of makerAsset to buy into remaining amount - // of takerAsset to sell, assuming entire amount can be sold in the current order - uint256 remainingTakerAssetFillAmount = getPartialAmountFloor( - orders[i].takerAssetAmount, - orders[i].makerAssetAmount, - remainingMakerAssetFillAmount - ); - - // Attempt to sell the remaining amount of takerAsset - FillResults memory singleFillResults = fillOrderInternal( - orders[i], - remainingTakerAssetFillAmount, - signatures[i] - ); - - // Update amounts filled and fees paid by maker and taker - addFillResults(totalFillResults, singleFillResults); - - // Stop execution if the entire amount of makerAsset has been bought - if (totalFillResults.makerAssetFilledAmount >= makerAssetFillAmount) { - break; - } - } - return totalFillResults; - } - - /// @dev Synchronously executes multiple fill orders in a single transaction until total amount is bought by taker. - /// Returns false if the transaction would otherwise revert. - /// @param orders Array of order specifications. - /// @param makerAssetFillAmount Desired amount of makerAsset to buy. - /// @param signatures Proofs that orders have been signed by makers. - /// @return Amounts filled and fees paid by makers and taker. - function marketBuyOrdersNoThrow( - LibOrder.Order[] memory orders, - uint256 makerAssetFillAmount, - bytes[] memory signatures - ) - public - returns (FillResults memory totalFillResults) - { - bytes memory makerAssetData = orders[0].makerAssetData; - - uint256 ordersLength = orders.length; - for (uint256 i = 0; i != ordersLength; i++) { - - // We assume that asset being bought by taker is the same for each order. - // Rather than passing this in as calldata, we copy the makerAssetData from the first order onto all later orders. - orders[i].makerAssetData = makerAssetData; - - // Calculate the remaining amount of makerAsset to buy - uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount); - - // Convert the remaining amount of makerAsset to buy into remaining amount - // of takerAsset to sell, assuming entire amount can be sold in the current order - uint256 remainingTakerAssetFillAmount = getPartialAmountFloor( - orders[i].takerAssetAmount, - orders[i].makerAssetAmount, - remainingMakerAssetFillAmount - ); - - // Attempt to sell the remaining amount of takerAsset - FillResults memory singleFillResults = fillOrderNoThrow( - orders[i], - remainingTakerAssetFillAmount, - signatures[i] - ); - - // Update amounts filled and fees paid by maker and taker - addFillResults(totalFillResults, singleFillResults); - - // Stop execution if the entire amount of makerAsset has been bought - if (totalFillResults.makerAssetFilledAmount >= makerAssetFillAmount) { - break; - } - } - return totalFillResults; - } - - /// @dev Synchronously cancels multiple orders in a single transaction. - /// @param orders Array of order specifications. - function batchCancelOrders(LibOrder.Order[] memory orders) - public - nonReentrant - { - uint256 ordersLength = orders.length; - for (uint256 i = 0; i != ordersLength; i++) { - cancelOrderInternal(orders[i]); - } - } - - /// @dev Fetches information for all passed in orders. - /// @param orders Array of order specifications. - /// @return Array of OrderInfo instances that correspond to each order. - function getOrdersInfo(LibOrder.Order[] memory orders) - public - view - returns (LibOrder.OrderInfo[] memory) - { - uint256 ordersLength = orders.length; - LibOrder.OrderInfo[] memory ordersInfo = new LibOrder.OrderInfo[](ordersLength); - for (uint256 i = 0; i != ordersLength; i++) { - ordersInfo[i] = getOrderInfo(orders[i]); - } - return ordersInfo; - } - - /// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled. - /// @param order Order struct containing order specifications. - /// @param takerAssetFillAmount Desired amount of takerAsset to sell. - /// @param signature Proof that order has been created by maker. - function fillOrKillOrderInternal( - LibOrder.Order memory order, - uint256 takerAssetFillAmount, - bytes memory signature - ) - internal - returns (FillResults memory fillResults) - { - fillResults = fillOrderInternal( - order, - takerAssetFillAmount, - signature - ); - require( - fillResults.takerAssetFilledAmount == takerAssetFillAmount, - "COMPLETE_FILL_FAILED" - ); - return fillResults; - } -} diff --git a/contracts/protocol/contracts/protocol/Exchange/mixins/MAssetProxyDispatcher.sol b/contracts/protocol/contracts/protocol/Exchange/mixins/MAssetProxyDispatcher.sol deleted file mode 100644 index 05c2c4c0b..000000000 --- a/contracts/protocol/contracts/protocol/Exchange/mixins/MAssetProxyDispatcher.sol +++ /dev/null @@ -1,45 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity ^0.4.24; - -import "@0x/contracts-interfaces/contracts/protocol/Exchange/IAssetProxyDispatcher.sol"; - - -contract MAssetProxyDispatcher is - IAssetProxyDispatcher -{ - // Logs registration of new asset proxy - event AssetProxyRegistered( - bytes4 id, // Id of new registered AssetProxy. - address assetProxy // Address of new registered AssetProxy. - ); - - /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws. - /// @param assetData Byte array encoded for the asset. - /// @param from Address to transfer token from. - /// @param to Address to transfer token to. - /// @param amount Amount of token to transfer. - function dispatchTransferFrom( - bytes memory assetData, - address from, - address to, - uint256 amount - ) - internal; -} diff --git a/contracts/protocol/contracts/protocol/Exchange/mixins/MExchangeCore.sol b/contracts/protocol/contracts/protocol/Exchange/mixins/MExchangeCore.sol deleted file mode 100644 index 09dc491cd..000000000 --- a/contracts/protocol/contracts/protocol/Exchange/mixins/MExchangeCore.sol +++ /dev/null @@ -1,157 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity ^0.4.24; -pragma experimental ABIEncoderV2; - -import "@0x/contracts-libs/contracts/libs/LibOrder.sol"; -import "@0x/contracts-libs/contracts/libs/LibFillResults.sol"; -import "@0x/contracts-interfaces/contracts/protocol/Exchange/IExchangeCore.sol"; - - -contract MExchangeCore is - IExchangeCore -{ - // Fill event is emitted whenever an order is filled. - event Fill( - address indexed makerAddress, // Address that created the order. - address indexed feeRecipientAddress, // Address that received fees. - address takerAddress, // Address that filled the order. - address senderAddress, // Address that called the Exchange contract (msg.sender). - uint256 makerAssetFilledAmount, // Amount of makerAsset sold by maker and bought by taker. - uint256 takerAssetFilledAmount, // Amount of takerAsset sold by taker and bought by maker. - uint256 makerFeePaid, // Amount of ZRX paid to feeRecipient by maker. - uint256 takerFeePaid, // Amount of ZRX paid to feeRecipient by taker. - bytes32 indexed orderHash, // EIP712 hash of order (see LibOrder.getOrderHash). - bytes makerAssetData, // Encoded data specific to makerAsset. - bytes takerAssetData // Encoded data specific to takerAsset. - ); - - // Cancel event is emitted whenever an individual order is cancelled. - event Cancel( - address indexed makerAddress, // Address that created the order. - address indexed feeRecipientAddress, // Address that would have recieved fees if order was filled. - address senderAddress, // Address that called the Exchange contract (msg.sender). - bytes32 indexed orderHash, // EIP712 hash of order (see LibOrder.getOrderHash). - bytes makerAssetData, // Encoded data specific to makerAsset. - bytes takerAssetData // Encoded data specific to takerAsset. - ); - - // CancelUpTo event is emitted whenever `cancelOrdersUpTo` is executed succesfully. - event CancelUpTo( - address indexed makerAddress, // Orders cancelled must have been created by this address. - address indexed senderAddress, // Orders cancelled must have a `senderAddress` equal to this address. - uint256 orderEpoch // Orders with specified makerAddress and senderAddress with a salt less than this value are considered cancelled. - ); - - /// @dev Fills the input order. - /// @param order Order struct containing order specifications. - /// @param takerAssetFillAmount Desired amount of takerAsset to sell. - /// @param signature Proof that order has been created by maker. - /// @return Amounts filled and fees paid by maker and taker. - function fillOrderInternal( - LibOrder.Order memory order, - uint256 takerAssetFillAmount, - bytes memory signature - ) - internal - returns (LibFillResults.FillResults memory fillResults); - - /// @dev After calling, the order can not be filled anymore. - /// @param order Order struct containing order specifications. - function cancelOrderInternal(LibOrder.Order memory order) - internal; - - /// @dev Updates state with results of a fill order. - /// @param order that was filled. - /// @param takerAddress Address of taker who filled the order. - /// @param orderTakerAssetFilledAmount Amount of order already filled. - /// @return fillResults Amounts filled and fees paid by maker and taker. - function updateFilledState( - LibOrder.Order memory order, - address takerAddress, - bytes32 orderHash, - uint256 orderTakerAssetFilledAmount, - LibFillResults.FillResults memory fillResults - ) - internal; - - /// @dev Updates state with results of cancelling an order. - /// State is only updated if the order is currently fillable. - /// Otherwise, updating state would have no effect. - /// @param order that was cancelled. - /// @param orderHash Hash of order that was cancelled. - function updateCancelledState( - LibOrder.Order memory order, - bytes32 orderHash - ) - internal; - - /// @dev Validates context for fillOrder. Succeeds or throws. - /// @param order to be filled. - /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. - /// @param takerAddress Address of order taker. - /// @param signature Proof that the orders was created by its maker. - function assertFillableOrder( - LibOrder.Order memory order, - LibOrder.OrderInfo memory orderInfo, - address takerAddress, - bytes memory signature - ) - internal - view; - - /// @dev Validates context for fillOrder. Succeeds or throws. - /// @param order to be filled. - /// @param orderInfo Status, orderHash, and amount already filled of order. - /// @param takerAssetFillAmount Desired amount of order to fill by taker. - /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. - /// @param makerAssetFilledAmount Amount of makerAsset that will be transfered. - function assertValidFill( - LibOrder.Order memory order, - LibOrder.OrderInfo memory orderInfo, - uint256 takerAssetFillAmount, - uint256 takerAssetFilledAmount, - uint256 makerAssetFilledAmount - ) - internal - view; - - /// @dev Validates context for cancelOrder. Succeeds or throws. - /// @param order to be cancelled. - /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. - function assertValidCancel( - LibOrder.Order memory order, - LibOrder.OrderInfo memory orderInfo - ) - internal - view; - - /// @dev Calculates amounts filled and fees paid by maker and taker. - /// @param order to be filled. - /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. - /// @return fillResults Amounts filled and fees paid by maker and taker. - function calculateFillResults( - LibOrder.Order memory order, - uint256 takerAssetFilledAmount - ) - internal - pure - returns (LibFillResults.FillResults memory fillResults); - -} diff --git a/contracts/protocol/contracts/protocol/Exchange/mixins/MMatchOrders.sol b/contracts/protocol/contracts/protocol/Exchange/mixins/MMatchOrders.sol deleted file mode 100644 index 56ee895c4..000000000 --- a/contracts/protocol/contracts/protocol/Exchange/mixins/MMatchOrders.sol +++ /dev/null @@ -1,58 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ -pragma solidity ^0.4.24; -pragma experimental ABIEncoderV2; - -import "@0x/contracts-libs/contracts/libs/LibOrder.sol"; -import "@0x/contracts-libs/contracts/libs/LibFillResults.sol"; -import "@0x/contracts-interfaces/contracts/protocol/Exchange/IMatchOrders.sol"; - - -contract MMatchOrders is - IMatchOrders -{ - /// @dev Validates context for matchOrders. Succeeds or throws. - /// @param leftOrder First order to match. - /// @param rightOrder Second order to match. - function assertValidMatch( - LibOrder.Order memory leftOrder, - LibOrder.Order memory rightOrder - ) - internal - pure; - - /// @dev Calculates fill amounts for the matched orders. - /// Each order is filled at their respective price point. However, the calculations are - /// carried out as though the orders are both being filled at the right order's price point. - /// The profit made by the leftOrder order goes to the taker (who matched the two orders). - /// @param leftOrder First order to match. - /// @param rightOrder Second order to match. - /// @param leftOrderTakerAssetFilledAmount Amount of left order already filled. - /// @param rightOrderTakerAssetFilledAmount Amount of right order already filled. - /// @param matchedFillResults Amounts to fill and fees to pay by maker and taker of matched orders. - function calculateMatchedFillResults( - LibOrder.Order memory leftOrder, - LibOrder.Order memory rightOrder, - uint256 leftOrderTakerAssetFilledAmount, - uint256 rightOrderTakerAssetFilledAmount - ) - internal - pure - returns (LibFillResults.MatchedFillResults memory matchedFillResults); - -} diff --git a/contracts/protocol/contracts/protocol/Exchange/mixins/MSignatureValidator.sol b/contracts/protocol/contracts/protocol/Exchange/mixins/MSignatureValidator.sol deleted file mode 100644 index 6407760d4..000000000 --- a/contracts/protocol/contracts/protocol/Exchange/mixins/MSignatureValidator.sol +++ /dev/null @@ -1,75 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity ^0.4.24; - -import "@0x/contracts-interfaces/contracts/protocol/Exchange/ISignatureValidator.sol"; - - -contract MSignatureValidator is - ISignatureValidator -{ - event SignatureValidatorApproval( - address indexed signerAddress, // Address that approves or disapproves a contract to verify signatures. - address indexed validatorAddress, // Address of signature validator contract. - bool approved // Approval or disapproval of validator contract. - ); - - // Allowed signature types. - enum SignatureType { - Illegal, // 0x00, default value - Invalid, // 0x01 - EIP712, // 0x02 - EthSign, // 0x03 - Wallet, // 0x04 - Validator, // 0x05 - PreSigned, // 0x06 - NSignatureTypes // 0x07, number of signature types. Always leave at end. - } - - /// @dev Verifies signature using logic defined by Wallet contract. - /// @param hash Any 32 byte hash. - /// @param walletAddress Address that should have signed the given hash - /// and defines its own signature verification method. - /// @param signature Proof that the hash has been signed by signer. - /// @return True if the address recovered from the provided signature matches the input signer address. - function isValidWalletSignature( - bytes32 hash, - address walletAddress, - bytes signature - ) - internal - view - returns (bool isValid); - - /// @dev Verifies signature using logic defined by Validator contract. - /// @param validatorAddress Address of validator contract. - /// @param hash Any 32 byte hash. - /// @param signerAddress Address that should have signed the given hash. - /// @param signature Proof that the hash has been signed by signer. - /// @return True if the address recovered from the provided signature matches the input signer address. - function isValidValidatorSignature( - address validatorAddress, - bytes32 hash, - address signerAddress, - bytes signature - ) - internal - view - returns (bool isValid); -} diff --git a/contracts/protocol/contracts/protocol/Exchange/mixins/MTransactions.sol b/contracts/protocol/contracts/protocol/Exchange/mixins/MTransactions.sol deleted file mode 100644 index 04dd716d7..000000000 --- a/contracts/protocol/contracts/protocol/Exchange/mixins/MTransactions.sol +++ /dev/null @@ -1,58 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ -pragma solidity ^0.4.24; - -import "@0x/contracts-interfaces/contracts/protocol/Exchange/ITransactions.sol"; - - -contract MTransactions is - ITransactions -{ - // Hash for the EIP712 ZeroEx Transaction Schema - bytes32 constant internal EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = keccak256(abi.encodePacked( - "ZeroExTransaction(", - "uint256 salt,", - "address signerAddress,", - "bytes data", - ")" - )); - - /// @dev Calculates EIP712 hash of the Transaction. - /// @param salt Arbitrary number to ensure uniqueness of transaction hash. - /// @param signerAddress Address of transaction signer. - /// @param data AbiV2 encoded calldata. - /// @return EIP712 hash of the Transaction. - function hashZeroExTransaction( - uint256 salt, - address signerAddress, - bytes memory data - ) - internal - pure - returns (bytes32 result); - - /// @dev The current function will be called in the context of this address (either 0x transaction signer or `msg.sender`). - /// If calling a fill function, this address will represent the taker. - /// If calling a cancel function, this address will represent the maker. - /// @return Signer of 0x transaction if entry point is `executeTransaction`. - /// `msg.sender` if entry point is any other function. - function getCurrentContextAddress() - internal - view - returns (address); -} diff --git a/contracts/protocol/contracts/protocol/Exchange/mixins/MWrapperFunctions.sol b/contracts/protocol/contracts/protocol/Exchange/mixins/MWrapperFunctions.sol deleted file mode 100644 index 2ff3b8790..000000000 --- a/contracts/protocol/contracts/protocol/Exchange/mixins/MWrapperFunctions.sol +++ /dev/null @@ -1,41 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity ^0.4.24; -pragma experimental ABIEncoderV2; - -import "@0x/contracts-libs/contracts/libs/LibOrder.sol"; -import "@0x/contracts-libs/contracts/libs/LibFillResults.sol"; -import "@0x/contracts-interfaces/contracts/protocol/Exchange/IWrapperFunctions.sol"; - - -contract MWrapperFunctions is - IWrapperFunctions -{ - /// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled. - /// @param order LibOrder.Order struct containing order specifications. - /// @param takerAssetFillAmount Desired amount of takerAsset to sell. - /// @param signature Proof that order has been created by maker. - function fillOrKillOrderInternal( - LibOrder.Order memory order, - uint256 takerAssetFillAmount, - bytes memory signature - ) - internal - returns (LibFillResults.FillResults memory fillResults); -} diff --git a/contracts/protocol/contracts/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol b/contracts/protocol/contracts/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol deleted file mode 100644 index ad71fc9a1..000000000 --- a/contracts/protocol/contracts/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol +++ /dev/null @@ -1,37 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; - -import "../../protocol/Exchange/MixinAssetProxyDispatcher.sol"; - - -contract TestAssetProxyDispatcher is - MixinAssetProxyDispatcher -{ - function publicDispatchTransferFrom( - bytes memory assetData, - address from, - address to, - uint256 amount - ) - public - { - dispatchTransferFrom(assetData, from, to, amount); - } -} diff --git a/contracts/protocol/contracts/test/TestAssetProxyOwner/TestAssetProxyOwner.sol b/contracts/protocol/contracts/test/TestAssetProxyOwner/TestAssetProxyOwner.sol deleted file mode 100644 index 52c66cb56..000000000 --- a/contracts/protocol/contracts/test/TestAssetProxyOwner/TestAssetProxyOwner.sol +++ /dev/null @@ -1,58 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; - -import "../../protocol/AssetProxyOwner/AssetProxyOwner.sol"; - - -// solhint-disable no-empty-blocks -contract TestAssetProxyOwner is - AssetProxyOwner -{ - constructor ( - address[] memory _owners, - address[] memory _assetProxyContracts, - uint256 _required, - uint256 _secondsTimeLocked - ) - public - AssetProxyOwner(_owners, _assetProxyContracts, _required, _secondsTimeLocked) - {} - - function testValidRemoveAuthorizedAddressAtIndexTx(uint256 id) - public - view - validRemoveAuthorizedAddressAtIndexTx(id) - returns (bool) - { - // Do nothing. We expect reverts through the modifier - return true; - } - - /// @dev Compares first 4 bytes of byte array to `removeAuthorizedAddressAtIndex` function selector. - /// @param data Transaction data. - /// @return Successful if data is a call to `removeAuthorizedAddressAtIndex`. - function isFunctionRemoveAuthorizedAddressAtIndex(bytes memory data) - public - pure - returns (bool) - { - return data.readBytes4(0) == REMOVE_AUTHORIZED_ADDRESS_AT_INDEX_SELECTOR; - } -} diff --git a/contracts/protocol/contracts/test/TestExchangeInternals/TestExchangeInternals.sol b/contracts/protocol/contracts/test/TestExchangeInternals/TestExchangeInternals.sol deleted file mode 100644 index 27187f8f8..000000000 --- a/contracts/protocol/contracts/test/TestExchangeInternals/TestExchangeInternals.sol +++ /dev/null @@ -1,191 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; - -import "../../protocol/Exchange/Exchange.sol"; - - -// solhint-disable no-empty-blocks -contract TestExchangeInternals is - Exchange -{ - constructor () - public - Exchange("") - {} - - /// @dev Adds properties of both FillResults instances. - /// Modifies the first FillResults instance specified. - /// Note that this function has been modified from the original - // internal version to return the FillResults. - /// @param totalFillResults Fill results instance that will be added onto. - /// @param singleFillResults Fill results instance that will be added to totalFillResults. - /// @return newTotalFillResults The result of adding singleFillResults to totalFilResults. - function publicAddFillResults(FillResults memory totalFillResults, FillResults memory singleFillResults) - public - pure - returns (FillResults memory) - { - addFillResults(totalFillResults, singleFillResults); - return totalFillResults; - } - - /// @dev Calculates amounts filled and fees paid by maker and taker. - /// @param order to be filled. - /// @param takerAssetFilledAmount Amount of takerAsset that will be filled. - /// @return fillResults Amounts filled and fees paid by maker and taker. - function publicCalculateFillResults( - Order memory order, - uint256 takerAssetFilledAmount - ) - public - pure - returns (FillResults memory fillResults) - { - return calculateFillResults(order, takerAssetFilledAmount); - } - - /// @dev Calculates partial value given a numerator and denominator. - /// Reverts if rounding error is >= 0.1% - /// @param numerator Numerator. - /// @param denominator Denominator. - /// @param target Value to calculate partial of. - /// @return Partial value of target. - function publicSafeGetPartialAmountFloor( - uint256 numerator, - uint256 denominator, - uint256 target - ) - public - pure - returns (uint256 partialAmount) - { - return safeGetPartialAmountFloor(numerator, denominator, target); - } - - /// @dev Calculates partial value given a numerator and denominator. - /// Reverts if rounding error is >= 0.1% - /// @param numerator Numerator. - /// @param denominator Denominator. - /// @param target Value to calculate partial of. - /// @return Partial value of target. - function publicSafeGetPartialAmountCeil( - uint256 numerator, - uint256 denominator, - uint256 target - ) - public - pure - returns (uint256 partialAmount) - { - return safeGetPartialAmountCeil(numerator, denominator, target); - } - - /// @dev Calculates partial value given a numerator and denominator. - /// @param numerator Numerator. - /// @param denominator Denominator. - /// @param target Value to calculate partial of. - /// @return Partial value of target. - function publicGetPartialAmountFloor( - uint256 numerator, - uint256 denominator, - uint256 target - ) - public - pure - returns (uint256 partialAmount) - { - return getPartialAmountFloor(numerator, denominator, target); - } - - /// @dev Calculates partial value given a numerator and denominator. - /// @param numerator Numerator. - /// @param denominator Denominator. - /// @param target Value to calculate partial of. - /// @return Partial value of target. - function publicGetPartialAmountCeil( - uint256 numerator, - uint256 denominator, - uint256 target - ) - public - pure - returns (uint256 partialAmount) - { - return getPartialAmountCeil(numerator, denominator, target); - } - - /// @dev Checks if rounding error >= 0.1%. - /// @param numerator Numerator. - /// @param denominator Denominator. - /// @param target Value to multiply with numerator/denominator. - /// @return Rounding error is present. - function publicIsRoundingErrorFloor( - uint256 numerator, - uint256 denominator, - uint256 target - ) - public - pure - returns (bool isError) - { - return isRoundingErrorFloor(numerator, denominator, target); - } - - /// @dev Checks if rounding error >= 0.1%. - /// @param numerator Numerator. - /// @param denominator Denominator. - /// @param target Value to multiply with numerator/denominator. - /// @return Rounding error is present. - function publicIsRoundingErrorCeil( - uint256 numerator, - uint256 denominator, - uint256 target - ) - public - pure - returns (bool isError) - { - return isRoundingErrorCeil(numerator, denominator, target); - } - - /// @dev Updates state with results of a fill order. - /// @param order that was filled. - /// @param takerAddress Address of taker who filled the order. - /// @param orderTakerAssetFilledAmount Amount of order already filled. - /// @return fillResults Amounts filled and fees paid by maker and taker. - function publicUpdateFilledState( - Order memory order, - address takerAddress, - bytes32 orderHash, - uint256 orderTakerAssetFilledAmount, - FillResults memory fillResults - ) - public - { - updateFilledState( - order, - takerAddress, - orderHash, - orderTakerAssetFilledAmount, - fillResults - ); - } -} diff --git a/contracts/protocol/contracts/test/TestSignatureValidator/TestSignatureValidator.sol b/contracts/protocol/contracts/test/TestSignatureValidator/TestSignatureValidator.sol deleted file mode 100644 index ea3e2de59..000000000 --- a/contracts/protocol/contracts/test/TestSignatureValidator/TestSignatureValidator.sol +++ /dev/null @@ -1,45 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; - -import "../../protocol/Exchange/MixinSignatureValidator.sol"; -import "../../protocol/Exchange/MixinTransactions.sol"; - - -contract TestSignatureValidator is - MixinSignatureValidator, - MixinTransactions -{ - function publicIsValidSignature( - bytes32 hash, - address signer, - bytes memory signature - ) - public - view - returns (bool isValid) - { - isValid = isValidSignature( - hash, - signer, - signature - ); - return isValid; - } -} diff --git a/contracts/protocol/contracts/test/TestStaticCallReceiver/TestStaticCallReceiver.sol b/contracts/protocol/contracts/test/TestStaticCallReceiver/TestStaticCallReceiver.sol deleted file mode 100644 index d08da7303..000000000 --- a/contracts/protocol/contracts/test/TestStaticCallReceiver/TestStaticCallReceiver.sol +++ /dev/null @@ -1,81 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; - -import "@0x/contracts-tokens/contracts/tokens/ERC20Token/IERC20Token.sol"; - - -// solhint-disable no-unused-vars -contract TestStaticCallReceiver { - - uint256 internal state = 1; - - /// @dev Updates state and returns true. Intended to be used with `Validator` signature type. - /// @param hash Message hash that is signed. - /// @param signerAddress Address that should have signed the given hash. - /// @param signature Proof of signing. - /// @return Validity of order signature. - function isValidSignature( - bytes32 hash, - address signerAddress, - bytes signature - ) - external - returns (bool isValid) - { - updateState(); - return true; - } - - /// @dev Updates state and returns true. Intended to be used with `Wallet` signature type. - /// @param hash Message hash that is signed. - /// @param signature Proof of signing. - /// @return Validity of order signature. - function isValidSignature( - bytes32 hash, - bytes signature - ) - external - returns (bool isValid) - { - updateState(); - return true; - } - - /// @dev Approves an ERC20 token to spend tokens from this address. - /// @param token Address of ERC20 token. - /// @param spender Address that will spend tokens. - /// @param value Amount of tokens spender is approved to spend. - function approveERC20( - address token, - address spender, - uint256 value - ) - external - { - IERC20Token(token).approve(spender, value); - } - - /// @dev Increments state variable. - function updateState() - internal - { - state++; - } -} diff --git a/contracts/protocol/package.json b/contracts/protocol/package.json deleted file mode 100644 index d1ef70a70..000000000 --- a/contracts/protocol/package.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "name": "@0x/contracts-protocol", - "version": "2.2.3", - "engines": { - "node": ">=6.12" - }, - "description": "Smart contract components of 0x protocol", - "main": "lib/src/index.js", - "directories": { - "test": "test" - }, - "scripts": { - "build": "yarn pre_build && tsc -b", - "build:ci": "yarn build", - "pre_build": "run-s compile generate_contract_wrappers", - "test": "yarn run_mocha", - "rebuild_and_test": "run-s build test", - "test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov", - "test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html", - "test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha", - "run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit", - "compile": "sol-compiler", - "watch": "sol-compiler -w", - "clean": "shx rm -rf lib generated-artifacts generated-wrappers", - "generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../../node_modules/@0x/abi-gen-templates/contract.handlebars --partials '../../node_modules/@0x/abi-gen-templates/partials/**/*.handlebars' --output generated-wrappers --backend ethers", - "lint": "tslint --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts", - "coverage:report:text": "istanbul report text", - "coverage:report:html": "istanbul report html && open coverage/index.html", - "profiler:report:html": "istanbul report html && open coverage/index.html", - "coverage:report:lcov": "istanbul report lcov", - "test:circleci": "yarn test", - "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" - }, - "config": { - "abis": "generated-artifacts/@(AssetProxyOwner|ERC20Proxy|ERC721Proxy|Exchange|MixinAuthorizable|MultiAssetProxy|TestSignatureValidator|TestAssetProxyOwner|TestAssetProxyDispatcher|TestExchangeInternals|TestStaticCallReceiver).json" - }, - "repository": { - "type": "git", - "url": "https://github.com/0xProject/0x-monorepo.git" - }, - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/0xProject/0x-monorepo/issues" - }, - "homepage": "https://github.com/0xProject/0x-monorepo/contracts/protocol/README.md", - "devDependencies": { - "@0x/abi-gen": "^1.0.22", - "@0x/dev-utils": "^1.0.24", - "@0x/sol-compiler": "^2.0.2", - "@0x/subproviders": "^2.1.11", - "@0x/tslint-config": "^2.0.2", - "@types/bn.js": "^4.11.0", - "@types/lodash": "4.14.104", - "@types/node": "*", - "@types/yargs": "^10.0.0", - "chai": "^4.0.1", - "chai-as-promised": "^7.1.0", - "chai-bignumber": "^3.0.0", - "dirty-chai": "^2.0.1", - "ethereumjs-abi": "0.6.5", - "make-promises-safe": "^1.1.0", - "mocha": "^4.1.0", - "npm-run-all": "^4.1.2", - "shx": "^0.2.2", - "solhint": "^1.4.1", - "tslint": "5.11.0", - "typescript": "3.0.1", - "yargs": "^10.0.3" - }, - "dependencies": { - "@0x/base-contract": "^3.0.13", - "@0x/contracts-examples": "^1.0.6", - "@0x/contracts-interfaces": "^1.0.6", - "@0x/contracts-libs": "^1.0.6", - "@0x/contracts-multisig": "^1.0.6", - "@0x/contracts-test-utils": "^2.0.1", - "@0x/contracts-tokens": "^1.0.6", - "@0x/contracts-utils": "^1.0.6", - "@0x/order-utils": "^3.1.2", - "@0x/types": "^1.5.2", - "@0x/typescript-typings": "^3.0.8", - "@0x/utils": "^3.0.1", - "@0x/web3-wrapper": "^3.2.4", - "@types/js-combinatorics": "^0.5.29", - "bn.js": "^4.11.8", - "ethereum-types": "^1.1.6", - "ethereumjs-util": "^5.1.1", - "lodash": "^4.17.5" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/contracts/protocol/src/artifacts/index.ts b/contracts/protocol/src/artifacts/index.ts deleted file mode 100644 index c5d12f10b..000000000 --- a/contracts/protocol/src/artifacts/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ContractArtifact } from 'ethereum-types'; - -import * as AssetProxyOwner from '../../generated-artifacts/AssetProxyOwner.json'; -import * as ERC20Proxy from '../../generated-artifacts/ERC20Proxy.json'; -import * as ERC721Proxy from '../../generated-artifacts/ERC721Proxy.json'; -import * as Exchange from '../../generated-artifacts/Exchange.json'; -import * as MixinAuthorizable from '../../generated-artifacts/MixinAuthorizable.json'; -import * as MultiAssetProxy from '../../generated-artifacts/MultiAssetProxy.json'; -import * as TestAssetProxyDispatcher from '../../generated-artifacts/TestAssetProxyDispatcher.json'; -import * as TestAssetProxyOwner from '../../generated-artifacts/TestAssetProxyOwner.json'; -import * as TestExchangeInternals from '../../generated-artifacts/TestExchangeInternals.json'; -import * as TestSignatureValidator from '../../generated-artifacts/TestSignatureValidator.json'; -import * as TestStaticCallReceiver from '../../generated-artifacts/TestStaticCallReceiver.json'; - -export const artifacts = { - AssetProxyOwner: AssetProxyOwner as ContractArtifact, - ERC20Proxy: ERC20Proxy as ContractArtifact, - ERC721Proxy: ERC721Proxy as ContractArtifact, - Exchange: Exchange as ContractArtifact, - MixinAuthorizable: MixinAuthorizable as ContractArtifact, - MultiAssetProxy: MultiAssetProxy as ContractArtifact, - TestAssetProxyDispatcher: TestAssetProxyDispatcher as ContractArtifact, - TestAssetProxyOwner: TestAssetProxyOwner as ContractArtifact, - TestExchangeInternals: TestExchangeInternals as ContractArtifact, - TestSignatureValidator: TestSignatureValidator as ContractArtifact, - TestStaticCallReceiver: TestStaticCallReceiver as ContractArtifact, -}; diff --git a/contracts/protocol/src/index.ts b/contracts/protocol/src/index.ts deleted file mode 100644 index ba813e7ca..000000000 --- a/contracts/protocol/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './artifacts'; -export * from './wrappers'; -export * from '../test/utils'; diff --git a/contracts/protocol/src/wrappers/index.ts b/contracts/protocol/src/wrappers/index.ts deleted file mode 100644 index 01b121054..000000000 --- a/contracts/protocol/src/wrappers/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export * from '../../generated-wrappers/asset_proxy_owner'; -export * from '../../generated-wrappers/erc20_proxy'; -export * from '../../generated-wrappers/erc721_proxy'; -export * from '../../generated-wrappers/exchange'; -export * from '../../generated-wrappers/mixin_authorizable'; -export * from '../../generated-wrappers/test_asset_proxy_dispatcher'; -export * from '../../generated-wrappers/test_asset_proxy_owner'; -export * from '../../generated-wrappers/test_exchange_internals'; -export * from '../../generated-wrappers/test_signature_validator'; -export * from '../../generated-wrappers/test_static_call_receiver'; diff --git a/contracts/protocol/test/asset_proxy/authorizable.ts b/contracts/protocol/test/asset_proxy/authorizable.ts deleted file mode 100644 index 853d18be0..000000000 --- a/contracts/protocol/test/asset_proxy/authorizable.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { - chaiSetup, - constants, - expectTransactionFailedAsync, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { RevertReason } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import * as _ from 'lodash'; - -import { MixinAuthorizableContract } from '../../generated-wrappers/mixin_authorizable'; -import { artifacts } from '../../src/artifacts'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe('Authorizable', () => { - let owner: string; - let notOwner: string; - let address: string; - let authorizable: MixinAuthorizableContract; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - [owner, address, notOwner] = _.slice(accounts, 0, 3); - authorizable = await MixinAuthorizableContract.deployFrom0xArtifactAsync( - artifacts.MixinAuthorizable, - provider, - txDefaults, - ); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('addAuthorizedAddress', () => { - it('should throw if not called by owner', async () => { - return expectTransactionFailedAsync( - authorizable.addAuthorizedAddress.sendTransactionAsync(notOwner, { from: notOwner }), - RevertReason.OnlyContractOwner, - ); - }); - it('should allow owner to add an authorized address', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const isAuthorized = await authorizable.authorized.callAsync(address); - expect(isAuthorized).to.be.true(); - }); - it('should throw if owner attempts to authorize a duplicate address', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - return expectTransactionFailedAsync( - authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), - RevertReason.TargetAlreadyAuthorized, - ); - }); - }); - - describe('removeAuthorizedAddress', () => { - it('should throw if not called by owner', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - return expectTransactionFailedAsync( - authorizable.removeAuthorizedAddress.sendTransactionAsync(address, { - from: notOwner, - }), - RevertReason.OnlyContractOwner, - ); - }); - - it('should allow owner to remove an authorized address', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await authorizable.removeAuthorizedAddress.sendTransactionAsync(address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const isAuthorized = await authorizable.authorized.callAsync(address); - expect(isAuthorized).to.be.false(); - }); - - it('should throw if owner attempts to remove an address that is not authorized', async () => { - return expectTransactionFailedAsync( - authorizable.removeAuthorizedAddress.sendTransactionAsync(address, { - from: owner, - }), - RevertReason.TargetNotAuthorized, - ); - }); - }); - - describe('removeAuthorizedAddressAtIndex', () => { - it('should throw if not called by owner', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const index = new BigNumber(0); - return expectTransactionFailedAsync( - authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, { - from: notOwner, - }), - RevertReason.OnlyContractOwner, - ); - }); - it('should throw if index is >= authorities.length', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const index = new BigNumber(1); - return expectTransactionFailedAsync( - authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, { - from: owner, - }), - RevertReason.IndexOutOfBounds, - ); - }); - it('should throw if owner attempts to remove an address that is not authorized', async () => { - const index = new BigNumber(0); - return expectTransactionFailedAsync( - authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, { - from: owner, - }), - RevertReason.TargetNotAuthorized, - ); - }); - it('should throw if address at index does not match target', async () => { - const address1 = address; - const address2 = notOwner; - await web3Wrapper.awaitTransactionSuccessAsync( - await authorizable.addAuthorizedAddress.sendTransactionAsync(address1, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await authorizable.addAuthorizedAddress.sendTransactionAsync(address2, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const address1Index = new BigNumber(0); - return expectTransactionFailedAsync( - authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address2, address1Index, { - from: owner, - }), - RevertReason.AuthorizedAddressMismatch, - ); - }); - it('should allow owner to remove an authorized address', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const index = new BigNumber(0); - await web3Wrapper.awaitTransactionSuccessAsync( - await authorizable.removeAuthorizedAddressAtIndex.sendTransactionAsync(address, index, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const isAuthorized = await authorizable.authorized.callAsync(address); - expect(isAuthorized).to.be.false(); - }); - }); - - describe('getAuthorizedAddresses', () => { - it('should return all authorized addresses', async () => { - const initial = await authorizable.getAuthorizedAddresses.callAsync(); - expect(initial).to.have.length(0); - await web3Wrapper.awaitTransactionSuccessAsync( - await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const afterAdd = await authorizable.getAuthorizedAddresses.callAsync(); - expect(afterAdd).to.have.length(1); - expect(afterAdd).to.include(address); - - await web3Wrapper.awaitTransactionSuccessAsync( - await authorizable.removeAuthorizedAddress.sendTransactionAsync(address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const afterRemove = await authorizable.getAuthorizedAddresses.callAsync(); - expect(afterRemove).to.have.length(0); - }); - }); -}); diff --git a/contracts/protocol/test/asset_proxy/proxies.ts b/contracts/protocol/test/asset_proxy/proxies.ts deleted file mode 100644 index c34be9c6a..000000000 --- a/contracts/protocol/test/asset_proxy/proxies.ts +++ /dev/null @@ -1,1285 +0,0 @@ -import { artifacts as interfacesArtifacts, IAssetDataContract, IAssetProxyContract } from '@0x/contracts-interfaces'; -import { - chaiSetup, - constants, - expectTransactionFailedAsync, - expectTransactionFailedWithoutReasonAsync, - LogDecoder, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { - artifacts as tokensArtifacts, - DummyERC20TokenContract, - DummyERC20TokenTransferEventArgs, - DummyERC721ReceiverContract, - DummyERC721TokenContract, - DummyMultipleReturnERC20TokenContract, - DummyNoReturnERC20TokenContract, -} from '@0x/contracts-tokens'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { RevertReason } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import { LogWithDecodedArgs } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; -import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; -import { MultiAssetProxyContract } from '../../generated-wrappers/multi_asset_proxy'; -import { artifacts } from '../../src/artifacts'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -const assetProxyInterface = new IAssetProxyContract( - interfacesArtifacts.IAssetProxy.compilerOutput.abi, - constants.NULL_ADDRESS, - provider, -); -const assetDataInterface = new IAssetDataContract( - interfacesArtifacts.IAssetData.compilerOutput.abi, - constants.NULL_ADDRESS, - provider, -); - -// tslint:disable:no-unnecessary-type-assertion -describe('Asset Transfer Proxies', () => { - let owner: string; - let notAuthorized: string; - let authorized: string; - let fromAddress: string; - let toAddress: string; - - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - let erc721TokenA: DummyERC721TokenContract; - let erc721TokenB: DummyERC721TokenContract; - let erc721Receiver: DummyERC721ReceiverContract; - let erc20Proxy: ERC20ProxyContract; - let erc721Proxy: ERC721ProxyContract; - let noReturnErc20Token: DummyNoReturnERC20TokenContract; - let multipleReturnErc20Token: DummyMultipleReturnERC20TokenContract; - let multiAssetProxy: MultiAssetProxyContract; - - let erc20Wrapper: ERC20Wrapper; - let erc721Wrapper: ERC721Wrapper; - let erc721AFromTokenId: BigNumber; - let erc721BFromTokenId: BigNumber; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, notAuthorized, authorized, fromAddress, toAddress] = _.slice(accounts, 0, 5)); - - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - - // Deploy AssetProxies - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - erc721Proxy = await erc721Wrapper.deployProxyAsync(); - multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync( - artifacts.MultiAssetProxy, - provider, - txDefaults, - ); - - // Configure ERC20Proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - // Configure ERC721Proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(authorized, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - // Configure MultiAssetProxy - await web3Wrapper.awaitTransactionSuccessAsync( - await multiAssetProxy.addAuthorizedAddress.sendTransactionAsync(authorized, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc721Proxy.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - // Deploy and configure ERC20 tokens - const numDummyErc20ToDeploy = 2; - [erc20TokenA, erc20TokenB] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - noReturnErc20Token = await DummyNoReturnERC20TokenContract.deployFrom0xArtifactAsync( - tokensArtifacts.DummyNoReturnERC20Token, - provider, - txDefaults, - constants.DUMMY_TOKEN_NAME, - constants.DUMMY_TOKEN_SYMBOL, - constants.DUMMY_TOKEN_DECIMALS, - constants.DUMMY_TOKEN_TOTAL_SUPPLY, - ); - multipleReturnErc20Token = await DummyMultipleReturnERC20TokenContract.deployFrom0xArtifactAsync( - tokensArtifacts.DummyMultipleReturnERC20Token, - provider, - txDefaults, - constants.DUMMY_TOKEN_NAME, - constants.DUMMY_TOKEN_SYMBOL, - constants.DUMMY_TOKEN_DECIMALS, - constants.DUMMY_TOKEN_TOTAL_SUPPLY, - ); - - await erc20Wrapper.setBalancesAndAllowancesAsync(); - await web3Wrapper.awaitTransactionSuccessAsync( - await noReturnErc20Token.setBalance.sendTransactionAsync(fromAddress, constants.INITIAL_ERC20_BALANCE), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await noReturnErc20Token.approve.sendTransactionAsync( - erc20Proxy.address, - constants.INITIAL_ERC20_ALLOWANCE, - { from: fromAddress }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await multipleReturnErc20Token.setBalance.sendTransactionAsync( - fromAddress, - constants.INITIAL_ERC20_BALANCE, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await multipleReturnErc20Token.approve.sendTransactionAsync( - erc20Proxy.address, - constants.INITIAL_ERC20_ALLOWANCE, - { from: fromAddress }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - // Deploy and configure ERC721 tokens and receiver - [erc721TokenA, erc721TokenB] = await erc721Wrapper.deployDummyTokensAsync(); - erc721Receiver = await DummyERC721ReceiverContract.deployFrom0xArtifactAsync( - tokensArtifacts.DummyERC721Receiver, - provider, - txDefaults, - ); - - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - erc721AFromTokenId = erc721Balances[fromAddress][erc721TokenA.address][0]; - erc721BFromTokenId = erc721Balances[fromAddress][erc721TokenB.address][0]; - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - - describe('ERC20Proxy', () => { - it('should revert if undefined function is called', async () => { - const undefinedSelector = '0x01020304'; - await expectTransactionFailedWithoutReasonAsync( - web3Wrapper.sendTransactionAsync({ - from: owner, - to: erc20Proxy.address, - value: constants.ZERO_AMOUNT, - data: undefinedSelector, - }), - ); - }); - it('should have an id of 0xf47261b0', async () => { - const proxyId = await erc20Proxy.getProxyId.callAsync(); - const expectedProxyId = '0xf47261b0'; - expect(proxyId).to.equal(expectedProxyId); - }); - describe('transferFrom', () => { - it('should successfully transfer tokens', async () => { - // Construct ERC20 asset data - const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); - // Perform a transfer from fromAddress to toAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const amount = new BigNumber(10); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - encodedAssetData, - fromAddress, - toAddress, - amount, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: erc20Proxy.address, - data, - from: authorized, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Verify transfer was successful - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( - erc20Balances[fromAddress][erc20TokenA.address].minus(amount), - ); - expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( - erc20Balances[toAddress][erc20TokenA.address].plus(amount), - ); - }); - - it('should successfully transfer tokens that do not return a value', async () => { - // Construct ERC20 asset data - const encodedAssetData = assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address); - // Perform a transfer from fromAddress to toAddress - const initialFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress); - const initialToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress); - const amount = new BigNumber(10); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - encodedAssetData, - fromAddress, - toAddress, - amount, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: erc20Proxy.address, - data, - from: authorized, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Verify transfer was successful - const newFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress); - const newToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress); - expect(newFromBalance).to.be.bignumber.equal(initialFromBalance.minus(amount)); - expect(newToBalance).to.be.bignumber.equal(initialToBalance.plus(amount)); - }); - - it('should successfully transfer tokens and ignore extra assetData', async () => { - // Construct ERC20 asset data - const extraData = '0102030405060708'; - const encodedAssetData = `${assetDataUtils.encodeERC20AssetData(erc20TokenA.address)}${extraData}`; - // Perform a transfer from fromAddress to toAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const amount = new BigNumber(10); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - encodedAssetData, - fromAddress, - toAddress, - amount, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: erc20Proxy.address, - data, - from: authorized, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Verify transfer was successful - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( - erc20Balances[fromAddress][erc20TokenA.address].minus(amount), - ); - expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( - erc20Balances[toAddress][erc20TokenA.address].plus(amount), - ); - }); - - it('should do nothing if transferring 0 amount of a token', async () => { - // Construct ERC20 asset data - const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); - // Perform a transfer from fromAddress to toAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const amount = new BigNumber(0); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - encodedAssetData, - fromAddress, - toAddress, - amount, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: erc20Proxy.address, - data, - from: authorized, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Verify transfer was successful - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( - erc20Balances[fromAddress][erc20TokenA.address], - ); - expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( - erc20Balances[toAddress][erc20TokenA.address], - ); - }); - - it('should revert if allowances are too low', async () => { - // Construct ERC20 asset data - const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); - // Create allowance less than transfer amount. Set allowance on proxy. - const allowance = new BigNumber(0); - const amount = new BigNumber(10); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - encodedAssetData, - fromAddress, - toAddress, - amount, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, allowance, { - from: fromAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - // Perform a transfer; expect this to fail. - await expectTransactionFailedAsync( - web3Wrapper.sendTransactionAsync({ - to: erc20Proxy.address, - data, - from: authorized, - }), - RevertReason.TransferFailed, - ); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.deep.equal(erc20Balances); - }); - - it('should revert if allowances are too low and token does not return a value', async () => { - // Construct ERC20 asset data - const encodedAssetData = assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address); - // Create allowance less than transfer amount. Set allowance on proxy. - const allowance = new BigNumber(0); - const amount = new BigNumber(10); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - encodedAssetData, - fromAddress, - toAddress, - amount, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await noReturnErc20Token.approve.sendTransactionAsync(erc20Proxy.address, allowance, { - from: fromAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const initialFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress); - const initialToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress); - // Perform a transfer; expect this to fail. - await expectTransactionFailedAsync( - web3Wrapper.sendTransactionAsync({ - to: erc20Proxy.address, - data, - from: authorized, - }), - RevertReason.TransferFailed, - ); - const newFromBalance = await noReturnErc20Token.balanceOf.callAsync(fromAddress); - const newToBalance = await noReturnErc20Token.balanceOf.callAsync(toAddress); - expect(newFromBalance).to.be.bignumber.equal(initialFromBalance); - expect(newToBalance).to.be.bignumber.equal(initialToBalance); - }); - - it('should revert if caller is not authorized', async () => { - // Construct ERC20 asset data - const encodedAssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); - // Perform a transfer from fromAddress to toAddress - const amount = new BigNumber(10); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - encodedAssetData, - fromAddress, - toAddress, - amount, - ); - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - await expectTransactionFailedAsync( - web3Wrapper.sendTransactionAsync({ - to: erc20Proxy.address, - data, - from: notAuthorized, - }), - RevertReason.SenderNotAuthorized, - ); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.deep.equal(erc20Balances); - }); - - it('should revert if token returns more than 32 bytes', async () => { - // Construct ERC20 asset data - const encodedAssetData = assetDataUtils.encodeERC20AssetData(multipleReturnErc20Token.address); - const amount = new BigNumber(10); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - encodedAssetData, - fromAddress, - toAddress, - amount, - ); - const initialFromBalance = await multipleReturnErc20Token.balanceOf.callAsync(fromAddress); - const initialToBalance = await multipleReturnErc20Token.balanceOf.callAsync(toAddress); - // Perform a transfer; expect this to fail. - await expectTransactionFailedAsync( - web3Wrapper.sendTransactionAsync({ - to: erc20Proxy.address, - data, - from: authorized, - }), - RevertReason.TransferFailed, - ); - const newFromBalance = await multipleReturnErc20Token.balanceOf.callAsync(fromAddress); - const newToBalance = await multipleReturnErc20Token.balanceOf.callAsync(toAddress); - expect(newFromBalance).to.be.bignumber.equal(initialFromBalance); - expect(newToBalance).to.be.bignumber.equal(initialToBalance); - }); - }); - }); - - describe('ERC721Proxy', () => { - it('should revert if undefined function is called', async () => { - const undefinedSelector = '0x01020304'; - await expectTransactionFailedWithoutReasonAsync( - web3Wrapper.sendTransactionAsync({ - from: owner, - to: erc721Proxy.address, - value: constants.ZERO_AMOUNT, - data: undefinedSelector, - }), - ); - }); - it('should have an id of 0x02571792', async () => { - const proxyId = await erc721Proxy.getProxyId.callAsync(); - const expectedProxyId = '0x02571792'; - expect(proxyId).to.equal(expectedProxyId); - }); - describe('transferFrom', () => { - it('should successfully transfer tokens', async () => { - // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); - // Verify pre-condition - const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(ownerFromAsset).to.be.equal(fromAddress); - // Perform a transfer from fromAddress to toAddress - const amount = new BigNumber(1); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - encodedAssetData, - fromAddress, - toAddress, - amount, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: erc721Proxy.address, - data, - from: authorized, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Verify transfer was successful - const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(newOwnerFromAsset).to.be.bignumber.equal(toAddress); - }); - - it('should successfully transfer tokens and ignore extra assetData', async () => { - // Construct ERC721 asset data - const extraData = '0102030405060708'; - const encodedAssetData = `${assetDataUtils.encodeERC721AssetData( - erc721TokenA.address, - erc721AFromTokenId, - )}${extraData}`; - // Verify pre-condition - const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(ownerFromAsset).to.be.equal(fromAddress); - // Perform a transfer from fromAddress to toAddress - const amount = new BigNumber(1); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - encodedAssetData, - fromAddress, - toAddress, - amount, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: erc721Proxy.address, - data, - from: authorized, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Verify transfer was successful - const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(newOwnerFromAsset).to.be.bignumber.equal(toAddress); - }); - - it('should not call onERC721Received when transferring to a smart contract', async () => { - // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); - // Verify pre-condition - const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(ownerFromAsset).to.be.equal(fromAddress); - // Perform a transfer from fromAddress to toAddress - const amount = new BigNumber(1); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - encodedAssetData, - fromAddress, - erc721Receiver.address, - amount, - ); - const logDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...tokensArtifacts }); - const tx = await logDecoder.getTxWithDecodedLogsAsync( - await web3Wrapper.sendTransactionAsync({ - to: erc721Proxy.address, - data, - from: authorized, - gas: constants.MAX_TRANSFER_FROM_GAS, - }), - ); - // Verify that no log was emitted by erc721 receiver - expect(tx.logs.length).to.be.equal(1); - // Verify transfer was successful - const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(newOwnerFromAsset).to.be.bignumber.equal(erc721Receiver.address); - }); - - it('should revert if transferring 0 amount of a token', async () => { - // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); - // Verify pre-condition - const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(ownerFromAsset).to.be.equal(fromAddress); - // Perform a transfer from fromAddress to toAddress - const amount = new BigNumber(0); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - encodedAssetData, - fromAddress, - toAddress, - amount, - ); - await expectTransactionFailedAsync( - web3Wrapper.sendTransactionAsync({ - to: erc721Proxy.address, - data, - from: authorized, - }), - RevertReason.InvalidAmount, - ); - const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(newOwner).to.be.equal(ownerFromAsset); - }); - - it('should revert if transferring > 1 amount of a token', async () => { - // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); - // Verify pre-condition - const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(ownerFromAsset).to.be.equal(fromAddress); - // Perform a transfer from fromAddress to toAddress - const amount = new BigNumber(500); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - encodedAssetData, - fromAddress, - toAddress, - amount, - ); - await expectTransactionFailedAsync( - web3Wrapper.sendTransactionAsync({ - to: erc721Proxy.address, - data, - from: authorized, - }), - RevertReason.InvalidAmount, - ); - const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(newOwner).to.be.equal(ownerFromAsset); - }); - - it('should revert if allowances are too low', async () => { - // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); - // Verify pre-condition - const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(ownerFromAsset).to.be.equal(fromAddress); - // Remove transfer approval for fromAddress. - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721TokenA.approve.sendTransactionAsync(constants.NULL_ADDRESS, erc721AFromTokenId, { - from: fromAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Perform a transfer; expect this to fail. - const amount = new BigNumber(1); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - encodedAssetData, - fromAddress, - toAddress, - amount, - ); - await expectTransactionFailedAsync( - web3Wrapper.sendTransactionAsync({ - to: erc721Proxy.address, - data, - from: authorized, - }), - RevertReason.TransferFailed, - ); - const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(newOwner).to.be.equal(ownerFromAsset); - }); - - it('should revert if caller is not authorized', async () => { - // Construct ERC721 asset data - const encodedAssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); - // Verify pre-condition - const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(ownerFromAsset).to.be.equal(fromAddress); - // Perform a transfer from fromAddress to toAddress - const amount = new BigNumber(1); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - encodedAssetData, - fromAddress, - toAddress, - amount, - ); - await expectTransactionFailedAsync( - web3Wrapper.sendTransactionAsync({ - to: erc721Proxy.address, - data, - from: notAuthorized, - }), - RevertReason.SenderNotAuthorized, - ); - const newOwner = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(newOwner).to.be.equal(ownerFromAsset); - }); - }); - }); - describe('MultiAssetProxy', () => { - it('should revert if undefined function is called', async () => { - const undefinedSelector = '0x01020304'; - await expectTransactionFailedWithoutReasonAsync( - web3Wrapper.sendTransactionAsync({ - from: owner, - to: multiAssetProxy.address, - value: constants.ZERO_AMOUNT, - data: undefinedSelector, - }), - ); - }); - it('should have an id of 0x94cfcdd7', async () => { - const proxyId = await multiAssetProxy.getProxyId.callAsync(); - // first 4 bytes of `keccak256('MultiAsset(uint256[],bytes[])')` - const expectedProxyId = '0x94cfcdd7'; - expect(proxyId).to.equal(expectedProxyId); - }); - describe('transferFrom', () => { - it('should transfer a single ERC20 token', async () => { - const inputAmount = new BigNumber(1); - const erc20Amount = new BigNumber(10); - const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); - const amounts = [erc20Amount]; - const nestedAssetData = [erc20AssetData]; - const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - 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), - ); - }); - it('should dispatch an ERC20 transfer when input amount is 0', async () => { - const inputAmount = constants.ZERO_AMOUNT; - const erc20Amount = new BigNumber(10); - const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); - const amounts = [erc20Amount]; - const nestedAssetData = [erc20AssetData]; - const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const logDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...tokensArtifacts }); - const tx = await logDecoder.getTxWithDecodedLogsAsync( - await web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - }), - ); - expect(tx.logs.length).to.be.equal(1); - const log = tx.logs[0] as LogWithDecodedArgs; - const transferEventName = 'Transfer'; - expect(log.event).to.equal(transferEventName); - expect(log.args._value).to.be.bignumber.equal(constants.ZERO_AMOUNT); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.deep.equal(erc20Balances); - }); - it('should successfully transfer multiple of the same ERC20 token', async () => { - const inputAmount = new BigNumber(1); - const erc20Amount1 = new BigNumber(10); - const erc20Amount2 = new BigNumber(20); - const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); - const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); - const amounts = [erc20Amount1, erc20Amount2]; - const nestedAssetData = [erc20AssetData1, erc20AssetData2]; - const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const totalAmount = inputAmount.times(erc20Amount1).plus(inputAmount.times(erc20Amount2)); - 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), - ); - }); - it('should successfully transfer multiple different ERC20 tokens', async () => { - const inputAmount = new BigNumber(1); - const erc20Amount1 = new BigNumber(10); - const erc20Amount2 = new BigNumber(20); - const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); - const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address); - const amounts = [erc20Amount1, erc20Amount2]; - const nestedAssetData = [erc20AssetData1, erc20AssetData2]; - const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const totalErc20AAmount = inputAmount.times(erc20Amount1); - const totalErc20BAmount = inputAmount.times(erc20Amount2); - expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( - erc20Balances[fromAddress][erc20TokenA.address].minus(totalErc20AAmount), - ); - expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( - erc20Balances[toAddress][erc20TokenA.address].plus(totalErc20AAmount), - ); - expect(newBalances[fromAddress][erc20TokenB.address]).to.be.bignumber.equal( - erc20Balances[fromAddress][erc20TokenB.address].minus(totalErc20BAmount), - ); - expect(newBalances[toAddress][erc20TokenB.address]).to.be.bignumber.equal( - erc20Balances[toAddress][erc20TokenB.address].plus(totalErc20BAmount), - ); - }); - it('should transfer a single ERC721 token', async () => { - const inputAmount = new BigNumber(1); - const erc721Amount = new BigNumber(1); - const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); - const amounts = [erc721Amount]; - const nestedAssetData = [erc721AssetData]; - const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(ownerFromAsset).to.be.equal(fromAddress); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const newOwnerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(newOwnerFromAsset).to.be.equal(toAddress); - }); - it('should successfully transfer multiple of the same ERC721 token', async () => { - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - const erc721AFromTokenId2 = erc721Balances[fromAddress][erc721TokenA.address][1]; - const erc721AssetData1 = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); - const erc721AssetData2 = assetDataUtils.encodeERC721AssetData( - erc721TokenA.address, - erc721AFromTokenId2, - ); - const inputAmount = new BigNumber(1); - const erc721Amount = new BigNumber(1); - const amounts = [erc721Amount, erc721Amount]; - const nestedAssetData = [erc721AssetData1, erc721AssetData2]; - const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - const ownerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(ownerFromAsset1).to.be.equal(fromAddress); - const ownerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2); - expect(ownerFromAsset2).to.be.equal(fromAddress); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - gas: constants.MAX_TRANSFER_FROM_GAS, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const newOwnerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - const newOwnerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2); - expect(newOwnerFromAsset1).to.be.equal(toAddress); - expect(newOwnerFromAsset2).to.be.equal(toAddress); - }); - it('should successfully transfer multiple different ERC721 tokens', async () => { - const erc721AssetData1 = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); - const erc721AssetData2 = assetDataUtils.encodeERC721AssetData(erc721TokenB.address, erc721BFromTokenId); - const inputAmount = new BigNumber(1); - const erc721Amount = new BigNumber(1); - const amounts = [erc721Amount, erc721Amount]; - const nestedAssetData = [erc721AssetData1, erc721AssetData2]; - const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - const ownerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(ownerFromAsset1).to.be.equal(fromAddress); - const ownerFromAsset2 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId); - expect(ownerFromAsset2).to.be.equal(fromAddress); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - gas: constants.MAX_TRANSFER_FROM_GAS, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const newOwnerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - const newOwnerFromAsset2 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId); - expect(newOwnerFromAsset1).to.be.equal(toAddress); - expect(newOwnerFromAsset2).to.be.equal(toAddress); - }); - it('should successfully transfer a combination of ERC20 and ERC721 tokens', async () => { - 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 amounts = [erc20Amount, erc721Amount]; - const nestedAssetData = [erc20AssetData, erc721AssetData]; - const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(ownerFromAsset).to.be.equal(fromAddress); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - 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); - }); - it('should successfully transfer tokens and ignore extra assetData', async () => { - 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 amounts = [erc20Amount, erc721Amount]; - const nestedAssetData = [erc20AssetData, erc721AssetData]; - const extraData = '0102030405060708'; - const assetData = `${assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData)}${extraData}`; - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const ownerFromAsset = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(ownerFromAsset).to.be.equal(fromAddress); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - 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); - }); - it('should successfully transfer correct amounts when the `amount` > 1', async () => { - const inputAmount = new BigNumber(100); - const erc20Amount1 = new BigNumber(10); - const erc20Amount2 = new BigNumber(20); - const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); - const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address); - const amounts = [erc20Amount1, erc20Amount2]; - const nestedAssetData = [erc20AssetData1, erc20AssetData2]; - const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const totalErc20AAmount = inputAmount.times(erc20Amount1); - const totalErc20BAmount = inputAmount.times(erc20Amount2); - expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( - erc20Balances[fromAddress][erc20TokenA.address].minus(totalErc20AAmount), - ); - expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( - erc20Balances[toAddress][erc20TokenA.address].plus(totalErc20AAmount), - ); - expect(newBalances[fromAddress][erc20TokenB.address]).to.be.bignumber.equal( - erc20Balances[fromAddress][erc20TokenB.address].minus(totalErc20BAmount), - ); - expect(newBalances[toAddress][erc20TokenB.address]).to.be.bignumber.equal( - erc20Balances[toAddress][erc20TokenB.address].plus(totalErc20BAmount), - ); - }); - it('should successfully transfer a large amount of tokens', async () => { - const inputAmount = new BigNumber(1); - const erc20Amount1 = new BigNumber(10); - const erc20Amount2 = new BigNumber(20); - const erc20AssetData1 = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); - const erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20TokenB.address); - const erc721Amount = new BigNumber(1); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - const erc721AFromTokenId2 = erc721Balances[fromAddress][erc721TokenA.address][1]; - const erc721BFromTokenId2 = erc721Balances[fromAddress][erc721TokenB.address][1]; - const erc721AssetData1 = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); - const erc721AssetData2 = assetDataUtils.encodeERC721AssetData( - erc721TokenA.address, - erc721AFromTokenId2, - ); - const erc721AssetData3 = assetDataUtils.encodeERC721AssetData(erc721TokenB.address, erc721BFromTokenId); - const erc721AssetData4 = assetDataUtils.encodeERC721AssetData( - erc721TokenB.address, - erc721BFromTokenId2, - ); - const amounts = [erc721Amount, erc20Amount1, erc721Amount, erc20Amount2, erc721Amount, erc721Amount]; - const nestedAssetData = [ - erc721AssetData1, - erc20AssetData1, - erc721AssetData2, - erc20AssetData2, - erc721AssetData3, - erc721AssetData4, - ]; - const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - const ownerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - expect(ownerFromAsset1).to.be.equal(fromAddress); - const ownerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2); - expect(ownerFromAsset2).to.be.equal(fromAddress); - const ownerFromAsset3 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId); - expect(ownerFromAsset3).to.be.equal(fromAddress); - const ownerFromAsset4 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId2); - expect(ownerFromAsset4).to.be.equal(fromAddress); - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - await web3Wrapper.awaitTransactionSuccessAsync( - await web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - gas: constants.MAX_EXECUTE_TRANSACTION_GAS, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const newOwnerFromAsset1 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId); - const newOwnerFromAsset2 = await erc721TokenA.ownerOf.callAsync(erc721AFromTokenId2); - const newOwnerFromAsset3 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId); - const newOwnerFromAsset4 = await erc721TokenB.ownerOf.callAsync(erc721BFromTokenId2); - expect(newOwnerFromAsset1).to.be.equal(toAddress); - expect(newOwnerFromAsset2).to.be.equal(toAddress); - expect(newOwnerFromAsset3).to.be.equal(toAddress); - expect(newOwnerFromAsset4).to.be.equal(toAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const totalErc20AAmount = inputAmount.times(erc20Amount1); - const totalErc20BAmount = inputAmount.times(erc20Amount2); - expect(newBalances[fromAddress][erc20TokenA.address]).to.be.bignumber.equal( - erc20Balances[fromAddress][erc20TokenA.address].minus(totalErc20AAmount), - ); - expect(newBalances[toAddress][erc20TokenA.address]).to.be.bignumber.equal( - erc20Balances[toAddress][erc20TokenA.address].plus(totalErc20AAmount), - ); - expect(newBalances[fromAddress][erc20TokenB.address]).to.be.bignumber.equal( - erc20Balances[fromAddress][erc20TokenB.address].minus(totalErc20BAmount), - ); - expect(newBalances[toAddress][erc20TokenB.address]).to.be.bignumber.equal( - erc20Balances[toAddress][erc20TokenB.address].plus(totalErc20BAmount), - ); - }); - it('should revert if a single transfer fails', async () => { - const inputAmount = new BigNumber(1); - const erc20Amount = new BigNumber(10); - const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); - // 2 is an invalid erc721 amount - const erc721Amount = new BigNumber(2); - const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); - const amounts = [erc20Amount, erc721Amount]; - const nestedAssetData = [erc20AssetData, erc721AssetData]; - const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - await expectTransactionFailedAsync( - web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - }), - RevertReason.InvalidAmount, - ); - }); - it('should revert if an AssetProxy is not registered', async () => { - 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 invalidProxyId = '0x12345678'; - const invalidErc721AssetData = `${invalidProxyId}${erc721AssetData.slice(10)}`; - const amounts = [erc20Amount, erc721Amount]; - const nestedAssetData = [erc20AssetData, invalidErc721AssetData]; - // HACK: This is used to get around validation built into assetDataUtils - const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - await expectTransactionFailedAsync( - web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - }), - RevertReason.AssetProxyDoesNotExist, - ); - }); - it('should revert if the length of `amounts` does not match the length of `nestedAssetData`', async () => { - const inputAmount = new BigNumber(1); - const erc20Amount = new BigNumber(10); - const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); - const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721TokenA.address, erc721AFromTokenId); - const amounts = [erc20Amount]; - const nestedAssetData = [erc20AssetData, erc721AssetData]; - // HACK: This is used to get around validation built into assetDataUtils - const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - await expectTransactionFailedAsync( - web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - }), - RevertReason.LengthMismatch, - ); - }); - it('should revert if amounts multiplication results in an overflow', async () => { - const inputAmount = new BigNumber(2).pow(128); - const erc20Amount = new BigNumber(2).pow(128); - const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); - const amounts = [erc20Amount]; - const nestedAssetData = [erc20AssetData]; - const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - await expectTransactionFailedAsync( - web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - }), - RevertReason.Uint256Overflow, - ); - }); - it('should revert if an element of `nestedAssetData` is < 4 bytes long', async () => { - const inputAmount = new BigNumber(1); - const erc20Amount = new BigNumber(10); - const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20TokenA.address); - const erc721Amount = new BigNumber(1); - const erc721AssetData = '0x123456'; - const amounts = [erc20Amount, erc721Amount]; - const nestedAssetData = [erc20AssetData, erc721AssetData]; - // HACK: This is used to get around validation built into assetDataUtils - const assetData = assetDataInterface.MultiAsset.getABIEncodedTransactionData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - await expectTransactionFailedAsync( - web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: authorized, - }), - RevertReason.LengthGreaterThan3Required, - ); - }); - it('should revert if caller is not authorized', async () => { - 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 amounts = [erc20Amount, erc721Amount]; - const nestedAssetData = [erc20AssetData, erc721AssetData]; - const assetData = assetDataUtils.encodeMultiAssetData(amounts, nestedAssetData); - const data = assetProxyInterface.transferFrom.getABIEncodedTransactionData( - assetData, - fromAddress, - toAddress, - inputAmount, - ); - await expectTransactionFailedAsync( - web3Wrapper.sendTransactionAsync({ - to: multiAssetProxy.address, - data, - from: notAuthorized, - }), - RevertReason.SenderNotAuthorized, - ); - }); - }); - }); -}); -// tslint:enable:no-unnecessary-type-assertion -// tslint:disable:max-file-line-count diff --git a/contracts/protocol/test/exchange/core.ts b/contracts/protocol/test/exchange/core.ts deleted file mode 100644 index f88be1c94..000000000 --- a/contracts/protocol/test/exchange/core.ts +++ /dev/null @@ -1,1157 +0,0 @@ -import { - chaiSetup, - constants, - ERC20BalancesByOwner, - expectTransactionFailedAsync, - getLatestBlockTimestampAsync, - increaseTimeAndMineBlockAsync, - OrderFactory, - OrderStatus, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { - artifacts as tokensArtifacts, - DummyERC20TokenContract, - DummyERC20TokenTransferEventArgs, - DummyERC721TokenContract, - DummyNoReturnERC20TokenContract, - ReentrantERC20TokenContract, -} from '@0x/contracts-tokens'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; -import { RevertReason, SignatureType, SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import { LogWithDecodedArgs } from 'ethereum-types'; -import ethUtil = require('ethereumjs-util'); -import * as _ from 'lodash'; - -import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; -import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; -import { ExchangeCancelEventArgs, ExchangeContract } from '../../generated-wrappers/exchange'; -import { MultiAssetProxyContract } from '../../generated-wrappers/multi_asset_proxy'; -import { TestStaticCallReceiverContract } from '../../generated-wrappers/test_static_call_receiver'; -import { artifacts } from '../../src/artifacts'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -// tslint:disable:no-unnecessary-type-assertion -describe('Exchange core', () => { - let makerAddress: string; - let owner: string; - let takerAddress: string; - let feeRecipientAddress: string; - - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - let zrxToken: DummyERC20TokenContract; - let erc721Token: DummyERC721TokenContract; - let noReturnErc20Token: DummyNoReturnERC20TokenContract; - let reentrantErc20Token: ReentrantERC20TokenContract; - let exchange: ExchangeContract; - let erc20Proxy: ERC20ProxyContract; - let erc721Proxy: ERC721ProxyContract; - let multiAssetProxy: MultiAssetProxyContract; - let maliciousWallet: TestStaticCallReceiverContract; - let maliciousValidator: TestStaticCallReceiverContract; - - let signedOrder: SignedOrder; - let erc20Balances: ERC20BalancesByOwner; - let exchangeWrapper: ExchangeWrapper; - let erc20Wrapper: ERC20Wrapper; - let erc721Wrapper: ERC721Wrapper; - let orderFactory: OrderFactory; - - let erc721MakerAssetIds: BigNumber[]; - let erc721TakerAssetIds: BigNumber[]; - - let defaultMakerAssetAddress: string; - let defaultTakerAssetAddress: string; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = _.slice(accounts, 0, 4)); - - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - - // Deploy AssetProxies, Exchange, tokens, and malicious contracts - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - erc721Proxy = await erc721Wrapper.deployProxyAsync(); - multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync( - artifacts.MultiAssetProxy, - provider, - txDefaults, - ); - const numDummyErc20ToDeploy = 3; - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - exchange = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - assetDataUtils.encodeERC20AssetData(zrxToken.address), - ); - maliciousWallet = maliciousValidator = await TestStaticCallReceiverContract.deployFrom0xArtifactAsync( - artifacts.TestStaticCallReceiver, - provider, - txDefaults, - ); - reentrantErc20Token = await ReentrantERC20TokenContract.deployFrom0xArtifactAsync( - tokensArtifacts.ReentrantERC20Token, - provider, - txDefaults, - exchange.address, - ); - - // Configure ERC20Proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - // Configure ERC721Proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(multiAssetProxy.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - // Configure MultiAssetProxy - await web3Wrapper.awaitTransactionSuccessAsync( - await multiAssetProxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await multiAssetProxy.registerAssetProxy.sendTransactionAsync(erc721Proxy.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - // Configure Exchange - exchangeWrapper = new ExchangeWrapper(exchange, provider); - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(multiAssetProxy.address, owner); - - // Configure ERC20 tokens - await erc20Wrapper.setBalancesAndAllowancesAsync(); - - // Configure ERC721 tokens - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address]; - erc721TakerAssetIds = erc721Balances[takerAddress][erc721Token.address]; - - // Configure order defaults - defaultMakerAssetAddress = erc20TokenA.address; - defaultTakerAssetAddress = erc20TokenB.address; - const defaultOrderParams = { - ...constants.STATIC_ORDER_PARAMS, - exchangeAddress: exchange.address, - makerAddress, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('fillOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - signedOrder = await orderFactory.newSignedOrderAsync(); - }); - - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow fillOrder to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - RevertReason.TransferFailed, - ); - }); - }); - }; - describe('fillOrder reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should throw if signature is invalid', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - - const v = ethUtil.toBuffer(signedOrder.signature.slice(0, 4)); - const invalidR = ethUtil.sha3('invalidR'); - const invalidS = ethUtil.sha3('invalidS'); - const signatureType = ethUtil.toBuffer(`0x${signedOrder.signature.slice(-2)}`); - const invalidSigBuff = Buffer.concat([v, invalidR, invalidS, signatureType]); - const invalidSigHex = `0x${invalidSigBuff.toString('hex')}`; - signedOrder.signature = invalidSigHex; - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - RevertReason.InvalidOrderSignature, - ); - }); - - it('should throw if no value is filled', async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('should revert if `isValidSignature` tries to update state when SignatureType=Wallet', async () => { - const maliciousMakerAddress = maliciousWallet.address; - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20TokenA.setBalance.sendTransactionAsync( - maliciousMakerAddress, - constants.INITIAL_ERC20_BALANCE, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await maliciousWallet.approveERC20.sendTransactionAsync( - erc20TokenA.address, - erc20Proxy.address, - constants.INITIAL_ERC20_ALLOWANCE, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAddress: maliciousMakerAddress, - makerFee: constants.ZERO_AMOUNT, - }); - signedOrder.signature = `0x0${SignatureType.Wallet}`; - await expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - RevertReason.WalletError, - ); - }); - - it('should revert if `isValidSignature` tries to update state when SignatureType=Validator', async () => { - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await exchange.setSignatureValidatorApproval.sendTransactionAsync( - maliciousValidator.address, - isApproved, - { from: makerAddress }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - signedOrder.signature = `${maliciousValidator.address}0${SignatureType.Validator}`; - await expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - RevertReason.ValidatorError, - ); - }); - - it('should not emit transfer events for transfers where from == to', async () => { - const txReceipt = await exchangeWrapper.fillOrderAsync(signedOrder, makerAddress); - const logs = txReceipt.logs; - const transferLogs = _.filter( - logs, - log => (log as LogWithDecodedArgs).event === 'Transfer', - ); - expect(transferLogs.length).to.be.equal(2); - expect((transferLogs[0] as LogWithDecodedArgs).address).to.be.equal( - zrxToken.address, - ); - expect((transferLogs[0] as LogWithDecodedArgs).args._from).to.be.equal( - makerAddress, - ); - expect((transferLogs[0] as LogWithDecodedArgs).args._to).to.be.equal( - feeRecipientAddress, - ); - expect( - (transferLogs[0] as LogWithDecodedArgs).args._value, - ).to.be.bignumber.equal(signedOrder.makerFee); - expect((transferLogs[1] as LogWithDecodedArgs).address).to.be.equal( - zrxToken.address, - ); - expect((transferLogs[1] as LogWithDecodedArgs).args._from).to.be.equal( - makerAddress, - ); - expect((transferLogs[1] as LogWithDecodedArgs).args._to).to.be.equal( - feeRecipientAddress, - ); - expect( - (transferLogs[1] as LogWithDecodedArgs).args._value, - ).to.be.bignumber.equal(signedOrder.takerFee); - }); - }); - - describe('Testing exchange of ERC20 tokens with no return values', () => { - before(async () => { - noReturnErc20Token = await DummyNoReturnERC20TokenContract.deployFrom0xArtifactAsync( - tokensArtifacts.DummyNoReturnERC20Token, - provider, - txDefaults, - constants.DUMMY_TOKEN_NAME, - constants.DUMMY_TOKEN_SYMBOL, - constants.DUMMY_TOKEN_DECIMALS, - constants.DUMMY_TOKEN_TOTAL_SUPPLY, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await noReturnErc20Token.setBalance.sendTransactionAsync(makerAddress, constants.INITIAL_ERC20_BALANCE), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await noReturnErc20Token.approve.sendTransactionAsync( - erc20Proxy.address, - constants.INITIAL_ERC20_ALLOWANCE, - { from: makerAddress }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - }); - it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - }); - - const initialMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const initialTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const initialFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); - - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - - const finalMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const finalTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const finalFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); - - expect(finalMakerBalanceA).to.be.bignumber.equal(initialMakerBalanceA.minus(signedOrder.makerAssetAmount)); - expect(finalMakerBalanceB).to.be.bignumber.equal(initialMakerBalanceB.plus(signedOrder.takerAssetAmount)); - expect(finalTakerBalanceA).to.be.bignumber.equal(initialTakerBalanceA.plus(signedOrder.makerAssetAmount)); - expect(finalTakerBalanceB).to.be.bignumber.equal(initialTakerBalanceB.minus(signedOrder.takerAssetAmount)); - expect(finalMakerZrxBalance).to.be.bignumber.equal(initialMakerZrxBalance.minus(signedOrder.makerFee)); - expect(finalTakerZrxBalance).to.be.bignumber.equal(initialTakerZrxBalance.minus(signedOrder.takerFee)); - expect(finalFeeRecipientZrxBalance).to.be.bignumber.equal( - initialFeeRecipientZrxBalance.plus(signedOrder.makerFee.plus(signedOrder.takerFee)), - ); - }); - it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - }); - - const initialMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const initialTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const initialFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); - - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - - const finalMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const finalTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const finalFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); - - expect(finalMakerBalanceA).to.be.bignumber.equal(initialMakerBalanceA.minus(signedOrder.makerAssetAmount)); - expect(finalMakerBalanceB).to.be.bignumber.equal(initialMakerBalanceB.plus(signedOrder.takerAssetAmount)); - expect(finalTakerBalanceA).to.be.bignumber.equal(initialTakerBalanceA.plus(signedOrder.makerAssetAmount)); - expect(finalTakerBalanceB).to.be.bignumber.equal(initialTakerBalanceB.minus(signedOrder.takerAssetAmount)); - expect(finalMakerZrxBalance).to.be.bignumber.equal(initialMakerZrxBalance.minus(signedOrder.makerFee)); - expect(finalTakerZrxBalance).to.be.bignumber.equal(initialTakerZrxBalance.minus(signedOrder.takerFee)); - expect(finalFeeRecipientZrxBalance).to.be.bignumber.equal( - initialFeeRecipientZrxBalance.plus(signedOrder.makerFee.plus(signedOrder.takerFee)), - ); - }); - it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(noReturnErc20Token.address), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), - }); - - const initialMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const initialTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const initialFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); - - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - - const finalMakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(makerAddress); - const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const finalTakerBalanceA = await noReturnErc20Token.balanceOf.callAsync(takerAddress); - const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const finalFeeRecipientZrxBalance = await zrxToken.balanceOf.callAsync(feeRecipientAddress); - - expect(finalMakerBalanceA).to.be.bignumber.equal(initialMakerBalanceA.minus(signedOrder.makerAssetAmount)); - expect(finalMakerBalanceB).to.be.bignumber.equal(initialMakerBalanceB.plus(signedOrder.takerAssetAmount)); - expect(finalTakerBalanceA).to.be.bignumber.equal(initialTakerBalanceA.plus(signedOrder.makerAssetAmount)); - expect(finalTakerBalanceB).to.be.bignumber.equal(initialTakerBalanceB.minus(signedOrder.takerAssetAmount)); - expect(finalMakerZrxBalance).to.be.bignumber.equal(initialMakerZrxBalance.minus(signedOrder.makerFee)); - expect(finalTakerZrxBalance).to.be.bignumber.equal(initialTakerZrxBalance.minus(signedOrder.takerFee)); - expect(finalFeeRecipientZrxBalance).to.be.bignumber.equal( - initialFeeRecipientZrxBalance.plus(signedOrder.makerFee.plus(signedOrder.takerFee)), - ); - }); - }); - - describe('cancelOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - signedOrder = await orderFactory.newSignedOrderAsync(); - }); - - it('should throw if not sent by maker', async () => { - return expectTransactionFailedAsync( - exchangeWrapper.cancelOrderAsync(signedOrder, takerAddress), - RevertReason.InvalidMaker, - ); - }); - - it('should throw if makerAssetAmount is 0', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(0), - }); - - return expectTransactionFailedAsync( - exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('should throw if takerAssetAmount is 0', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - takerAssetAmount: new BigNumber(0), - }); - - return expectTransactionFailedAsync( - exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('should be able to cancel a full order', async () => { - await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount: signedOrder.takerAssetAmount.div(2), - }), - RevertReason.OrderUnfillable, - ); - }); - - it('should log 1 event with correct arguments', async () => { - const res = await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); - expect(res.logs).to.have.length(1); - - const log = res.logs[0] as LogWithDecodedArgs; - const logArgs = log.args; - - expect(signedOrder.makerAddress).to.be.equal(logArgs.makerAddress); - expect(signedOrder.makerAddress).to.be.equal(logArgs.senderAddress); - expect(signedOrder.feeRecipientAddress).to.be.equal(logArgs.feeRecipientAddress); - expect(signedOrder.makerAssetData).to.be.equal(logArgs.makerAssetData); - expect(signedOrder.takerAssetData).to.be.equal(logArgs.takerAssetData); - expect(orderHashUtils.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash); - }); - - it('should throw if already cancelled', async () => { - await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); - return expectTransactionFailedAsync( - exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('should throw if order is expired', async () => { - const currentTimestamp = await getLatestBlockTimestampAsync(); - signedOrder = await orderFactory.newSignedOrderAsync({ - expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10), - }); - return expectTransactionFailedAsync( - exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('should throw if rounding error is greater than 0.1%', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1001), - takerAssetAmount: new BigNumber(3), - }); - - const fillTakerAssetAmount1 = new BigNumber(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount: fillTakerAssetAmount1, - }); - - const fillTakerAssetAmount2 = new BigNumber(1); - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount: fillTakerAssetAmount2, - }), - RevertReason.RoundingError, - ); - }); - }); - - describe('cancelOrdersUpTo', () => { - it('should fail to set orderEpoch less than current orderEpoch', async () => { - const orderEpoch = new BigNumber(1); - await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress); - const lesserOrderEpoch = new BigNumber(0); - return expectTransactionFailedAsync( - exchangeWrapper.cancelOrdersUpToAsync(lesserOrderEpoch, makerAddress), - RevertReason.InvalidNewOrderEpoch, - ); - }); - - it('should fail to set orderEpoch equal to existing orderEpoch', async () => { - const orderEpoch = new BigNumber(1); - await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress); - return expectTransactionFailedAsync( - exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress), - RevertReason.InvalidNewOrderEpoch, - ); - }); - - it('should cancel only orders with a orderEpoch less than existing orderEpoch', async () => { - // Cancel all transactions with a orderEpoch less than 1 - const orderEpoch = new BigNumber(1); - await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress); - - // Create 3 orders with orderEpoch values: 0,1,2,3 - // Since we cancelled with orderEpoch=1, orders with orderEpoch<=1 will not be processed - erc20Balances = await erc20Wrapper.getBalancesAsync(); - const signedOrders = [ - await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(9), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(9), 18), - salt: new BigNumber(0), - }), - await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(79), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(79), 18), - salt: new BigNumber(1), - }), - await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(979), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(979), 18), - salt: new BigNumber(2), - }), - await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(7979), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(7979), 18), - salt: new BigNumber(3), - }), - ]; - await exchangeWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress, { - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 600000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - const fillMakerAssetAmount = signedOrders[2].makerAssetAmount.plus(signedOrders[3].makerAssetAmount); - const fillTakerAssetAmount = signedOrders[2].takerAssetAmount.plus(signedOrders[3].takerAssetAmount); - const makerFee = signedOrders[2].makerFee.plus(signedOrders[3].makerFee); - const takerFee = signedOrders[2].takerFee.plus(signedOrders[3].takerFee); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(fillMakerAssetAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].plus(fillTakerAssetAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFee), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(fillTakerAssetAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(fillMakerAssetAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), - ); - }); - }); - - describe('Testing Exchange of ERC721 Tokens', () => { - it('should throw when maker does not own the token with id makerAssetId', async () => { - // Construct Exchange parameters - const makerAssetId = erc721TakerAssetIds[0]; - const takerAssetId = erc721TakerAssetIds[1]; - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - takerAssetAmount: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), - }); - // Verify pre-conditions - const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(initialOwnerMakerAsset).to.be.bignumber.not.equal(makerAddress); - const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); - // Call Exchange - const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - RevertReason.TransferFailed, - ); - }); - - it('should throw when taker does not own the token with id takerAssetId', async () => { - // Construct Exchange parameters - const makerAssetId = erc721MakerAssetIds[0]; - const takerAssetId = erc721MakerAssetIds[1]; - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - takerAssetAmount: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), - }); - // Verify pre-conditions - const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress); - const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(initialOwnerTakerAsset).to.be.bignumber.not.equal(takerAddress); - // Call Exchange - const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - RevertReason.TransferFailed, - ); - }); - - it('should throw when makerAssetAmount is greater than 1', async () => { - // Construct Exchange parameters - const makerAssetId = erc721MakerAssetIds[0]; - const takerAssetId = erc721TakerAssetIds[0]; - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(2), - takerAssetAmount: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), - }); - // Verify pre-conditions - const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress); - const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); - // Call Exchange - const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - RevertReason.InvalidAmount, - ); - }); - - it('should throw when takerAssetAmount is greater than 1', async () => { - // Construct Exchange parameters - const makerAssetId = erc721MakerAssetIds[0]; - const takerAssetId = erc721TakerAssetIds[0]; - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - takerAssetAmount: new BigNumber(500), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), - }); - // Verify pre-conditions - const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress); - const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); - // Call Exchange - const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - RevertReason.InvalidAmount, - ); - }); - - it('should throw on partial fill', async () => { - // Construct Exchange parameters - const makerAssetId = erc721MakerAssetIds[0]; - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - }); - // Call Exchange - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - RevertReason.RoundingError, - ); - }); - }); - - describe('Testing exchange of multiple assets', () => { - it('should allow multiple assets to be exchanged for a single asset', async () => { - const makerAmounts = [new BigNumber(10), new BigNumber(20)]; - const makerNestedAssetData = [ - assetDataUtils.encodeERC20AssetData(erc20TokenA.address), - assetDataUtils.encodeERC20AssetData(erc20TokenB.address), - ]; - const makerAssetData = assetDataUtils.encodeMultiAssetData(makerAmounts, makerNestedAssetData); - const makerAssetAmount = new BigNumber(1); - const takerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - const takerAssetAmount = new BigNumber(10); - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData, - takerAssetData, - makerAssetAmount, - takerAssetAmount, - makerFee: constants.ZERO_AMOUNT, - takerFee: constants.ZERO_AMOUNT, - }); - - const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - - const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - - expect(finalMakerBalanceA).to.be.bignumber.equal( - initialMakerBalanceA.minus(makerAmounts[0].times(makerAssetAmount)), - ); - expect(finalMakerBalanceB).to.be.bignumber.equal( - initialMakerBalanceB.minus(makerAmounts[1].times(makerAssetAmount)), - ); - expect(finalMakerZrxBalance).to.be.bignumber.equal(initialMakerZrxBalance.plus(takerAssetAmount)); - expect(finalTakerBalanceA).to.be.bignumber.equal( - initialTakerBalanceA.plus(makerAmounts[0].times(makerAssetAmount)), - ); - expect(finalTakerBalanceB).to.be.bignumber.equal( - initialTakerBalanceB.plus(makerAmounts[1].times(makerAssetAmount)), - ); - expect(finalTakerZrxBalance).to.be.bignumber.equal(initialTakerZrxBalance.minus(takerAssetAmount)); - }); - it('should allow multiple assets to be exchanged for multiple assets', async () => { - const makerAmounts = [new BigNumber(10), new BigNumber(20)]; - const makerNestedAssetData = [ - assetDataUtils.encodeERC20AssetData(erc20TokenA.address), - assetDataUtils.encodeERC20AssetData(erc20TokenB.address), - ]; - const makerAssetData = assetDataUtils.encodeMultiAssetData(makerAmounts, makerNestedAssetData); - const makerAssetAmount = new BigNumber(1); - const takerAmounts = [new BigNumber(10), new BigNumber(1)]; - const takerAssetId = erc721TakerAssetIds[0]; - const takerNestedAssetData = [ - assetDataUtils.encodeERC20AssetData(zrxToken.address), - assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), - ]; - const takerAssetData = assetDataUtils.encodeMultiAssetData(takerAmounts, takerNestedAssetData); - const takerAssetAmount = new BigNumber(1); - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData, - takerAssetData, - makerAssetAmount, - takerAssetAmount, - makerFee: constants.ZERO_AMOUNT, - takerFee: constants.ZERO_AMOUNT, - }); - - const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); - - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - - const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - const finalOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - - expect(finalMakerBalanceA).to.be.bignumber.equal( - initialMakerBalanceA.minus(makerAmounts[0].times(makerAssetAmount)), - ); - expect(finalMakerBalanceB).to.be.bignumber.equal( - initialMakerBalanceB.minus(makerAmounts[1].times(makerAssetAmount)), - ); - expect(finalMakerZrxBalance).to.be.bignumber.equal( - initialMakerZrxBalance.plus(takerAmounts[0].times(takerAssetAmount)), - ); - expect(finalTakerBalanceA).to.be.bignumber.equal( - initialTakerBalanceA.plus(makerAmounts[0].times(makerAssetAmount)), - ); - expect(finalTakerBalanceB).to.be.bignumber.equal( - initialTakerBalanceB.plus(makerAmounts[1].times(makerAssetAmount)), - ); - expect(finalTakerZrxBalance).to.be.bignumber.equal( - initialTakerZrxBalance.minus(takerAmounts[0].times(takerAssetAmount)), - ); - expect(finalOwnerTakerAsset).to.be.equal(makerAddress); - }); - it('should allow an order selling multiple assets to be partially filled', async () => { - const makerAmounts = [new BigNumber(10), new BigNumber(20)]; - const makerNestedAssetData = [ - assetDataUtils.encodeERC20AssetData(erc20TokenA.address), - assetDataUtils.encodeERC20AssetData(erc20TokenB.address), - ]; - const makerAssetData = assetDataUtils.encodeMultiAssetData(makerAmounts, makerNestedAssetData); - const makerAssetAmount = new BigNumber(30); - const takerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - const takerAssetAmount = new BigNumber(10); - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData, - takerAssetData, - makerAssetAmount, - takerAssetAmount, - makerFee: constants.ZERO_AMOUNT, - takerFee: constants.ZERO_AMOUNT, - }); - - const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - - const takerAssetFillAmount = takerAssetAmount.dividedToIntegerBy(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount, - }); - - const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - - expect(finalMakerBalanceA).to.be.bignumber.equal( - initialMakerBalanceA.minus( - makerAmounts[0].times( - makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalMakerBalanceB).to.be.bignumber.equal( - initialMakerBalanceB.minus( - makerAmounts[1].times( - makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalMakerZrxBalance).to.be.bignumber.equal( - initialMakerZrxBalance.plus( - takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ); - expect(finalTakerBalanceA).to.be.bignumber.equal( - initialTakerBalanceA.plus( - makerAmounts[0].times( - makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalTakerBalanceB).to.be.bignumber.equal( - initialTakerBalanceB.plus( - makerAmounts[1].times( - makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalTakerZrxBalance).to.be.bignumber.equal( - initialTakerZrxBalance.minus( - takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ); - }); - it('should allow an order buying multiple assets to be partially filled', async () => { - const takerAmounts = [new BigNumber(10), new BigNumber(20)]; - const takerNestedAssetData = [ - assetDataUtils.encodeERC20AssetData(erc20TokenA.address), - assetDataUtils.encodeERC20AssetData(erc20TokenB.address), - ]; - const takerAssetData = assetDataUtils.encodeMultiAssetData(takerAmounts, takerNestedAssetData); - const takerAssetAmount = new BigNumber(30); - const makerAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - const makerAssetAmount = new BigNumber(10); - signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData, - takerAssetData, - makerAssetAmount, - takerAssetAmount, - makerFee: constants.ZERO_AMOUNT, - takerFee: constants.ZERO_AMOUNT, - }); - - const initialMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const initialMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const initialMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const initialTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const initialTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const initialTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - - const takerAssetFillAmount = takerAssetAmount.dividedToIntegerBy(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount, - }); - - const finalMakerBalanceA = await erc20TokenA.balanceOf.callAsync(makerAddress); - const finalMakerBalanceB = await erc20TokenB.balanceOf.callAsync(makerAddress); - const finalMakerZrxBalance = await zrxToken.balanceOf.callAsync(makerAddress); - const finalTakerBalanceA = await erc20TokenA.balanceOf.callAsync(takerAddress); - const finalTakerBalanceB = await erc20TokenB.balanceOf.callAsync(takerAddress); - const finalTakerZrxBalance = await zrxToken.balanceOf.callAsync(takerAddress); - - expect(finalMakerBalanceA).to.be.bignumber.equal( - initialMakerBalanceA.plus( - takerAmounts[0].times( - takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalMakerBalanceB).to.be.bignumber.equal( - initialMakerBalanceB.plus( - takerAmounts[1].times( - takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalMakerZrxBalance).to.be.bignumber.equal( - initialMakerZrxBalance.minus( - makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ); - expect(finalTakerBalanceA).to.be.bignumber.equal( - initialTakerBalanceA.minus( - takerAmounts[0].times( - takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalTakerBalanceB).to.be.bignumber.equal( - initialTakerBalanceB.minus( - takerAmounts[1].times( - takerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ), - ); - expect(finalTakerZrxBalance).to.be.bignumber.equal( - initialTakerZrxBalance.plus( - makerAssetAmount.times(takerAssetFillAmount).dividedToIntegerBy(takerAssetAmount), - ), - ); - }); - }); - - describe('getOrderInfo', () => { - beforeEach(async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should return the correct orderInfo for an unfilled valid order', async () => { - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - 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); - }); - it('should return the correct orderInfo for a fully filled order', async () => { - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount; - const expectedOrderStatus = OrderStatus.FullyFilled; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for a partially filled order', async () => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); - 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); - }); - it('should return the correct orderInfo for a cancelled and unfilled order', async () => { - await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - const expectedOrderStatus = OrderStatus.Cancelled; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for a cancelled and partially filled order', async () => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); - await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = takerAssetFillAmount; - const expectedOrderStatus = OrderStatus.Cancelled; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for an expired and unfilled order', async () => { - const currentTimestamp = await getLatestBlockTimestampAsync(); - const timeUntilExpiration = signedOrder.expirationTimeSeconds.minus(currentTimestamp).toNumber(); - await increaseTimeAndMineBlockAsync(timeUntilExpiration); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - const expectedOrderStatus = OrderStatus.Expired; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for an expired and partially filled order', async () => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); - const currentTimestamp = await getLatestBlockTimestampAsync(); - const timeUntilExpiration = signedOrder.expirationTimeSeconds.minus(currentTimestamp).toNumber(); - await increaseTimeAndMineBlockAsync(timeUntilExpiration); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = takerAssetFillAmount; - const expectedOrderStatus = OrderStatus.Expired; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for an expired and fully filled order', async () => { - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - const currentTimestamp = await getLatestBlockTimestampAsync(); - const timeUntilExpiration = signedOrder.expirationTimeSeconds.minus(currentTimestamp).toNumber(); - await increaseTimeAndMineBlockAsync(timeUntilExpiration); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount; - // FULLY_FILLED takes precedence over EXPIRED - const expectedOrderStatus = OrderStatus.FullyFilled; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for an order with a makerAssetAmount of 0', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ makerAssetAmount: new BigNumber(0) }); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - const expectedOrderStatus = OrderStatus.InvalidMakerAssetAmount; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - it('should return the correct orderInfo for an order with a takerAssetAmount of 0', async () => { - signedOrder = await orderFactory.newSignedOrderAsync({ takerAssetAmount: new BigNumber(0) }); - const orderInfo = await exchangeWrapper.getOrderInfoAsync(signedOrder); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - const expectedOrderStatus = OrderStatus.InvalidTakerAssetAmount; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); -}); -// tslint:disable:max-file-line-count -// tslint:enable:no-unnecessary-type-assertion diff --git a/contracts/protocol/test/exchange/dispatcher.ts b/contracts/protocol/test/exchange/dispatcher.ts deleted file mode 100644 index eca1b38f1..000000000 --- a/contracts/protocol/test/exchange/dispatcher.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { - chaiSetup, - constants, - expectTransactionFailedAsync, - LogDecoder, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { DummyERC20TokenContract } from '@0x/contracts-tokens'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { AssetProxyId, RevertReason } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import { LogWithDecodedArgs } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; -import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; -import { - TestAssetProxyDispatcherAssetProxyRegisteredEventArgs, - TestAssetProxyDispatcherContract, -} from '../../generated-wrappers/test_asset_proxy_dispatcher'; -import { artifacts } from '../../src/artifacts'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -// tslint:disable:no-unnecessary-type-assertion -describe('AssetProxyDispatcher', () => { - let owner: string; - let notOwner: string; - let makerAddress: string; - let takerAddress: string; - - let zrxToken: DummyERC20TokenContract; - let erc20Proxy: ERC20ProxyContract; - let erc721Proxy: ERC721ProxyContract; - let assetProxyDispatcher: TestAssetProxyDispatcherContract; - - let erc20Wrapper: ERC20Wrapper; - let erc721Wrapper: ERC721Wrapper; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - // Setup accounts & addresses - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, notOwner, makerAddress, takerAddress] = _.slice(accounts, 0, 4)); - - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - - const numDummyErc20ToDeploy = 1; - [zrxToken] = await erc20Wrapper.deployDummyTokensAsync(numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS); - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - - erc721Proxy = await erc721Wrapper.deployProxyAsync(); - - assetProxyDispatcher = await TestAssetProxyDispatcherContract.deployFrom0xArtifactAsync( - artifacts.TestAssetProxyDispatcher, - provider, - txDefaults, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(assetProxyDispatcher.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('registerAssetProxy', () => { - it('should record proxy upon registration', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); - expect(proxyAddress).to.be.equal(erc20Proxy.address); - }); - - it('should be able to record multiple proxies', async () => { - // Record first proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - let proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); - expect(proxyAddress).to.be.equal(erc20Proxy.address); - // Record another proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc721Proxy.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC721); - expect(proxyAddress).to.be.equal(erc721Proxy.address); - }); - - it('should throw if a proxy with the same id is already registered', async () => { - // Initial registration - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); - expect(proxyAddress).to.be.equal(erc20Proxy.address); - // Deploy a new version of the ERC20 Transfer Proxy contract - const newErc20TransferProxy = await ERC20ProxyContract.deployFrom0xArtifactAsync( - artifacts.ERC20Proxy, - provider, - txDefaults, - ); - // Register new ERC20 Transfer Proxy contract - return expectTransactionFailedAsync( - assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(newErc20TransferProxy.address, { - from: owner, - }), - RevertReason.AssetProxyAlreadyExists, - ); - }); - - it('should throw if requesting address is not owner', async () => { - return expectTransactionFailedAsync( - assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: notOwner }), - RevertReason.OnlyContractOwner, - ); - }); - - it('should log an event with correct arguments when an asset proxy is registered', async () => { - const logDecoder = new LogDecoder(web3Wrapper, artifacts); - const txReceipt = await logDecoder.getTxWithDecodedLogsAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - ); - const logs = txReceipt.logs; - const log = logs[0] as LogWithDecodedArgs; - expect(log.args.id).to.equal(AssetProxyId.ERC20); - expect(log.args.assetProxy).to.equal(erc20Proxy.address); - }); - }); - - describe('getAssetProxy', () => { - it('should return correct address of registered proxy', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); - expect(proxyAddress).to.be.equal(erc20Proxy.address); - }); - - it('should return NULL address if requesting non-existent proxy', async () => { - const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); - expect(proxyAddress).to.be.equal(constants.NULL_ADDRESS); - }); - }); - - describe('dispatchTransferFrom', () => { - it('should dispatch transfer to registered proxy', async () => { - // Register ERC20 proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Construct metadata for ERC20 proxy - const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - - // Perform a transfer from makerAddress to takerAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const amount = new BigNumber(10); - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( - encodedAssetData, - makerAddress, - takerAddress, - amount, - { from: owner }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Verify transfer was successful - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(amount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].plus(amount), - ); - }); - - it('should not dispatch a transfer if amount == 0', async () => { - // Register ERC20 proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Construct metadata for ERC20 proxy - const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - - // Perform a transfer from makerAddress to takerAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const amount = constants.ZERO_AMOUNT; - const txReceipt = await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( - encodedAssetData, - makerAddress, - takerAddress, - amount, - { from: owner }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - expect(txReceipt.logs.length).to.be.equal(0); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.deep.equal(erc20Balances); - }); - - it('should not dispatch a transfer if from == to', async () => { - // Register ERC20 proxy - await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Construct metadata for ERC20 proxy - const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - - // Perform a transfer from makerAddress to takerAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const amount = new BigNumber(10); - const txReceipt = await web3Wrapper.awaitTransactionSuccessAsync( - await assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( - encodedAssetData, - makerAddress, - makerAddress, - amount, - { from: owner }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - expect(txReceipt.logs.length).to.be.equal(0); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.deep.equal(erc20Balances); - }); - - it('should throw if dispatching to unregistered proxy', async () => { - // Construct metadata for ERC20 proxy - const encodedAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - // Perform a transfer from makerAddress to takerAddress - const amount = new BigNumber(10); - return expectTransactionFailedAsync( - assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( - encodedAssetData, - makerAddress, - takerAddress, - amount, - { from: owner }, - ), - RevertReason.AssetProxyDoesNotExist, - ); - }); - }); -}); -// tslint:enable:no-unnecessary-type-assertion diff --git a/contracts/protocol/test/exchange/fill_order.ts b/contracts/protocol/test/exchange/fill_order.ts deleted file mode 100644 index 2bdbe4855..000000000 --- a/contracts/protocol/test/exchange/fill_order.ts +++ /dev/null @@ -1,314 +0,0 @@ -import { - AllowanceAmountScenario, - AssetDataScenario, - BalanceAmountScenario, - chaiSetup, - ExpirationTimeSecondsScenario, - FeeRecipientAddressScenario, - FillScenario, - OrderAssetAmountScenario, - provider, - TakerAssetFillAmountScenario, - TakerScenario, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import * as _ from 'lodash'; - -import { - FillOrderCombinatorialUtils, - fillOrderCombinatorialUtilsFactoryAsync, -} from '../utils/fill_order_combinatorial_utils'; - -chaiSetup.configure(); -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -const defaultFillScenario = { - orderScenario: { - takerScenario: TakerScenario.Unspecified, - feeRecipientScenario: FeeRecipientAddressScenario.EthUserAddress, - makerAssetAmountScenario: OrderAssetAmountScenario.Large, - takerAssetAmountScenario: OrderAssetAmountScenario.Large, - makerFeeScenario: OrderAssetAmountScenario.Large, - takerFeeScenario: OrderAssetAmountScenario.Large, - expirationTimeSecondsScenario: ExpirationTimeSecondsScenario.InFuture, - makerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, - takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount, - makerStateScenario: { - traderAssetBalance: BalanceAmountScenario.Higher, - traderAssetAllowance: AllowanceAmountScenario.Higher, - zrxFeeBalance: BalanceAmountScenario.Higher, - zrxFeeAllowance: AllowanceAmountScenario.Higher, - }, - takerStateScenario: { - traderAssetBalance: BalanceAmountScenario.Higher, - traderAssetAllowance: AllowanceAmountScenario.Higher, - zrxFeeBalance: BalanceAmountScenario.Higher, - zrxFeeAllowance: AllowanceAmountScenario.Higher, - }, -}; - -describe('FillOrder Tests', () => { - let fillOrderCombinatorialUtils: FillOrderCombinatorialUtils; - - before(async () => { - await blockchainLifecycle.startAsync(); - fillOrderCombinatorialUtils = await fillOrderCombinatorialUtilsFactoryAsync(web3Wrapper, txDefaults); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('fillOrder', () => { - const test = (fillScenarios: FillScenario[]) => { - _.forEach(fillScenarios, fillScenario => { - const description = `Combinatorial OrderFill: ${JSON.stringify(fillScenario)}`; - it(description, async () => { - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - }); - }; - - const allFillScenarios = FillOrderCombinatorialUtils.generateFillOrderCombinations(); - describe('Combinatorially generated fills orders', () => test(allFillScenarios)); - - it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => { - const fillScenario = { - ...defaultFillScenario, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - takerAssetAmountScenario: OrderAssetAmountScenario.Small, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetAmountScenario: OrderAssetAmountScenario.Small, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount with zero decimals', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetAmountScenario: OrderAssetAmountScenario.Small, - makerAssetDataScenario: AssetDataScenario.ERC20ZeroDecimals, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - it('should transfer the correct amounts when taker is specified and order is claimed by taker', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - takerScenario: TakerScenario.CorrectlySpecified, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - it('should fill remaining value if takerAssetFillAmount > remaining takerAssetAmount', async () => { - const fillScenario = { - ...defaultFillScenario, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - it('should throw when taker is specified and order is claimed by other', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - takerScenario: TakerScenario.IncorrectlySpecified, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if makerAssetAmount is 0', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetAmountScenario: OrderAssetAmountScenario.Zero, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if takerAssetAmount is 0', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - takerAssetAmountScenario: OrderAssetAmountScenario.Zero, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if takerAssetFillAmount is 0', async () => { - const fillScenario = { - ...defaultFillScenario, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.Zero, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if an order is expired', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - expirationTimeSecondsScenario: ExpirationTimeSecondsScenario.InPast, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if maker erc20Balances are too low to fill order', async () => { - const fillScenario = { - ...defaultFillScenario, - makerStateScenario: { - ...defaultFillScenario.makerStateScenario, - traderAssetBalance: BalanceAmountScenario.TooLow, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if taker erc20Balances are too low to fill order', async () => { - const fillScenario = { - ...defaultFillScenario, - takerStateScenario: { - ...defaultFillScenario.makerStateScenario, - traderAssetBalance: BalanceAmountScenario.TooLow, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if maker allowances are too low to fill order', async () => { - const fillScenario = { - ...defaultFillScenario, - makerStateScenario: { - ...defaultFillScenario.makerStateScenario, - traderAssetAllowance: AllowanceAmountScenario.TooLow, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should throw if taker allowances are too low to fill order', async () => { - const fillScenario = { - ...defaultFillScenario, - takerStateScenario: { - ...defaultFillScenario.makerStateScenario, - traderAssetAllowance: AllowanceAmountScenario.TooLow, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - }); - - describe('Testing exchange of ERC721 Tokens', () => { - it('should successfully exchange a single token between the maker and taker (via fillOrder)', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetDataScenario: AssetDataScenario.ERC721, - takerAssetDataScenario: AssetDataScenario.ERC721, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should successfully fill order when makerAsset is ERC721 and takerAsset is ERC20', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetDataScenario: AssetDataScenario.ERC721, - takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario, true); - }); - - it('should successfully fill order when makerAsset is ERC20 and takerAsset is ERC721', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, - takerAssetDataScenario: AssetDataScenario.ERC721, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should successfully fill order when makerAsset is ERC721 and approveAll is set for it', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetDataScenario: AssetDataScenario.ERC721, - takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, - makerStateScenario: { - ...defaultFillScenario.makerStateScenario, - traderAssetAllowance: AllowanceAmountScenario.Unlimited, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - - it('should successfully fill order when makerAsset and takerAsset are ERC721 and approveAll is set for them', async () => { - const fillScenario = { - ...defaultFillScenario, - orderScenario: { - ...defaultFillScenario.orderScenario, - makerAssetDataScenario: AssetDataScenario.ERC721, - takerAssetDataScenario: AssetDataScenario.ERC721, - }, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, - makerStateScenario: { - ...defaultFillScenario.makerStateScenario, - traderAssetAllowance: AllowanceAmountScenario.Unlimited, - }, - takerStateScenario: { - ...defaultFillScenario.takerStateScenario, - traderAssetAllowance: AllowanceAmountScenario.Unlimited, - }, - }; - await fillOrderCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); - }); - }); -}); diff --git a/contracts/protocol/test/exchange/internal.ts b/contracts/protocol/test/exchange/internal.ts deleted file mode 100644 index 6ff1fdc2f..000000000 --- a/contracts/protocol/test/exchange/internal.ts +++ /dev/null @@ -1,472 +0,0 @@ -import { - bytes32Values, - chaiSetup, - constants, - FillResults, - getRevertReasonOrErrorMessageForSendTransactionAsync, - provider, - testCombinatoriallyWithReferenceFuncAsync, - txDefaults, - uint256Values, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { Order, RevertReason, SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import * as _ from 'lodash'; - -import { TestExchangeInternalsContract } from '../../generated-wrappers/test_exchange_internals'; -import { artifacts } from '../../src/artifacts'; - -chaiSetup.configure(); -const expect = chai.expect; - -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -const MAX_UINT256 = new BigNumber(2).pow(256).minus(1); - -const emptyOrder: Order = { - senderAddress: constants.NULL_ADDRESS, - makerAddress: constants.NULL_ADDRESS, - takerAddress: constants.NULL_ADDRESS, - makerFee: new BigNumber(0), - takerFee: new BigNumber(0), - makerAssetAmount: new BigNumber(0), - takerAssetAmount: new BigNumber(0), - makerAssetData: '0x', - takerAssetData: '0x', - salt: new BigNumber(0), - exchangeAddress: constants.NULL_ADDRESS, - feeRecipientAddress: constants.NULL_ADDRESS, - expirationTimeSeconds: new BigNumber(0), -}; - -const emptySignedOrder: SignedOrder = { - ...emptyOrder, - signature: '', -}; - -const overflowErrorForCall = new Error(RevertReason.Uint256Overflow); - -describe('Exchange core internal functions', () => { - let testExchange: TestExchangeInternalsContract; - let overflowErrorForSendTransaction: Error | undefined; - let divisionByZeroErrorForCall: Error | undefined; - let roundingErrorForCall: Error | undefined; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( - artifacts.TestExchangeInternals, - provider, - txDefaults, - ); - overflowErrorForSendTransaction = new Error( - await getRevertReasonOrErrorMessageForSendTransactionAsync(RevertReason.Uint256Overflow), - ); - divisionByZeroErrorForCall = new Error(RevertReason.DivisionByZero); - roundingErrorForCall = new Error(RevertReason.RoundingError); - }); - // Note(albrow): Don't forget to add beforeEach and afterEach calls to reset - // the blockchain state for any tests which modify it! - - async function referenceIsRoundingErrorFloorAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise { - if (denominator.eq(0)) { - throw divisionByZeroErrorForCall; - } - if (numerator.eq(0)) { - return false; - } - if (target.eq(0)) { - return false; - } - const product = numerator.multipliedBy(target); - const remainder = product.mod(denominator); - const remainderTimes1000 = remainder.multipliedBy('1000'); - const isError = remainderTimes1000.gte(product); - if (product.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - if (remainderTimes1000.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - return isError; - } - - async function referenceIsRoundingErrorCeilAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise { - if (denominator.eq(0)) { - throw divisionByZeroErrorForCall; - } - if (numerator.eq(0)) { - return false; - } - if (target.eq(0)) { - return false; - } - const product = numerator.multipliedBy(target); - const remainder = product.mod(denominator); - const error = denominator.minus(remainder).mod(denominator); - const errorTimes1000 = error.multipliedBy('1000'); - const isError = errorTimes1000.gte(product); - if (product.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - if (errorTimes1000.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - return isError; - } - - async function referenceSafeGetPartialAmountFloorAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise { - if (denominator.eq(0)) { - throw divisionByZeroErrorForCall; - } - const isRoundingError = await referenceIsRoundingErrorFloorAsync(numerator, denominator, target); - if (isRoundingError) { - throw roundingErrorForCall; - } - const product = numerator.multipliedBy(target); - if (product.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - return product.dividedToIntegerBy(denominator); - } - - describe('addFillResults', async () => { - function makeFillResults(value: BigNumber): FillResults { - return { - makerAssetFilledAmount: value, - takerAssetFilledAmount: value, - makerFeePaid: value, - takerFeePaid: value, - }; - } - async function referenceAddFillResultsAsync( - totalValue: BigNumber, - singleValue: BigNumber, - ): Promise { - // Note(albrow): Here, each of totalFillResults and - // singleFillResults will consist of fields with the same values. - // This should be safe because none of the fields in a given - // FillResults are ever used together in a mathemetical operation. - // They are only used with the corresponding field from *the other* - // FillResults, which are different. - const totalFillResults = makeFillResults(totalValue); - const singleFillResults = makeFillResults(singleValue); - // HACK(albrow): _.mergeWith mutates the first argument! To - // workaround this we use _.cloneDeep. - return _.mergeWith( - _.cloneDeep(totalFillResults), - singleFillResults, - (totalVal: BigNumber, singleVal: BigNumber) => { - const newTotal = totalVal.plus(singleVal); - if (newTotal.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - return newTotal; - }, - ); - } - async function testAddFillResultsAsync(totalValue: BigNumber, singleValue: BigNumber): Promise { - const totalFillResults = makeFillResults(totalValue); - const singleFillResults = makeFillResults(singleValue); - return testExchange.publicAddFillResults.callAsync(totalFillResults, singleFillResults); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'addFillResults', - referenceAddFillResultsAsync, - testAddFillResultsAsync, - [uint256Values, uint256Values], - ); - }); - - describe('calculateFillResults', async () => { - function makeOrder( - makerAssetAmount: BigNumber, - takerAssetAmount: BigNumber, - makerFee: BigNumber, - takerFee: BigNumber, - ): Order { - return { - ...emptyOrder, - makerAssetAmount, - takerAssetAmount, - makerFee, - takerFee, - }; - } - async function referenceCalculateFillResultsAsync( - orderTakerAssetAmount: BigNumber, - takerAssetFilledAmount: BigNumber, - otherAmount: BigNumber, - ): Promise { - // Note(albrow): Here we are re-using the same value (otherAmount) - // for order.makerAssetAmount, order.makerFee, and order.takerFee. - // This should be safe because they are never used with each other - // in any mathematical operation in either the reference TypeScript - // implementation or the Solidity implementation of - // calculateFillResults. - const makerAssetFilledAmount = await referenceSafeGetPartialAmountFloorAsync( - takerAssetFilledAmount, - orderTakerAssetAmount, - otherAmount, - ); - const order = makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount); - const orderMakerAssetAmount = order.makerAssetAmount; - return { - makerAssetFilledAmount, - takerAssetFilledAmount, - makerFeePaid: await referenceSafeGetPartialAmountFloorAsync( - makerAssetFilledAmount, - orderMakerAssetAmount, - otherAmount, - ), - takerFeePaid: await referenceSafeGetPartialAmountFloorAsync( - takerAssetFilledAmount, - orderTakerAssetAmount, - otherAmount, - ), - }; - } - async function testCalculateFillResultsAsync( - orderTakerAssetAmount: BigNumber, - takerAssetFilledAmount: BigNumber, - otherAmount: BigNumber, - ): Promise { - const order = makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount); - return testExchange.publicCalculateFillResults.callAsync(order, takerAssetFilledAmount); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'calculateFillResults', - referenceCalculateFillResultsAsync, - testCalculateFillResultsAsync, - [uint256Values, uint256Values, uint256Values], - ); - }); - - describe('getPartialAmountFloor', async () => { - async function referenceGetPartialAmountFloorAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise { - if (denominator.eq(0)) { - throw divisionByZeroErrorForCall; - } - const product = numerator.multipliedBy(target); - if (product.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - return product.dividedToIntegerBy(denominator); - } - async function testGetPartialAmountFloorAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise { - return testExchange.publicGetPartialAmountFloor.callAsync(numerator, denominator, target); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'getPartialAmountFloor', - referenceGetPartialAmountFloorAsync, - testGetPartialAmountFloorAsync, - [uint256Values, uint256Values, uint256Values], - ); - }); - - describe('getPartialAmountCeil', async () => { - async function referenceGetPartialAmountCeilAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise { - if (denominator.eq(0)) { - throw divisionByZeroErrorForCall; - } - const product = numerator.multipliedBy(target); - const offset = product.plus(denominator.minus(1)); - if (offset.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - const result = offset.dividedToIntegerBy(denominator); - if (product.mod(denominator).eq(0)) { - expect(result.multipliedBy(denominator)).to.be.bignumber.eq(product); - } else { - expect(result.multipliedBy(denominator)).to.be.bignumber.gt(product); - } - return result; - } - async function testGetPartialAmountCeilAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise { - return testExchange.publicGetPartialAmountCeil.callAsync(numerator, denominator, target); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'getPartialAmountCeil', - referenceGetPartialAmountCeilAsync, - testGetPartialAmountCeilAsync, - [uint256Values, uint256Values, uint256Values], - ); - }); - - describe('safeGetPartialAmountFloor', async () => { - async function testSafeGetPartialAmountFloorAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise { - return testExchange.publicSafeGetPartialAmountFloor.callAsync(numerator, denominator, target); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'safeGetPartialAmountFloor', - referenceSafeGetPartialAmountFloorAsync, - testSafeGetPartialAmountFloorAsync, - [uint256Values, uint256Values, uint256Values], - ); - }); - - describe('safeGetPartialAmountCeil', async () => { - async function referenceSafeGetPartialAmountCeilAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise { - if (denominator.eq(0)) { - throw divisionByZeroErrorForCall; - } - const isRoundingError = await referenceIsRoundingErrorCeilAsync(numerator, denominator, target); - if (isRoundingError) { - throw roundingErrorForCall; - } - const product = numerator.multipliedBy(target); - const offset = product.plus(denominator.minus(1)); - if (offset.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForCall; - } - const result = offset.dividedToIntegerBy(denominator); - if (product.mod(denominator).eq(0)) { - expect(result.multipliedBy(denominator)).to.be.bignumber.eq(product); - } else { - expect(result.multipliedBy(denominator)).to.be.bignumber.gt(product); - } - return result; - } - async function testSafeGetPartialAmountCeilAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise { - return testExchange.publicSafeGetPartialAmountCeil.callAsync(numerator, denominator, target); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'safeGetPartialAmountCeil', - referenceSafeGetPartialAmountCeilAsync, - testSafeGetPartialAmountCeilAsync, - [uint256Values, uint256Values, uint256Values], - ); - }); - - describe('isRoundingErrorFloor', async () => { - async function testIsRoundingErrorFloorAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise { - return testExchange.publicIsRoundingErrorFloor.callAsync(numerator, denominator, target); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'isRoundingErrorFloor', - referenceIsRoundingErrorFloorAsync, - testIsRoundingErrorFloorAsync, - [uint256Values, uint256Values, uint256Values], - ); - }); - - describe('isRoundingErrorCeil', async () => { - async function testIsRoundingErrorCeilAsync( - numerator: BigNumber, - denominator: BigNumber, - target: BigNumber, - ): Promise { - return testExchange.publicIsRoundingErrorCeil.callAsync(numerator, denominator, target); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'isRoundingErrorCeil', - referenceIsRoundingErrorCeilAsync, - testIsRoundingErrorCeilAsync, - [uint256Values, uint256Values, uint256Values], - ); - }); - - describe('updateFilledState', async () => { - // Note(albrow): Since updateFilledState modifies the state by calling - // sendTransaction, we must reset the state after each test. - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - async function referenceUpdateFilledStateAsync( - takerAssetFilledAmount: BigNumber, - orderTakerAssetFilledAmount: BigNumber, - // tslint:disable-next-line:no-unused-variable - orderHash: string, - ): Promise { - const totalFilledAmount = takerAssetFilledAmount.plus(orderTakerAssetFilledAmount); - if (totalFilledAmount.isGreaterThan(MAX_UINT256)) { - throw overflowErrorForSendTransaction; - } - return totalFilledAmount; - } - async function testUpdateFilledStateAsync( - takerAssetFilledAmount: BigNumber, - orderTakerAssetFilledAmount: BigNumber, - orderHash: string, - ): Promise { - const fillResults = { - makerAssetFilledAmount: new BigNumber(0), - takerAssetFilledAmount, - makerFeePaid: new BigNumber(0), - takerFeePaid: new BigNumber(0), - }; - await web3Wrapper.awaitTransactionSuccessAsync( - await testExchange.publicUpdateFilledState.sendTransactionAsync( - emptySignedOrder, - constants.NULL_ADDRESS, - orderHash, - orderTakerAssetFilledAmount, - fillResults, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - return testExchange.filled.callAsync(orderHash); - } - await testCombinatoriallyWithReferenceFuncAsync( - 'updateFilledState', - referenceUpdateFilledStateAsync, - testUpdateFilledStateAsync, - [uint256Values, uint256Values, bytes32Values], - ); - }); -}); diff --git a/contracts/protocol/test/exchange/match_orders.ts b/contracts/protocol/test/exchange/match_orders.ts deleted file mode 100644 index 8e943c237..000000000 --- a/contracts/protocol/test/exchange/match_orders.ts +++ /dev/null @@ -1,1284 +0,0 @@ -import { - chaiSetup, - constants, - ERC20BalancesByOwner, - ERC721TokenIdsByOwner, - expectTransactionFailedAsync, - OrderFactory, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { - artifacts as tokensArtifacts, - DummyERC20TokenContract, - DummyERC721TokenContract, - ReentrantERC20TokenContract, -} from '@0x/contracts-tokens'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { RevertReason } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import * as _ from 'lodash'; - -import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; -import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { TestExchangeInternalsContract } from '../../generated-wrappers/test_exchange_internals'; -import { artifacts } from '../../src/artifacts'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; -import { MatchOrderTester } from '../utils/match_order_tester'; - -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -chaiSetup.configure(); -const expect = chai.expect; - -describe('matchOrders', () => { - let makerAddressLeft: string; - let makerAddressRight: string; - let owner: string; - let takerAddress: string; - let feeRecipientAddressLeft: string; - let feeRecipientAddressRight: string; - - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - let zrxToken: DummyERC20TokenContract; - let erc721Token: DummyERC721TokenContract; - let reentrantErc20Token: ReentrantERC20TokenContract; - let exchange: ExchangeContract; - let erc20Proxy: ERC20ProxyContract; - let erc721Proxy: ERC721ProxyContract; - - let erc20BalancesByOwner: ERC20BalancesByOwner; - let erc721TokenIdsByOwner: ERC721TokenIdsByOwner; - let exchangeWrapper: ExchangeWrapper; - let erc20Wrapper: ERC20Wrapper; - let erc721Wrapper: ERC721Wrapper; - let orderFactoryLeft: OrderFactory; - let orderFactoryRight: OrderFactory; - - let erc721LeftMakerAssetIds: BigNumber[]; - let erc721RightMakerAssetIds: BigNumber[]; - - let defaultERC20MakerAssetAddress: string; - let defaultERC20TakerAssetAddress: string; - let defaultERC721AssetAddress: string; - - let matchOrderTester: MatchOrderTester; - - let testExchange: TestExchangeInternalsContract; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - // Create accounts - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - // Hack(albrow): Both Prettier and TSLint insert a trailing comma below - // but that is invalid syntax as of TypeScript version >= 2.8. We don't - // have the right fine-grained configuration options in TSLint, - // Prettier, or TypeScript, to reconcile this, so we will just have to - // wait for them to sort it out. We disable TSLint and Prettier for - // this part of the code for now. This occurs several times in this - // file. See https://github.com/prettier/prettier/issues/4624. - // prettier-ignore - const usedAddresses = ([ - owner, - makerAddressLeft, - makerAddressRight, - takerAddress, - feeRecipientAddressLeft, - // tslint:disable-next-line:trailing-comma - feeRecipientAddressRight - ] = _.slice(accounts, 0, 6)); - // Create wrappers - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - // Deploy ERC20 token & ERC20 proxy - const numDummyErc20ToDeploy = 3; - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - // Deploy ERC721 token and proxy - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - erc721Proxy = await erc721Wrapper.deployProxyAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - erc721LeftMakerAssetIds = erc721Balances[makerAddressLeft][erc721Token.address]; - erc721RightMakerAssetIds = erc721Balances[makerAddressRight][erc721Token.address]; - // Depoy exchange - exchange = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - assetDataUtils.encodeERC20AssetData(zrxToken.address), - ); - exchangeWrapper = new ExchangeWrapper(exchange, provider); - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); - // Authorize ERC20 and ERC721 trades by exchange - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - reentrantErc20Token = await ReentrantERC20TokenContract.deployFrom0xArtifactAsync( - tokensArtifacts.ReentrantERC20Token, - provider, - txDefaults, - exchange.address, - ); - - // Set default addresses - defaultERC20MakerAssetAddress = erc20TokenA.address; - defaultERC20TakerAssetAddress = erc20TokenB.address; - defaultERC721AssetAddress = erc721Token.address; - // Create default order parameters - const defaultOrderParamsLeft = { - ...constants.STATIC_ORDER_PARAMS, - makerAddress: makerAddressLeft, - exchangeAddress: exchange.address, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - feeRecipientAddress: feeRecipientAddressLeft, - }; - const defaultOrderParamsRight = { - ...constants.STATIC_ORDER_PARAMS, - makerAddress: makerAddressRight, - exchangeAddress: exchange.address, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - feeRecipientAddress: feeRecipientAddressRight, - }; - const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)]; - orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParamsLeft); - const privateKeyRight = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressRight)]; - orderFactoryRight = new OrderFactory(privateKeyRight, defaultOrderParamsRight); - // Set match order tester - matchOrderTester = new MatchOrderTester(exchangeWrapper, erc20Wrapper, erc721Wrapper, zrxToken.address); - testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( - artifacts.TestExchangeInternals, - provider, - txDefaults, - ); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('matchOrders', () => { - beforeEach(async () => { - erc20BalancesByOwner = await erc20Wrapper.getBalancesAsync(); - erc721TokenIdsByOwner = await erc721Wrapper.getBalancesAsync(); - }); - - it('Should transfer correct amounts when right order is fully filled and values pass isRoundingErrorFloor but fail isRoundingErrorCeil', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(17), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(98), 0), - feeRecipientAddress: feeRecipientAddressLeft, - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - feeRecipientAddress: feeRecipientAddressRight, - }); - // Assert is rounding error ceil & not rounding error floor - // These assertions are taken from MixinMatchOrders::calculateMatchedFillResults - // The rounding error is derived computating how much the left maker will sell. - const numerator = signedOrderLeft.makerAssetAmount; - const denominator = signedOrderLeft.takerAssetAmount; - const target = signedOrderRight.makerAssetAmount; - const isRoundingErrorCeil = await testExchange.publicIsRoundingErrorCeil.callAsync( - numerator, - denominator, - target, - ); - expect(isRoundingErrorCeil).to.be.true(); - const isRoundingErrorFloor = await testExchange.publicIsRoundingErrorFloor.callAsync( - numerator, - denominator, - target, - ); - expect(isRoundingErrorFloor).to.be.false(); - // Match signedOrderLeft with signedOrderRight - // Note that the left maker received a slightly better sell price. - // This is intentional; see note in MixinMatchOrders.calculateMatchedFillResults. - // Because the left maker received a slightly more favorable sell price, the fee - // paid by the left taker is slightly higher than that paid by the left maker. - // Fees can be thought of as a tax paid by the seller, derived from the sale price. - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), // 76.47% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('Should transfer correct amounts when left order is fully filled and values pass isRoundingErrorCeil but fail isRoundingErrorFloor', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(15), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 0), - feeRecipientAddress: feeRecipientAddressLeft, - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(97), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(14), 0), - feeRecipientAddress: feeRecipientAddressRight, - }); - // Assert is rounding error floor & not rounding error ceil - // These assertions are taken from MixinMatchOrders::calculateMatchedFillResults - // The rounding error is derived computating how much the right maker will buy. - const numerator = signedOrderRight.takerAssetAmount; - const denominator = signedOrderRight.makerAssetAmount; - const target = signedOrderLeft.takerAssetAmount; - const isRoundingErrorFloor = await testExchange.publicIsRoundingErrorFloor.callAsync( - numerator, - denominator, - target, - ); - expect(isRoundingErrorFloor).to.be.true(); - const isRoundingErrorCeil = await testExchange.publicIsRoundingErrorCeil.callAsync( - numerator, - denominator, - target, - ); - expect(isRoundingErrorCeil).to.be.false(); - // Match signedOrderLeft with signedOrderRight - // Note that the right maker received a slightly better purchase price. - // This is intentional; see note in MixinMatchOrders.calculateMatchedFillResults. - // Because the right maker received a slightly more favorable buy price, the fee - // paid by the right taker is slightly higher than that paid by the right maker. - // Fees can be thought of as a tax paid by the seller, derived from the sale price. - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(15), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('92.7835051546391752'), 16), // 92.78% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber('92.8571428571428571'), 16), // 92.85% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('Should give right maker a better buy price when rounding', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(16), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), - feeRecipientAddress: feeRecipientAddressLeft, - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(83), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(49), 0), - feeRecipientAddress: feeRecipientAddressRight, - }); - // Note: - // The correct price buy price for the right maker would yield (49/83) * 22 = 12.988 units - // of the left maker asset. This gets rounded up to 13, giving the right maker a better price. - // Note: - // The maker/taker fee percentage paid on the right order differs because - // they received different sale prices. The right maker pays a - // fee slightly lower than the right taker. - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(16), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('26.5060240963855421'), 16), // 26.506% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber('26.5306122448979591'), 16), // 26.531% - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('Should give left maker a better sell price when rounding', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(12), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(97), 0), - feeRecipientAddress: feeRecipientAddressLeft, - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - feeRecipientAddress: feeRecipientAddressRight, - }); - // Note: - // The maker/taker fee percentage paid on the left order differs because - // they received different sale prices. The left maker pays a fee - // slightly lower than the left taker. - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(11), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.6666666666666666'), 16), // 91.6% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.7525773195876288'), 16), // 91.75% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('Should give right maker and right taker a favorable fee price when rounding', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(16), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), - feeRecipientAddress: feeRecipientAddressLeft, - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(83), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(49), 0), - feeRecipientAddress: feeRecipientAddressRight, - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 0), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 0), - }); - // Note: - // The maker/taker fee percentage paid on the right order differs because - // they received different sale prices. The right maker pays a - // fee slightly lower than the right taker. - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(16), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(22), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2650), 0), // 2650.6 rounded down tro 2650 - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(2653), 0), // 2653.1 rounded down to 2653 - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('Should give left maker and left taker a favorable fee price when rounding', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(12), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(97), 0), - feeRecipientAddress: feeRecipientAddressLeft, - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 0), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 0), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - feeRecipientAddress: feeRecipientAddressRight, - }); - // Note: - // The maker/taker fee percentage paid on the left order differs because - // they received different sale prices. The left maker pays a - // fee slightly lower than the left taker. - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(11), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(9166), 0), // 9166.6 rounded down to 9166 - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(89), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(9175), 0), // 9175.2 rounded down to 9175 - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('Should transfer correct amounts when right order fill amount deviates from amount derived by `Exchange.fillOrder`', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1005), 0), - feeRecipientAddress: feeRecipientAddressLeft, - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2126), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1063), 0), - feeRecipientAddress: feeRecipientAddressRight, - }); - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1005), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - // Notes: - // i. - // The left order is fully filled by the right order, so the right maker must sell 1005 units of their asset to the left maker. - // By selling 1005 units, the right maker should theoretically receive 502.5 units of the left maker's asset. - // Since the transfer amount must be an integer, this value must be rounded down to 502 or up to 503. - // ii. - // If the right order were filled via `Exchange.fillOrder` the respective fill amounts would be [1004, 502] or [1006, 503]. - // It follows that we cannot trigger a sale of 1005 units of the right maker's asset through `Exchange.fillOrder`. - // iii. - // For an optimal match, the algorithm must choose either [1005, 502] or [1005, 503] as fill amounts for the right order. - // The algorithm favors the right maker when the exchange rate must be rounded, so the final fill for the right order is [1005, 503]. - // iv. - // The right maker fee differs from the right taker fee because their exchange rate differs. - // The right maker always receives the better exchange and fee price. - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1005), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(503), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('47.2718720602069614'), 16), // 47.27% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(497), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber('47.3189087488240827'), 16), // 47.31% - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow matchOrders to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAddress: makerAddressRight, - takerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feeRecipientAddress: feeRecipientAddressRight, - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await expectTransactionFailedAsync( - exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), - RevertReason.TransferFailed, - ); - }); - }); - }; - describe('matchOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should transfer the correct amounts when orders completely fill each other', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match signedOrderLeft with signedOrderRight - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - }); - // Match signedOrderLeft with signedOrderRight - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts when left order is completely filled and right order is partially filled', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18), - }); - // Match signedOrderLeft with signedOrderRight - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 16), // 50% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 16), // 50% - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts when right order is completely filled and left order is partially filled', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match signedOrderLeft with signedOrderRight - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - // Match signedOrderLeft with signedOrderRight - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts when consecutive calls are used to completely fill the left order', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - let newERC20BalancesByOwner: ERC20BalancesByOwner; - let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner; - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 16), // 10% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - // prettier-ignore - [ - newERC20BalancesByOwner, - // tslint:disable-next-line:trailing-comma - newERC721TokenIdsByOwner - ] = await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - // Construct second right order - // Note: This order needs makerAssetAmount=90/takerAssetAmount=[anything <= 45] to fully fill the right order. - // However, we use 100/50 to ensure a partial fill as we want to go down the "left fill" - // branch in the contract twice for this test. - const signedOrderRight2 = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), - }); - // Match signedOrderLeft with signedOrderRight2 - const leftTakerAssetFilledAmount = signedOrderRight.makerAssetAmount; - const rightTakerAssetFilledAmount = new BigNumber(0); - const expectedTransferAmounts2 = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(45), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90% (10% paid earlier) - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(45), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90% (10% paid earlier) - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(90), 16), // 90% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight2, - takerAddress, - newERC20BalancesByOwner, - newERC721TokenIdsByOwner, - expectedTransferAmounts2, - leftTakerAssetFilledAmount, - rightTakerAssetFilledAmount, - ); - }); - - it('should transfer the correct amounts when consecutive calls are used to completely fill the right order', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - }); - // Match orders - let newERC20BalancesByOwner: ERC20BalancesByOwner; - let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner; - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 16), // 4% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(6), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 16), // 4% - }; - // prettier-ignore - [ - newERC20BalancesByOwner, - // tslint:disable-next-line:trailing-comma - newERC721TokenIdsByOwner - ] = await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - - // Create second left order - // Note: This order needs makerAssetAmount=96/takerAssetAmount=48 to fully fill the right order. - // However, we use 100/50 to ensure a partial fill as we want to go down the "right fill" - // branch in the contract twice for this test. - const signedOrderLeft2 = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), - }); - // Match signedOrderLeft2 with signedOrderRight - const leftTakerAssetFilledAmount = new BigNumber(0); - const takerAmountReceived = newERC20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress].minus( - erc20BalancesByOwner[takerAddress][defaultERC20MakerAssetAddress], - ); - const rightTakerAssetFilledAmount = signedOrderLeft.makerAssetAmount.minus(takerAmountReceived); - const expectedTransferAmounts2 = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(48), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(48), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(96), 16), // 96% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft2, - signedOrderRight, - takerAddress, - newERC20BalancesByOwner, - newERC721TokenIdsByOwner, - expectedTransferAmounts2, - leftTakerAssetFilledAmount, - rightTakerAssetFilledAmount, - ); - }); - - it('should transfer the correct amounts if fee recipient is the same across both matched orders', async () => { - const feeRecipientAddress = feeRecipientAddressLeft; - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feeRecipientAddress, - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feeRecipientAddress, - }); - // Match orders - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts if taker is also the left order maker', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - takerAddress = signedOrderLeft.makerAddress; - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts if taker is also the right order maker', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - takerAddress = signedOrderRight.makerAddress; - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts if taker is also the left fee recipient', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - takerAddress = feeRecipientAddressLeft; - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts if taker is also the right fee recipient', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - takerAddress = feeRecipientAddressRight; - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer the correct amounts if left maker is the left fee recipient and right maker is the right fee recipient', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('Should throw if left order is not fillable', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Cancel left order - await exchangeWrapper.cancelOrderAsync(signedOrderLeft, signedOrderLeft.makerAddress); - // Match orders - return expectTransactionFailedAsync( - exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('Should throw if right order is not fillable', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Cancel right order - await exchangeWrapper.cancelOrderAsync(signedOrderRight, signedOrderRight.makerAddress); - // Match orders - return expectTransactionFailedAsync( - exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('should throw if there is not a positive spread', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), - }); - // Match orders - return expectTransactionFailedAsync( - exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), - RevertReason.NegativeSpreadRequired, - ); - }); - - it('should throw if the left maker asset is not equal to the right taker asset ', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - return expectTransactionFailedAsync( - exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), - // We are assuming assetData fields of the right order are the - // reverse of the left order, rather than checking equality. This - // saves a bunch of gas, but as a result if the assetData fields are - // off then the failure ends up happening at signature validation - RevertReason.InvalidOrderSignature, - ); - }); - - it('should throw if the right maker asset is not equal to the left taker asset', async () => { - // Create orders to match - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - }); - // Match orders - return expectTransactionFailedAsync( - exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), - RevertReason.InvalidOrderSignature, - ); - }); - - it('should transfer correct amounts when left order maker asset is an ERC721 token', async () => { - // Create orders to match - const erc721TokenToTransfer = erc721LeftMakerAssetIds[0]; - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), - makerAssetAmount: new BigNumber(1), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: new BigNumber(1), - }); - // Match orders - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 50% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - - it('should transfer correct amounts when right order maker asset is an ERC721 token', async () => { - // Create orders to match - const erc721TokenToTransfer = erc721RightMakerAssetIds[0]; - const signedOrderLeft = await orderFactoryLeft.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - takerAssetAmount: new BigNumber(1), - }); - const signedOrderRight = await orderFactoryRight.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), - makerAssetAmount: new BigNumber(1), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), 18), - }); - // Match orders - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(8), 18), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - await matchOrderTester.matchOrdersAndAssertEffectsAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - expectedTransferAmounts, - ); - }); - }); -}); // tslint:disable-line:max-file-line-count diff --git a/contracts/protocol/test/exchange/signature_validator.ts b/contracts/protocol/test/exchange/signature_validator.ts deleted file mode 100644 index 3f1786b5b..000000000 --- a/contracts/protocol/test/exchange/signature_validator.ts +++ /dev/null @@ -1,526 +0,0 @@ -import { artifacts as examplesArtifacts, ValidatorContract, WalletContract } from '@0x/contracts-examples'; -import { - addressUtils, - chaiSetup, - constants, - expectContractCallFailedAsync, - LogDecoder, - OrderFactory, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { artifacts as tokensArtifacts } from '@0x/contracts-tokens'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils, orderHashUtils, signatureUtils } from '@0x/order-utils'; -import { RevertReason, SignatureType, SignedOrder } from '@0x/types'; -import * as chai from 'chai'; -import { LogWithDecodedArgs } from 'ethereum-types'; -import ethUtil = require('ethereumjs-util'); - -import { - artifacts, - TestSignatureValidatorContract, - TestSignatureValidatorSignatureValidatorApprovalEventArgs, - TestStaticCallReceiverContract, -} from '../../src'; - -chaiSetup.configure(); -const expect = chai.expect; - -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -// tslint:disable:no-unnecessary-type-assertion -describe('MixinSignatureValidator', () => { - let signedOrder: SignedOrder; - let orderFactory: OrderFactory; - let signatureValidator: TestSignatureValidatorContract; - let testWallet: WalletContract; - let testValidator: ValidatorContract; - let maliciousWallet: TestStaticCallReceiverContract; - let maliciousValidator: TestStaticCallReceiverContract; - let signerAddress: string; - let signerPrivateKey: Buffer; - let notSignerAddress: string; - let notSignerPrivateKey: Buffer; - let signatureValidatorLogDecoder: LogDecoder; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const makerAddress = accounts[0]; - signerAddress = makerAddress; - notSignerAddress = accounts[1]; - signatureValidator = await TestSignatureValidatorContract.deployFrom0xArtifactAsync( - artifacts.TestSignatureValidator, - provider, - txDefaults, - ); - testWallet = await WalletContract.deployFrom0xArtifactAsync( - examplesArtifacts.Wallet, - provider, - txDefaults, - signerAddress, - ); - testValidator = await ValidatorContract.deployFrom0xArtifactAsync( - examplesArtifacts.Validator, - provider, - txDefaults, - signerAddress, - ); - maliciousWallet = maliciousValidator = await TestStaticCallReceiverContract.deployFrom0xArtifactAsync( - artifacts.TestStaticCallReceiver, - provider, - txDefaults, - ); - signatureValidatorLogDecoder = new LogDecoder(web3Wrapper, { ...artifacts, ...tokensArtifacts }); - await web3Wrapper.awaitTransactionSuccessAsync( - await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync(testValidator.address, true, { - from: signerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( - maliciousValidator.address, - true, - { - from: signerAddress, - }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - const defaultOrderParams = { - ...constants.STATIC_ORDER_PARAMS, - exchangeAddress: signatureValidator.address, - makerAddress, - feeRecipientAddress: addressUtils.generatePseudoRandomAddress(), - makerAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), - takerAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), - }; - signerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; - notSignerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(notSignerAddress)]; - orderFactory = new OrderFactory(signerPrivateKey, defaultOrderParams); - }); - - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - - describe('isValidSignature', () => { - beforeEach(async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - }); - - it('should revert when signature is empty', async () => { - const emptySignature = '0x'; - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - return expectContractCallFailedAsync( - signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signedOrder.makerAddress, - emptySignature, - ), - RevertReason.LengthGreaterThan0Required, - ); - }); - - it('should revert when signature type is unsupported', async () => { - const unsupportedSignatureType = SignatureType.NSignatureTypes; - const unsupportedSignatureHex = `0x${Buffer.from([unsupportedSignatureType]).toString('hex')}`; - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - return expectContractCallFailedAsync( - signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signedOrder.makerAddress, - unsupportedSignatureHex, - ), - RevertReason.SignatureUnsupported, - ); - }); - - it('should revert when SignatureType=Illegal', async () => { - const unsupportedSignatureHex = `0x${Buffer.from([SignatureType.Illegal]).toString('hex')}`; - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - return expectContractCallFailedAsync( - signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signedOrder.makerAddress, - unsupportedSignatureHex, - ), - RevertReason.SignatureIllegal, - ); - }); - - it('should return false when SignatureType=Invalid and signature has a length of zero', async () => { - const signatureHex = `0x${Buffer.from([SignatureType.Invalid]).toString('hex')}`; - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signedOrder.makerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.false(); - }); - - it('should revert when SignatureType=Invalid and signature length is non-zero', async () => { - const fillerData = ethUtil.toBuffer('0xdeadbeef'); - const signatureType = ethUtil.toBuffer(`0x${SignatureType.Invalid}`); - const signatureBuffer = Buffer.concat([fillerData, signatureType]); - const signatureHex = ethUtil.bufferToHex(signatureBuffer); - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - return expectContractCallFailedAsync( - signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signedOrder.makerAddress, - signatureHex, - ), - RevertReason.Length0Required, - ); - }); - - it('should return true when SignatureType=EIP712 and signature is valid', async () => { - // Create EIP712 signature - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashBuffer = ethUtil.toBuffer(orderHashHex); - const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey); - // Create 0x signature from EIP712 signature - const signature = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ecSignature.r, - ecSignature.s, - ethUtil.toBuffer(`0x${SignatureType.EIP712}`), - ]); - const signatureHex = ethUtil.bufferToHex(signature); - // Validate signature - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.true(); - }); - - it('should return false when SignatureType=EIP712 and signature is invalid', async () => { - // Create EIP712 signature - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashBuffer = ethUtil.toBuffer(orderHashHex); - const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey); - // Create 0x signature from EIP712 signature - const signature = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ecSignature.r, - ecSignature.s, - ethUtil.toBuffer(`0x${SignatureType.EIP712}`), - ]); - const signatureHex = ethUtil.bufferToHex(signature); - // Validate signature. - // This will fail because `signerAddress` signed the message, but we're passing in `notSignerAddress` - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - notSignerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.false(); - }); - - it('should return true when SignatureType=EthSign and signature is valid', async () => { - // Create EthSign signature - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix(orderHashHex); - const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex); - const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey); - // Create 0x signature from EthSign signature - const signature = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ecSignature.r, - ecSignature.s, - ethUtil.toBuffer(`0x${SignatureType.EthSign}`), - ]); - const signatureHex = ethUtil.bufferToHex(signature); - // Validate signature - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.true(); - }); - - it('should return false when SignatureType=EthSign and signature is invalid', async () => { - // Create EthSign signature - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix(orderHashHex); - const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex); - const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey); - // Create 0x signature from EthSign signature - const signature = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ecSignature.r, - ecSignature.s, - ethUtil.toBuffer(`0x${SignatureType.EthSign}`), - ]); - const signatureHex = ethUtil.bufferToHex(signature); - // Validate signature. - // This will fail because `signerAddress` signed the message, but we're passing in `notSignerAddress` - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - notSignerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.false(); - }); - - it('should return true when SignatureType=Wallet and signature is valid', async () => { - // Create EIP712 signature - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashBuffer = ethUtil.toBuffer(orderHashHex); - const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey); - // Create 0x signature from EIP712 signature - const signature = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ecSignature.r, - ecSignature.s, - ethUtil.toBuffer(`0x${SignatureType.Wallet}`), - ]); - const signatureHex = ethUtil.bufferToHex(signature); - // Validate signature - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - testWallet.address, - signatureHex, - ); - expect(isValidSignature).to.be.true(); - }); - - it('should return false when SignatureType=Wallet and signature is invalid', async () => { - // Create EIP712 signature using a private key that does not belong to the wallet owner. - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashBuffer = ethUtil.toBuffer(orderHashHex); - const notWalletOwnerPrivateKey = notSignerPrivateKey; - const ecSignature = ethUtil.ecsign(orderHashBuffer, notWalletOwnerPrivateKey); - // Create 0x signature from EIP712 signature - const signature = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ecSignature.r, - ecSignature.s, - ethUtil.toBuffer(`0x${SignatureType.Wallet}`), - ]); - const signatureHex = ethUtil.bufferToHex(signature); - // Validate signature - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - testWallet.address, - signatureHex, - ); - expect(isValidSignature).to.be.false(); - }); - - it('should revert when `isValidSignature` attempts to update state and SignatureType=Wallet', async () => { - // Create EIP712 signature - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const orderHashBuffer = ethUtil.toBuffer(orderHashHex); - const ecSignature = ethUtil.ecsign(orderHashBuffer, signerPrivateKey); - // Create 0x signature from EIP712 signature - const signature = Buffer.concat([ - ethUtil.toBuffer(ecSignature.v), - ecSignature.r, - ecSignature.s, - ethUtil.toBuffer(`0x${SignatureType.Wallet}`), - ]); - const signatureHex = ethUtil.bufferToHex(signature); - await expectContractCallFailedAsync( - signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - maliciousWallet.address, - signatureHex, - ), - RevertReason.WalletError, - ); - }); - - it('should return true when SignatureType=Validator, signature is valid and validator is approved', async () => { - const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`); - const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); - const signature = Buffer.concat([validatorAddress, signatureType]); - const signatureHex = ethUtil.bufferToHex(signature); - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.true(); - }); - - it('should return false when SignatureType=Validator, signature is invalid and validator is approved', async () => { - const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`); - const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); - const signature = Buffer.concat([validatorAddress, signatureType]); - const signatureHex = ethUtil.bufferToHex(signature); - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - // This will return false because we signed the message with `signerAddress`, but - // are validating against `notSignerAddress` - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - notSignerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.false(); - }); - - it('should revert when `isValidSignature` attempts to update state and SignatureType=Validator', async () => { - const validatorAddress = ethUtil.toBuffer(`${maliciousValidator.address}`); - const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); - const signature = Buffer.concat([validatorAddress, signatureType]); - const signatureHex = ethUtil.bufferToHex(signature); - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - await expectContractCallFailedAsync( - signatureValidator.publicIsValidSignature.callAsync(orderHashHex, signerAddress, signatureHex), - RevertReason.ValidatorError, - ); - }); - it('should return false when SignatureType=Validator, signature is valid and validator is not approved', async () => { - // Set approval of signature validator to false - await web3Wrapper.awaitTransactionSuccessAsync( - await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( - testValidator.address, - false, - { from: signerAddress }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Validate signature - const validatorAddress = ethUtil.toBuffer(`${testValidator.address}`); - const signatureType = ethUtil.toBuffer(`0x${SignatureType.Validator}`); - const signature = Buffer.concat([validatorAddress, signatureType]); - const signatureHex = ethUtil.bufferToHex(signature); - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.false(); - }); - - it('should return true when SignatureType=Presigned and signer has presigned hash', async () => { - // Presign hash - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - await web3Wrapper.awaitTransactionSuccessAsync( - await signatureValidator.preSign.sendTransactionAsync( - orderHashHex, - signedOrder.makerAddress, - signedOrder.signature, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - // Validate presigned signature - const signature = ethUtil.toBuffer(`0x${SignatureType.PreSigned}`); - const signatureHex = ethUtil.bufferToHex(signature); - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signedOrder.makerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.true(); - }); - - it('should return false when SignatureType=Presigned and signer has not presigned hash', async () => { - const signature = ethUtil.toBuffer(`0x${SignatureType.PreSigned}`); - const signatureHex = ethUtil.bufferToHex(signature); - const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - orderHashHex, - signedOrder.makerAddress, - signatureHex, - ); - expect(isValidSignature).to.be.false(); - }); - - it('should return true when message was signed by a Trezor One (firmware version 1.6.2)', async () => { - // messageHash translates to 0x2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b - const messageHash = ethUtil.bufferToHex(ethUtil.toBuffer('++++++++++++++++++++++++++++++++')); - const signer = '0xc28b145f10f0bcf0fc000e778615f8fd73490bad'; - const v = ethUtil.toBuffer('0x1c'); - const r = ethUtil.toBuffer('0x7b888b596ccf87f0bacab0dcb483124973f7420f169b4824d7a12534ac1e9832'); - const s = ethUtil.toBuffer('0x0c8e14f7edc01459e13965f1da56e0c23ed11e2cca932571eee1292178f90424'); - const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`); - const signature = Buffer.concat([v, r, s, trezorSignatureType]); - const signatureHex = ethUtil.bufferToHex(signature); - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - messageHash, - signer, - signatureHex, - ); - expect(isValidSignature).to.be.true(); - }); - - it('should return true when message was signed by a Trezor Model T (firmware version 2.0.7)', async () => { - // messageHash translates to 0x2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b - const messageHash = ethUtil.bufferToHex(ethUtil.toBuffer('++++++++++++++++++++++++++++++++')); - const signer = '0x98ce6d9345e8ffa7d99ee0822272fae9d2c0e895'; - const v = ethUtil.toBuffer('0x1c'); - const r = ethUtil.toBuffer('0x423b71062c327f0ec4fe199b8da0f34185e59b4c1cb4cc23df86cac4a601fb3f'); - const s = ethUtil.toBuffer('0x53810d6591b5348b7ee08ee812c874b0fdfb942c9849d59512c90e295221091f'); - const trezorSignatureType = ethUtil.toBuffer(`0x${SignatureType.EthSign}`); - const signature = Buffer.concat([v, r, s, trezorSignatureType]); - const signatureHex = ethUtil.bufferToHex(signature); - const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync( - messageHash, - signer, - signatureHex, - ); - expect(isValidSignature).to.be.true(); - }); - }); - - describe('setSignatureValidatorApproval', () => { - it('should emit a SignatureValidatorApprovalSet with correct args when a validator is approved', async () => { - const approval = true; - const res = await signatureValidatorLogDecoder.getTxWithDecodedLogsAsync( - await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( - testValidator.address, - approval, - { - from: signerAddress, - }, - ), - ); - expect(res.logs.length).to.equal(1); - const log = res.logs[0] as LogWithDecodedArgs; - const logArgs = log.args; - expect(logArgs.signerAddress).to.equal(signerAddress); - expect(logArgs.validatorAddress).to.equal(testValidator.address); - expect(logArgs.approved).to.equal(approval); - }); - it('should emit a SignatureValidatorApprovalSet with correct args when a validator is disapproved', async () => { - const approval = false; - const res = await signatureValidatorLogDecoder.getTxWithDecodedLogsAsync( - await signatureValidator.setSignatureValidatorApproval.sendTransactionAsync( - testValidator.address, - approval, - { - from: signerAddress, - }, - ), - ); - expect(res.logs.length).to.equal(1); - const log = res.logs[0] as LogWithDecodedArgs; - const logArgs = log.args; - expect(logArgs.signerAddress).to.equal(signerAddress); - expect(logArgs.validatorAddress).to.equal(testValidator.address); - expect(logArgs.approved).to.equal(approval); - }); - }); -}); -// tslint:disable:max-file-line-count -// tslint:enable:no-unnecessary-type-assertion diff --git a/contracts/protocol/test/exchange/transactions.ts b/contracts/protocol/test/exchange/transactions.ts deleted file mode 100644 index e8c775f3e..000000000 --- a/contracts/protocol/test/exchange/transactions.ts +++ /dev/null @@ -1,466 +0,0 @@ -import { artifacts as examplesArtifacts, ExchangeWrapperContract, WhitelistContract } from '@0x/contracts-examples'; -import { - chaiSetup, - constants, - ERC20BalancesByOwner, - expectTransactionFailedAsync, - OrderFactory, - orderUtils, - provider, - SignedTransaction, - TransactionFactory, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { DummyERC20TokenContract } from '@0x/contracts-tokens'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils'; -import { OrderWithoutExchangeAddress, RevertReason, SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import * as _ from 'lodash'; - -import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { artifacts } from '../../src/artifacts'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe('Exchange transactions', () => { - let senderAddress: string; - let owner: string; - let makerAddress: string; - let takerAddress: string; - let feeRecipientAddress: string; - - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - let zrxToken: DummyERC20TokenContract; - let exchange: ExchangeContract; - let erc20Proxy: ERC20ProxyContract; - - let erc20Balances: ERC20BalancesByOwner; - let signedOrder: SignedOrder; - let signedTx: SignedTransaction; - let orderWithoutExchangeAddress: OrderWithoutExchangeAddress; - let orderFactory: OrderFactory; - let makerTransactionFactory: TransactionFactory; - let takerTransactionFactory: TransactionFactory; - let exchangeWrapper: ExchangeWrapper; - let erc20Wrapper: ERC20Wrapper; - - let defaultMakerTokenAddress: string; - let defaultTakerTokenAddress: string; - let makerPrivateKey: Buffer; - let takerPrivateKey: Buffer; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, senderAddress, makerAddress, takerAddress, feeRecipientAddress] = _.slice( - accounts, - 0, - 5, - )); - - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - - const numDummyErc20ToDeploy = 3; - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - - exchange = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - assetDataUtils.encodeERC20AssetData(zrxToken.address), - ); - exchangeWrapper = new ExchangeWrapper(exchange, provider); - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - defaultMakerTokenAddress = erc20TokenA.address; - defaultTakerTokenAddress = erc20TokenB.address; - - const defaultOrderParams = { - ...constants.STATIC_ORDER_PARAMS, - senderAddress, - exchangeAddress: exchange.address, - makerAddress, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerTokenAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerTokenAddress), - }; - makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; - takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(takerAddress)]; - orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams); - makerTransactionFactory = new TransactionFactory(makerPrivateKey, exchange.address); - takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchange.address); - }); - describe('executeTransaction', () => { - describe('fillOrder', () => { - let takerAssetFillAmount: BigNumber; - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - signedOrder = await orderFactory.newSignedOrderAsync(); - orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); - - takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - const data = exchange.fillOrder.getABIEncodedTransactionData( - orderWithoutExchangeAddress, - takerAssetFillAmount, - signedOrder.signature, - ); - signedTx = takerTransactionFactory.newSignedTransaction(data); - }); - - it('should throw if not called by specified sender', async () => { - return expectTransactionFailedAsync( - exchangeWrapper.executeTransactionAsync(signedTx, takerAddress), - RevertReason.FailedExecution, - ); - }); - - it('should transfer the correct amounts when signed by taker and called by sender', async () => { - await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFeePaid = signedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFeePaid = signedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - expect(newBalances[makerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerTokenAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerTokenAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[takerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerTokenAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerTokenAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFeePaid.plus(takerFeePaid)), - ); - }); - - it('should throw if the a 0x transaction with the same transactionHash has already been executed', async () => { - await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - return expectTransactionFailedAsync( - exchangeWrapper.executeTransactionAsync(signedTx, senderAddress), - RevertReason.InvalidTxHash, - ); - }); - - it('should reset the currentContextAddress', async () => { - await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - const currentContextAddress = await exchange.currentContextAddress.callAsync(); - expect(currentContextAddress).to.equal(constants.NULL_ADDRESS); - }); - }); - - describe('cancelOrder', () => { - beforeEach(async () => { - const data = exchange.cancelOrder.getABIEncodedTransactionData(orderWithoutExchangeAddress); - signedTx = makerTransactionFactory.newSignedTransaction(data); - }); - - it('should throw if not called by specified sender', async () => { - return expectTransactionFailedAsync( - exchangeWrapper.executeTransactionAsync(signedTx, makerAddress), - RevertReason.FailedExecution, - ); - }); - - it('should cancel the order when signed by maker and called by sender', async () => { - await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - return expectTransactionFailedAsync( - exchangeWrapper.fillOrderAsync(signedOrder, senderAddress), - RevertReason.OrderUnfillable, - ); - }); - }); - - describe('cancelOrdersUpTo', () => { - let exchangeWrapperContract: ExchangeWrapperContract; - - before(async () => { - exchangeWrapperContract = await ExchangeWrapperContract.deployFrom0xArtifactAsync( - examplesArtifacts.ExchangeWrapper, - provider, - txDefaults, - exchange.address, - ); - }); - - it("should cancel an order if called from the order's sender", async () => { - const orderSalt = new BigNumber(0); - signedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: exchangeWrapperContract.address, - salt: orderSalt, - }); - const targetOrderEpoch = orderSalt.plus(1); - const cancelData = exchange.cancelOrdersUpTo.getABIEncodedTransactionData(targetOrderEpoch); - const signedCancelTx = makerTransactionFactory.newSignedTransaction(cancelData); - await exchangeWrapperContract.cancelOrdersUpTo.sendTransactionAsync( - targetOrderEpoch, - signedCancelTx.salt, - signedCancelTx.signature, - { - from: makerAddress, - }, - ); - - const takerAssetFillAmount = signedOrder.takerAssetAmount; - orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); - const fillData = exchange.fillOrder.getABIEncodedTransactionData( - orderWithoutExchangeAddress, - takerAssetFillAmount, - signedOrder.signature, - ); - const signedFillTx = takerTransactionFactory.newSignedTransaction(fillData); - return expectTransactionFailedAsync( - exchangeWrapperContract.fillOrder.sendTransactionAsync( - orderWithoutExchangeAddress, - takerAssetFillAmount, - signedFillTx.salt, - signedOrder.signature, - signedFillTx.signature, - { from: takerAddress }, - ), - RevertReason.FailedExecution, - ); - }); - - it("should not cancel an order if not called from the order's sender", async () => { - const orderSalt = new BigNumber(0); - signedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: exchangeWrapperContract.address, - salt: orderSalt, - }); - const targetOrderEpoch = orderSalt.plus(1); - await exchangeWrapper.cancelOrdersUpToAsync(targetOrderEpoch, makerAddress); - - erc20Balances = await erc20Wrapper.getBalancesAsync(); - const takerAssetFillAmount = signedOrder.takerAssetAmount; - orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); - const data = exchange.fillOrder.getABIEncodedTransactionData( - orderWithoutExchangeAddress, - takerAssetFillAmount, - signedOrder.signature, - ); - signedTx = takerTransactionFactory.newSignedTransaction(data); - await exchangeWrapperContract.fillOrder.sendTransactionAsync( - orderWithoutExchangeAddress, - takerAssetFillAmount, - signedTx.salt, - signedOrder.signature, - signedTx.signature, - { from: takerAddress }, - ); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFeePaid = signedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFeePaid = signedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - expect(newBalances[makerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerTokenAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerTokenAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[takerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerTokenAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerTokenAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFeePaid.plus(takerFeePaid)), - ); - }); - }); - }); - - describe('Whitelist', () => { - let whitelist: WhitelistContract; - let whitelistOrderFactory: OrderFactory; - - before(async () => { - whitelist = await WhitelistContract.deployFrom0xArtifactAsync( - examplesArtifacts.Whitelist, - provider, - txDefaults, - exchange.address, - ); - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await exchange.setSignatureValidatorApproval.sendTransactionAsync(whitelist.address, isApproved, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const defaultOrderParams = { - ...constants.STATIC_ORDER_PARAMS, - senderAddress: whitelist.address, - exchangeAddress: exchange.address, - makerAddress, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerTokenAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerTokenAddress), - }; - whitelistOrderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams); - }); - - beforeEach(async () => { - signedOrder = await whitelistOrderFactory.newSignedOrderAsync(); - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - - it('should revert if maker has not been whitelisted', async () => { - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await whitelist.updateWhitelistStatus.sendTransactionAsync(takerAddress, isApproved, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); - const takerAssetFillAmount = signedOrder.takerAssetAmount; - const salt = generatePseudoRandomSalt(); - return expectTransactionFailedAsync( - whitelist.fillOrderIfWhitelisted.sendTransactionAsync( - orderWithoutExchangeAddress, - takerAssetFillAmount, - salt, - signedOrder.signature, - { from: takerAddress }, - ), - RevertReason.MakerNotWhitelisted, - ); - }); - - it('should revert if taker has not been whitelisted', async () => { - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await whitelist.updateWhitelistStatus.sendTransactionAsync(makerAddress, isApproved, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); - const takerAssetFillAmount = signedOrder.takerAssetAmount; - const salt = generatePseudoRandomSalt(); - return expectTransactionFailedAsync( - whitelist.fillOrderIfWhitelisted.sendTransactionAsync( - orderWithoutExchangeAddress, - takerAssetFillAmount, - salt, - signedOrder.signature, - { from: takerAddress }, - ), - RevertReason.TakerNotWhitelisted, - ); - }); - - it('should fill the order if maker and taker have been whitelisted', async () => { - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await whitelist.updateWhitelistStatus.sendTransactionAsync(makerAddress, isApproved, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - await web3Wrapper.awaitTransactionSuccessAsync( - await whitelist.updateWhitelistStatus.sendTransactionAsync(takerAddress, isApproved, { from: owner }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); - const takerAssetFillAmount = signedOrder.takerAssetAmount; - const salt = generatePseudoRandomSalt(); - await web3Wrapper.awaitTransactionSuccessAsync( - await whitelist.fillOrderIfWhitelisted.sendTransactionAsync( - orderWithoutExchangeAddress, - takerAssetFillAmount, - salt, - signedOrder.signature, - { from: takerAddress }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAssetFillAmount = signedOrder.makerAssetAmount; - const makerFeePaid = signedOrder.makerFee; - const takerFeePaid = signedOrder.takerFee; - - expect(newBalances[makerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerTokenAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerTokenAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[takerAddress][defaultTakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerTokenAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerTokenAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerTokenAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFeePaid.plus(takerFeePaid)), - ); - }); - }); -}); diff --git a/contracts/protocol/test/exchange/wrapper.ts b/contracts/protocol/test/exchange/wrapper.ts deleted file mode 100644 index 49ba5a2e5..000000000 --- a/contracts/protocol/test/exchange/wrapper.ts +++ /dev/null @@ -1,1461 +0,0 @@ -import { - chaiSetup, - constants, - ERC20BalancesByOwner, - expectTransactionFailedAsync, - getLatestBlockTimestampAsync, - increaseTimeAndMineBlockAsync, - OrderFactory, - OrderStatus, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { - artifacts as tokensArtifacts, - DummyERC20TokenContract, - DummyERC721TokenContract, - ReentrantERC20TokenContract, -} from '@0x/contracts-tokens'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; -import { RevertReason, SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import * as _ from 'lodash'; - -import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; -import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { artifacts } from '../../src/artifacts'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe('Exchange wrappers', () => { - let makerAddress: string; - let owner: string; - let takerAddress: string; - let feeRecipientAddress: string; - - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - let zrxToken: DummyERC20TokenContract; - let erc721Token: DummyERC721TokenContract; - let exchange: ExchangeContract; - let erc20Proxy: ERC20ProxyContract; - let erc721Proxy: ERC721ProxyContract; - let reentrantErc20Token: ReentrantERC20TokenContract; - - let exchangeWrapper: ExchangeWrapper; - let erc20Wrapper: ERC20Wrapper; - let erc721Wrapper: ERC721Wrapper; - let erc20Balances: ERC20BalancesByOwner; - let orderFactory: OrderFactory; - - let erc721MakerAssetId: BigNumber; - let erc721TakerAssetId: BigNumber; - - let defaultMakerAssetAddress: string; - let defaultTakerAssetAddress: string; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress] = _.slice(accounts, 0, 4)); - - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - - const numDummyErc20ToDeploy = 3; - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - erc721Proxy = await erc721Wrapper.deployProxyAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - erc721MakerAssetId = erc721Balances[makerAddress][erc721Token.address][0]; - erc721TakerAssetId = erc721Balances[takerAddress][erc721Token.address][0]; - - exchange = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - assetDataUtils.encodeERC20AssetData(zrxToken.address), - ); - exchangeWrapper = new ExchangeWrapper(exchange, provider); - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); - - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchange.address, { - from: owner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - reentrantErc20Token = await ReentrantERC20TokenContract.deployFrom0xArtifactAsync( - tokensArtifacts.ReentrantERC20Token, - provider, - txDefaults, - exchange.address, - ); - - defaultMakerAssetAddress = erc20TokenA.address; - defaultTakerAssetAddress = erc20TokenB.address; - - const defaultOrderParams = { - ...constants.STATIC_ORDER_PARAMS, - exchangeAddress: exchange.address, - makerAddress, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('fillOrKillOrder', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow fillOrKillOrder to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await expectTransactionFailedAsync( - exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress), - RevertReason.TransferFailed, - ); - }); - }); - }; - describe('fillOrKillOrder reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should transfer the correct amounts', async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), - }); - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - await exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFee = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFee = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFee), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFilledAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), - ); - }); - - it('should throw if a signedOrder is expired', async () => { - const currentTimestamp = await getLatestBlockTimestampAsync(); - const signedOrder = await orderFactory.newSignedOrderAsync({ - expirationTimeSeconds: new BigNumber(currentTimestamp).minus(10), - }); - - return expectTransactionFailedAsync( - exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress), - RevertReason.OrderUnfillable, - ); - }); - - it('should throw if entire takerAssetFillAmount not filled', async () => { - const signedOrder = await orderFactory.newSignedOrderAsync(); - - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount: signedOrder.takerAssetAmount.div(2), - }); - - return expectTransactionFailedAsync( - exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress), - RevertReason.CompleteFillFailed, - ); - }); - }); - - describe('fillOrderNoThrow', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow fillOrderNoThrow to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(erc20Balances).to.deep.equal(newBalances); - }); - }); - }; - describe('fillOrderNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should transfer the correct amounts', async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), - }); - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress, { - takerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 250000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFee = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFee = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFee), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFilledAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), - ); - }); - - it('should not change erc20Balances if maker erc20Balances are too low to fill order', async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18), - }); - - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not change erc20Balances if taker erc20Balances are too low to fill order', async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18), - }); - - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not change erc20Balances if maker allowances are too low to fill order', async () => { - const signedOrder = await orderFactory.newSignedOrderAsync(); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, constants.INITIAL_ERC20_ALLOWANCE, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not change erc20Balances if taker allowances are too low to fill order', async () => { - const signedOrder = await orderFactory.newSignedOrderAsync(); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, constants.INITIAL_ERC20_ALLOWANCE, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not change erc20Balances if makerAssetAddress is ZRX, makerAssetAmount + makerFee > maker balance', async () => { - const makerZRXBalance = new BigNumber(erc20Balances[makerAddress][zrxToken.address]); - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: makerZRXBalance, - makerFee: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }); - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not change erc20Balances if makerAssetAddress is ZRX, makerAssetAmount + makerFee > maker allowance', async () => { - const makerZRXAllowance = await zrxToken.allowance.callAsync(makerAddress, erc20Proxy.address); - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(makerZRXAllowance), - makerFee: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }); - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not change erc20Balances if takerAssetAddress is ZRX, takerAssetAmount + takerFee > taker balance', async () => { - const takerZRXBalance = new BigNumber(erc20Balances[takerAddress][zrxToken.address]); - const signedOrder = await orderFactory.newSignedOrderAsync({ - takerAssetAmount: takerZRXBalance, - takerFee: new BigNumber(1), - takerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }); - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not change erc20Balances if takerAssetAddress is ZRX, takerAssetAmount + takerFee > taker allowance', async () => { - const takerZRXAllowance = await zrxToken.allowance.callAsync(takerAddress, erc20Proxy.address); - const signedOrder = await orderFactory.newSignedOrderAsync({ - takerAssetAmount: new BigNumber(takerZRXAllowance), - takerFee: new BigNumber(1), - takerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }); - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should successfully exchange ERC721 tokens', async () => { - // Construct Exchange parameters - const makerAssetId = erc721MakerAssetId; - const takerAssetId = erc721TakerAssetId; - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: new BigNumber(1), - takerAssetAmount: new BigNumber(1), - makerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), - }); - // Verify pre-conditions - const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress); - const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); - // Call Exchange - const takerAssetFillAmount = signedOrder.takerAssetAmount; - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress, { - takerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 280000, - }); - // Verify post-conditions - const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress); - const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(newOwnerTakerAsset).to.be.bignumber.equal(makerAddress); - }); - }); - - describe('batch functions', () => { - let signedOrders: SignedOrder[]; - beforeEach(async () => { - signedOrders = [ - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - ]; - }); - - describe('batchFillOrders', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow batchFillOrders to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await expectTransactionFailedAsync( - exchangeWrapper.batchFillOrdersAsync([signedOrder], takerAddress), - RevertReason.TransferFailed, - ); - }); - }); - }; - describe('batchFillOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should transfer the correct amounts', async () => { - const takerAssetFillAmounts: BigNumber[] = []; - const makerAssetAddress = erc20TokenA.address; - const takerAssetAddress = erc20TokenB.address; - _.forEach(signedOrders, signedOrder => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFee = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFee = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - takerAssetFillAmounts.push(takerAssetFillAmount); - erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ - makerAssetAddress - ].minus(makerAssetFilledAmount); - erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][ - takerAssetAddress - ].plus(takerAssetFillAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - makerFee, - ); - erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][ - makerAssetAddress - ].plus(makerAssetFilledAmount); - erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][ - takerAssetAddress - ].minus(takerAssetFillAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(makerFee.plus(takerFee)); - }); - - await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { - takerAssetFillAmounts, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - }); - - describe('batchFillOrKillOrders', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow batchFillOrKillOrders to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await expectTransactionFailedAsync( - exchangeWrapper.batchFillOrKillOrdersAsync([signedOrder], takerAddress), - RevertReason.TransferFailed, - ); - }); - }); - }; - describe('batchFillOrKillOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should transfer the correct amounts', async () => { - const takerAssetFillAmounts: BigNumber[] = []; - const makerAssetAddress = erc20TokenA.address; - const takerAssetAddress = erc20TokenB.address; - _.forEach(signedOrders, signedOrder => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFee = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFee = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - takerAssetFillAmounts.push(takerAssetFillAmount); - erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ - makerAssetAddress - ].minus(makerAssetFilledAmount); - erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][ - takerAssetAddress - ].plus(takerAssetFillAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - makerFee, - ); - erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][ - makerAssetAddress - ].plus(makerAssetFilledAmount); - erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][ - takerAssetAddress - ].minus(takerAssetFillAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(makerFee.plus(takerFee)); - }); - - await exchangeWrapper.batchFillOrKillOrdersAsync(signedOrders, takerAddress, { - takerAssetFillAmounts, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should throw if a single signedOrder does not fill the expected amount', async () => { - const takerAssetFillAmounts: BigNumber[] = []; - _.forEach(signedOrders, signedOrder => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - takerAssetFillAmounts.push(takerAssetFillAmount); - }); - - await exchangeWrapper.fillOrKillOrderAsync(signedOrders[0], takerAddress); - - return expectTransactionFailedAsync( - exchangeWrapper.batchFillOrKillOrdersAsync(signedOrders, takerAddress, { - takerAssetFillAmounts, - }), - RevertReason.OrderUnfillable, - ); - }); - }); - - describe('batchFillOrdersNoThrow', async () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow batchFillOrdersNoThrow to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await exchangeWrapper.batchFillOrdersNoThrowAsync([signedOrder], takerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(erc20Balances).to.deep.equal(newBalances); - }); - }); - }; - describe('batchFillOrdersNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should transfer the correct amounts', async () => { - const takerAssetFillAmounts: BigNumber[] = []; - const makerAssetAddress = erc20TokenA.address; - const takerAssetAddress = erc20TokenB.address; - _.forEach(signedOrders, signedOrder => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFee = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFee = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - takerAssetFillAmounts.push(takerAssetFillAmount); - erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ - makerAssetAddress - ].minus(makerAssetFilledAmount); - erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][ - takerAssetAddress - ].plus(takerAssetFillAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - makerFee, - ); - erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][ - makerAssetAddress - ].plus(makerAssetFilledAmount); - erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][ - takerAssetAddress - ].minus(takerAssetFillAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(makerFee.plus(takerFee)); - }); - - await exchangeWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress, { - takerAssetFillAmounts, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 600000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not throw if an order is invalid and fill the remaining orders', async () => { - const takerAssetFillAmounts: BigNumber[] = []; - const makerAssetAddress = erc20TokenA.address; - const takerAssetAddress = erc20TokenB.address; - - const invalidOrder = { - ...signedOrders[0], - signature: '0x00', - }; - const validOrders = signedOrders.slice(1); - - takerAssetFillAmounts.push(invalidOrder.takerAssetAmount.div(2)); - _.forEach(validOrders, signedOrder => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFee = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFee = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - takerAssetFillAmounts.push(takerAssetFillAmount); - erc20Balances[makerAddress][makerAssetAddress] = erc20Balances[makerAddress][ - makerAssetAddress - ].minus(makerAssetFilledAmount); - erc20Balances[makerAddress][takerAssetAddress] = erc20Balances[makerAddress][ - takerAssetAddress - ].plus(takerAssetFillAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - makerFee, - ); - erc20Balances[takerAddress][makerAssetAddress] = erc20Balances[takerAddress][ - makerAssetAddress - ].plus(makerAssetFilledAmount); - erc20Balances[takerAddress][takerAssetAddress] = erc20Balances[takerAddress][ - takerAssetAddress - ].minus(takerAssetFillAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(makerFee.plus(takerFee)); - }); - - const newOrders = [invalidOrder, ...validOrders]; - await exchangeWrapper.batchFillOrdersNoThrowAsync(newOrders, takerAddress, { - takerAssetFillAmounts, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 450000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - }); - - describe('marketSellOrders', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow marketSellOrders to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await expectTransactionFailedAsync( - exchangeWrapper.marketSellOrdersAsync([signedOrder], takerAddress, { - takerAssetFillAmount: signedOrder.takerAssetAmount, - }), - RevertReason.TransferFailed, - ); - }); - }); - }; - describe('marketSellOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should stop when the entire takerAssetFillAmount is filled', async () => { - const takerAssetFillAmount = signedOrders[0].takerAssetAmount.plus( - signedOrders[1].takerAssetAmount.div(2), - ); - await exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, { - takerAssetFillAmount, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAssetFilledAmount = signedOrders[0].makerAssetAmount.plus( - signedOrders[1].makerAssetAmount.dividedToIntegerBy(2), - ); - const makerFee = signedOrders[0].makerFee.plus(signedOrders[1].makerFee.dividedToIntegerBy(2)); - const takerFee = signedOrders[0].takerFee.plus(signedOrders[1].takerFee.dividedToIntegerBy(2)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFee), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFilledAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), - ); - }); - - it('should fill all signedOrders if cannot fill entire takerAssetFillAmount', async () => { - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); - _.forEach(signedOrders, signedOrder => { - erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ - defaultMakerAssetAddress - ].minus(signedOrder.makerAssetAmount); - erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ - defaultTakerAssetAddress - ].plus(signedOrder.takerAssetAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - signedOrder.makerFee, - ); - erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ - defaultMakerAssetAddress - ].plus(signedOrder.makerAssetAmount); - erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ - defaultTakerAssetAddress - ].minus(signedOrder.takerAssetAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - signedOrder.takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); - }); - await exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, { - takerAssetFillAmount, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should throw when a signedOrder does not use the same takerAssetAddress', async () => { - signedOrders = [ - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }), - await orderFactory.newSignedOrderAsync(), - ]; - - return expectTransactionFailedAsync( - exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, { - takerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18), - }), - // We simply use the takerAssetData from the first order for all orders. - // If they are not the same, the contract throws when validating the order signature - RevertReason.InvalidOrderSignature, - ); - }); - }); - - describe('marketSellOrdersNoThrow', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow marketSellOrdersNoThrow to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await exchangeWrapper.marketSellOrdersNoThrowAsync([signedOrder], takerAddress, { - takerAssetFillAmount: signedOrder.takerAssetAmount, - }); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(erc20Balances).to.deep.equal(newBalances); - }); - }); - }; - describe('marketSellOrdersNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should stop when the entire takerAssetFillAmount is filled', async () => { - const takerAssetFillAmount = signedOrders[0].takerAssetAmount.plus( - signedOrders[1].takerAssetAmount.div(2), - ); - await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, { - takerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 6000000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAssetFilledAmount = signedOrders[0].makerAssetAmount.plus( - signedOrders[1].makerAssetAmount.dividedToIntegerBy(2), - ); - const makerFee = signedOrders[0].makerFee.plus(signedOrders[1].makerFee.dividedToIntegerBy(2)); - const takerFee = signedOrders[0].takerFee.plus(signedOrders[1].takerFee.dividedToIntegerBy(2)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].plus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFee), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFilledAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), - ); - }); - - it('should fill all signedOrders if cannot fill entire takerAssetFillAmount', async () => { - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); - _.forEach(signedOrders, signedOrder => { - erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ - defaultMakerAssetAddress - ].minus(signedOrder.makerAssetAmount); - erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ - defaultTakerAssetAddress - ].plus(signedOrder.takerAssetAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - signedOrder.makerFee, - ); - erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ - defaultMakerAssetAddress - ].plus(signedOrder.makerAssetAmount); - erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ - defaultTakerAssetAddress - ].minus(signedOrder.takerAssetAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - signedOrder.takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); - }); - await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, { - takerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 600000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not fill a signedOrder that does not use the same takerAssetAddress', async () => { - signedOrders = [ - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }), - ]; - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); - const filledSignedOrders = signedOrders.slice(0, -1); - _.forEach(filledSignedOrders, signedOrder => { - erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ - defaultMakerAssetAddress - ].minus(signedOrder.makerAssetAmount); - erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ - defaultTakerAssetAddress - ].plus(signedOrder.takerAssetAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - signedOrder.makerFee, - ); - erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ - defaultMakerAssetAddress - ].plus(signedOrder.makerAssetAmount); - erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ - defaultTakerAssetAddress - ].minus(signedOrder.takerAssetAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - signedOrder.takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); - }); - await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, { - takerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 600000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - }); - - describe('marketBuyOrders', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow marketBuyOrders to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await expectTransactionFailedAsync( - exchangeWrapper.marketBuyOrdersAsync([signedOrder], takerAddress, { - makerAssetFillAmount: signedOrder.makerAssetAmount, - }), - RevertReason.TransferFailed, - ); - }); - }); - }; - describe('marketBuyOrders reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should stop when the entire makerAssetFillAmount is filled', async () => { - const makerAssetFillAmount = signedOrders[0].makerAssetAmount.plus( - signedOrders[1].makerAssetAmount.div(2), - ); - await exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, { - makerAssetFillAmount, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAmountBought = signedOrders[0].takerAssetAmount.plus( - signedOrders[1].takerAssetAmount.dividedToIntegerBy(2), - ); - const makerFee = signedOrders[0].makerFee.plus(signedOrders[1].makerFee.dividedToIntegerBy(2)); - const takerFee = signedOrders[0].takerFee.plus(signedOrders[1].takerFee.dividedToIntegerBy(2)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].plus(makerAmountBought), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFee), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(makerAmountBought), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), - ); - }); - - it('should fill all signedOrders if cannot fill entire makerAssetFillAmount', async () => { - const makerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); - _.forEach(signedOrders, signedOrder => { - erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ - defaultMakerAssetAddress - ].minus(signedOrder.makerAssetAmount); - erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ - defaultTakerAssetAddress - ].plus(signedOrder.takerAssetAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - signedOrder.makerFee, - ); - erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ - defaultMakerAssetAddress - ].plus(signedOrder.makerAssetAmount); - erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ - defaultTakerAssetAddress - ].minus(signedOrder.takerAssetAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - signedOrder.takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); - }); - await exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, { - makerAssetFillAmount, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should throw when a signedOrder does not use the same makerAssetAddress', async () => { - signedOrders = [ - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }), - await orderFactory.newSignedOrderAsync(), - ]; - - return expectTransactionFailedAsync( - exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, { - makerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18), - }), - RevertReason.InvalidOrderSignature, - ); - }); - }); - - describe('marketBuyOrdersNoThrow', () => { - const reentrancyTest = (functionNames: string[]) => { - _.forEach(functionNames, async (functionName: string, functionId: number) => { - const description = `should not allow marketBuyOrdersNoThrow to reenter the Exchange contract via ${functionName}`; - it(description, async () => { - const signedOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(reentrantErc20Token.address), - }); - await web3Wrapper.awaitTransactionSuccessAsync( - await reentrantErc20Token.setCurrentFunction.sendTransactionAsync(functionId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await exchangeWrapper.marketBuyOrdersNoThrowAsync([signedOrder], takerAddress, { - makerAssetFillAmount: signedOrder.makerAssetAmount, - }); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(erc20Balances).to.deep.equal(newBalances); - }); - }); - }; - describe('marketBuyOrdersNoThrow reentrancy tests', () => reentrancyTest(constants.FUNCTIONS_WITH_MUTEX)); - - it('should stop when the entire makerAssetFillAmount is filled', async () => { - const makerAssetFillAmount = signedOrders[0].makerAssetAmount.plus( - signedOrders[1].makerAssetAmount.div(2), - ); - await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, { - makerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 600000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAmountBought = signedOrders[0].takerAssetAmount.plus( - signedOrders[1].takerAssetAmount.dividedToIntegerBy(2), - ); - const makerFee = signedOrders[0].makerFee.plus(signedOrders[1].makerFee.dividedToIntegerBy(2)); - const takerFee = signedOrders[0].takerFee.plus(signedOrders[1].takerFee.dividedToIntegerBy(2)); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].plus(makerAmountBought), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFee), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(makerAmountBought), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].plus(makerAssetFillAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].plus(makerFee.plus(takerFee)), - ); - }); - - it('should fill all signedOrders if cannot fill entire makerAssetFillAmount', async () => { - const makerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); - _.forEach(signedOrders, signedOrder => { - erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ - defaultMakerAssetAddress - ].minus(signedOrder.makerAssetAmount); - erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ - defaultTakerAssetAddress - ].plus(signedOrder.takerAssetAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - signedOrder.makerFee, - ); - erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ - defaultMakerAssetAddress - ].plus(signedOrder.makerAssetAmount); - erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ - defaultTakerAssetAddress - ].minus(signedOrder.takerAssetAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - signedOrder.takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); - }); - await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, { - makerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 600000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - - it('should not fill a signedOrder that does not use the same makerAssetAddress', async () => { - signedOrders = [ - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - }), - ]; - - const makerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); - const filledSignedOrders = signedOrders.slice(0, -1); - _.forEach(filledSignedOrders, signedOrder => { - erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ - defaultMakerAssetAddress - ].minus(signedOrder.makerAssetAmount); - erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ - defaultTakerAssetAddress - ].plus(signedOrder.takerAssetAmount); - erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( - signedOrder.makerFee, - ); - erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ - defaultMakerAssetAddress - ].plus(signedOrder.makerAssetAmount); - erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ - defaultTakerAssetAddress - ].minus(signedOrder.takerAssetAmount); - erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( - signedOrder.takerFee, - ); - erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ - zrxToken.address - ].plus(signedOrder.makerFee.plus(signedOrder.takerFee)); - }); - await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, { - makerAssetFillAmount, - // HACK(albrow): We need to hardcode the gas estimate here because - // the Geth gas estimator doesn't work with the way we use - // delegatecall and swallow errors. - gas: 600000, - }); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances).to.be.deep.equal(erc20Balances); - }); - }); - - describe('batchCancelOrders', () => { - it('should be able to cancel multiple signedOrders', async () => { - const takerAssetCancelAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount); - await exchangeWrapper.batchCancelOrdersAsync(signedOrders, makerAddress); - - await exchangeWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress, { - takerAssetFillAmounts: takerAssetCancelAmounts, - }); - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(erc20Balances).to.be.deep.equal(newBalances); - }); - }); - - describe('getOrdersInfo', () => { - beforeEach(async () => { - signedOrders = [ - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - ]; - }); - it('should get the correct information for multiple unfilled orders', async () => { - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(3); - _.forEach(signedOrders, (signedOrder, index) => { - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - const expectedOrderStatus = OrderStatus.Fillable; - const orderInfo = ordersInfo[index]; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); - it('should get the correct information for multiple partially filled orders', async () => { - const takerAssetFillAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount.div(2)); - await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmounts }); - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(3); - _.forEach(signedOrders, (signedOrder, index) => { - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount.div(2); - const expectedOrderStatus = OrderStatus.Fillable; - const orderInfo = ordersInfo[index]; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); - it('should get the correct information for multiple fully filled orders', async () => { - await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress); - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(3); - _.forEach(signedOrders, (signedOrder, index) => { - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount; - const expectedOrderStatus = OrderStatus.FullyFilled; - const orderInfo = ordersInfo[index]; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); - it('should get the correct information for multiple cancelled and unfilled orders', async () => { - await exchangeWrapper.batchCancelOrdersAsync(signedOrders, makerAddress); - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(3); - _.forEach(signedOrders, (signedOrder, index) => { - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - const expectedOrderStatus = OrderStatus.Cancelled; - const orderInfo = ordersInfo[index]; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); - it('should get the correct information for multiple cancelled and partially filled orders', async () => { - const takerAssetFillAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount.div(2)); - await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmounts }); - await exchangeWrapper.batchCancelOrdersAsync(signedOrders, makerAddress); - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(3); - _.forEach(signedOrders, (signedOrder, index) => { - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount.div(2); - const expectedOrderStatus = OrderStatus.Cancelled; - const orderInfo = ordersInfo[index]; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); - it('should get the correct information for multiple expired and unfilled orders', async () => { - const currentTimestamp = await getLatestBlockTimestampAsync(); - const timeUntilExpiration = signedOrders[0].expirationTimeSeconds.minus(currentTimestamp).toNumber(); - await increaseTimeAndMineBlockAsync(timeUntilExpiration); - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(3); - _.forEach(signedOrders, (signedOrder, index) => { - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = new BigNumber(0); - const expectedOrderStatus = OrderStatus.Expired; - const orderInfo = ordersInfo[index]; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); - it('should get the correct information for multiple expired and partially filled orders', async () => { - const takerAssetFillAmounts = _.map(signedOrders, signedOrder => signedOrder.takerAssetAmount.div(2)); - await exchangeWrapper.batchFillOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmounts }); - const currentTimestamp = await getLatestBlockTimestampAsync(); - const timeUntilExpiration = signedOrders[0].expirationTimeSeconds.minus(currentTimestamp).toNumber(); - await increaseTimeAndMineBlockAsync(timeUntilExpiration); - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(3); - _.forEach(signedOrders, (signedOrder, index) => { - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - const expectedTakerAssetFilledAmount = signedOrder.takerAssetAmount.div(2); - const expectedOrderStatus = OrderStatus.Expired; - const orderInfo = ordersInfo[index]; - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(expectedTakerAssetFilledAmount); - expect(orderInfo.orderStatus).to.equal(expectedOrderStatus); - }); - }); - it('should get the correct information for a mix of unfilled, partially filled, fully filled, cancelled, and expired orders', async () => { - const unfilledOrder = await orderFactory.newSignedOrderAsync(); - const partiallyFilledOrder = await orderFactory.newSignedOrderAsync(); - await exchangeWrapper.fillOrderAsync(partiallyFilledOrder, takerAddress, { - takerAssetFillAmount: partiallyFilledOrder.takerAssetAmount.div(2), - }); - const fullyFilledOrder = await orderFactory.newSignedOrderAsync(); - await exchangeWrapper.fillOrderAsync(fullyFilledOrder, takerAddress); - const cancelledOrder = await orderFactory.newSignedOrderAsync(); - await exchangeWrapper.cancelOrderAsync(cancelledOrder, makerAddress); - const currentTimestamp = await getLatestBlockTimestampAsync(); - const expiredOrder = await orderFactory.newSignedOrderAsync({ - expirationTimeSeconds: new BigNumber(currentTimestamp), - }); - signedOrders = [unfilledOrder, partiallyFilledOrder, fullyFilledOrder, cancelledOrder, expiredOrder]; - const ordersInfo = await exchangeWrapper.getOrdersInfoAsync(signedOrders); - expect(ordersInfo.length).to.be.equal(5); - - const expectedUnfilledOrderHash = orderHashUtils.getOrderHashHex(unfilledOrder); - const expectedUnfilledTakerAssetFilledAmount = new BigNumber(0); - const expectedUnfilledOrderStatus = OrderStatus.Fillable; - const unfilledOrderInfo = ordersInfo[0]; - expect(unfilledOrderInfo.orderHash).to.be.equal(expectedUnfilledOrderHash); - expect(unfilledOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal( - expectedUnfilledTakerAssetFilledAmount, - ); - expect(unfilledOrderInfo.orderStatus).to.be.equal(expectedUnfilledOrderStatus); - - const expectedPartialOrderHash = orderHashUtils.getOrderHashHex(partiallyFilledOrder); - const expectedPartialTakerAssetFilledAmount = partiallyFilledOrder.takerAssetAmount.div(2); - const expectedPartialOrderStatus = OrderStatus.Fillable; - const partialOrderInfo = ordersInfo[1]; - expect(partialOrderInfo.orderHash).to.be.equal(expectedPartialOrderHash); - expect(partialOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal( - expectedPartialTakerAssetFilledAmount, - ); - expect(partialOrderInfo.orderStatus).to.be.equal(expectedPartialOrderStatus); - - const expectedFilledOrderHash = orderHashUtils.getOrderHashHex(fullyFilledOrder); - const expectedFilledTakerAssetFilledAmount = fullyFilledOrder.takerAssetAmount; - const expectedFilledOrderStatus = OrderStatus.FullyFilled; - const filledOrderInfo = ordersInfo[2]; - expect(filledOrderInfo.orderHash).to.be.equal(expectedFilledOrderHash); - expect(filledOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal( - expectedFilledTakerAssetFilledAmount, - ); - expect(filledOrderInfo.orderStatus).to.be.equal(expectedFilledOrderStatus); - - const expectedCancelledOrderHash = orderHashUtils.getOrderHashHex(cancelledOrder); - const expectedCancelledTakerAssetFilledAmount = new BigNumber(0); - const expectedCancelledOrderStatus = OrderStatus.Cancelled; - const cancelledOrderInfo = ordersInfo[3]; - expect(cancelledOrderInfo.orderHash).to.be.equal(expectedCancelledOrderHash); - expect(cancelledOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal( - expectedCancelledTakerAssetFilledAmount, - ); - expect(cancelledOrderInfo.orderStatus).to.be.equal(expectedCancelledOrderStatus); - - const expectedExpiredOrderHash = orderHashUtils.getOrderHashHex(expiredOrder); - const expectedExpiredTakerAssetFilledAmount = new BigNumber(0); - const expectedExpiredOrderStatus = OrderStatus.Expired; - const expiredOrderInfo = ordersInfo[4]; - expect(expiredOrderInfo.orderHash).to.be.equal(expectedExpiredOrderHash); - expect(expiredOrderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal( - expectedExpiredTakerAssetFilledAmount, - ); - expect(expiredOrderInfo.orderStatus).to.be.equal(expectedExpiredOrderStatus); - }); - }); - }); -}); // tslint:disable-line:max-file-line-count diff --git a/contracts/protocol/test/global_hooks.ts b/contracts/protocol/test/global_hooks.ts deleted file mode 100644 index f8ace376a..000000000 --- a/contracts/protocol/test/global_hooks.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { env, EnvVars } from '@0x/dev-utils'; - -import { coverage, profiler, provider } from '@0x/contracts-test-utils'; -before('start web3 provider', () => { - provider.start(); -}); -after('generate coverage report', async () => { - if (env.parseBoolean(EnvVars.SolidityCoverage)) { - const coverageSubprovider = coverage.getCoverageSubproviderSingleton(); - await coverageSubprovider.writeCoverageAsync(); - } - if (env.parseBoolean(EnvVars.SolidityProfiler)) { - const profilerSubprovider = profiler.getProfilerSubproviderSingleton(); - await profilerSubprovider.writeProfilerOutputAsync(); - } - provider.stop(); -}); diff --git a/contracts/protocol/test/multisig/asset_proxy_owner.ts b/contracts/protocol/test/multisig/asset_proxy_owner.ts deleted file mode 100644 index daebfb7fb..000000000 --- a/contracts/protocol/test/multisig/asset_proxy_owner.ts +++ /dev/null @@ -1,506 +0,0 @@ -import { - chaiSetup, - constants, - expectContractCallFailedAsync, - expectContractCreationFailedAsync, - expectTransactionFailedAsync, - expectTransactionFailedWithoutReasonAsync, - increaseTimeAndMineBlockAsync, - provider, - sendTransactionResult, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { RevertReason } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import { LogWithDecodedArgs } from 'ethereum-types'; - -import { - AssetProxyOwnerAssetProxyRegistrationEventArgs, - AssetProxyOwnerContract, - AssetProxyOwnerExecutionEventArgs, - AssetProxyOwnerExecutionFailureEventArgs, - AssetProxyOwnerSubmissionEventArgs, -} from '../../generated-wrappers/asset_proxy_owner'; -import { MixinAuthorizableContract } from '../../generated-wrappers/mixin_authorizable'; -import { TestAssetProxyOwnerContract } from '../../generated-wrappers/test_asset_proxy_owner'; -import { artifacts } from '../../src/artifacts'; -import { AssetProxyOwnerWrapper } from '../utils/asset_proxy_owner_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -// tslint:disable:no-unnecessary-type-assertion -describe('AssetProxyOwner', () => { - let owners: string[]; - let authorized: string; - let notOwner: string; - const REQUIRED_APPROVALS = new BigNumber(2); - const SECONDS_TIME_LOCKED = new BigNumber(1000000); - - let erc20Proxy: MixinAuthorizableContract; - let erc721Proxy: MixinAuthorizableContract; - let testAssetProxyOwner: TestAssetProxyOwnerContract; - let assetProxyOwnerWrapper: AssetProxyOwnerWrapper; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - owners = [accounts[0], accounts[1]]; - authorized = accounts[2]; - notOwner = accounts[3]; - const initialOwner = accounts[0]; - erc20Proxy = await MixinAuthorizableContract.deployFrom0xArtifactAsync( - artifacts.MixinAuthorizable, - provider, - txDefaults, - ); - erc721Proxy = await MixinAuthorizableContract.deployFrom0xArtifactAsync( - artifacts.MixinAuthorizable, - provider, - txDefaults, - ); - const defaultAssetProxyContractAddresses: string[] = []; - testAssetProxyOwner = await TestAssetProxyOwnerContract.deployFrom0xArtifactAsync( - artifacts.TestAssetProxyOwner, - provider, - txDefaults, - owners, - defaultAssetProxyContractAddresses, - REQUIRED_APPROVALS, - SECONDS_TIME_LOCKED, - ); - assetProxyOwnerWrapper = new AssetProxyOwnerWrapper(testAssetProxyOwner, provider); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.transferOwnership.sendTransactionAsync(testAssetProxyOwner.address, { - from: initialOwner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.transferOwnership.sendTransactionAsync(testAssetProxyOwner.address, { - from: initialOwner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - - describe('constructor', () => { - it('should register passed in assetProxyContracts', async () => { - const assetProxyContractAddresses = [erc20Proxy.address, erc721Proxy.address]; - const newMultiSig = await AssetProxyOwnerContract.deployFrom0xArtifactAsync( - artifacts.AssetProxyOwner, - provider, - txDefaults, - owners, - assetProxyContractAddresses, - REQUIRED_APPROVALS, - SECONDS_TIME_LOCKED, - ); - const isErc20ProxyRegistered = await newMultiSig.isAssetProxyRegistered.callAsync(erc20Proxy.address); - const isErc721ProxyRegistered = await newMultiSig.isAssetProxyRegistered.callAsync(erc721Proxy.address); - expect(isErc20ProxyRegistered).to.equal(true); - expect(isErc721ProxyRegistered).to.equal(true); - }); - it('should throw if a null address is included in assetProxyContracts', async () => { - const assetProxyContractAddresses = [erc20Proxy.address, constants.NULL_ADDRESS]; - return expectContractCreationFailedAsync( - (AssetProxyOwnerContract.deployFrom0xArtifactAsync( - artifacts.AssetProxyOwner, - provider, - txDefaults, - owners, - assetProxyContractAddresses, - REQUIRED_APPROVALS, - SECONDS_TIME_LOCKED, - ) as any) as sendTransactionResult, - RevertReason.InvalidAssetProxy, - ); - }); - }); - - describe('isFunctionRemoveAuthorizedAddressAtIndex', () => { - it('should return false if data is not for removeAuthorizedAddressAtIndex', async () => { - const notRemoveAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData( - owners[0], - ); - - const isFunctionRemoveAuthorizedAddressAtIndex = await testAssetProxyOwner.isFunctionRemoveAuthorizedAddressAtIndex.callAsync( - notRemoveAuthorizedAddressData, - ); - expect(isFunctionRemoveAuthorizedAddressAtIndex).to.be.false(); - }); - - it('should return true if data is for removeAuthorizedAddressAtIndex', async () => { - const index = new BigNumber(0); - const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( - owners[0], - index, - ); - const isFunctionRemoveAuthorizedAddressAtIndex = await testAssetProxyOwner.isFunctionRemoveAuthorizedAddressAtIndex.callAsync( - removeAuthorizedAddressAtIndexData, - ); - expect(isFunctionRemoveAuthorizedAddressAtIndex).to.be.true(); - }); - }); - - describe('registerAssetProxy', () => { - it('should throw if not called by multisig', async () => { - const isRegistered = true; - return expectTransactionFailedWithoutReasonAsync( - testAssetProxyOwner.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, isRegistered, { - from: owners[0], - }), - ); - }); - - it('should register an address if called by multisig after timelock', async () => { - const addressToRegister = erc20Proxy.address; - const isRegistered = true; - const registerAssetProxyData = testAssetProxyOwner.registerAssetProxy.getABIEncodedTransactionData( - addressToRegister, - isRegistered, - ); - const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync( - testAssetProxyOwner.address, - registerAssetProxyData, - owners[0], - ); - - const log = submitTxRes.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); - await increaseTimeAndMineBlockAsync(SECONDS_TIME_LOCKED.toNumber()); - - const executeTxRes = await assetProxyOwnerWrapper.executeTransactionAsync(txId, owners[0]); - const registerLog = executeTxRes.logs[0] as LogWithDecodedArgs< - AssetProxyOwnerAssetProxyRegistrationEventArgs - >; - expect(registerLog.args.assetProxyContract).to.equal(addressToRegister); - expect(registerLog.args.isRegistered).to.equal(isRegistered); - - const isAssetProxyRegistered = await testAssetProxyOwner.isAssetProxyRegistered.callAsync( - addressToRegister, - ); - expect(isAssetProxyRegistered).to.equal(isRegistered); - }); - - it('should fail if registering a null address', async () => { - const addressToRegister = constants.NULL_ADDRESS; - const isRegistered = true; - const registerAssetProxyData = testAssetProxyOwner.registerAssetProxy.getABIEncodedTransactionData( - addressToRegister, - isRegistered, - ); - const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync( - testAssetProxyOwner.address, - registerAssetProxyData, - owners[0], - ); - const log = submitTxRes.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); - await increaseTimeAndMineBlockAsync(SECONDS_TIME_LOCKED.toNumber()); - - const executeTxRes = await assetProxyOwnerWrapper.executeTransactionAsync(txId, owners[0]); - const failureLog = executeTxRes.logs[0] as LogWithDecodedArgs; - expect(failureLog.args.transactionId).to.be.bignumber.equal(txId); - - const isAssetProxyRegistered = await testAssetProxyOwner.isAssetProxyRegistered.callAsync( - addressToRegister, - ); - expect(isAssetProxyRegistered).to.equal(false); - }); - }); - - describe('Calling removeAuthorizedAddressAtIndex', () => { - const erc20Index = new BigNumber(0); - const erc721Index = new BigNumber(1); - before('authorize both proxies and register erc20 proxy', async () => { - // Only register ERC20 proxy - const addressToRegister = erc20Proxy.address; - const isRegistered = true; - const registerAssetProxyData = testAssetProxyOwner.registerAssetProxy.getABIEncodedTransactionData( - addressToRegister, - isRegistered, - ); - const registerAssetProxySubmitRes = await assetProxyOwnerWrapper.submitTransactionAsync( - testAssetProxyOwner.address, - registerAssetProxyData, - owners[0], - ); - const registerAssetProxySubmitLog = registerAssetProxySubmitRes.logs[0] as LogWithDecodedArgs< - AssetProxyOwnerSubmissionEventArgs - >; - - const addAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData(authorized); - const erc20AddAuthorizedAddressSubmitRes = await assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - addAuthorizedAddressData, - owners[0], - ); - const erc721AddAuthorizedAddressSubmitRes = await assetProxyOwnerWrapper.submitTransactionAsync( - erc721Proxy.address, - addAuthorizedAddressData, - owners[0], - ); - const erc20AddAuthorizedAddressSubmitLog = erc20AddAuthorizedAddressSubmitRes.logs[0] as LogWithDecodedArgs< - AssetProxyOwnerSubmissionEventArgs - >; - const erc721AddAuthorizedAddressSubmitLog = erc721AddAuthorizedAddressSubmitRes - .logs[0] as LogWithDecodedArgs; - - const registerAssetProxyTxId = registerAssetProxySubmitLog.args.transactionId; - const erc20AddAuthorizedAddressTxId = erc20AddAuthorizedAddressSubmitLog.args.transactionId; - const erc721AddAuthorizedAddressTxId = erc721AddAuthorizedAddressSubmitLog.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(registerAssetProxyTxId, owners[1]); - await assetProxyOwnerWrapper.confirmTransactionAsync(erc20AddAuthorizedAddressTxId, owners[1]); - await assetProxyOwnerWrapper.confirmTransactionAsync(erc721AddAuthorizedAddressTxId, owners[1]); - await increaseTimeAndMineBlockAsync(SECONDS_TIME_LOCKED.toNumber()); - await assetProxyOwnerWrapper.executeTransactionAsync(registerAssetProxyTxId, owners[0]); - await assetProxyOwnerWrapper.executeTransactionAsync(erc20AddAuthorizedAddressTxId, owners[0], { - gas: constants.MAX_EXECUTE_TRANSACTION_GAS, - }); - await assetProxyOwnerWrapper.executeTransactionAsync(erc721AddAuthorizedAddressTxId, owners[0], { - gas: constants.MAX_EXECUTE_TRANSACTION_GAS, - }); - }); - - describe('validRemoveAuthorizedAddressAtIndexTx', () => { - it('should revert if data is not for removeAuthorizedAddressAtIndex and proxy is registered', async () => { - const notRemoveAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData( - authorized, - ); - const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - notRemoveAuthorizedAddressData, - owners[0], - ); - const log = submitTxRes.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - return expectContractCallFailedAsync( - testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync(txId), - RevertReason.InvalidFunctionSelector, - ); - }); - - it('should return true if data is for removeAuthorizedAddressAtIndex and proxy is registered', async () => { - const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( - authorized, - erc20Index, - ); - const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - removeAuthorizedAddressAtIndexData, - owners[0], - ); - const log = submitTxRes.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - const isValidRemoveAuthorizedAddressAtIndexTx = await testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync( - txId, - ); - expect(isValidRemoveAuthorizedAddressAtIndexTx).to.be.true(); - }); - - it('should revert if data is for removeAuthorizedAddressAtIndex and proxy is not registered', async () => { - const removeAuthorizedAddressAtIndexData = erc721Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( - authorized, - erc721Index, - ); - const submitTxRes = await assetProxyOwnerWrapper.submitTransactionAsync( - erc721Proxy.address, - removeAuthorizedAddressAtIndexData, - owners[0], - ); - const log = submitTxRes.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - return expectContractCallFailedAsync( - testAssetProxyOwner.testValidRemoveAuthorizedAddressAtIndexTx.callAsync(txId), - RevertReason.UnregisteredAssetProxy, - ); - }); - }); - - describe('executeRemoveAuthorizedAddressAtIndex', () => { - it('should throw without the required confirmations', async () => { - const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( - authorized, - erc20Index, - ); - const res = await assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - removeAuthorizedAddressAtIndexData, - owners[0], - ); - const log = res.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - - return expectTransactionFailedAsync( - testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { - from: owners[1], - }), - RevertReason.TxNotFullyConfirmed, - ); - }); - - it('should throw if tx destination is not registered', async () => { - const removeAuthorizedAddressAtIndexData = erc721Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( - authorized, - erc721Index, - ); - const res = await assetProxyOwnerWrapper.submitTransactionAsync( - erc721Proxy.address, - removeAuthorizedAddressAtIndexData, - owners[0], - ); - const log = res.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); - - return expectTransactionFailedAsync( - testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { - from: owners[1], - }), - RevertReason.UnregisteredAssetProxy, - ); - }); - - it('should throw if tx data is not for removeAuthorizedAddressAtIndex', async () => { - const newAuthorized = owners[1]; - const addAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData( - newAuthorized, - ); - const res = await assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - addAuthorizedAddressData, - owners[0], - ); - const log = res.logs[0] as LogWithDecodedArgs; - const txId = log.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); - - return expectTransactionFailedAsync( - testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { - from: owners[1], - }), - RevertReason.InvalidFunctionSelector, - ); - }); - - it('should execute removeAuthorizedAddressAtIndex for registered address if fully confirmed and called by owner', async () => { - const isAuthorizedBefore = await erc20Proxy.authorized.callAsync(authorized); - expect(isAuthorizedBefore).to.equal(true); - - const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( - authorized, - erc20Index, - ); - const submitRes = await assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - removeAuthorizedAddressAtIndexData, - owners[0], - ); - const submitLog = submitRes.logs[0] as LogWithDecodedArgs; - const txId = submitLog.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); - - const execRes = await assetProxyOwnerWrapper.executeRemoveAuthorizedAddressAtIndexAsync( - txId, - owners[0], - ); - const execLog = execRes.logs[1] as LogWithDecodedArgs; - expect(execLog.args.transactionId).to.be.bignumber.equal(txId); - - const tx = await testAssetProxyOwner.transactions.callAsync(txId); - const isExecuted = tx[3]; - expect(isExecuted).to.equal(true); - - const isAuthorizedAfter = await erc20Proxy.authorized.callAsync(authorized); - expect(isAuthorizedAfter).to.equal(false); - }); - - it('should execute removeAuthorizedAddressAtIndex for registered address if fully confirmed and called by non-owner', async () => { - const isAuthorizedBefore = await erc20Proxy.authorized.callAsync(authorized); - expect(isAuthorizedBefore).to.equal(true); - - const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( - authorized, - erc20Index, - ); - const submitRes = await assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - removeAuthorizedAddressAtIndexData, - owners[0], - ); - const submitLog = submitRes.logs[0] as LogWithDecodedArgs; - const txId = submitLog.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); - - const execRes = await assetProxyOwnerWrapper.executeRemoveAuthorizedAddressAtIndexAsync(txId, notOwner); - const execLog = execRes.logs[1] as LogWithDecodedArgs; - expect(execLog.args.transactionId).to.be.bignumber.equal(txId); - - const tx = await testAssetProxyOwner.transactions.callAsync(txId); - const isExecuted = tx[3]; - expect(isExecuted).to.equal(true); - - const isAuthorizedAfter = await erc20Proxy.authorized.callAsync(authorized); - expect(isAuthorizedAfter).to.equal(false); - }); - - it('should throw if already executed', async () => { - const removeAuthorizedAddressAtIndexData = erc20Proxy.removeAuthorizedAddressAtIndex.getABIEncodedTransactionData( - authorized, - erc20Index, - ); - const submitRes = await assetProxyOwnerWrapper.submitTransactionAsync( - erc20Proxy.address, - removeAuthorizedAddressAtIndexData, - owners[0], - ); - const submitLog = submitRes.logs[0] as LogWithDecodedArgs; - const txId = submitLog.args.transactionId; - - await assetProxyOwnerWrapper.confirmTransactionAsync(txId, owners[1]); - - const execRes = await assetProxyOwnerWrapper.executeRemoveAuthorizedAddressAtIndexAsync( - txId, - owners[0], - ); - const execLog = execRes.logs[1] as LogWithDecodedArgs; - expect(execLog.args.transactionId).to.be.bignumber.equal(txId); - - const tx = await testAssetProxyOwner.transactions.callAsync(txId); - const isExecuted = tx[3]; - expect(isExecuted).to.equal(true); - - return expectTransactionFailedWithoutReasonAsync( - testAssetProxyOwner.executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync(txId, { - from: owners[1], - }), - ); - }); - }); - }); -}); -// tslint:disable-line max-file-line-count diff --git a/contracts/protocol/test/utils/asset_proxy_owner_wrapper.ts b/contracts/protocol/test/utils/asset_proxy_owner_wrapper.ts deleted file mode 100644 index df23658d8..000000000 --- a/contracts/protocol/test/utils/asset_proxy_owner_wrapper.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { LogDecoder } from '@0x/contracts-test-utils'; -import { artifacts as tokensArtifacts } from '@0x/contracts-tokens'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { AssetProxyOwnerContract } from '../../generated-wrappers/asset_proxy_owner'; -import { artifacts } from '../../src/artifacts'; - -export class AssetProxyOwnerWrapper { - private readonly _assetProxyOwner: AssetProxyOwnerContract; - private readonly _web3Wrapper: Web3Wrapper; - private readonly _logDecoder: LogDecoder; - constructor(assetproxyOwnerContract: AssetProxyOwnerContract, provider: Provider) { - this._assetProxyOwner = assetproxyOwnerContract; - this._web3Wrapper = new Web3Wrapper(provider); - this._logDecoder = new LogDecoder(this._web3Wrapper, { ...artifacts, ...tokensArtifacts }); - } - public async submitTransactionAsync( - destination: string, - data: string, - from: string, - opts: { value?: BigNumber } = {}, - ): Promise { - const value = _.isUndefined(opts.value) ? new BigNumber(0) : opts.value; - const txHash = await this._assetProxyOwner.submitTransaction.sendTransactionAsync(destination, value, data, { - from, - }); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async confirmTransactionAsync(txId: BigNumber, from: string): Promise { - const txHash = await this._assetProxyOwner.confirmTransaction.sendTransactionAsync(txId, { from }); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async revokeConfirmationAsync(txId: BigNumber, from: string): Promise { - const txHash = await this._assetProxyOwner.revokeConfirmation.sendTransactionAsync(txId, { from }); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async executeTransactionAsync( - txId: BigNumber, - from: string, - opts: { gas?: number } = {}, - ): Promise { - const txHash = await this._assetProxyOwner.executeTransaction.sendTransactionAsync(txId, { - from, - gas: opts.gas, - }); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async executeRemoveAuthorizedAddressAtIndexAsync( - txId: BigNumber, - from: string, - ): Promise { - // tslint:disable-next-line:no-unnecessary-type-assertion - const txHash = await (this - ._assetProxyOwner as AssetProxyOwnerContract).executeRemoveAuthorizedAddressAtIndex.sendTransactionAsync( - txId, - { - from, - }, - ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } -} diff --git a/contracts/protocol/test/utils/asset_wrapper.ts b/contracts/protocol/test/utils/asset_wrapper.ts deleted file mode 100644 index e4090ad74..000000000 --- a/contracts/protocol/test/utils/asset_wrapper.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { AbstractAssetWrapper, constants } from '@0x/contracts-test-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { AssetProxyId } from '@0x/types'; -import { BigNumber, errorUtils } from '@0x/utils'; -import * as _ from 'lodash'; - -import { ERC20Wrapper } from './erc20_wrapper'; -import { ERC721Wrapper } from './erc721_wrapper'; - -interface ProxyIdToAssetWrappers { - [proxyId: string]: AbstractAssetWrapper; -} - -/** - * This class abstracts away the differences between ERC20 and ERC721 tokens so that - * the logic that uses it does not need to care what standard a token belongs to. - */ -export class AssetWrapper { - private readonly _proxyIdToAssetWrappers: ProxyIdToAssetWrappers; - constructor(assetWrappers: AbstractAssetWrapper[]) { - this._proxyIdToAssetWrappers = {}; - _.each(assetWrappers, assetWrapper => { - const proxyId = assetWrapper.getProxyId(); - this._proxyIdToAssetWrappers[proxyId] = assetWrapper; - }); - } - public async getBalanceAsync(userAddress: string, assetData: string): Promise { - const proxyId = assetDataUtils.decodeAssetProxyId(assetData); - switch (proxyId) { - case AssetProxyId.ERC20: { - const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper; - const balance = await erc20Wrapper.getBalanceAsync(userAddress, assetData); - return balance; - } - case AssetProxyId.ERC721: { - const assetWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper; - const assetProxyData = assetDataUtils.decodeERC721AssetData(assetData); - const isOwner = await assetWrapper.isOwnerAsync( - userAddress, - assetProxyData.tokenAddress, - assetProxyData.tokenId, - ); - const balance = isOwner ? new BigNumber(1) : new BigNumber(0); - return balance; - } - default: - throw errorUtils.spawnSwitchErr('proxyId', proxyId); - } - } - public async setBalanceAsync(userAddress: string, assetData: string, desiredBalance: BigNumber): Promise { - const proxyId = assetDataUtils.decodeAssetProxyId(assetData); - switch (proxyId) { - case AssetProxyId.ERC20: { - const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper; - await erc20Wrapper.setBalanceAsync(userAddress, assetData, desiredBalance); - return; - } - case AssetProxyId.ERC721: { - if (!desiredBalance.eq(0) && !desiredBalance.eq(1)) { - throw new Error(`Balance for ERC721 token can only be set to 0 or 1. Got: ${desiredBalance}`); - } - const erc721Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper; - const assetProxyData = assetDataUtils.decodeERC721AssetData(assetData); - const doesTokenExist = erc721Wrapper.doesTokenExistAsync( - assetProxyData.tokenAddress, - assetProxyData.tokenId, - ); - if (!doesTokenExist && desiredBalance.eq(1)) { - await erc721Wrapper.mintAsync(assetProxyData.tokenAddress, assetProxyData.tokenId, userAddress); - return; - } else if (!doesTokenExist && desiredBalance.eq(0)) { - return; // noop - } - const tokenOwner = await erc721Wrapper.ownerOfAsync( - assetProxyData.tokenAddress, - assetProxyData.tokenId, - ); - if (userAddress !== tokenOwner && desiredBalance.eq(1)) { - await erc721Wrapper.transferFromAsync( - assetProxyData.tokenAddress, - assetProxyData.tokenId, - tokenOwner, - userAddress, - ); - } else if (tokenOwner === userAddress && desiredBalance.eq(0)) { - // Transfer token to someone else - const userAddresses = await (erc721Wrapper as any)._web3Wrapper.getAvailableAddressesAsync(); - const nonOwner = _.find(userAddresses, a => a !== userAddress); - await erc721Wrapper.transferFromAsync( - assetProxyData.tokenAddress, - assetProxyData.tokenId, - tokenOwner, - nonOwner, - ); - return; - } else if ( - (userAddress !== tokenOwner && desiredBalance.eq(0)) || - (tokenOwner === userAddress && desiredBalance.eq(1)) - ) { - return; // noop - } - break; - } - default: - throw errorUtils.spawnSwitchErr('proxyId', proxyId); - } - } - public async getProxyAllowanceAsync(userAddress: string, assetData: string): Promise { - const proxyId = assetDataUtils.decodeAssetProxyId(assetData); - switch (proxyId) { - case AssetProxyId.ERC20: { - const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper; - const allowance = await erc20Wrapper.getProxyAllowanceAsync(userAddress, assetData); - return allowance; - } - case AssetProxyId.ERC721: { - const assetWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper; - const erc721ProxyData = assetDataUtils.decodeERC721AssetData(assetData); - const isProxyApprovedForAll = await assetWrapper.isProxyApprovedForAllAsync( - userAddress, - erc721ProxyData.tokenAddress, - ); - if (isProxyApprovedForAll) { - return constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; - } - - const isProxyApproved = await assetWrapper.isProxyApprovedAsync( - erc721ProxyData.tokenAddress, - erc721ProxyData.tokenId, - ); - const allowance = isProxyApproved ? new BigNumber(1) : new BigNumber(0); - return allowance; - } - default: - throw errorUtils.spawnSwitchErr('proxyId', proxyId); - } - } - public async setProxyAllowanceAsync( - userAddress: string, - assetData: string, - desiredAllowance: BigNumber, - ): Promise { - const proxyId = assetDataUtils.decodeAssetProxyId(assetData); - switch (proxyId) { - case AssetProxyId.ERC20: { - const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper; - await erc20Wrapper.setAllowanceAsync(userAddress, assetData, desiredAllowance); - return; - } - case AssetProxyId.ERC721: { - if ( - !desiredAllowance.eq(0) && - !desiredAllowance.eq(1) && - !desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS) - ) { - throw new Error( - `Allowance for ERC721 token can only be set to 0, 1 or 2^256-1. Got: ${desiredAllowance}`, - ); - } - const erc721Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper; - const assetProxyData = assetDataUtils.decodeERC721AssetData(assetData); - - const doesTokenExist = await erc721Wrapper.doesTokenExistAsync( - assetProxyData.tokenAddress, - assetProxyData.tokenId, - ); - if (!doesTokenExist) { - throw new Error( - `Cannot setProxyAllowance on non-existent token: ${assetProxyData.tokenAddress} ${ - assetProxyData.tokenId - }`, - ); - } - const isProxyApprovedForAll = await erc721Wrapper.isProxyApprovedForAllAsync( - userAddress, - assetProxyData.tokenAddress, - ); - if (!isProxyApprovedForAll && desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) { - const isApproved = true; - await erc721Wrapper.approveProxyForAllAsync( - assetProxyData.tokenAddress, - assetProxyData.tokenId, - isApproved, - ); - } else if (isProxyApprovedForAll && desiredAllowance.eq(0)) { - const isApproved = false; - await erc721Wrapper.approveProxyForAllAsync( - assetProxyData.tokenAddress, - assetProxyData.tokenId, - isApproved, - ); - } else if (isProxyApprovedForAll && desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) { - return; // Noop - } - - const isProxyApproved = await erc721Wrapper.isProxyApprovedAsync( - assetProxyData.tokenAddress, - assetProxyData.tokenId, - ); - if (!isProxyApproved && desiredAllowance.eq(1)) { - await erc721Wrapper.approveProxyAsync(assetProxyData.tokenAddress, assetProxyData.tokenId); - } else if (isProxyApproved && desiredAllowance.eq(0)) { - // Remove approval - await erc721Wrapper.approveAsync( - constants.NULL_ADDRESS, - assetProxyData.tokenAddress, - assetProxyData.tokenId, - ); - } else if ( - (!isProxyApproved && desiredAllowance.eq(0)) || - (isProxyApproved && desiredAllowance.eq(1)) - ) { - return; // noop - } - - break; - } - default: - throw errorUtils.spawnSwitchErr('proxyId', proxyId); - } - } -} diff --git a/contracts/protocol/test/utils/erc20_wrapper.ts b/contracts/protocol/test/utils/erc20_wrapper.ts deleted file mode 100644 index e5ab0e36c..000000000 --- a/contracts/protocol/test/utils/erc20_wrapper.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { constants, ERC20BalancesByOwner, txDefaults } from '@0x/contracts-test-utils'; -import { artifacts as tokensArtifacts, DummyERC20TokenContract } from '@0x/contracts-tokens'; -import { assetDataUtils } from '@0x/order-utils'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import { Provider } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { ERC20ProxyContract } from '../../generated-wrappers/erc20_proxy'; -import { artifacts } from '../../src/artifacts'; - -export class ERC20Wrapper { - private readonly _tokenOwnerAddresses: string[]; - private readonly _contractOwnerAddress: string; - private readonly _web3Wrapper: Web3Wrapper; - private readonly _provider: Provider; - private readonly _dummyTokenContracts: DummyERC20TokenContract[]; - private _proxyContract?: ERC20ProxyContract; - private _proxyIdIfExists?: string; - /** - * Instanitates an ERC20Wrapper - * @param provider Web3 provider to use for all JSON RPC requests - * @param tokenOwnerAddresses Addresses that we want to endow as owners for dummy ERC20 tokens - * @param contractOwnerAddress Desired owner of the contract - * Instance of ERC20Wrapper - */ - constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) { - this._dummyTokenContracts = []; - this._web3Wrapper = new Web3Wrapper(provider); - this._provider = provider; - this._tokenOwnerAddresses = tokenOwnerAddresses; - this._contractOwnerAddress = contractOwnerAddress; - } - public async deployDummyTokensAsync( - numberToDeploy: number, - decimals: BigNumber, - ): Promise { - for (let i = 0; i < numberToDeploy; i++) { - this._dummyTokenContracts.push( - await DummyERC20TokenContract.deployFrom0xArtifactAsync( - tokensArtifacts.DummyERC20Token, - this._provider, - txDefaults, - constants.DUMMY_TOKEN_NAME, - constants.DUMMY_TOKEN_SYMBOL, - decimals, - constants.DUMMY_TOKEN_TOTAL_SUPPLY, - ), - ); - } - return this._dummyTokenContracts; - } - public async deployProxyAsync(): Promise { - this._proxyContract = await ERC20ProxyContract.deployFrom0xArtifactAsync( - artifacts.ERC20Proxy, - this._provider, - txDefaults, - ); - this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync(); - return this._proxyContract; - } - public getProxyId(): string { - this._validateProxyContractExistsOrThrow(); - return this._proxyIdIfExists as string; - } - public async setBalancesAndAllowancesAsync(): Promise { - this._validateDummyTokenContractsExistOrThrow(); - this._validateProxyContractExistsOrThrow(); - for (const dummyTokenContract of this._dummyTokenContracts) { - for (const tokenOwnerAddress of this._tokenOwnerAddresses) { - await this._web3Wrapper.awaitTransactionSuccessAsync( - await dummyTokenContract.setBalance.sendTransactionAsync( - tokenOwnerAddress, - constants.INITIAL_ERC20_BALANCE, - { from: this._contractOwnerAddress }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await this._web3Wrapper.awaitTransactionSuccessAsync( - await dummyTokenContract.approve.sendTransactionAsync( - (this._proxyContract as ERC20ProxyContract).address, - constants.INITIAL_ERC20_ALLOWANCE, - { from: tokenOwnerAddress }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - } - } - } - public async getBalanceAsync(userAddress: string, assetData: string): Promise { - const tokenContract = this._getTokenContractFromAssetData(assetData); - const balance = new BigNumber(await tokenContract.balanceOf.callAsync(userAddress)); - return balance; - } - public async setBalanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise { - const tokenContract = this._getTokenContractFromAssetData(assetData); - await this._web3Wrapper.awaitTransactionSuccessAsync( - await tokenContract.setBalance.sendTransactionAsync(userAddress, amount, { - from: this._contractOwnerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - } - public async getProxyAllowanceAsync(userAddress: string, assetData: string): Promise { - const tokenContract = this._getTokenContractFromAssetData(assetData); - const proxyAddress = (this._proxyContract as ERC20ProxyContract).address; - const allowance = new BigNumber(await tokenContract.allowance.callAsync(userAddress, proxyAddress)); - return allowance; - } - public async setAllowanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise { - const tokenContract = this._getTokenContractFromAssetData(assetData); - const proxyAddress = (this._proxyContract as ERC20ProxyContract).address; - await this._web3Wrapper.awaitTransactionSuccessAsync( - await tokenContract.approve.sendTransactionAsync(proxyAddress, amount, { - from: userAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - } - public async getBalancesAsync(): Promise { - this._validateDummyTokenContractsExistOrThrow(); - const balancesByOwner: ERC20BalancesByOwner = {}; - const balances: BigNumber[] = []; - const balanceInfo: Array<{ tokenOwnerAddress: string; tokenAddress: string }> = []; - for (const dummyTokenContract of this._dummyTokenContracts) { - for (const tokenOwnerAddress of this._tokenOwnerAddresses) { - balances.push(await dummyTokenContract.balanceOf.callAsync(tokenOwnerAddress)); - balanceInfo.push({ - tokenOwnerAddress, - tokenAddress: dummyTokenContract.address, - }); - } - } - _.forEach(balances, (balance, balanceIndex) => { - const tokenAddress = balanceInfo[balanceIndex].tokenAddress; - const tokenOwnerAddress = balanceInfo[balanceIndex].tokenOwnerAddress; - if (_.isUndefined(balancesByOwner[tokenOwnerAddress])) { - balancesByOwner[tokenOwnerAddress] = {}; - } - const wrappedBalance = new BigNumber(balance); - balancesByOwner[tokenOwnerAddress][tokenAddress] = wrappedBalance; - }); - return balancesByOwner; - } - public addDummyTokenContract(dummy: DummyERC20TokenContract): void { - if (!_.isUndefined(this._dummyTokenContracts)) { - this._dummyTokenContracts.push(dummy); - } - } - public addTokenOwnerAddress(address: string): void { - this._tokenOwnerAddresses.push(address); - } - public getTokenOwnerAddresses(): string[] { - return this._tokenOwnerAddresses; - } - public getTokenAddresses(): string[] { - const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address); - return tokenAddresses; - } - private _getTokenContractFromAssetData(assetData: string): DummyERC20TokenContract { - const erc20ProxyData = assetDataUtils.decodeERC20AssetData(assetData); - const tokenAddress = erc20ProxyData.tokenAddress; - const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress); - if (_.isUndefined(tokenContractIfExists)) { - throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`); - } - return tokenContractIfExists; - } - private _validateDummyTokenContractsExistOrThrow(): void { - if (_.isUndefined(this._dummyTokenContracts)) { - throw new Error('Dummy ERC20 tokens not yet deployed, please call "deployDummyTokensAsync"'); - } - } - private _validateProxyContractExistsOrThrow(): void { - if (_.isUndefined(this._proxyContract)) { - throw new Error('ERC20 proxy contract not yet deployed, please call "deployProxyAsync"'); - } - } -} diff --git a/contracts/protocol/test/utils/erc721_wrapper.ts b/contracts/protocol/test/utils/erc721_wrapper.ts deleted file mode 100644 index 1f658ce86..000000000 --- a/contracts/protocol/test/utils/erc721_wrapper.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { constants, ERC721TokenIdsByOwner, txDefaults } from '@0x/contracts-test-utils'; -import { artifacts as tokensArtifacts, DummyERC721TokenContract } from '@0x/contracts-tokens'; -import { generatePseudoRandomSalt } from '@0x/order-utils'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import { Provider } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { ERC721ProxyContract } from '../../generated-wrappers/erc721_proxy'; -import { artifacts } from '../../src/artifacts'; - -export class ERC721Wrapper { - private readonly _tokenOwnerAddresses: string[]; - private readonly _contractOwnerAddress: string; - private readonly _web3Wrapper: Web3Wrapper; - private readonly _provider: Provider; - private readonly _dummyTokenContracts: DummyERC721TokenContract[]; - private _proxyContract?: ERC721ProxyContract; - private _proxyIdIfExists?: string; - private _initialTokenIdsByOwner: ERC721TokenIdsByOwner = {}; - constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) { - this._web3Wrapper = new Web3Wrapper(provider); - this._provider = provider; - this._dummyTokenContracts = []; - this._tokenOwnerAddresses = tokenOwnerAddresses; - this._contractOwnerAddress = contractOwnerAddress; - } - public async deployDummyTokensAsync(): Promise { - // tslint:disable-next-line:no-unused-variable - for (const i of _.times(constants.NUM_DUMMY_ERC721_TO_DEPLOY)) { - this._dummyTokenContracts.push( - await DummyERC721TokenContract.deployFrom0xArtifactAsync( - tokensArtifacts.DummyERC721Token, - this._provider, - txDefaults, - constants.DUMMY_TOKEN_NAME, - constants.DUMMY_TOKEN_SYMBOL, - ), - ); - } - return this._dummyTokenContracts; - } - public async deployProxyAsync(): Promise { - this._proxyContract = await ERC721ProxyContract.deployFrom0xArtifactAsync( - artifacts.ERC721Proxy, - this._provider, - txDefaults, - ); - this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync(); - return this._proxyContract; - } - public getProxyId(): string { - this._validateProxyContractExistsOrThrow(); - return this._proxyIdIfExists as string; - } - public async setBalancesAndAllowancesAsync(): Promise { - this._validateDummyTokenContractsExistOrThrow(); - this._validateProxyContractExistsOrThrow(); - this._initialTokenIdsByOwner = {}; - for (const dummyTokenContract of this._dummyTokenContracts) { - for (const tokenOwnerAddress of this._tokenOwnerAddresses) { - // tslint:disable-next-line:no-unused-variable - for (const i of _.times(constants.NUM_ERC721_TOKENS_TO_MINT)) { - const tokenId = generatePseudoRandomSalt(); - await this.mintAsync(dummyTokenContract.address, tokenId, tokenOwnerAddress); - if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress])) { - this._initialTokenIdsByOwner[tokenOwnerAddress] = { - [dummyTokenContract.address]: [], - }; - } - if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address])) { - this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address] = []; - } - this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address].push(tokenId); - - await this.approveProxyAsync(dummyTokenContract.address, tokenId); - } - } - } - } - public async doesTokenExistAsync(tokenAddress: string, tokenId: BigNumber): Promise { - const tokenContract = this._getTokenContractFromAssetData(tokenAddress); - const owner = await tokenContract.ownerOf.callAsync(tokenId); - const doesExist = owner !== constants.NULL_ADDRESS; - return doesExist; - } - public async approveProxyAsync(tokenAddress: string, tokenId: BigNumber): Promise { - const proxyAddress = (this._proxyContract as ERC721ProxyContract).address; - await this.approveAsync(proxyAddress, tokenAddress, tokenId); - } - public async approveProxyForAllAsync(tokenAddress: string, tokenId: BigNumber, isApproved: boolean): Promise { - const tokenContract = this._getTokenContractFromAssetData(tokenAddress); - const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId); - const proxyAddress = (this._proxyContract as ERC721ProxyContract).address; - await this._web3Wrapper.awaitTransactionSuccessAsync( - await tokenContract.setApprovalForAll.sendTransactionAsync(proxyAddress, isApproved, { - from: tokenOwner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - } - public async approveAsync(to: string, tokenAddress: string, tokenId: BigNumber): Promise { - const tokenContract = this._getTokenContractFromAssetData(tokenAddress); - const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId); - await this._web3Wrapper.awaitTransactionSuccessAsync( - await tokenContract.approve.sendTransactionAsync(to, tokenId, { - from: tokenOwner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - } - public async transferFromAsync( - tokenAddress: string, - tokenId: BigNumber, - currentOwner: string, - userAddress: string, - ): Promise { - const tokenContract = this._getTokenContractFromAssetData(tokenAddress); - await this._web3Wrapper.awaitTransactionSuccessAsync( - await tokenContract.transferFrom.sendTransactionAsync(currentOwner, userAddress, tokenId, { - from: currentOwner, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - } - public async mintAsync(tokenAddress: string, tokenId: BigNumber, userAddress: string): Promise { - const tokenContract = this._getTokenContractFromAssetData(tokenAddress); - await this._web3Wrapper.awaitTransactionSuccessAsync( - await tokenContract.mint.sendTransactionAsync(userAddress, tokenId, { - from: this._contractOwnerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - } - public async burnAsync(tokenAddress: string, tokenId: BigNumber, owner: string): Promise { - const tokenContract = this._getTokenContractFromAssetData(tokenAddress); - await this._web3Wrapper.awaitTransactionSuccessAsync( - await tokenContract.burn.sendTransactionAsync(owner, tokenId, { - from: this._contractOwnerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - } - public async ownerOfAsync(tokenAddress: string, tokenId: BigNumber): Promise { - const tokenContract = this._getTokenContractFromAssetData(tokenAddress); - const owner = await tokenContract.ownerOf.callAsync(tokenId); - return owner; - } - public async isOwnerAsync(userAddress: string, tokenAddress: string, tokenId: BigNumber): Promise { - const tokenContract = this._getTokenContractFromAssetData(tokenAddress); - const tokenOwner = await tokenContract.ownerOf.callAsync(tokenId); - const isOwner = tokenOwner === userAddress; - return isOwner; - } - public async isProxyApprovedForAllAsync(userAddress: string, tokenAddress: string): Promise { - this._validateProxyContractExistsOrThrow(); - const tokenContract = this._getTokenContractFromAssetData(tokenAddress); - const operator = (this._proxyContract as ERC721ProxyContract).address; - const didApproveAll = await tokenContract.isApprovedForAll.callAsync(userAddress, operator); - return didApproveAll; - } - public async isProxyApprovedAsync(tokenAddress: string, tokenId: BigNumber): Promise { - this._validateProxyContractExistsOrThrow(); - const tokenContract = this._getTokenContractFromAssetData(tokenAddress); - const approvedAddress = await tokenContract.getApproved.callAsync(tokenId); - const proxyAddress = (this._proxyContract as ERC721ProxyContract).address; - const isProxyAnApprovedOperator = approvedAddress === proxyAddress; - return isProxyAnApprovedOperator; - } - public async getBalancesAsync(): Promise { - this._validateDummyTokenContractsExistOrThrow(); - this._validateBalancesAndAllowancesSetOrThrow(); - const tokenIdsByOwner: ERC721TokenIdsByOwner = {}; - const tokenOwnerAddresses: string[] = []; - const tokenInfo: Array<{ tokenId: BigNumber; tokenAddress: string }> = []; - for (const dummyTokenContract of this._dummyTokenContracts) { - for (const tokenOwnerAddress of this._tokenOwnerAddresses) { - const initialTokenOwnerIds = this._initialTokenIdsByOwner[tokenOwnerAddress][ - dummyTokenContract.address - ]; - for (const tokenId of initialTokenOwnerIds) { - tokenOwnerAddresses.push(await dummyTokenContract.ownerOf.callAsync(tokenId)); - tokenInfo.push({ - tokenId, - tokenAddress: dummyTokenContract.address, - }); - } - } - } - _.forEach(tokenOwnerAddresses, (tokenOwnerAddress, ownerIndex) => { - const tokenAddress = tokenInfo[ownerIndex].tokenAddress; - const tokenId = tokenInfo[ownerIndex].tokenId; - if (_.isUndefined(tokenIdsByOwner[tokenOwnerAddress])) { - tokenIdsByOwner[tokenOwnerAddress] = { - [tokenAddress]: [], - }; - } - if (_.isUndefined(tokenIdsByOwner[tokenOwnerAddress][tokenAddress])) { - tokenIdsByOwner[tokenOwnerAddress][tokenAddress] = []; - } - tokenIdsByOwner[tokenOwnerAddress][tokenAddress].push(tokenId); - }); - return tokenIdsByOwner; - } - public getTokenOwnerAddresses(): string[] { - return this._tokenOwnerAddresses; - } - public getTokenAddresses(): string[] { - const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address); - return tokenAddresses; - } - private _getTokenContractFromAssetData(tokenAddress: string): DummyERC721TokenContract { - const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress); - if (_.isUndefined(tokenContractIfExists)) { - throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`); - } - return tokenContractIfExists; - } - private _validateDummyTokenContractsExistOrThrow(): void { - if (_.isUndefined(this._dummyTokenContracts)) { - throw new Error('Dummy ERC721 tokens not yet deployed, please call "deployDummyTokensAsync"'); - } - } - private _validateProxyContractExistsOrThrow(): void { - if (_.isUndefined(this._proxyContract)) { - throw new Error('ERC721 proxy contract not yet deployed, please call "deployProxyAsync"'); - } - } - private _validateBalancesAndAllowancesSetOrThrow(): void { - if (_.keys(this._initialTokenIdsByOwner).length === 0) { - throw new Error( - 'Dummy ERC721 balances and allowances not yet set, please call "setBalancesAndAllowancesAsync"', - ); - } - } -} diff --git a/contracts/protocol/test/utils/exchange_wrapper.ts b/contracts/protocol/test/utils/exchange_wrapper.ts deleted file mode 100644 index 437882b41..000000000 --- a/contracts/protocol/test/utils/exchange_wrapper.ts +++ /dev/null @@ -1,292 +0,0 @@ -import { - FillResults, - formatters, - LogDecoder, - OrderInfo, - orderUtils, - SignedTransaction, -} from '@0x/contracts-test-utils'; -import { artifacts as tokensArtifacts } from '@0x/contracts-tokens'; -import { SignedOrder } from '@0x/types'; -import { AbiEncoder, BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import { MethodAbi, Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { artifacts } from '../../src/artifacts'; - -import { AbiDecodedFillOrderData } from './types'; - -export class ExchangeWrapper { - private readonly _exchange: ExchangeContract; - private readonly _web3Wrapper: Web3Wrapper; - private readonly _logDecoder: LogDecoder; - constructor(exchangeContract: ExchangeContract, provider: Provider) { - this._exchange = exchangeContract; - this._web3Wrapper = new Web3Wrapper(provider); - this._logDecoder = new LogDecoder(this._web3Wrapper, { ...artifacts, ...tokensArtifacts }); - } - public async fillOrderAsync( - signedOrder: SignedOrder, - from: string, - opts: { takerAssetFillAmount?: BigNumber } = {}, - ): Promise { - const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); - const txHash = await this._exchange.fillOrder.sendTransactionAsync( - params.order, - params.takerAssetFillAmount, - params.signature, - { from }, - ); - const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return txReceipt; - } - public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise { - const params = orderUtils.createCancel(signedOrder); - const txHash = await this._exchange.cancelOrder.sendTransactionAsync(params.order, { from }); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async fillOrKillOrderAsync( - signedOrder: SignedOrder, - from: string, - opts: { takerAssetFillAmount?: BigNumber } = {}, - ): Promise { - const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); - const txHash = await this._exchange.fillOrKillOrder.sendTransactionAsync( - params.order, - params.takerAssetFillAmount, - params.signature, - { from }, - ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async fillOrderNoThrowAsync( - signedOrder: SignedOrder, - from: string, - opts: { takerAssetFillAmount?: BigNumber; gas?: number } = {}, - ): Promise { - const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); - const txHash = await this._exchange.fillOrderNoThrow.sendTransactionAsync( - params.order, - params.takerAssetFillAmount, - params.signature, - { from, gas: opts.gas }, - ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async batchFillOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmounts?: BigNumber[] } = {}, - ): Promise { - const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); - const txHash = await this._exchange.batchFillOrders.sendTransactionAsync( - params.orders, - params.takerAssetFillAmounts, - params.signatures, - { from }, - ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async batchFillOrKillOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmounts?: BigNumber[] } = {}, - ): Promise { - const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); - const txHash = await this._exchange.batchFillOrKillOrders.sendTransactionAsync( - params.orders, - params.takerAssetFillAmounts, - params.signatures, - { from }, - ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async batchFillOrdersNoThrowAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number } = {}, - ): Promise { - const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); - const txHash = await this._exchange.batchFillOrdersNoThrow.sendTransactionAsync( - params.orders, - params.takerAssetFillAmounts, - params.signatures, - { from, gas: opts.gas }, - ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async marketSellOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmount: BigNumber }, - ): Promise { - const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); - const txHash = await this._exchange.marketSellOrders.sendTransactionAsync( - params.orders, - params.takerAssetFillAmount, - params.signatures, - { from }, - ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async marketSellOrdersNoThrowAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmount: BigNumber; gas?: number }, - ): Promise { - const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); - const txHash = await this._exchange.marketSellOrdersNoThrow.sendTransactionAsync( - params.orders, - params.takerAssetFillAmount, - params.signatures, - { from, gas: opts.gas }, - ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async marketBuyOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { makerAssetFillAmount: BigNumber }, - ): Promise { - const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); - const txHash = await this._exchange.marketBuyOrders.sendTransactionAsync( - params.orders, - params.makerAssetFillAmount, - params.signatures, - { from }, - ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async marketBuyOrdersNoThrowAsync( - orders: SignedOrder[], - from: string, - opts: { makerAssetFillAmount: BigNumber; gas?: number }, - ): Promise { - const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); - const txHash = await this._exchange.marketBuyOrdersNoThrow.sendTransactionAsync( - params.orders, - params.makerAssetFillAmount, - params.signatures, - { from, gas: opts.gas }, - ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async batchCancelOrdersAsync( - orders: SignedOrder[], - from: string, - ): Promise { - const params = formatters.createBatchCancel(orders); - const txHash = await this._exchange.batchCancelOrders.sendTransactionAsync(params.orders, { from }); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async cancelOrdersUpToAsync(salt: BigNumber, from: string): Promise { - const txHash = await this._exchange.cancelOrdersUpTo.sendTransactionAsync(salt, { from }); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async registerAssetProxyAsync( - assetProxyAddress: string, - from: string, - ): Promise { - const txHash = await this._exchange.registerAssetProxy.sendTransactionAsync(assetProxyAddress, { from }); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async executeTransactionAsync( - signedTx: SignedTransaction, - from: string, - ): Promise { - const txHash = await this._exchange.executeTransaction.sendTransactionAsync( - signedTx.salt, - signedTx.signerAddress, - signedTx.data, - signedTx.signature, - { from }, - ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise { - const filledAmount = await this._exchange.filled.callAsync(orderHashHex); - return filledAmount; - } - public async isCancelledAsync(orderHashHex: string): Promise { - const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex); - return isCancelled; - } - public async getOrderEpochAsync(makerAddress: string, senderAddress: string): Promise { - const orderEpoch = await this._exchange.orderEpoch.callAsync(makerAddress, senderAddress); - return orderEpoch; - } - public async getOrderInfoAsync(signedOrder: SignedOrder): Promise { - const orderInfo = await this._exchange.getOrderInfo.callAsync(signedOrder); - return orderInfo; - } - public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise { - const ordersInfo = (await this._exchange.getOrdersInfo.callAsync(signedOrders)) as OrderInfo[]; - return ordersInfo; - } - public async matchOrdersAsync( - signedOrderLeft: SignedOrder, - signedOrderRight: SignedOrder, - from: string, - ): Promise { - const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight); - const txHash = await this._exchange.matchOrders.sendTransactionAsync( - params.left, - params.right, - params.leftSignature, - params.rightSignature, - { from }, - ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; - } - public async getFillOrderResultsAsync( - signedOrder: SignedOrder, - from: string, - opts: { takerAssetFillAmount?: BigNumber } = {}, - ): Promise { - const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); - const fillResults = await this._exchange.fillOrder.callAsync( - params.order, - params.takerAssetFillAmount, - params.signature, - { from }, - ); - return fillResults; - } - public abiEncodeFillOrder(signedOrder: SignedOrder, opts: { takerAssetFillAmount?: BigNumber } = {}): string { - const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); - const data = this._exchange.fillOrder.getABIEncodedTransactionData( - params.order, - params.takerAssetFillAmount, - params.signature, - ); - return data; - } - public abiDecodeFillOrder(data: string): AbiDecodedFillOrderData { - // Lookup fillOrder ABI in exchange abi - const fillOrderAbi = _.find(this._exchange.abi, { name: 'fillOrder' }) as MethodAbi; - // Decode input data - const abiEncoder = new AbiEncoder.Method(fillOrderAbi); - const decodedData = abiEncoder.decode(data) as AbiDecodedFillOrderData; - return decodedData; - } - public getExchangeAddress(): string { - return this._exchange.address; - } -} diff --git a/contracts/protocol/test/utils/fill_order_combinatorial_utils.ts b/contracts/protocol/test/utils/fill_order_combinatorial_utils.ts deleted file mode 100644 index c2f530e5b..000000000 --- a/contracts/protocol/test/utils/fill_order_combinatorial_utils.ts +++ /dev/null @@ -1,928 +0,0 @@ -import { artifacts as libsArtifacts, TestLibsContract } from '@0x/contracts-libs'; -import { - AllowanceAmountScenario, - AssetDataScenario, - BalanceAmountScenario, - chaiSetup, - constants, - expectTransactionFailedAsync, - ExpirationTimeSecondsScenario, - FeeRecipientAddressScenario, - FillScenario, - OrderAssetAmountScenario, - orderUtils, - signingUtils, - TakerAssetFillAmountScenario, - TakerScenario, - TraderStateScenario, -} from '@0x/contracts-test-utils'; -import { - assetDataUtils, - BalanceAndProxyAllowanceLazyStore, - ExchangeTransferSimulator, - orderHashUtils, - OrderStateUtils, - OrderValidationUtils, -} from '@0x/order-utils'; -import { AssetProxyId, RevertReason, SignatureType, SignedOrder } from '@0x/types'; -import { BigNumber, errorUtils, logUtils } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import { LogWithDecodedArgs, Provider, TxData } from 'ethereum-types'; -import * as _ from 'lodash'; -import 'make-promises-safe'; - -import { ExchangeContract, ExchangeFillEventArgs } from '../../generated-wrappers/exchange'; -import { artifacts } from '../../src/artifacts'; - -import { AssetWrapper } from './asset_wrapper'; -import { ERC20Wrapper } from './erc20_wrapper'; -import { ERC721Wrapper } from './erc721_wrapper'; -import { ExchangeWrapper } from './exchange_wrapper'; -import { OrderFactoryFromScenario } from './order_factory_from_scenario'; -import { SimpleAssetBalanceAndProxyAllowanceFetcher } from './simple_asset_balance_and_proxy_allowance_fetcher'; -import { SimpleOrderFilledCancelledFetcher } from './simple_order_filled_cancelled_fetcher'; - -chaiSetup.configure(); -const expect = chai.expect; - -/** - * Instantiates a new instance of FillOrderCombinatorialUtils. Since this method has some - * required async setup, a factory method is required. - * @param web3Wrapper Web3Wrapper instance - * @param txDefaults Default Ethereum tx options - * @return FillOrderCombinatorialUtils instance - */ -export async function fillOrderCombinatorialUtilsFactoryAsync( - web3Wrapper: Web3Wrapper, - txDefaults: Partial, -): Promise { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const userAddresses = _.slice(accounts, 0, 5); - const [ownerAddress, makerAddress, takerAddress] = userAddresses; - const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)]; - - const provider = web3Wrapper.getProvider(); - const erc20Wrapper = new ERC20Wrapper(provider, userAddresses, ownerAddress); - const erc721Wrapper = new ERC721Wrapper(provider, userAddresses, ownerAddress); - - const erc20EighteenDecimalTokenCount = 3; - const eighteenDecimals = new BigNumber(18); - const [ - erc20EighteenDecimalTokenA, - erc20EighteenDecimalTokenB, - zrxToken, - ] = await erc20Wrapper.deployDummyTokensAsync(erc20EighteenDecimalTokenCount, eighteenDecimals); - const zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - - const erc20FiveDecimalTokenCount = 2; - const fiveDecimals = new BigNumber(5); - const [erc20FiveDecimalTokenA, erc20FiveDecimalTokenB] = await erc20Wrapper.deployDummyTokensAsync( - erc20FiveDecimalTokenCount, - fiveDecimals, - ); - const zeroDecimals = new BigNumber(0); - const erc20ZeroDecimalTokenCount = 2; - const [erc20ZeroDecimalTokenA, erc20ZeroDecimalTokenB] = await erc20Wrapper.deployDummyTokensAsync( - erc20ZeroDecimalTokenCount, - zeroDecimals, - ); - const erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - - const [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - const erc721Proxy = await erc721Wrapper.deployProxyAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - - const assetWrapper = new AssetWrapper([erc20Wrapper, erc721Wrapper]); - - const exchangeContract = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - zrxAssetData, - ); - const exchangeWrapper = new ExchangeWrapper(exchangeContract, provider); - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, ownerAddress); - await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, ownerAddress); - - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, { - from: ownerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, { - from: ownerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - - const orderFactory = new OrderFactoryFromScenario( - userAddresses, - zrxToken.address, - [erc20EighteenDecimalTokenA.address, erc20EighteenDecimalTokenB.address], - [erc20FiveDecimalTokenA.address, erc20FiveDecimalTokenB.address], - [erc20ZeroDecimalTokenA.address, erc20ZeroDecimalTokenB.address], - erc721Token, - erc721Balances, - exchangeContract.address, - ); - - const testLibsContract = await TestLibsContract.deployFrom0xArtifactAsync( - libsArtifacts.TestLibs, - provider, - txDefaults, - ); - - const fillOrderCombinatorialUtils = new FillOrderCombinatorialUtils( - orderFactory, - ownerAddress, - makerAddress, - makerPrivateKey, - takerAddress, - zrxAssetData, - exchangeWrapper, - assetWrapper, - testLibsContract, - ); - return fillOrderCombinatorialUtils; -} - -export class FillOrderCombinatorialUtils { - public orderFactory: OrderFactoryFromScenario; - public ownerAddress: string; - public makerAddress: string; - public makerPrivateKey: Buffer; - public takerAddress: string; - public zrxAssetData: string; - public exchangeWrapper: ExchangeWrapper; - public assetWrapper: AssetWrapper; - public testLibsContract: TestLibsContract; - public static generateFillOrderCombinations(): FillScenario[] { - const takerScenarios = [ - TakerScenario.Unspecified, - // TakerScenario.CorrectlySpecified, - // TakerScenario.IncorrectlySpecified, - ]; - const feeRecipientScenarios = [ - FeeRecipientAddressScenario.EthUserAddress, - // FeeRecipientAddressScenario.BurnAddress, - ]; - const makerAssetAmountScenario = [ - OrderAssetAmountScenario.Large, - // OrderAssetAmountScenario.Zero, - // OrderAssetAmountScenario.Small, - ]; - const takerAssetAmountScenario = [ - OrderAssetAmountScenario.Large, - // OrderAssetAmountScenario.Zero, - // OrderAssetAmountScenario.Small, - ]; - const makerFeeScenario = [ - OrderAssetAmountScenario.Large, - // OrderAssetAmountScenario.Small, - // OrderAssetAmountScenario.Zero, - ]; - const takerFeeScenario = [ - OrderAssetAmountScenario.Large, - // OrderAssetAmountScenario.Small, - // OrderAssetAmountScenario.Zero, - ]; - const expirationTimeSecondsScenario = [ - ExpirationTimeSecondsScenario.InFuture, - ExpirationTimeSecondsScenario.InPast, - ]; - const makerAssetDataScenario = [ - AssetDataScenario.ERC20FiveDecimals, - AssetDataScenario.ERC20NonZRXEighteenDecimals, - AssetDataScenario.ERC721, - AssetDataScenario.ZRXFeeToken, - ]; - const takerAssetDataScenario = [ - AssetDataScenario.ERC20FiveDecimals, - AssetDataScenario.ERC20NonZRXEighteenDecimals, - AssetDataScenario.ERC721, - AssetDataScenario.ZRXFeeToken, - ]; - const takerAssetFillAmountScenario = [ - TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, - // TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount, - // TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount, - ]; - const makerAssetBalanceScenario = [ - BalanceAmountScenario.Higher, - // BalanceAmountScenario.Exact, - // BalanceAmountScenario.TooLow, - ]; - const makerAssetAllowanceScenario = [ - AllowanceAmountScenario.Higher, - // AllowanceAmountScenario.Exact, - // AllowanceAmountScenario.TooLow, - // AllowanceAmountScenario.Unlimited, - ]; - const makerZRXBalanceScenario = [ - BalanceAmountScenario.Higher, - // BalanceAmountScenario.Exact, - // BalanceAmountScenario.TooLow, - ]; - const makerZRXAllowanceScenario = [ - AllowanceAmountScenario.Higher, - // AllowanceAmountScenario.Exact, - // AllowanceAmountScenario.TooLow, - // AllowanceAmountScenario.Unlimited, - ]; - const takerAssetBalanceScenario = [ - BalanceAmountScenario.Higher, - // BalanceAmountScenario.Exact, - // BalanceAmountScenario.TooLow, - ]; - const takerAssetAllowanceScenario = [ - AllowanceAmountScenario.Higher, - // AllowanceAmountScenario.Exact, - // AllowanceAmountScenario.TooLow, - // AllowanceAmountScenario.Unlimited, - ]; - const takerZRXBalanceScenario = [ - BalanceAmountScenario.Higher, - // BalanceAmountScenario.Exact, - // BalanceAmountScenario.TooLow, - ]; - const takerZRXAllowanceScenario = [ - AllowanceAmountScenario.Higher, - // AllowanceAmountScenario.Exact, - // AllowanceAmountScenario.TooLow, - // AllowanceAmountScenario.Unlimited, - ]; - const fillScenarioArrays = FillOrderCombinatorialUtils._getAllCombinations([ - takerScenarios, - feeRecipientScenarios, - makerAssetAmountScenario, - takerAssetAmountScenario, - makerFeeScenario, - takerFeeScenario, - expirationTimeSecondsScenario, - makerAssetDataScenario, - takerAssetDataScenario, - takerAssetFillAmountScenario, - makerAssetBalanceScenario, - makerAssetAllowanceScenario, - makerZRXBalanceScenario, - makerZRXAllowanceScenario, - takerAssetBalanceScenario, - takerAssetAllowanceScenario, - takerZRXBalanceScenario, - takerZRXAllowanceScenario, - ]); - - const fillScenarios = _.map(fillScenarioArrays, fillScenarioArray => { - // tslint:disable:custom-no-magic-numbers - const fillScenario: FillScenario = { - orderScenario: { - takerScenario: fillScenarioArray[0] as TakerScenario, - feeRecipientScenario: fillScenarioArray[1] as FeeRecipientAddressScenario, - makerAssetAmountScenario: fillScenarioArray[2] as OrderAssetAmountScenario, - takerAssetAmountScenario: fillScenarioArray[3] as OrderAssetAmountScenario, - makerFeeScenario: fillScenarioArray[4] as OrderAssetAmountScenario, - takerFeeScenario: fillScenarioArray[5] as OrderAssetAmountScenario, - expirationTimeSecondsScenario: fillScenarioArray[6] as ExpirationTimeSecondsScenario, - makerAssetDataScenario: fillScenarioArray[7] as AssetDataScenario, - takerAssetDataScenario: fillScenarioArray[8] as AssetDataScenario, - }, - takerAssetFillAmountScenario: fillScenarioArray[9] as TakerAssetFillAmountScenario, - makerStateScenario: { - traderAssetBalance: fillScenarioArray[10] as BalanceAmountScenario, - traderAssetAllowance: fillScenarioArray[11] as AllowanceAmountScenario, - zrxFeeBalance: fillScenarioArray[12] as BalanceAmountScenario, - zrxFeeAllowance: fillScenarioArray[13] as AllowanceAmountScenario, - }, - takerStateScenario: { - traderAssetBalance: fillScenarioArray[14] as BalanceAmountScenario, - traderAssetAllowance: fillScenarioArray[15] as AllowanceAmountScenario, - zrxFeeBalance: fillScenarioArray[16] as BalanceAmountScenario, - zrxFeeAllowance: fillScenarioArray[17] as AllowanceAmountScenario, - }, - }; - // tslint:enable:custom-no-magic-numbers - return fillScenario; - }); - - return fillScenarios; - } - /** - * Recursive implementation of generating all combinations of the supplied - * string-containing arrays. - */ - private static _getAllCombinations(arrays: string[][]): string[][] { - // Base case - if (arrays.length === 1) { - const remainingValues = _.map(arrays[0], val => { - return [val]; - }); - return remainingValues; - } else { - const result = []; - const restOfArrays = arrays.slice(1); - const allCombinationsOfRemaining = FillOrderCombinatorialUtils._getAllCombinations(restOfArrays); // recur with the rest of array - // tslint:disable:prefer-for-of - for (let i = 0; i < allCombinationsOfRemaining.length; i++) { - for (let j = 0; j < arrays[0].length; j++) { - result.push([arrays[0][j], ...allCombinationsOfRemaining[i]]); - } - } - // tslint:enable:prefer-for-of - return result; - } - } - constructor( - orderFactory: OrderFactoryFromScenario, - ownerAddress: string, - makerAddress: string, - makerPrivateKey: Buffer, - takerAddress: string, - zrxAssetData: string, - exchangeWrapper: ExchangeWrapper, - assetWrapper: AssetWrapper, - testLibsContract: TestLibsContract, - ) { - this.orderFactory = orderFactory; - this.ownerAddress = ownerAddress; - this.makerAddress = makerAddress; - this.makerPrivateKey = makerPrivateKey; - this.takerAddress = takerAddress; - this.zrxAssetData = zrxAssetData; - this.exchangeWrapper = exchangeWrapper; - this.assetWrapper = assetWrapper; - this.testLibsContract = testLibsContract; - } - public async testFillOrderScenarioAsync( - provider: Provider, - fillScenario: FillScenario, - isVerbose: boolean = false, - ): Promise { - // 1. Generate order - const order = this.orderFactory.generateOrder(fillScenario.orderScenario); - - // 2. Sign order - const orderHashBuff = orderHashUtils.getOrderHashBuffer(order); - const signature = signingUtils.signMessage(orderHashBuff, this.makerPrivateKey, SignatureType.EthSign); - const signedOrder = { - ...order, - signature: `0x${signature.toString('hex')}`, - }; - - const balanceAndProxyAllowanceFetcher = new SimpleAssetBalanceAndProxyAllowanceFetcher(this.assetWrapper); - const orderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher( - this.exchangeWrapper, - this.zrxAssetData, - ); - - // 3. Figure out fill amount - const takerAssetFillAmount = await this._getTakerAssetFillAmountAsync( - signedOrder, - fillScenario.takerAssetFillAmountScenario, - balanceAndProxyAllowanceFetcher, - orderFilledCancelledFetcher, - ); - - // 4. Permutate the maker and taker balance/allowance scenarios - await this._modifyTraderStateAsync( - fillScenario.makerStateScenario, - fillScenario.takerStateScenario, - signedOrder, - takerAssetFillAmount, - ); - - // 5. If I fill it by X, what are the resulting balances/allowances/filled amounts expected? - const orderValidationUtils = new OrderValidationUtils(orderFilledCancelledFetcher, provider); - const lazyStore = new BalanceAndProxyAllowanceLazyStore(balanceAndProxyAllowanceFetcher); - const exchangeTransferSimulator = new ExchangeTransferSimulator(lazyStore); - - let fillRevertReasonIfExists; - try { - await orderValidationUtils.validateFillOrderThrowIfInvalidAsync( - exchangeTransferSimulator, - provider, - signedOrder, - takerAssetFillAmount, - this.takerAddress, - this.zrxAssetData, - ); - if (isVerbose) { - logUtils.log(`Expecting fillOrder to succeed.`); - } - } catch (err) { - fillRevertReasonIfExists = err.message; - if (isVerbose) { - logUtils.log(`Expecting fillOrder to fail with:`); - logUtils.log(err); - } - } - - // 6. Fill the order - await this._fillOrderAndAssertOutcomeAsync( - signedOrder, - takerAssetFillAmount, - lazyStore, - fillRevertReasonIfExists, - ); - - await this._abiEncodeFillOrderAndAssertOutcomeAsync(signedOrder, takerAssetFillAmount); - } - private async _fillOrderAndAssertOutcomeAsync( - signedOrder: SignedOrder, - takerAssetFillAmount: BigNumber, - lazyStore: BalanceAndProxyAllowanceLazyStore, - fillRevertReasonIfExists: RevertReason | undefined, - ): Promise { - if (!_.isUndefined(fillRevertReasonIfExists)) { - return expectTransactionFailedAsync( - this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, { takerAssetFillAmount }), - fillRevertReasonIfExists, - ); - } - - const makerAddress = signedOrder.makerAddress; - const makerAssetData = signedOrder.makerAssetData; - const takerAssetData = signedOrder.takerAssetData; - const feeRecipient = signedOrder.feeRecipientAddress; - - const expMakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(makerAssetData, makerAddress); - const expMakerAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync(makerAssetData, makerAddress); - const expTakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(takerAssetData, makerAddress); - const expZRXAssetBalanceOfMaker = await lazyStore.getBalanceAsync(this.zrxAssetData, makerAddress); - const expZRXAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync(this.zrxAssetData, makerAddress); - const expTakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(takerAssetData, this.takerAddress); - const expTakerAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync(takerAssetData, this.takerAddress); - const expMakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(makerAssetData, this.takerAddress); - const expZRXAssetBalanceOfTaker = await lazyStore.getBalanceAsync(this.zrxAssetData, this.takerAddress); - const expZRXAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync( - this.zrxAssetData, - this.takerAddress, - ); - const expZRXAssetBalanceOfFeeRecipient = await lazyStore.getBalanceAsync(this.zrxAssetData, feeRecipient); - - const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - const alreadyFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash); - const remainingTakerAmountToFill = signedOrder.takerAssetAmount.minus(alreadyFilledTakerAmount); - const expFilledTakerAmount = takerAssetFillAmount.gt(remainingTakerAmountToFill) - ? remainingTakerAmountToFill - : alreadyFilledTakerAmount.plus(takerAssetFillAmount); - - const expFilledMakerAmount = orderUtils.getPartialAmountFloor( - expFilledTakerAmount, - signedOrder.takerAssetAmount, - signedOrder.makerAssetAmount, - ); - const expMakerFeePaid = orderUtils.getPartialAmountFloor( - expFilledTakerAmount, - signedOrder.takerAssetAmount, - signedOrder.makerFee, - ); - const expTakerFeePaid = orderUtils.getPartialAmountFloor( - expFilledTakerAmount, - signedOrder.takerAssetAmount, - signedOrder.takerFee, - ); - const fillResults = await this.exchangeWrapper.getFillOrderResultsAsync(signedOrder, this.takerAddress, { - takerAssetFillAmount, - }); - expect(fillResults.takerAssetFilledAmount).to.be.bignumber.equal( - expFilledTakerAmount, - 'takerAssetFilledAmount', - ); - expect(fillResults.makerAssetFilledAmount).to.be.bignumber.equal( - expFilledMakerAmount, - 'makerAssetFilledAmount', - ); - expect(fillResults.takerFeePaid).to.be.bignumber.equal(expTakerFeePaid, 'takerFeePaid'); - expect(fillResults.makerFeePaid).to.be.bignumber.equal(expMakerFeePaid, 'makerFeePaid'); - - // - Let's fill the order! - const txReceipt = await this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, { - takerAssetFillAmount, - }); - - const actFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash); - expect(actFilledTakerAmount).to.be.bignumber.equal(expFilledTakerAmount, 'filledTakerAmount'); - - const exchangeLogs = _.filter( - txReceipt.logs, - txLog => txLog.address === this.exchangeWrapper.getExchangeAddress(), - ); - expect(exchangeLogs.length).to.be.equal(1, 'logs length'); - // tslint:disable-next-line:no-unnecessary-type-assertion - const log = txReceipt.logs[0] as LogWithDecodedArgs; - expect(log.args.makerAddress).to.be.equal(makerAddress, 'log.args.makerAddress'); - expect(log.args.takerAddress).to.be.equal(this.takerAddress, 'log.args.this.takerAddress'); - expect(log.args.feeRecipientAddress).to.be.equal(feeRecipient, 'log.args.feeRecipientAddress'); - expect(log.args.makerAssetFilledAmount).to.be.bignumber.equal( - expFilledMakerAmount, - 'log.args.makerAssetFilledAmount', - ); - expect(log.args.takerAssetFilledAmount).to.be.bignumber.equal( - expFilledTakerAmount, - 'log.args.takerAssetFilledAmount', - ); - expect(log.args.makerFeePaid).to.be.bignumber.equal(expMakerFeePaid, 'log.args.makerFeePaid'); - expect(log.args.takerFeePaid).to.be.bignumber.equal(expTakerFeePaid, 'logs.args.takerFeePaid'); - expect(log.args.orderHash).to.be.equal(orderHash, 'log.args.orderHash'); - expect(log.args.makerAssetData).to.be.equal(makerAssetData, 'log.args.makerAssetData'); - expect(log.args.takerAssetData).to.be.equal(takerAssetData, 'log.args.takerAssetData'); - - const actMakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, makerAssetData); - expect(actMakerAssetBalanceOfMaker).to.be.bignumber.equal( - expMakerAssetBalanceOfMaker, - 'makerAssetBalanceOfMaker', - ); - - const actMakerAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync( - makerAddress, - makerAssetData, - ); - expect(actMakerAssetAllowanceOfMaker).to.be.bignumber.equal( - expMakerAssetAllowanceOfMaker, - 'makerAssetAllowanceOfMaker', - ); - - const actTakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, takerAssetData); - expect(actTakerAssetBalanceOfMaker).to.be.bignumber.equal( - expTakerAssetBalanceOfMaker, - 'takerAssetBalanceOfMaker', - ); - - const actZRXAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, this.zrxAssetData); - expect(actZRXAssetBalanceOfMaker).to.be.bignumber.equal(expZRXAssetBalanceOfMaker, 'ZRXAssetBalanceOfMaker'); - - const actZRXAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync( - makerAddress, - this.zrxAssetData, - ); - expect(actZRXAssetAllowanceOfMaker).to.be.bignumber.equal( - expZRXAssetAllowanceOfMaker, - 'ZRXAssetAllowanceOfMaker', - ); - - const actTakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, takerAssetData); - expect(actTakerAssetBalanceOfTaker).to.be.bignumber.equal( - expTakerAssetBalanceOfTaker, - 'TakerAssetBalanceOfTaker', - ); - - const actTakerAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync( - this.takerAddress, - takerAssetData, - ); - - expect(actTakerAssetAllowanceOfTaker).to.be.bignumber.equal( - expTakerAssetAllowanceOfTaker, - 'TakerAssetAllowanceOfTaker', - ); - - const actMakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, makerAssetData); - expect(actMakerAssetBalanceOfTaker).to.be.bignumber.equal( - expMakerAssetBalanceOfTaker, - 'MakerAssetBalanceOfTaker', - ); - - const actZRXAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, this.zrxAssetData); - expect(actZRXAssetBalanceOfTaker).to.be.bignumber.equal(expZRXAssetBalanceOfTaker, 'ZRXAssetBalanceOfTaker'); - - const actZRXAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync( - this.takerAddress, - this.zrxAssetData, - ); - expect(actZRXAssetAllowanceOfTaker).to.be.bignumber.equal( - expZRXAssetAllowanceOfTaker, - 'ZRXAssetAllowanceOfTaker', - ); - - const actZRXAssetBalanceOfFeeRecipient = await this.assetWrapper.getBalanceAsync( - feeRecipient, - this.zrxAssetData, - ); - expect(actZRXAssetBalanceOfFeeRecipient).to.be.bignumber.equal( - expZRXAssetBalanceOfFeeRecipient, - 'ZRXAssetBalanceOfFeeRecipient', - ); - } - private async _abiEncodeFillOrderAndAssertOutcomeAsync( - signedOrder: SignedOrder, - takerAssetFillAmount: BigNumber, - ): Promise { - const params = orderUtils.createFill(signedOrder, takerAssetFillAmount); - const abiDataEncodedByContract = await this.testLibsContract.publicAbiEncodeFillOrder.callAsync( - params.order, - params.takerAssetFillAmount, - params.signature, - ); - const paramsDecodedByClient = this.exchangeWrapper.abiDecodeFillOrder(abiDataEncodedByContract); - expect(paramsDecodedByClient).to.be.deep.equal(params, 'ABIEncodedFillOrderData'); - } - private async _getTakerAssetFillAmountAsync( - signedOrder: SignedOrder, - takerAssetFillAmountScenario: TakerAssetFillAmountScenario, - balanceAndProxyAllowanceFetcher: SimpleAssetBalanceAndProxyAllowanceFetcher, - orderFilledCancelledFetcher: SimpleOrderFilledCancelledFetcher, - ): Promise { - const orderStateUtils = new OrderStateUtils(balanceAndProxyAllowanceFetcher, orderFilledCancelledFetcher); - const fillableTakerAssetAmount = await orderStateUtils.getMaxFillableTakerAssetAmountAsync( - signedOrder, - this.takerAddress, - ); - - let takerAssetFillAmount; - switch (takerAssetFillAmountScenario) { - case TakerAssetFillAmountScenario.Zero: - takerAssetFillAmount = new BigNumber(0); - break; - - case TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount: - takerAssetFillAmount = fillableTakerAssetAmount; - break; - - case TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount: - takerAssetFillAmount = fillableTakerAssetAmount.plus(1); - break; - - case TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount: - const takerAssetProxyId = assetDataUtils.decodeAssetProxyId(signedOrder.takerAssetData); - const makerAssetProxyId = assetDataUtils.decodeAssetProxyId(signedOrder.makerAssetData); - const isEitherAssetERC721 = - takerAssetProxyId === AssetProxyId.ERC721 || makerAssetProxyId === AssetProxyId.ERC721; - if (isEitherAssetERC721) { - throw new Error( - 'Cannot test `TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount` together with ERC721 assets since orders involving ERC721 must always be filled exactly.', - ); - } - takerAssetFillAmount = fillableTakerAssetAmount.div(2).integerValue(BigNumber.ROUND_FLOOR); - break; - - default: - throw errorUtils.spawnSwitchErr('TakerAssetFillAmountScenario', takerAssetFillAmountScenario); - } - - return takerAssetFillAmount; - } - private async _modifyTraderStateAsync( - makerStateScenario: TraderStateScenario, - takerStateScenario: TraderStateScenario, - signedOrder: SignedOrder, - takerAssetFillAmount: BigNumber, - ): Promise { - const makerAssetFillAmount = orderUtils.getPartialAmountFloor( - takerAssetFillAmount, - signedOrder.takerAssetAmount, - signedOrder.makerAssetAmount, - ); - switch (makerStateScenario.traderAssetBalance) { - case BalanceAmountScenario.Higher: - break; // Noop since this is already the default - - case BalanceAmountScenario.TooLow: - if (makerAssetFillAmount.eq(0)) { - throw new Error(`Cannot set makerAssetBalanceOfMaker TooLow if makerAssetFillAmount is 0`); - } - const tooLowBalance = makerAssetFillAmount.minus(1); - await this.assetWrapper.setBalanceAsync( - signedOrder.makerAddress, - signedOrder.makerAssetData, - tooLowBalance, - ); - break; - - case BalanceAmountScenario.Exact: - const exactBalance = makerAssetFillAmount; - await this.assetWrapper.setBalanceAsync( - signedOrder.makerAddress, - signedOrder.makerAssetData, - exactBalance, - ); - break; - - default: - throw errorUtils.spawnSwitchErr( - 'makerStateScenario.traderAssetBalance', - makerStateScenario.traderAssetBalance, - ); - } - - const makerFee = orderUtils.getPartialAmountFloor( - takerAssetFillAmount, - signedOrder.takerAssetAmount, - signedOrder.makerFee, - ); - switch (makerStateScenario.zrxFeeBalance) { - case BalanceAmountScenario.Higher: - break; // Noop since this is already the default - - case BalanceAmountScenario.TooLow: - if (makerFee.eq(0)) { - throw new Error(`Cannot set zrxAsserBalanceOfMaker TooLow if makerFee is 0`); - } - const tooLowBalance = makerFee.minus(1); - await this.assetWrapper.setBalanceAsync(signedOrder.makerAddress, this.zrxAssetData, tooLowBalance); - break; - - case BalanceAmountScenario.Exact: - const exactBalance = makerFee; - await this.assetWrapper.setBalanceAsync(signedOrder.makerAddress, this.zrxAssetData, exactBalance); - break; - - default: - throw errorUtils.spawnSwitchErr('makerStateScenario.zrxFeeBalance', makerStateScenario.zrxFeeBalance); - } - - switch (makerStateScenario.traderAssetAllowance) { - case AllowanceAmountScenario.Higher: - break; // Noop since this is already the default - - case AllowanceAmountScenario.TooLow: - const tooLowAllowance = makerAssetFillAmount.minus(1); - await this.assetWrapper.setProxyAllowanceAsync( - signedOrder.makerAddress, - signedOrder.makerAssetData, - tooLowAllowance, - ); - break; - - case AllowanceAmountScenario.Exact: - const exactAllowance = makerAssetFillAmount; - await this.assetWrapper.setProxyAllowanceAsync( - signedOrder.makerAddress, - signedOrder.makerAssetData, - exactAllowance, - ); - break; - - case AllowanceAmountScenario.Unlimited: - await this.assetWrapper.setProxyAllowanceAsync( - signedOrder.makerAddress, - signedOrder.makerAssetData, - constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, - ); - break; - - default: - throw errorUtils.spawnSwitchErr( - 'makerStateScenario.traderAssetAllowance', - makerStateScenario.traderAssetAllowance, - ); - } - - switch (makerStateScenario.zrxFeeAllowance) { - case AllowanceAmountScenario.Higher: - break; // Noop since this is already the default - - case AllowanceAmountScenario.TooLow: - const tooLowAllowance = makerFee.minus(1); - await this.assetWrapper.setProxyAllowanceAsync( - signedOrder.makerAddress, - this.zrxAssetData, - tooLowAllowance, - ); - break; - - case AllowanceAmountScenario.Exact: - const exactAllowance = makerFee; - await this.assetWrapper.setProxyAllowanceAsync( - signedOrder.makerAddress, - this.zrxAssetData, - exactAllowance, - ); - break; - - case AllowanceAmountScenario.Unlimited: - await this.assetWrapper.setProxyAllowanceAsync( - signedOrder.makerAddress, - this.zrxAssetData, - constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, - ); - break; - - default: - throw errorUtils.spawnSwitchErr( - 'makerStateScenario.zrxFeeAllowance', - makerStateScenario.zrxFeeAllowance, - ); - } - - switch (takerStateScenario.traderAssetBalance) { - case BalanceAmountScenario.Higher: - break; // Noop since this is already the default - - case BalanceAmountScenario.TooLow: - if (takerAssetFillAmount.eq(0)) { - throw new Error(`Cannot set takerAssetBalanceOfTaker TooLow if takerAssetFillAmount is 0`); - } - const tooLowBalance = takerAssetFillAmount.minus(1); - await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerAssetData, tooLowBalance); - break; - - case BalanceAmountScenario.Exact: - const exactBalance = takerAssetFillAmount; - await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerAssetData, exactBalance); - break; - - default: - throw errorUtils.spawnSwitchErr( - 'takerStateScenario.traderAssetBalance', - takerStateScenario.traderAssetBalance, - ); - } - - const takerFee = orderUtils.getPartialAmountFloor( - takerAssetFillAmount, - signedOrder.takerAssetAmount, - signedOrder.takerFee, - ); - switch (takerStateScenario.zrxFeeBalance) { - case BalanceAmountScenario.Higher: - break; // Noop since this is already the default - - case BalanceAmountScenario.TooLow: - if (takerFee.eq(0)) { - throw new Error(`Cannot set zrxAssetBalanceOfTaker TooLow if takerFee is 0`); - } - const tooLowBalance = takerFee.minus(1); - await this.assetWrapper.setBalanceAsync(this.takerAddress, this.zrxAssetData, tooLowBalance); - break; - - case BalanceAmountScenario.Exact: - const exactBalance = takerFee; - await this.assetWrapper.setBalanceAsync(this.takerAddress, this.zrxAssetData, exactBalance); - break; - - default: - throw errorUtils.spawnSwitchErr('takerStateScenario.zrxFeeBalance', takerStateScenario.zrxFeeBalance); - } - - switch (takerStateScenario.traderAssetAllowance) { - case AllowanceAmountScenario.Higher: - break; // Noop since this is already the default - - case AllowanceAmountScenario.TooLow: - const tooLowAllowance = takerAssetFillAmount.minus(1); - await this.assetWrapper.setProxyAllowanceAsync( - this.takerAddress, - signedOrder.takerAssetData, - tooLowAllowance, - ); - break; - - case AllowanceAmountScenario.Exact: - const exactAllowance = takerAssetFillAmount; - await this.assetWrapper.setProxyAllowanceAsync( - this.takerAddress, - signedOrder.takerAssetData, - exactAllowance, - ); - break; - - case AllowanceAmountScenario.Unlimited: - await this.assetWrapper.setProxyAllowanceAsync( - this.takerAddress, - signedOrder.takerAssetData, - constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, - ); - break; - - default: - throw errorUtils.spawnSwitchErr( - 'takerStateScenario.traderAssetAllowance', - takerStateScenario.traderAssetAllowance, - ); - } - - switch (takerStateScenario.zrxFeeAllowance) { - case AllowanceAmountScenario.Higher: - break; // Noop since this is already the default - - case AllowanceAmountScenario.TooLow: - const tooLowAllowance = takerFee.minus(1); - await this.assetWrapper.setProxyAllowanceAsync(this.takerAddress, this.zrxAssetData, tooLowAllowance); - break; - - case AllowanceAmountScenario.Exact: - const exactAllowance = takerFee; - await this.assetWrapper.setProxyAllowanceAsync(this.takerAddress, this.zrxAssetData, exactAllowance); - break; - - case AllowanceAmountScenario.Unlimited: - await this.assetWrapper.setProxyAllowanceAsync( - this.takerAddress, - this.zrxAssetData, - constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, - ); - break; - - default: - throw errorUtils.spawnSwitchErr( - 'takerStateScenario.zrxFeeAllowance', - takerStateScenario.zrxFeeAllowance, - ); - } - } -} // tslint:disable:max-file-line-count diff --git a/contracts/protocol/test/utils/index.ts b/contracts/protocol/test/utils/index.ts deleted file mode 100644 index 75cd88666..000000000 --- a/contracts/protocol/test/utils/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './exchange_wrapper'; -export * from './erc20_wrapper'; -export * from './erc721_wrapper'; diff --git a/contracts/protocol/test/utils/match_order_tester.ts b/contracts/protocol/test/utils/match_order_tester.ts deleted file mode 100644 index 0ba8017c5..000000000 --- a/contracts/protocol/test/utils/match_order_tester.ts +++ /dev/null @@ -1,564 +0,0 @@ -import { - chaiSetup, - ERC20BalancesByOwner, - ERC721TokenIdsByOwner, - OrderInfo, - OrderStatus, - TransferAmountsByMatchOrders as TransferAmounts, - TransferAmountsLoggedByMatchOrders as LoggedTransferAmounts, -} from '@0x/contracts-test-utils'; -import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; -import { AssetProxyId, SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import * as _ from 'lodash'; - -import { TransactionReceiptWithDecodedLogs } from '../../../../node_modules/ethereum-types'; - -import { ERC20Wrapper } from './erc20_wrapper'; -import { ERC721Wrapper } from './erc721_wrapper'; -import { ExchangeWrapper } from './exchange_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; - -export class MatchOrderTester { - private readonly _exchangeWrapper: ExchangeWrapper; - private readonly _erc20Wrapper: ERC20Wrapper; - private readonly _erc721Wrapper: ERC721Wrapper; - private readonly _feeTokenAddress: string; - /// @dev Checks values from the logs produced by Exchange.matchOrders against the expected transfer amounts. - /// Values include the amounts transferred from the left/right makers and taker, along with - /// the fees paid on each matched order. These are also the return values of MatchOrders. - /// @param signedOrderLeft First matched order. - /// @param signedOrderRight Second matched order. - /// @param transactionReceipt Transaction receipt and logs produced by Exchange.matchOrders. - /// @param takerAddress Address of taker (account that called Exchange.matchOrders) - /// @param expectedTransferAmounts Expected amounts transferred as a result of order matching. - private static async _assertLogsAsync( - signedOrderLeft: SignedOrder, - signedOrderRight: SignedOrder, - transactionReceipt: TransactionReceiptWithDecodedLogs, - takerAddress: string, - expectedTransferAmounts: TransferAmounts, - ): Promise { - // Should have two fill event logs -- one for each order. - const transactionFillLogs = _.filter(transactionReceipt.logs, ['event', 'Fill']); - expect(transactionFillLogs.length, 'Checking number of logs').to.be.equal(2); - // First log is for left fill - const leftLog = (transactionFillLogs[0] as any).args as LoggedTransferAmounts; - expect(leftLog.makerAddress, 'Checking logged maker address of left order').to.be.equal( - signedOrderLeft.makerAddress, - ); - expect(leftLog.takerAddress, 'Checking logged taker address of right order').to.be.equal(takerAddress); - const amountBoughtByLeftMaker = new BigNumber(leftLog.takerAssetFilledAmount); - const amountSoldByLeftMaker = new BigNumber(leftLog.makerAssetFilledAmount); - const feePaidByLeftMaker = new BigNumber(leftLog.makerFeePaid); - const feePaidByTakerLeft = new BigNumber(leftLog.takerFeePaid); - // Second log is for right fill - const rightLog = (transactionFillLogs[1] as any).args as LoggedTransferAmounts; - expect(rightLog.makerAddress, 'Checking logged maker address of right order').to.be.equal( - signedOrderRight.makerAddress, - ); - expect(rightLog.takerAddress, 'Checking loggerd taker address of right order').to.be.equal(takerAddress); - const amountBoughtByRightMaker = new BigNumber(rightLog.takerAssetFilledAmount); - const amountSoldByRightMaker = new BigNumber(rightLog.makerAssetFilledAmount); - const feePaidByRightMaker = new BigNumber(rightLog.makerFeePaid); - const feePaidByTakerRight = new BigNumber(rightLog.takerFeePaid); - // Derive amount received by taker - const amountReceivedByTaker = amountSoldByLeftMaker.minus(amountBoughtByRightMaker); - // Assert log values - left order - expect(amountBoughtByLeftMaker, 'Checking logged amount bought by left maker').to.be.bignumber.equal( - expectedTransferAmounts.amountBoughtByLeftMaker, - ); - expect(amountSoldByLeftMaker, 'Checking logged amount sold by left maker').to.be.bignumber.equal( - expectedTransferAmounts.amountSoldByLeftMaker, - ); - expect(feePaidByLeftMaker, 'Checking logged fee paid by left maker').to.be.bignumber.equal( - expectedTransferAmounts.feePaidByLeftMaker, - ); - expect(feePaidByTakerLeft, 'Checking logged fee paid on left order by taker').to.be.bignumber.equal( - expectedTransferAmounts.feePaidByTakerLeft, - ); - // Assert log values - right order - expect(amountBoughtByRightMaker, 'Checking logged amount bought by right maker').to.be.bignumber.equal( - expectedTransferAmounts.amountBoughtByRightMaker, - ); - expect(amountSoldByRightMaker, 'Checking logged amount sold by right maker').to.be.bignumber.equal( - expectedTransferAmounts.amountSoldByRightMaker, - ); - expect(feePaidByRightMaker, 'Checking logged fee paid by right maker').to.be.bignumber.equal( - expectedTransferAmounts.feePaidByRightMaker, - ); - expect(feePaidByTakerRight, 'Checking logged fee paid on right order by taker').to.be.bignumber.equal( - expectedTransferAmounts.feePaidByTakerRight, - ); - // Assert derived amount received by taker - expect(amountReceivedByTaker, 'Checking logged amount received by taker').to.be.bignumber.equal( - expectedTransferAmounts.amountReceivedByTaker, - ); - } - /// @dev Asserts all expected ERC20 and ERC721 account holdings match the real holdings. - /// @param expectedERC20BalancesByOwner Expected ERC20 balances. - /// @param realERC20BalancesByOwner Real ERC20 balances. - /// @param expectedERC721TokenIdsByOwner Expected ERC721 token owners. - /// @param realERC721TokenIdsByOwner Real ERC20 token owners. - private static async _assertAllKnownBalancesAsync( - expectedERC20BalancesByOwner: ERC20BalancesByOwner, - realERC20BalancesByOwner: ERC20BalancesByOwner, - expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner, - realERC721TokenIdsByOwner: ERC721TokenIdsByOwner, - ): Promise { - // ERC20 Balances - const areERC20BalancesEqual = _.isEqual(expectedERC20BalancesByOwner, realERC20BalancesByOwner); - expect(areERC20BalancesEqual, 'Checking all known ERC20 account balances').to.be.true(); - // ERC721 Token Ids - const sortedExpectedNewERC721TokenIdsByOwner = _.mapValues(expectedERC721TokenIdsByOwner, tokenIdsByOwner => { - _.mapValues(tokenIdsByOwner, tokenIds => { - _.sortBy(tokenIds); - }); - }); - const sortedNewERC721TokenIdsByOwner = _.mapValues(realERC721TokenIdsByOwner, tokenIdsByOwner => { - _.mapValues(tokenIdsByOwner, tokenIds => { - _.sortBy(tokenIds); - }); - }); - const areERC721TokenIdsEqual = _.isEqual( - sortedExpectedNewERC721TokenIdsByOwner, - sortedNewERC721TokenIdsByOwner, - ); - expect(areERC721TokenIdsEqual, 'Checking all known ERC721 account balances').to.be.true(); - } - /// @dev Constructs new MatchOrderTester. - /// @param exchangeWrapper Used to call to the Exchange. - /// @param erc20Wrapper Used to fetch ERC20 balances. - /// @param erc721Wrapper Used to fetch ERC721 token owners. - /// @param feeTokenAddress Address of ERC20 fee token. - constructor( - exchangeWrapper: ExchangeWrapper, - erc20Wrapper: ERC20Wrapper, - erc721Wrapper: ERC721Wrapper, - feeTokenAddress: string, - ) { - this._exchangeWrapper = exchangeWrapper; - this._erc20Wrapper = erc20Wrapper; - this._erc721Wrapper = erc721Wrapper; - this._feeTokenAddress = feeTokenAddress; - } - /// @dev Matches two complementary orders and asserts results. - /// @param signedOrderLeft First matched order. - /// @param signedOrderRight Second matched order. - /// @param takerAddress Address of taker (the address who matched the two orders) - /// @param erc20BalancesByOwner Current ERC20 balances. - /// @param erc721TokenIdsByOwner Current ERC721 token owners. - /// @param expectedTransferAmounts Expected amounts transferred as a result of order matching. - /// @param initialLeftOrderFilledAmount How much left order has been filled, prior to matching orders. - /// @param initialRightOrderFilledAmount How much the right order has been filled, prior to matching orders. - /// @return New ERC20 balances & ERC721 token owners. - public async matchOrdersAndAssertEffectsAsync( - signedOrderLeft: SignedOrder, - signedOrderRight: SignedOrder, - takerAddress: string, - erc20BalancesByOwner: ERC20BalancesByOwner, - erc721TokenIdsByOwner: ERC721TokenIdsByOwner, - expectedTransferAmounts: TransferAmounts, - initialLeftOrderFilledAmount: BigNumber = new BigNumber(0), - initialRightOrderFilledAmount: BigNumber = new BigNumber(0), - ): Promise<[ERC20BalancesByOwner, ERC721TokenIdsByOwner]> { - // Assert initial order states - await this._assertInitialOrderStatesAsync( - signedOrderLeft, - signedOrderRight, - initialLeftOrderFilledAmount, - initialRightOrderFilledAmount, - ); - // Match left & right orders - const transactionReceipt = await this._exchangeWrapper.matchOrdersAsync( - signedOrderLeft, - signedOrderRight, - takerAddress, - ); - const newERC20BalancesByOwner = await this._erc20Wrapper.getBalancesAsync(); - const newERC721TokenIdsByOwner = await this._erc721Wrapper.getBalancesAsync(); - // Assert logs - await MatchOrderTester._assertLogsAsync( - signedOrderLeft, - signedOrderRight, - transactionReceipt, - takerAddress, - expectedTransferAmounts, - ); - // Assert exchange state - await this._assertExchangeStateAsync( - signedOrderLeft, - signedOrderRight, - initialLeftOrderFilledAmount, - initialRightOrderFilledAmount, - expectedTransferAmounts, - ); - // Assert balances of makers, taker, and fee recipients - await this._assertBalancesAsync( - signedOrderLeft, - signedOrderRight, - erc20BalancesByOwner, - erc721TokenIdsByOwner, - newERC20BalancesByOwner, - newERC721TokenIdsByOwner, - expectedTransferAmounts, - takerAddress, - ); - return [newERC20BalancesByOwner, newERC721TokenIdsByOwner]; - } - /// @dev Asserts initial exchange state for the left and right orders. - /// @param signedOrderLeft First matched order. - /// @param signedOrderRight Second matched order. - /// @param expectedOrderFilledAmountLeft How much left order has been filled, prior to matching orders. - /// @param expectedOrderFilledAmountRight How much the right order has been filled, prior to matching orders. - private async _assertInitialOrderStatesAsync( - signedOrderLeft: SignedOrder, - signedOrderRight: SignedOrder, - expectedOrderFilledAmountLeft: BigNumber, - expectedOrderFilledAmountRight: BigNumber, - ): Promise { - // Assert left order initial state - const orderTakerAssetFilledAmountLeft = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( - orderHashUtils.getOrderHashHex(signedOrderLeft), - ); - expect(orderTakerAssetFilledAmountLeft, 'Checking inital state of left order').to.be.bignumber.equal( - expectedOrderFilledAmountLeft, - ); - // Assert right order initial state - const orderTakerAssetFilledAmountRight = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( - orderHashUtils.getOrderHashHex(signedOrderRight), - ); - expect(orderTakerAssetFilledAmountRight, 'Checking inital state of right order').to.be.bignumber.equal( - expectedOrderFilledAmountRight, - ); - } - /// @dev Asserts the exchange state against the expected amounts transferred by from matching orders. - /// @param signedOrderLeft First matched order. - /// @param signedOrderRight Second matched order. - /// @param initialLeftOrderFilledAmount How much left order has been filled, prior to matching orders. - /// @param initialRightOrderFilledAmount How much the right order has been filled, prior to matching orders. - /// @return TransferAmounts A struct containing the expected transfer amounts. - private async _assertExchangeStateAsync( - signedOrderLeft: SignedOrder, - signedOrderRight: SignedOrder, - initialLeftOrderFilledAmount: BigNumber, - initialRightOrderFilledAmount: BigNumber, - expectedTransferAmounts: TransferAmounts, - ): Promise { - // Assert state for left order: amount bought by left maker - let amountBoughtByLeftMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( - orderHashUtils.getOrderHashHex(signedOrderLeft), - ); - amountBoughtByLeftMaker = amountBoughtByLeftMaker.minus(initialLeftOrderFilledAmount); - expect(amountBoughtByLeftMaker, 'Checking exchange state for left order').to.be.bignumber.equal( - expectedTransferAmounts.amountBoughtByLeftMaker, - ); - // Assert state for right order: amount bought by right maker - let amountBoughtByRightMaker = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( - orderHashUtils.getOrderHashHex(signedOrderRight), - ); - amountBoughtByRightMaker = amountBoughtByRightMaker.minus(initialRightOrderFilledAmount); - expect(amountBoughtByRightMaker, 'Checking exchange state for right order').to.be.bignumber.equal( - expectedTransferAmounts.amountBoughtByRightMaker, - ); - // Assert left order status - const maxAmountBoughtByLeftMaker = signedOrderLeft.takerAssetAmount.minus(initialLeftOrderFilledAmount); - const leftOrderInfo: OrderInfo = await this._exchangeWrapper.getOrderInfoAsync(signedOrderLeft); - const leftExpectedStatus = expectedTransferAmounts.amountBoughtByLeftMaker.isEqualTo(maxAmountBoughtByLeftMaker) - ? OrderStatus.FullyFilled - : OrderStatus.Fillable; - expect(leftOrderInfo.orderStatus, 'Checking exchange status for left order').to.be.equal(leftExpectedStatus); - // Assert right order status - const maxAmountBoughtByRightMaker = signedOrderRight.takerAssetAmount.minus(initialRightOrderFilledAmount); - const rightOrderInfo: OrderInfo = await this._exchangeWrapper.getOrderInfoAsync(signedOrderRight); - const rightExpectedStatus = expectedTransferAmounts.amountBoughtByRightMaker.isEqualTo( - maxAmountBoughtByRightMaker, - ) - ? OrderStatus.FullyFilled - : OrderStatus.Fillable; - expect(rightOrderInfo.orderStatus, 'Checking exchange status for right order').to.be.equal(rightExpectedStatus); - } - /// @dev Asserts account balances after matching orders. - /// @param signedOrderLeft First matched order. - /// @param signedOrderRight Second matched order. - /// @param initialERC20BalancesByOwner ERC20 balances prior to order matching. - /// @param initialERC721TokenIdsByOwner ERC721 token owners prior to order matching. - /// @param finalERC20BalancesByOwner ERC20 balances after order matching. - /// @param finalERC721TokenIdsByOwner ERC721 token owners after order matching. - /// @param expectedTransferAmounts Expected amounts transferred as a result of order matching. - /// @param takerAddress Address of taker (account that called Exchange.matchOrders). - private async _assertBalancesAsync( - signedOrderLeft: SignedOrder, - signedOrderRight: SignedOrder, - initialERC20BalancesByOwner: ERC20BalancesByOwner, - initialERC721TokenIdsByOwner: ERC721TokenIdsByOwner, - finalERC20BalancesByOwner: ERC20BalancesByOwner, - finalERC721TokenIdsByOwner: ERC721TokenIdsByOwner, - expectedTransferAmounts: TransferAmounts, - takerAddress: string, - ): Promise { - let expectedERC20BalancesByOwner: ERC20BalancesByOwner; - let expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner; - [expectedERC20BalancesByOwner, expectedERC721TokenIdsByOwner] = this._calculateExpectedBalances( - signedOrderLeft, - signedOrderRight, - takerAddress, - initialERC20BalancesByOwner, - initialERC721TokenIdsByOwner, - expectedTransferAmounts, - ); - // Assert balances of makers, taker, and fee recipients - await this._assertMakerTakerAndFeeRecipientBalancesAsync( - signedOrderLeft, - signedOrderRight, - expectedERC20BalancesByOwner, - finalERC20BalancesByOwner, - expectedERC721TokenIdsByOwner, - finalERC721TokenIdsByOwner, - takerAddress, - ); - // Assert balances for all known accounts - await MatchOrderTester._assertAllKnownBalancesAsync( - expectedERC20BalancesByOwner, - finalERC20BalancesByOwner, - expectedERC721TokenIdsByOwner, - finalERC721TokenIdsByOwner, - ); - } - /// @dev Calculates the expected balances of order makers, fee recipients, and the taker, - /// as a result of matching two orders. - /// @param signedOrderRight First matched order. - /// @param signedOrderRight Second matched order. - /// @param takerAddress Address of taker (the address who matched the two orders) - /// @param erc20BalancesByOwner Current ERC20 balances. - /// @param erc721TokenIdsByOwner Current ERC721 token owners. - /// @param expectedTransferAmounts Expected amounts transferred as a result of order matching. - /// @return Expected ERC20 balances & ERC721 token owners after orders have been matched. - private _calculateExpectedBalances( - signedOrderLeft: SignedOrder, - signedOrderRight: SignedOrder, - takerAddress: string, - erc20BalancesByOwner: ERC20BalancesByOwner, - erc721TokenIdsByOwner: ERC721TokenIdsByOwner, - expectedTransferAmounts: TransferAmounts, - ): [ERC20BalancesByOwner, ERC721TokenIdsByOwner] { - const makerAddressLeft = signedOrderLeft.makerAddress; - const makerAddressRight = signedOrderRight.makerAddress; - const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress; - const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress; - // Operations are performed on copies of the balances - const expectedNewERC20BalancesByOwner = _.cloneDeep(erc20BalancesByOwner); - const expectedNewERC721TokenIdsByOwner = _.cloneDeep(erc721TokenIdsByOwner); - // Left Maker Asset (Right Taker Asset) - const makerAssetProxyIdLeft = assetDataUtils.decodeAssetProxyId(signedOrderLeft.makerAssetData); - if (makerAssetProxyIdLeft === AssetProxyId.ERC20) { - // Decode asset data - const erc20AssetData = assetDataUtils.decodeERC20AssetData(signedOrderLeft.makerAssetData); - const makerAssetAddressLeft = erc20AssetData.tokenAddress; - const takerAssetAddressRight = makerAssetAddressLeft; - // Left Maker - expectedNewERC20BalancesByOwner[makerAddressLeft][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ - makerAddressLeft - ][makerAssetAddressLeft].minus(expectedTransferAmounts.amountSoldByLeftMaker); - // Right Maker - expectedNewERC20BalancesByOwner[makerAddressRight][ - takerAssetAddressRight - ] = expectedNewERC20BalancesByOwner[makerAddressRight][takerAssetAddressRight].plus( - expectedTransferAmounts.amountBoughtByRightMaker, - ); - // Taker - expectedNewERC20BalancesByOwner[takerAddress][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ - takerAddress - ][makerAssetAddressLeft].plus(expectedTransferAmounts.amountReceivedByTaker); - } else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) { - // Decode asset data - const erc721AssetData = assetDataUtils.decodeERC721AssetData(signedOrderLeft.makerAssetData); - const makerAssetAddressLeft = erc721AssetData.tokenAddress; - const makerAssetIdLeft = erc721AssetData.tokenId; - const takerAssetAddressRight = makerAssetAddressLeft; - const takerAssetIdRight = makerAssetIdLeft; - // Left Maker - _.remove(expectedNewERC721TokenIdsByOwner[makerAddressLeft][makerAssetAddressLeft], makerAssetIdLeft); - // Right Maker - expectedNewERC721TokenIdsByOwner[makerAddressRight][takerAssetAddressRight].push(takerAssetIdRight); - // Taker: Since there is only 1 asset transferred, the taker does not receive any of the left maker asset. - } - // Left Taker Asset (Right Maker Asset) - // Note: This exchange is only between the order makers: the Taker does not receive any of the left taker asset. - const takerAssetProxyIdLeft = assetDataUtils.decodeAssetProxyId(signedOrderLeft.takerAssetData); - if (takerAssetProxyIdLeft === AssetProxyId.ERC20) { - // Decode asset data - const erc20AssetData = assetDataUtils.decodeERC20AssetData(signedOrderLeft.takerAssetData); - const takerAssetAddressLeft = erc20AssetData.tokenAddress; - const makerAssetAddressRight = takerAssetAddressLeft; - // Left Maker - expectedNewERC20BalancesByOwner[makerAddressLeft][takerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ - makerAddressLeft - ][takerAssetAddressLeft].plus(expectedTransferAmounts.amountBoughtByLeftMaker); - // Right Maker - expectedNewERC20BalancesByOwner[makerAddressRight][ - makerAssetAddressRight - ] = expectedNewERC20BalancesByOwner[makerAddressRight][makerAssetAddressRight].minus( - expectedTransferAmounts.amountSoldByRightMaker, - ); - } else if (takerAssetProxyIdLeft === AssetProxyId.ERC721) { - // Decode asset data - const erc721AssetData = assetDataUtils.decodeERC721AssetData(signedOrderRight.makerAssetData); - const makerAssetAddressRight = erc721AssetData.tokenAddress; - const makerAssetIdRight = erc721AssetData.tokenId; - const takerAssetAddressLeft = makerAssetAddressRight; - const takerAssetIdLeft = makerAssetIdRight; - // Right Maker - _.remove(expectedNewERC721TokenIdsByOwner[makerAddressRight][makerAssetAddressRight], makerAssetIdRight); - // Left Maker - expectedNewERC721TokenIdsByOwner[makerAddressLeft][takerAssetAddressLeft].push(takerAssetIdLeft); - } - // Left Maker Fees - expectedNewERC20BalancesByOwner[makerAddressLeft][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[ - makerAddressLeft - ][this._feeTokenAddress].minus(expectedTransferAmounts.feePaidByLeftMaker); - // Right Maker Fees - expectedNewERC20BalancesByOwner[makerAddressRight][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[ - makerAddressRight - ][this._feeTokenAddress].minus(expectedTransferAmounts.feePaidByRightMaker); - // Taker Fees - expectedNewERC20BalancesByOwner[takerAddress][this._feeTokenAddress] = expectedNewERC20BalancesByOwner[ - takerAddress - ][this._feeTokenAddress].minus( - expectedTransferAmounts.feePaidByTakerLeft.plus(expectedTransferAmounts.feePaidByTakerRight), - ); - // Left Fee Recipient Fees - expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][ - this._feeTokenAddress - ] = expectedNewERC20BalancesByOwner[feeRecipientAddressLeft][this._feeTokenAddress].plus( - expectedTransferAmounts.feePaidByLeftMaker.plus(expectedTransferAmounts.feePaidByTakerLeft), - ); - // Right Fee Recipient Fees - expectedNewERC20BalancesByOwner[feeRecipientAddressRight][ - this._feeTokenAddress - ] = expectedNewERC20BalancesByOwner[feeRecipientAddressRight][this._feeTokenAddress].plus( - expectedTransferAmounts.feePaidByRightMaker.plus(expectedTransferAmounts.feePaidByTakerRight), - ); - - return [expectedNewERC20BalancesByOwner, expectedNewERC721TokenIdsByOwner]; - } - /// @dev Asserts ERC20 account balances and ERC721 token holdings that result from order matching. - /// Specifically checks balances of makers, taker and fee recipients. - /// @param signedOrderLeft First matched order. - /// @param signedOrderRight Second matched order. - /// @param expectedERC20BalancesByOwner Expected ERC20 balances. - /// @param realERC20BalancesByOwner Real ERC20 balances. - /// @param expectedERC721TokenIdsByOwner Expected ERC721 token owners. - /// @param realERC721TokenIdsByOwner Real ERC20 token owners. - /// @param takerAddress Address of taker (account that called Exchange.matchOrders). - private async _assertMakerTakerAndFeeRecipientBalancesAsync( - signedOrderLeft: SignedOrder, - signedOrderRight: SignedOrder, - expectedERC20BalancesByOwner: ERC20BalancesByOwner, - realERC20BalancesByOwner: ERC20BalancesByOwner, - expectedERC721TokenIdsByOwner: ERC721TokenIdsByOwner, - realERC721TokenIdsByOwner: ERC721TokenIdsByOwner, - takerAddress: string, - ): Promise { - // Individual balance comparisons - const makerAssetProxyIdLeft = assetDataUtils.decodeAssetProxyId(signedOrderLeft.makerAssetData); - const makerERC20AssetDataLeft = - makerAssetProxyIdLeft === AssetProxyId.ERC20 - ? assetDataUtils.decodeERC20AssetData(signedOrderLeft.makerAssetData) - : assetDataUtils.decodeERC721AssetData(signedOrderLeft.makerAssetData); - const makerAssetAddressLeft = makerERC20AssetDataLeft.tokenAddress; - const makerAssetProxyIdRight = assetDataUtils.decodeAssetProxyId(signedOrderRight.makerAssetData); - const makerERC20AssetDataRight = - makerAssetProxyIdRight === AssetProxyId.ERC20 - ? assetDataUtils.decodeERC20AssetData(signedOrderRight.makerAssetData) - : assetDataUtils.decodeERC721AssetData(signedOrderRight.makerAssetData); - const makerAssetAddressRight = makerERC20AssetDataRight.tokenAddress; - if (makerAssetProxyIdLeft === AssetProxyId.ERC20) { - expect( - realERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft], - 'Checking left maker egress ERC20 account balance', - ).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft]); - expect( - realERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft], - 'Checking right maker ingress ERC20 account balance', - ).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft]); - expect( - realERC20BalancesByOwner[takerAddress][makerAssetAddressLeft], - 'Checking taker ingress ERC20 account balance', - ).to.be.bignumber.equal(expectedERC20BalancesByOwner[takerAddress][makerAssetAddressLeft]); - } else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) { - expect( - realERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft].sort(), - 'Checking left maker egress ERC721 account holdings', - ).to.be.deep.equal( - expectedERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressLeft].sort(), - ); - expect( - realERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft].sort(), - 'Checking right maker ERC721 account holdings', - ).to.be.deep.equal( - expectedERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressLeft].sort(), - ); - expect( - realERC721TokenIdsByOwner[takerAddress][makerAssetAddressLeft].sort(), - 'Checking taker ingress ERC721 account holdings', - ).to.be.deep.equal(expectedERC721TokenIdsByOwner[takerAddress][makerAssetAddressLeft].sort()); - } else { - throw new Error(`Unhandled Asset Proxy ID: ${makerAssetProxyIdLeft}`); - } - if (makerAssetProxyIdRight === AssetProxyId.ERC20) { - expect( - realERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight], - 'Checking left maker ingress ERC20 account balance', - ).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight]); - expect( - realERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressRight], - 'Checking right maker egress ERC20 account balance', - ).to.be.bignumber.equal( - expectedERC20BalancesByOwner[signedOrderRight.makerAddress][makerAssetAddressRight], - ); - } else if (makerAssetProxyIdRight === AssetProxyId.ERC721) { - expect( - realERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight].sort(), - 'Checking left maker ingress ERC721 account holdings', - ).to.be.deep.equal( - expectedERC721TokenIdsByOwner[signedOrderLeft.makerAddress][makerAssetAddressRight].sort(), - ); - expect( - realERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressRight], - 'Checking right maker agress ERC721 account holdings', - ).to.be.deep.equal(expectedERC721TokenIdsByOwner[signedOrderRight.makerAddress][makerAssetAddressRight]); - } else { - throw new Error(`Unhandled Asset Proxy ID: ${makerAssetProxyIdRight}`); - } - // Paid fees - expect( - realERC20BalancesByOwner[signedOrderLeft.makerAddress][this._feeTokenAddress], - 'Checking left maker egress ERC20 account fees', - ).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderLeft.makerAddress][this._feeTokenAddress]); - expect( - realERC20BalancesByOwner[signedOrderRight.makerAddress][this._feeTokenAddress], - 'Checking right maker egress ERC20 account fees', - ).to.be.bignumber.equal(expectedERC20BalancesByOwner[signedOrderRight.makerAddress][this._feeTokenAddress]); - expect( - realERC20BalancesByOwner[takerAddress][this._feeTokenAddress], - 'Checking taker egress ERC20 account fees', - ).to.be.bignumber.equal(expectedERC20BalancesByOwner[takerAddress][this._feeTokenAddress]); - // Received fees - expect( - realERC20BalancesByOwner[signedOrderLeft.feeRecipientAddress][this._feeTokenAddress], - 'Checking left fee recipient ingress ERC20 account fees', - ).to.be.bignumber.equal( - expectedERC20BalancesByOwner[signedOrderLeft.feeRecipientAddress][this._feeTokenAddress], - ); - expect( - realERC20BalancesByOwner[signedOrderRight.feeRecipientAddress][this._feeTokenAddress], - 'Checking right fee receipient ingress ERC20 account fees', - ).to.be.bignumber.equal( - expectedERC20BalancesByOwner[signedOrderRight.feeRecipientAddress][this._feeTokenAddress], - ); - } -} // tslint:disable-line:max-file-line-count diff --git a/contracts/protocol/test/utils/order_factory_from_scenario.ts b/contracts/protocol/test/utils/order_factory_from_scenario.ts deleted file mode 100644 index ecb4b2e28..000000000 --- a/contracts/protocol/test/utils/order_factory_from_scenario.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { - AssetDataScenario, - constants, - ERC721TokenIdsByOwner, - ExpirationTimeSecondsScenario, - FeeRecipientAddressScenario, - OrderAssetAmountScenario, - OrderScenario, - TakerScenario, -} from '@0x/contracts-test-utils'; -import { DummyERC721TokenContract } from '@0x/contracts-tokens'; -import { assetDataUtils, generatePseudoRandomSalt } from '@0x/order-utils'; -import { Order } from '@0x/types'; -import { BigNumber, errorUtils } from '@0x/utils'; - -const TEN_UNITS_EIGHTEEN_DECIMALS = new BigNumber(10_000_000_000_000_000_000); -const FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(5_000_000_000_000_000_000); -const POINT_ONE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(100_000_000_000_000_000); -const POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(50_000_000_000_000_000); -const TEN_UNITS_FIVE_DECIMALS = new BigNumber(1_000_000); -const FIVE_UNITS_FIVE_DECIMALS = new BigNumber(500_000); -const TEN_UNITS_ZERO_DECIMALS = new BigNumber(10); -const ONE_THOUSAND_UNITS_ZERO_DECIMALS = new BigNumber(1000); -const ONE_NFT_UNIT = new BigNumber(1); - -export class OrderFactoryFromScenario { - private readonly _userAddresses: string[]; - private readonly _zrxAddress: string; - private readonly _nonZrxERC20EighteenDecimalTokenAddresses: string[]; - private readonly _erc20FiveDecimalTokenAddresses: string[]; - private readonly _erc20ZeroDecimalTokenAddresses: string[]; - private readonly _erc721Token: DummyERC721TokenContract; - private readonly _erc721Balances: ERC721TokenIdsByOwner; - private readonly _exchangeAddress: string; - constructor( - userAddresses: string[], - zrxAddress: string, - nonZrxERC20EighteenDecimalTokenAddresses: string[], - erc20FiveDecimalTokenAddresses: string[], - erc20ZeroDecimalTokenAddresses: string[], - erc721Token: DummyERC721TokenContract, - erc721Balances: ERC721TokenIdsByOwner, - exchangeAddress: string, - ) { - this._userAddresses = userAddresses; - this._zrxAddress = zrxAddress; - this._nonZrxERC20EighteenDecimalTokenAddresses = nonZrxERC20EighteenDecimalTokenAddresses; - this._erc20FiveDecimalTokenAddresses = erc20FiveDecimalTokenAddresses; - this._erc20ZeroDecimalTokenAddresses = erc20ZeroDecimalTokenAddresses; - this._erc721Token = erc721Token; - this._erc721Balances = erc721Balances; - this._exchangeAddress = exchangeAddress; - } - public generateOrder(orderScenario: OrderScenario): Order { - const makerAddress = this._userAddresses[1]; - let takerAddress = this._userAddresses[2]; - const erc721MakerAssetIds = this._erc721Balances[makerAddress][this._erc721Token.address]; - const erc721TakerAssetIds = this._erc721Balances[takerAddress][this._erc721Token.address]; - let feeRecipientAddress; - let makerAssetAmount; - let takerAssetAmount; - let makerFee; - let takerFee; - let expirationTimeSeconds; - let makerAssetData; - let takerAssetData; - - switch (orderScenario.feeRecipientScenario) { - case FeeRecipientAddressScenario.BurnAddress: - feeRecipientAddress = constants.NULL_ADDRESS; - break; - case FeeRecipientAddressScenario.EthUserAddress: - feeRecipientAddress = this._userAddresses[4]; - break; - default: - throw errorUtils.spawnSwitchErr('FeeRecipientAddressScenario', orderScenario.feeRecipientScenario); - } - - switch (orderScenario.makerAssetDataScenario) { - case AssetDataScenario.ZRXFeeToken: - makerAssetData = assetDataUtils.encodeERC20AssetData(this._zrxAddress); - break; - case AssetDataScenario.ERC20NonZRXEighteenDecimals: - makerAssetData = assetDataUtils.encodeERC20AssetData(this._nonZrxERC20EighteenDecimalTokenAddresses[0]); - break; - case AssetDataScenario.ERC20FiveDecimals: - makerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[0]); - break; - case AssetDataScenario.ERC721: - makerAssetData = assetDataUtils.encodeERC721AssetData( - this._erc721Token.address, - erc721MakerAssetIds[0], - ); - break; - case AssetDataScenario.ERC20ZeroDecimals: - makerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20ZeroDecimalTokenAddresses[0]); - break; - default: - throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); - } - - switch (orderScenario.takerAssetDataScenario) { - case AssetDataScenario.ZRXFeeToken: - takerAssetData = assetDataUtils.encodeERC20AssetData(this._zrxAddress); - break; - case AssetDataScenario.ERC20NonZRXEighteenDecimals: - takerAssetData = assetDataUtils.encodeERC20AssetData(this._nonZrxERC20EighteenDecimalTokenAddresses[1]); - break; - case AssetDataScenario.ERC20FiveDecimals: - takerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[1]); - break; - case AssetDataScenario.ERC721: - takerAssetData = assetDataUtils.encodeERC721AssetData( - this._erc721Token.address, - erc721TakerAssetIds[0], - ); - break; - case AssetDataScenario.ERC20ZeroDecimals: - takerAssetData = assetDataUtils.encodeERC20AssetData(this._erc20ZeroDecimalTokenAddresses[1]); - break; - default: - throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); - } - - switch (orderScenario.makerAssetAmountScenario) { - case OrderAssetAmountScenario.Large: - switch (orderScenario.makerAssetDataScenario) { - case AssetDataScenario.ZRXFeeToken: - case AssetDataScenario.ERC20NonZRXEighteenDecimals: - makerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS; - break; - case AssetDataScenario.ERC20FiveDecimals: - makerAssetAmount = TEN_UNITS_FIVE_DECIMALS; - break; - case AssetDataScenario.ERC721: - makerAssetAmount = ONE_NFT_UNIT; - break; - case AssetDataScenario.ERC20ZeroDecimals: - makerAssetAmount = ONE_THOUSAND_UNITS_ZERO_DECIMALS; - break; - default: - throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); - } - break; - case OrderAssetAmountScenario.Small: - switch (orderScenario.makerAssetDataScenario) { - case AssetDataScenario.ZRXFeeToken: - case AssetDataScenario.ERC20NonZRXEighteenDecimals: - makerAssetAmount = FIVE_UNITS_EIGHTEEN_DECIMALS; - break; - case AssetDataScenario.ERC20FiveDecimals: - makerAssetAmount = FIVE_UNITS_FIVE_DECIMALS; - break; - case AssetDataScenario.ERC721: - makerAssetAmount = ONE_NFT_UNIT; - break; - case AssetDataScenario.ERC20ZeroDecimals: - makerAssetAmount = TEN_UNITS_ZERO_DECIMALS; - break; - default: - throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); - } - break; - case OrderAssetAmountScenario.Zero: - makerAssetAmount = new BigNumber(0); - break; - default: - throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.makerAssetAmountScenario); - } - - switch (orderScenario.takerAssetAmountScenario) { - case OrderAssetAmountScenario.Large: - switch (orderScenario.takerAssetDataScenario) { - case AssetDataScenario.ERC20NonZRXEighteenDecimals: - case AssetDataScenario.ZRXFeeToken: - takerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS; - break; - case AssetDataScenario.ERC20FiveDecimals: - takerAssetAmount = TEN_UNITS_FIVE_DECIMALS; - break; - case AssetDataScenario.ERC721: - takerAssetAmount = ONE_NFT_UNIT; - break; - case AssetDataScenario.ERC20ZeroDecimals: - takerAssetAmount = ONE_THOUSAND_UNITS_ZERO_DECIMALS; - break; - default: - throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); - } - break; - case OrderAssetAmountScenario.Small: - switch (orderScenario.takerAssetDataScenario) { - case AssetDataScenario.ERC20NonZRXEighteenDecimals: - case AssetDataScenario.ZRXFeeToken: - takerAssetAmount = FIVE_UNITS_EIGHTEEN_DECIMALS; - break; - case AssetDataScenario.ERC20FiveDecimals: - takerAssetAmount = FIVE_UNITS_FIVE_DECIMALS; - break; - case AssetDataScenario.ERC721: - takerAssetAmount = ONE_NFT_UNIT; - break; - case AssetDataScenario.ERC20ZeroDecimals: - takerAssetAmount = TEN_UNITS_ZERO_DECIMALS; - break; - default: - throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); - } - break; - case OrderAssetAmountScenario.Zero: - takerAssetAmount = new BigNumber(0); - break; - default: - throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.takerAssetAmountScenario); - } - - switch (orderScenario.makerFeeScenario) { - case OrderAssetAmountScenario.Large: - makerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS; - break; - case OrderAssetAmountScenario.Small: - makerFee = POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS; - break; - case OrderAssetAmountScenario.Zero: - makerFee = new BigNumber(0); - break; - default: - throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.makerFeeScenario); - } - - switch (orderScenario.takerFeeScenario) { - case OrderAssetAmountScenario.Large: - takerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS; - break; - case OrderAssetAmountScenario.Small: - takerFee = POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS; - break; - case OrderAssetAmountScenario.Zero: - takerFee = new BigNumber(0); - break; - default: - throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.takerFeeScenario); - } - - switch (orderScenario.expirationTimeSecondsScenario) { - case ExpirationTimeSecondsScenario.InFuture: - expirationTimeSeconds = new BigNumber(2524604400); // Close to infinite - break; - case ExpirationTimeSecondsScenario.InPast: - expirationTimeSeconds = new BigNumber(0); // Jan 1, 1970 - break; - default: - throw errorUtils.spawnSwitchErr( - 'ExpirationTimeSecondsScenario', - orderScenario.expirationTimeSecondsScenario, - ); - } - - switch (orderScenario.takerScenario) { - case TakerScenario.CorrectlySpecified: - break; // noop since takerAddress is already specified - - case TakerScenario.IncorrectlySpecified: - const notTaker = this._userAddresses[3]; - takerAddress = notTaker; - break; - - case TakerScenario.Unspecified: - takerAddress = constants.NULL_ADDRESS; - break; - - default: - throw errorUtils.spawnSwitchErr('TakerScenario', orderScenario.takerScenario); - } - - const order = { - senderAddress: constants.NULL_ADDRESS, - makerAddress, - takerAddress, - makerFee, - takerFee, - makerAssetAmount, - takerAssetAmount, - makerAssetData, - takerAssetData, - salt: generatePseudoRandomSalt(), - exchangeAddress: this._exchangeAddress, - feeRecipientAddress, - expirationTimeSeconds, - }; - - return order; - } -} diff --git a/contracts/protocol/test/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts b/contracts/protocol/test/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts deleted file mode 100644 index 64b7dedbe..000000000 --- a/contracts/protocol/test/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { AbstractBalanceAndProxyAllowanceFetcher } from '@0x/order-utils'; -import { BigNumber } from '@0x/utils'; - -import { AssetWrapper } from './asset_wrapper'; - -export class SimpleAssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher { - private readonly _assetWrapper: AssetWrapper; - constructor(assetWrapper: AssetWrapper) { - this._assetWrapper = assetWrapper; - } - public async getBalanceAsync(assetData: string, userAddress: string): Promise { - const balance = await this._assetWrapper.getBalanceAsync(userAddress, assetData); - return balance; - } - public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise { - const proxyAllowance = await this._assetWrapper.getProxyAllowanceAsync(userAddress, assetData); - return proxyAllowance; - } -} diff --git a/contracts/protocol/test/utils/simple_order_filled_cancelled_fetcher.ts b/contracts/protocol/test/utils/simple_order_filled_cancelled_fetcher.ts deleted file mode 100644 index af959e00e..000000000 --- a/contracts/protocol/test/utils/simple_order_filled_cancelled_fetcher.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { AbstractOrderFilledCancelledFetcher, orderHashUtils } from '@0x/order-utils'; -import { SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; - -import { ExchangeWrapper } from './exchange_wrapper'; - -export class SimpleOrderFilledCancelledFetcher implements AbstractOrderFilledCancelledFetcher { - private readonly _exchangeWrapper: ExchangeWrapper; - private readonly _zrxAssetData: string; - constructor(exchange: ExchangeWrapper, zrxAssetData: string) { - this._exchangeWrapper = exchange; - this._zrxAssetData = zrxAssetData; - } - public async getFilledTakerAmountAsync(orderHash: string): Promise { - const filledTakerAmount = new BigNumber(await this._exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash)); - return filledTakerAmount; - } - public async isOrderCancelledAsync(signedOrder: SignedOrder): Promise { - const orderHash = orderHashUtils.getOrderHashHex(signedOrder); - const isCancelled = await this._exchangeWrapper.isCancelledAsync(orderHash); - const orderEpoch = await this._exchangeWrapper.getOrderEpochAsync( - signedOrder.makerAddress, - signedOrder.senderAddress, - ); - const isCancelledByOrderEpoch = orderEpoch > signedOrder.salt; - return isCancelled || isCancelledByOrderEpoch; - } - public getZRXAssetData(): string { - return this._zrxAssetData; - } -} diff --git a/contracts/protocol/test/utils/types.ts b/contracts/protocol/test/utils/types.ts deleted file mode 100644 index 5f12635a7..000000000 --- a/contracts/protocol/test/utils/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; - -export interface AbiDecodedFillOrderData { - order: SignedOrder; - takerAssetFillAmount: BigNumber; - signature: string; -} diff --git a/contracts/protocol/tsconfig.json b/contracts/protocol/tsconfig.json deleted file mode 100644 index db872fc32..000000000 --- a/contracts/protocol/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "extends": "../../tsconfig", - "compilerOptions": { - "outDir": "lib", - "rootDir": ".", - "resolveJsonModule": true - }, - "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], - "files": [ - "./generated-artifacts/AssetProxyOwner.json", - "./generated-artifacts/ERC20Proxy.json", - "./generated-artifacts/ERC721Proxy.json", - "./generated-artifacts/Exchange.json", - "./generated-artifacts/MixinAuthorizable.json", - "./generated-artifacts/MultiAssetProxy.json", - "./generated-artifacts/TestAssetProxyDispatcher.json", - "./generated-artifacts/TestAssetProxyOwner.json", - "./generated-artifacts/TestExchangeInternals.json", - "./generated-artifacts/TestSignatureValidator.json", - "./generated-artifacts/TestStaticCallReceiver.json" - ], - "exclude": ["./deploy/solc/solc_bin"] -} diff --git a/contracts/protocol/tslint.json b/contracts/protocol/tslint.json deleted file mode 100644 index 1bb3ac2a2..000000000 --- a/contracts/protocol/tslint.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": ["@0x/tslint-config"], - "rules": { - "custom-no-magic-numbers": false - } -} -- cgit v1.2.3