diff --git a/.circleci/config.yml b/.circleci/config.yml index 28be36b941..8845d9cdff 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -90,13 +90,15 @@ jobs: - restore_cache: keys: - repo-{{ .Environment.CIRCLE_SHA1 }} - - run: yarn wsrun test:circleci @0x/contracts-multisig @0x/contracts-utils @0x/contracts-exchange-libs @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-asset-proxy @0x/contracts-exchange @0x/contracts-coordinator @0x/contracts-dev-utils @0x/contracts-staking + - run: yarn wsrun test:circleci @0x/contracts-multisig @0x/contracts-utils @0x/contracts-exchange-libs @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-asset-proxy @0x/contracts-exchange @0x/contracts-dev-utils @0x/contracts-staking # TODO(dorothy-zbornak): Re-enable after updating this package for 3.0. # - run: yarn wsrun test:circleci @0x/contracts-extensions # TODO(dorothy-zbornak): Re-enable after updating this package for 3.0. # - run: yarn wsrun test:circleci @0x/contracts-exchange-forwarder # TODO(dorothy-zbornak): Re-enable after this package is complete. # - run: yarn wsrun test:circleci @0x/contracts-staking + # TODO(abandeali): Re-enable after this package is complete. + # - run: yarn wsrun test:circleci @0x/contracts-coordinator test-contracts-geth: docker: - image: circleci/node:9-browsers diff --git a/contracts/asset-proxy/CHANGELOG.json b/contracts/asset-proxy/CHANGELOG.json index d546d3d0c5..3550e36264 100644 --- a/contracts/asset-proxy/CHANGELOG.json +++ b/contracts/asset-proxy/CHANGELOG.json @@ -5,6 +5,14 @@ { "note": "Disallow the zero address from being made an authorized address in MixinAuthorizable, and created an archive directory that includes an old version of Ownable", "pr": 2019 + }, + { + "note": "Remove `LibAssetProxyIds` contract", + "pr": 2055 + }, + { + "note": "Compile and export all contracts, artifacts, and wrappers by default", + "pr": 2055 } ] }, diff --git a/contracts/asset-proxy/compiler.json b/contracts/asset-proxy/compiler.json index 51ff4efa9b..6b74c612c2 100644 --- a/contracts/asset-proxy/compiler.json +++ b/contracts/asset-proxy/compiler.json @@ -22,17 +22,5 @@ ] } } - }, - "contracts": [ - "archive/MixinAuthorizable.sol", - "src/ERC1155Proxy.sol", - "src/ERC20Proxy.sol", - "src/ERC721Proxy.sol", - "src/MultiAssetProxy.sol", - "src/StaticCallProxy.sol", - "src/interfaces/IAssetData.sol", - "src/interfaces/IAssetProxy.sol", - "src/interfaces/IAuthorizable.sol", - "test/TestStaticCallTarget.sol" - ] + } } diff --git a/contracts/asset-proxy/contracts/src/libs/LibAssetProxyIds.sol b/contracts/asset-proxy/contracts/src/libs/LibAssetProxyIds.sol deleted file mode 100644 index d4fa173afd..0000000000 --- a/contracts/asset-proxy/contracts/src/libs/LibAssetProxyIds.sol +++ /dev/null @@ -1,40 +0,0 @@ -/* - - Copyright 2019 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity ^0.5.5; - - -contract LibAssetProxyIds { - - // AssetProxy Ids are equiavalent the first 4 bytes of the keccak256 hash of the function signature assigned to each AssetProxy. - - // ERC20Token(address) - bytes4 constant public ERC20_PROXY_ID = 0xf47261b0; - - // ERC721Token(address,uint256) - bytes4 constant public ERC721_PROXY_ID = 0x02571792; - - // ERC1155Assets(address,uint256[],uint256[],bytes) - bytes4 constant public ERC1155_PROXY_ID = 0xa7cb5fb7; - - // MultiAsset(uint256[],bytes[]) - bytes4 constant public MULTI_ASSET_PROXY_ID = 0x94cfcdd7; - - // StaticCall(address,bytes,bytes32) - bytes4 constant public STATIC_CALL_PROXY_ID = 0xc339d10a; -} diff --git a/contracts/asset-proxy/package.json b/contracts/asset-proxy/package.json index 1c4ed19af1..8f8b2a64e1 100644 --- a/contracts/asset-proxy/package.json +++ b/contracts/asset-proxy/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", - "pre_build": "run-s compile generate_contract_wrappers", + "pre_build": "run-s compile contracts:gen 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", @@ -34,7 +34,7 @@ "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis": "./generated-artifacts/@(ERC1155Proxy|ERC20Proxy|ERC721Proxy|IAssetData|IAssetProxy|IAuthorizable|MixinAuthorizable|MultiAssetProxy|StaticCallProxy|TestStaticCallTarget).json", + "abis": "./generated-artifacts/@(ERC1155Proxy|ERC20Proxy|ERC721Proxy|IAssetData|IAssetProxy|IAssetProxyDispatcher|IAuthorizable|MixinAssetProxyDispatcher|MixinAuthorizable|MultiAssetProxy|Ownable|StaticCallProxy|TestStaticCallTarget).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/asset-proxy/src/artifacts.ts b/contracts/asset-proxy/src/artifacts.ts index 6942df8793..85f9cbc640 100644 --- a/contracts/asset-proxy/src/artifacts.ts +++ b/contracts/asset-proxy/src/artifacts.ts @@ -10,20 +10,26 @@ import * as ERC20Proxy from '../generated-artifacts/ERC20Proxy.json'; import * as ERC721Proxy from '../generated-artifacts/ERC721Proxy.json'; import * as IAssetData from '../generated-artifacts/IAssetData.json'; import * as IAssetProxy from '../generated-artifacts/IAssetProxy.json'; +import * as IAssetProxyDispatcher from '../generated-artifacts/IAssetProxyDispatcher.json'; import * as IAuthorizable from '../generated-artifacts/IAuthorizable.json'; +import * as MixinAssetProxyDispatcher from '../generated-artifacts/MixinAssetProxyDispatcher.json'; import * as MixinAuthorizable from '../generated-artifacts/MixinAuthorizable.json'; import * as MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json'; +import * as Ownable from '../generated-artifacts/Ownable.json'; import * as StaticCallProxy from '../generated-artifacts/StaticCallProxy.json'; import * as TestStaticCallTarget from '../generated-artifacts/TestStaticCallTarget.json'; export const artifacts = { + MixinAssetProxyDispatcher: MixinAssetProxyDispatcher as ContractArtifact, + MixinAuthorizable: MixinAuthorizable as ContractArtifact, + Ownable: Ownable as ContractArtifact, ERC1155Proxy: ERC1155Proxy as ContractArtifact, ERC20Proxy: ERC20Proxy as ContractArtifact, ERC721Proxy: ERC721Proxy as ContractArtifact, - MixinAuthorizable: MixinAuthorizable as ContractArtifact, MultiAssetProxy: MultiAssetProxy as ContractArtifact, StaticCallProxy: StaticCallProxy as ContractArtifact, IAssetData: IAssetData as ContractArtifact, IAssetProxy: IAssetProxy as ContractArtifact, + IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact, IAuthorizable: IAuthorizable as ContractArtifact, TestStaticCallTarget: TestStaticCallTarget as ContractArtifact, }; diff --git a/contracts/asset-proxy/src/wrappers.ts b/contracts/asset-proxy/src/wrappers.ts index 4a7968fc12..a1d2bcb46d 100644 --- a/contracts/asset-proxy/src/wrappers.ts +++ b/contracts/asset-proxy/src/wrappers.ts @@ -8,8 +8,11 @@ export * from '../generated-wrappers/erc20_proxy'; export * from '../generated-wrappers/erc721_proxy'; export * from '../generated-wrappers/i_asset_data'; export * from '../generated-wrappers/i_asset_proxy'; +export * from '../generated-wrappers/i_asset_proxy_dispatcher'; export * from '../generated-wrappers/i_authorizable'; +export * from '../generated-wrappers/mixin_asset_proxy_dispatcher'; export * from '../generated-wrappers/mixin_authorizable'; export * from '../generated-wrappers/multi_asset_proxy'; +export * from '../generated-wrappers/ownable'; export * from '../generated-wrappers/static_call_proxy'; export * from '../generated-wrappers/test_static_call_target'; diff --git a/contracts/asset-proxy/tsconfig.json b/contracts/asset-proxy/tsconfig.json index c410288969..b601856b16 100644 --- a/contracts/asset-proxy/tsconfig.json +++ b/contracts/asset-proxy/tsconfig.json @@ -8,9 +8,12 @@ "generated-artifacts/ERC721Proxy.json", "generated-artifacts/IAssetData.json", "generated-artifacts/IAssetProxy.json", + "generated-artifacts/IAssetProxyDispatcher.json", "generated-artifacts/IAuthorizable.json", + "generated-artifacts/MixinAssetProxyDispatcher.json", "generated-artifacts/MixinAuthorizable.json", "generated-artifacts/MultiAssetProxy.json", + "generated-artifacts/Ownable.json", "generated-artifacts/StaticCallProxy.json", "generated-artifacts/TestStaticCallTarget.json" ], diff --git a/contracts/coordinator/CHANGELOG.json b/contracts/coordinator/CHANGELOG.json index 4bc47bafee..ae186a592e 100644 --- a/contracts/coordinator/CHANGELOG.json +++ b/contracts/coordinator/CHANGELOG.json @@ -33,6 +33,14 @@ { "note": "Update for new `marketXOrders` consolidation.", "pr": 2042 + }, + { + "note": "Use built in selectors instead of hard coded constants", + "pr": 2055 + }, + { + "note": "Compile and export all contracts, artifacts, and wrappers by default", + "pr": 2055 } ] }, diff --git a/contracts/coordinator/compiler.json b/contracts/coordinator/compiler.json index 96e1dbe630..0edd285700 100644 --- a/contracts/coordinator/compiler.json +++ b/contracts/coordinator/compiler.json @@ -21,6 +21,5 @@ ] } } - }, - "contracts": ["src/Coordinator.sol", "src/registry/CoordinatorRegistry.sol"] + } } diff --git a/contracts/coordinator/contracts/src/MixinCoordinatorApprovalVerifier.sol b/contracts/coordinator/contracts/src/MixinCoordinatorApprovalVerifier.sol index c0d9a1d07f..f010979c5f 100644 --- a/contracts/coordinator/contracts/src/MixinCoordinatorApprovalVerifier.sol +++ b/contracts/coordinator/contracts/src/MixinCoordinatorApprovalVerifier.sol @@ -19,11 +19,11 @@ pragma solidity ^0.5.9; pragma experimental ABIEncoderV2; -import "@0x/contracts-exchange-libs/contracts/src/LibExchangeSelectors.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol"; import "@0x/contracts-utils/contracts/src/LibBytes.sol"; import "@0x/contracts-utils/contracts/src/LibAddressArray.sol"; +import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; import "./libs/LibCoordinatorApproval.sol"; import "./interfaces/ISignatureValidator.sol"; import "./interfaces/ICoordinatorApprovalVerifier.sol"; @@ -31,7 +31,6 @@ import "./interfaces/ICoordinatorApprovalVerifier.sol"; // solhint-disable avoid-tx-origin contract MixinCoordinatorApprovalVerifier is - LibExchangeSelectors, LibCoordinatorApproval, LibZeroExTransaction, ISignatureValidator, @@ -84,9 +83,9 @@ contract MixinCoordinatorApprovalVerifier is { bytes4 selector = data.readBytes4(0); if ( - selector == FILL_ORDER_SELECTOR || - selector == FILL_ORDER_NO_THROW_SELECTOR || - selector == FILL_OR_KILL_ORDER_SELECTOR + selector == IExchange(address(0)).fillOrder.selector || + selector == IExchange(address(0)).fillOrderNoThrow.selector || + selector == IExchange(address(0)).fillOrKillOrder.selector ) { // Decode single order (LibOrder.Order memory order) = abi.decode( @@ -96,11 +95,11 @@ contract MixinCoordinatorApprovalVerifier is orders = new LibOrder.Order[](1); orders[0] = order; } else if ( - selector == BATCH_FILL_ORDERS_SELECTOR || - selector == BATCH_FILL_ORDERS_NO_THROW_SELECTOR || - selector == BATCH_FILL_OR_KILL_ORDERS_SELECTOR || - selector == MARKET_BUY_ORDERS_SELECTOR || - selector == MARKET_SELL_ORDERS_SELECTOR + selector == IExchange(address(0)).batchFillOrders.selector || + selector == IExchange(address(0)).batchFillOrdersNoThrow.selector || + selector == IExchange(address(0)).batchFillOrKillOrders.selector || + selector == IExchange(address(0)).marketBuyOrders.selector || + selector == IExchange(address(0)).marketSellOrders.selector ) { // Decode all orders // solhint-disable indent @@ -108,7 +107,7 @@ contract MixinCoordinatorApprovalVerifier is data.slice(4, data.length), (LibOrder.Order[]) ); - } else if (selector == MATCH_ORDERS_SELECTOR) { + } else if (selector == IExchange(address(0)).matchOrders.selector) { // Decode left and right orders (LibOrder.Order memory leftOrder, LibOrder.Order memory rightOrder) = abi.decode( data.slice(4, data.length), diff --git a/contracts/coordinator/contracts/src/libs/LibCoordinatorApproval.sol b/contracts/coordinator/contracts/src/libs/LibCoordinatorApproval.sol index 2d85aa4124..c8770eeb7f 100644 --- a/contracts/coordinator/contracts/src/libs/LibCoordinatorApproval.sol +++ b/contracts/coordinator/contracts/src/libs/LibCoordinatorApproval.sol @@ -34,7 +34,7 @@ contract LibCoordinatorApproval is // "uint256 approvalExpirationTimeSeconds", // ")" // )); - bytes32 constant internal EIP712_COORDINATOR_APPROVAL_SCHEMA_HASH = 0x2fbcdbaa76bc7589916958ae919dfbef04d23f6bbf26de6ff317b32c6cc01e05; + bytes32 constant public EIP712_COORDINATOR_APPROVAL_SCHEMA_HASH = 0x2fbcdbaa76bc7589916958ae919dfbef04d23f6bbf26de6ff317b32c6cc01e05; struct CoordinatorApproval { address txOrigin; // Required signer of Ethereum transaction that is submitting approval. diff --git a/contracts/coordinator/package.json b/contracts/coordinator/package.json index c64bd9c263..3cfdf27df0 100644 --- a/contracts/coordinator/package.json +++ b/contracts/coordinator/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", - "pre_build": "run-s compile generate_contract_wrappers", + "pre_build": "run-s compile contracts:gen 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", diff --git a/contracts/dev-utils/CHANGELOG.json b/contracts/dev-utils/CHANGELOG.json index b2137b6b63..e216383dca 100644 --- a/contracts/dev-utils/CHANGELOG.json +++ b/contracts/dev-utils/CHANGELOG.json @@ -1,4 +1,17 @@ [ + { + "version": "1.0.0", + "changes": [ + { + "note": "Use built in selectors instead of hard coded constants", + "pr": 2055 + }, + { + "note": "Compile and export all contracts, artifacts, and wrappers by default", + "pr": 2055 + } + ] + }, { "timestamp": 1563193019, "version": "0.0.4", diff --git a/contracts/dev-utils/compiler.json b/contracts/dev-utils/compiler.json index 2f174501e5..6b74c612c2 100644 --- a/contracts/dev-utils/compiler.json +++ b/contracts/dev-utils/compiler.json @@ -22,12 +22,5 @@ ] } } - }, - "contracts": [ - "src/DevUtils.sol", - "src/EthBalanceChecker.sol", - "src/LibAssetData.sol", - "src/LibTransactionDecoder.sol", - "src/OrderTransferSimulationUtils.sol" - ] + } } diff --git a/contracts/dev-utils/contracts/src/LibAssetData.sol b/contracts/dev-utils/contracts/src/LibAssetData.sol index c2d5dcc804..6b140ab2d1 100644 --- a/contracts/dev-utils/contracts/src/LibAssetData.sol +++ b/contracts/dev-utils/contracts/src/LibAssetData.sol @@ -20,32 +20,19 @@ pragma solidity ^0.5.5; pragma experimental ABIEncoderV2; import "@0x/contracts-utils/contracts/src/LibBytes.sol"; -import "@0x/contracts-asset-proxy/contracts/src/libs/LibAssetProxyIds.sol"; import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; +import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol"; +import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetProxy.sol"; +import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol"; +import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol"; +import "@0x/contracts-erc1155/contracts/src/interfaces/IERC1155.sol"; -contract LibAssetData is - LibAssetProxyIds -{ +contract LibAssetData { + // 2^256 - 1 uint256 constant internal _MAX_UINT256 = uint256(-1); - // ERC20 selectors - bytes4 constant internal _ERC20_BALANCE_OF_SELECTOR = 0x70a08231; - bytes4 constant internal _ERC20_ALLOWANCE_SELECTOR = 0xdd62ed3e; - - // ERC721 selectors - bytes4 constant internal _ERC721_OWNER_OF_SELECTOR = 0x6352211e; - bytes4 constant internal _ERC721_IS_APPROVED_FOR_ALL_SELECTOR = 0xe985e9c5; - bytes4 constant internal _ERC721_GET_APPROVED_SELECTOR = 0x081812fc; - - // ERC1155 selectors - bytes4 constant internal _ERC1155_BALANCE_OF_SELECTOR = 0x00fdd58e; - bytes4 constant internal _ERC1155_IS_APPROVED_FOR_ALL_SELECTOR = 0xe985e9c5; - - // `transferFrom` selector for all AssetProxy contracts - bytes4 constant internal _ASSET_PROXY_TRANSFER_FROM_SELECTOR = 0xa85e59e4; - using LibBytes for bytes; // solhint-disable var-name-mixedcase @@ -60,10 +47,10 @@ contract LibAssetData is public { _EXCHANGE = IExchange(_exchange); - _ERC20_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(ERC20_PROXY_ID); - _ERC721_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(ERC721_PROXY_ID); - _ERC1155_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(ERC1155_PROXY_ID); - _STATIC_CALL_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(STATIC_CALL_PROXY_ID); + _ERC20_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(IAssetData(address(0)).ERC20Token.selector); + _ERC721_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(IAssetData(address(0)).ERC721Token.selector); + _ERC1155_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(IAssetData(address(0)).ERC1155Assets.selector); + _STATIC_CALL_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(IAssetData(address(0)).StaticCall.selector); } /// @dev Returns the owner's balance of the assets(s) specified in @@ -81,23 +68,33 @@ contract LibAssetData is // Get id of AssetProxy contract bytes4 assetProxyId = assetData.readBytes4(0); - if (assetProxyId == ERC20_PROXY_ID) { + if (assetProxyId == IAssetData(address(0)).ERC20Token.selector) { // Get ERC20 token address address tokenAddress = assetData.readAddress(16); // Encode data for `balanceOf(ownerAddress)` - bytes memory balanceOfData = abi.encodeWithSelector(_ERC20_BALANCE_OF_SELECTOR, ownerAddress); + bytes memory balanceOfData = abi.encodeWithSelector( + IERC20Token(address(0)).balanceOf.selector, + ownerAddress + ); // Query balance (bool success, bytes memory returnData) = tokenAddress.staticcall(balanceOfData); balance = success && returnData.length == 32 ? returnData.readUint256(0) : 0; - } else if (assetProxyId == ERC721_PROXY_ID) { + } else if (assetProxyId == IAssetData(address(0)).ERC721Token.selector) { // Get ERC721 token address and id (, address tokenAddress, uint256 tokenId) = decodeERC721AssetData(assetData); // Check if id is owned by ownerAddress - balance = getERC721TokenOwner(tokenAddress, tokenId) == ownerAddress ? 1 : 0; - } else if (assetProxyId == ERC1155_PROXY_ID) { + bytes memory ownerOfCalldata = abi.encodeWithSelector( + IERC721Token(address(0)).ownerOf.selector, + tokenId + ); + + (bool success, bytes memory returnData) = tokenAddress.staticcall(ownerOfCalldata); + address currentOwnerAddress = (success && returnData.length == 32) ? returnData.readAddress(12) : address(0); + balance = currentOwnerAddress == ownerAddress ? 1 : 0; + } else if (assetProxyId == IAssetData(address(0)).ERC1155Assets.selector) { // Get ERC1155 token address, array of ids, and array of values (, address tokenAddress, uint256[] memory tokenIds, uint256[] memory tokenValues,) = decodeERC1155AssetData(assetData); @@ -105,7 +102,7 @@ contract LibAssetData is for (uint256 i = 0; i != length; i++) { // Encode data for `balanceOf(ownerAddress, tokenIds[i]) bytes memory balanceOfData = abi.encodeWithSelector( - _ERC1155_BALANCE_OF_SELECTOR, + IERC1155(address(0)).balanceOf.selector, ownerAddress, tokenIds[i] ); @@ -120,10 +117,10 @@ contract LibAssetData is balance = scaledBalance; } } - } else if (assetProxyId == STATIC_CALL_PROXY_ID) { + } else if (assetProxyId == IAssetData(address(0)).StaticCall.selector) { // Encode data for `staticCallProxy.transferFrom(assetData,...)` bytes memory transferFromData = abi.encodeWithSelector( - _ASSET_PROXY_TRANSFER_FROM_SELECTOR, + IAssetProxy(address(0)).transferFrom.selector, assetData, address(0), // `from` address is not used address(0), // `to` address is not used @@ -135,7 +132,7 @@ contract LibAssetData is // Success means that the staticcall can be made an unlimited amount of times balance = success ? _MAX_UINT256 : 0; - } else if (assetProxyId == MULTI_ASSET_PROXY_ID) { + } else if (assetProxyId == IAssetData(address(0)).MultiAsset.selector) { // Get array of values and array of assetDatas (, uint256[] memory assetAmounts, bytes[] memory nestedAssetData) = decodeMultiAssetData(assetData); @@ -190,7 +187,7 @@ contract LibAssetData is // Get id of AssetProxy contract bytes4 assetProxyId = assetData.readBytes4(0); - if (assetProxyId == MULTI_ASSET_PROXY_ID) { + if (assetProxyId == IAssetData(address(0)).MultiAsset.selector) { // Get array of values and array of assetDatas (, uint256[] memory amounts, bytes[] memory nestedAssetData) = decodeMultiAssetData(assetData); @@ -208,13 +205,13 @@ contract LibAssetData is return allowance; } - if (assetProxyId == ERC20_PROXY_ID) { + if (assetProxyId == IAssetData(address(0)).ERC20Token.selector) { // Get ERC20 token address address tokenAddress = assetData.readAddress(16); // Encode data for `allowance(ownerAddress, _ERC20_PROXY_ADDRESS)` bytes memory allowanceData = abi.encodeWithSelector( - _ERC20_ALLOWANCE_SELECTOR, + IERC20Token(address(0)).allowance.selector, ownerAddress, _ERC20_PROXY_ADDRESS ); @@ -222,13 +219,13 @@ contract LibAssetData is // Query allowance (bool success, bytes memory returnData) = tokenAddress.staticcall(allowanceData); allowance = success && returnData.length == 32 ? returnData.readUint256(0) : 0; - } else if (assetProxyId == ERC721_PROXY_ID) { + } else if (assetProxyId == IAssetData(address(0)).ERC721Token.selector) { // Get ERC721 token address and id (, address tokenAddress, uint256 tokenId) = decodeERC721AssetData(assetData); // Encode data for `isApprovedForAll(ownerAddress, _ERC721_PROXY_ADDRESS)` bytes memory isApprovedForAllData = abi.encodeWithSelector( - _ERC721_IS_APPROVED_FOR_ALL_SELECTOR, + IERC721Token(address(0)).isApprovedForAll.selector, ownerAddress, _ERC721_PROXY_ADDRESS ); @@ -238,7 +235,7 @@ contract LibAssetData is // If not approved for all, call `getApproved(tokenId)` if (!success || returnData.length != 32 || returnData.readUint256(0) != 1) { // Encode data for `getApproved(tokenId)` - bytes memory getApprovedData = abi.encodeWithSelector(_ERC721_GET_APPROVED_SELECTOR, tokenId); + bytes memory getApprovedData = abi.encodeWithSelector(IERC721Token(address(0)).getApproved.selector, tokenId); (success, returnData) = tokenAddress.staticcall(getApprovedData); // Allowance is 1 if successful and the approved address is the ERC721Proxy @@ -247,13 +244,13 @@ contract LibAssetData is // Allowance is 2^256 - 1 if `isApprovedForAll` returned true allowance = _MAX_UINT256; } - } else if (assetProxyId == ERC1155_PROXY_ID) { + } else if (assetProxyId == IAssetData(address(0)).ERC1155Assets.selector) { // Get ERC1155 token address (, address tokenAddress, , , ) = decodeERC1155AssetData(assetData); // Encode data for `isApprovedForAll(ownerAddress, _ERC1155_PROXY_ADDRESS)` bytes memory isApprovedForAllData = abi.encodeWithSelector( - _ERC1155_IS_APPROVED_FOR_ALL_SELECTOR, + IERC1155(address(0)).isApprovedForAll.selector, ownerAddress, _ERC1155_PROXY_ADDRESS ); @@ -261,7 +258,7 @@ contract LibAssetData is // Query allowance (bool success, bytes memory returnData) = tokenAddress.staticcall(isApprovedForAllData); allowance = success && returnData.length == 32 && returnData.readUint256(0) == 1 ? _MAX_UINT256 : 0; - } else if (assetProxyId == STATIC_CALL_PROXY_ID) { + } else if (assetProxyId == IAssetData(address(0)).StaticCall.selector) { // The StaticCallProxy does not require any approvals allowance = _MAX_UINT256; } @@ -327,7 +324,7 @@ contract LibAssetData is pure returns (bytes memory assetData) { - assetData = abi.encodeWithSelector(ERC20_PROXY_ID, tokenAddress); + assetData = abi.encodeWithSelector(IAssetData(address(0)).ERC20Token.selector, tokenAddress); return assetData; } @@ -346,7 +343,7 @@ contract LibAssetData is assetProxyId = assetData.readBytes4(0); require( - assetProxyId == ERC20_PROXY_ID, + assetProxyId == IAssetData(address(0)).ERC20Token.selector, "WRONG_PROXY_ID" ); @@ -364,7 +361,7 @@ contract LibAssetData is returns (bytes memory assetData) { assetData = abi.encodeWithSelector( - ERC721_PROXY_ID, + IAssetData(address(0)).ERC721Token.selector, tokenAddress, tokenId ); @@ -388,7 +385,7 @@ contract LibAssetData is assetProxyId = assetData.readBytes4(0); require( - assetProxyId == ERC721_PROXY_ID, + assetProxyId == IAssetData(address(0)).ERC721Token.selector, "WRONG_PROXY_ID" ); @@ -414,7 +411,7 @@ contract LibAssetData is returns (bytes memory assetData) { assetData = abi.encodeWithSelector( - ERC1155_PROXY_ID, + IAssetData(address(0)).ERC1155Assets.selector, tokenAddress, tokenIds, tokenValues, @@ -446,7 +443,7 @@ contract LibAssetData is assetProxyId = assetData.readBytes4(0); require( - assetProxyId == ERC1155_PROXY_ID, + assetProxyId == IAssetData(address(0)).ERC1155Assets.selector, "WRONG_PROXY_ID" ); @@ -482,7 +479,7 @@ contract LibAssetData is returns (bytes memory assetData) { assetData = abi.encodeWithSelector( - MULTI_ASSET_PROXY_ID, + IAssetData(address(0)).MultiAsset.selector, amounts, nestedAssetData ); @@ -507,7 +504,7 @@ contract LibAssetData is assetProxyId = assetData.readBytes4(0); require( - assetProxyId == MULTI_ASSET_PROXY_ID, + assetProxyId == IAssetData(address(0)).MultiAsset.selector, "WRONG_PROXY_ID" ); @@ -518,24 +515,4 @@ contract LibAssetData is ); // solhint-enable indent } - - /// @dev Calls `asset.ownerOf(tokenId)`, but returns a null owner instead of reverting on an unowned asset. - /// @param tokenAddress Address of ERC721 asset. - /// @param tokenId The identifier for the specific NFT. - /// @return Owner of tokenId or null address if unowned. - function getERC721TokenOwner(address tokenAddress, uint256 tokenId) - public - view - returns (address ownerAddress) - { - bytes memory ownerOfCalldata = abi.encodeWithSelector( - _ERC721_OWNER_OF_SELECTOR, - tokenId - ); - - (bool success, bytes memory returnData) = tokenAddress.staticcall(ownerOfCalldata); - - ownerAddress = (success && returnData.length == 32) ? returnData.readAddress(12) : address(0); - return ownerAddress; - } } diff --git a/contracts/dev-utils/contracts/src/LibTransactionDecoder.sol b/contracts/dev-utils/contracts/src/LibTransactionDecoder.sol index 3d596ba967..32d3c5e7ee 100644 --- a/contracts/dev-utils/contracts/src/LibTransactionDecoder.sol +++ b/contracts/dev-utils/contracts/src/LibTransactionDecoder.sol @@ -19,14 +19,13 @@ pragma solidity ^0.5.5; pragma experimental ABIEncoderV2; -import "@0x/contracts-exchange-libs/contracts/src/LibExchangeSelectors.sol"; +import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "@0x/contracts-utils/contracts/src/LibBytes.sol"; -contract LibTransactionDecoder is - LibExchangeSelectors -{ +contract LibTransactionDecoder { + using LibBytes for bytes; /// @dev Decodes the call data for an Exchange contract method call. @@ -47,66 +46,65 @@ contract LibTransactionDecoder is { bytes4 functionSelector = transactionData.readBytes4(0); - if (functionSelector == BATCH_CANCEL_ORDERS_SELECTOR) { + if (functionSelector == IExchange(address(0)).batchCancelOrders.selector) { functionName = "batchCancelOrders"; - } else if (functionSelector == BATCH_FILL_ORDERS_SELECTOR) { + } else if (functionSelector == IExchange(address(0)).batchFillOrders.selector) { functionName = "batchFillOrders"; - } else if (functionSelector == BATCH_FILL_ORDERS_NO_THROW_SELECTOR) { + } else if (functionSelector == IExchange(address(0)).batchFillOrdersNoThrow.selector) { functionName = "batchFillOrdersNoThrow"; - } else if (functionSelector == BATCH_FILL_OR_KILL_ORDERS_SELECTOR) { + } else if (functionSelector == IExchange(address(0)).batchFillOrKillOrders.selector) { functionName = "batchFillOrKillOrders"; - } else if (functionSelector == CANCEL_ORDER_SELECTOR) { + } else if (functionSelector == IExchange(address(0)).cancelOrder.selector) { functionName = "cancelOrder"; - } else if (functionSelector == FILL_ORDER_SELECTOR) { + } else if (functionSelector == IExchange(address(0)).fillOrder.selector) { functionName = "fillOrder"; - } else if (functionSelector == FILL_ORDER_NO_THROW_SELECTOR) { + } else if (functionSelector == IExchange(address(0)).fillOrderNoThrow.selector) { functionName = "fillOrderNoThrow"; - } else if (functionSelector == FILL_OR_KILL_ORDER_SELECTOR) { + } else if (functionSelector == IExchange(address(0)).fillOrKillOrder.selector) { functionName = "fillOrKillOrder"; - } else if (functionSelector == MARKET_BUY_ORDERS_SELECTOR) { + } else if (functionSelector == IExchange(address(0)).marketBuyOrders.selector) { functionName = "marketBuyOrders"; - } else if (functionSelector == MARKET_SELL_ORDERS_SELECTOR) { + } else if (functionSelector == IExchange(address(0)).marketSellOrders.selector) { functionName = "marketSellOrders"; - } else if (functionSelector == MATCH_ORDERS_SELECTOR) { + } else if (functionSelector == IExchange(address(0)).matchOrders.selector) { functionName = "matchOrders"; } else if ( - functionSelector == CANCEL_ORDERS_UP_TO_SELECTOR || - functionSelector == EXECUTE_TRANSACTION_SELECTOR - // TODO: add new noThrow cancel functions when https://github.com/0xProject/ZEIPs/issues/35 is merged. + functionSelector == IExchange(address(0)).cancelOrdersUpTo.selector || + functionSelector == IExchange(address(0)).executeTransaction.selector ) { revert("UNIMPLEMENTED"); } else { revert("UNKNOWN_FUNCTION_SELECTOR"); } - if (functionSelector == BATCH_CANCEL_ORDERS_SELECTOR) { + if (functionSelector == IExchange(address(0)).batchCancelOrders.selector) { // solhint-disable-next-line indent orders = abi.decode(transactionData.slice(4, transactionData.length), (LibOrder.Order[])); takerAssetFillAmounts = new uint256[](0); signatures = new bytes[](0); } else if ( - functionSelector == BATCH_FILL_OR_KILL_ORDERS_SELECTOR || - functionSelector == BATCH_FILL_ORDERS_NO_THROW_SELECTOR || - functionSelector == BATCH_FILL_ORDERS_SELECTOR + functionSelector == IExchange(address(0)).batchFillOrKillOrders.selector || + functionSelector == IExchange(address(0)).batchFillOrdersNoThrow.selector || + functionSelector == IExchange(address(0)).batchFillOrders.selector ) { (orders, takerAssetFillAmounts, signatures) = _makeReturnValuesForBatchFill(transactionData); - } else if (functionSelector == CANCEL_ORDER_SELECTOR) { + } else if (functionSelector == IExchange(address(0)).cancelOrder.selector) { orders = new LibOrder.Order[](1); orders[0] = abi.decode(transactionData.slice(4, transactionData.length), (LibOrder.Order)); takerAssetFillAmounts = new uint256[](0); signatures = new bytes[](0); } else if ( - functionSelector == FILL_OR_KILL_ORDER_SELECTOR || - functionSelector == FILL_ORDER_SELECTOR || - functionSelector == FILL_ORDER_NO_THROW_SELECTOR + functionSelector == IExchange(address(0)).fillOrKillOrder.selector || + functionSelector == IExchange(address(0)).fillOrder.selector || + functionSelector == IExchange(address(0)).fillOrderNoThrow.selector ) { (orders, takerAssetFillAmounts, signatures) = _makeReturnValuesForSingleOrderFill(transactionData); } else if ( - functionSelector == MARKET_BUY_ORDERS_SELECTOR || - functionSelector == MARKET_SELL_ORDERS_SELECTOR + functionSelector == IExchange(address(0)).marketBuyOrders.selector || + functionSelector == IExchange(address(0)).marketSellOrders.selector ) { (orders, takerAssetFillAmounts, signatures) = _makeReturnValuesForMarketFill(transactionData); - } else if (functionSelector == MATCH_ORDERS_SELECTOR) { + } else if (functionSelector == IExchange(address(0)).matchOrders.selector) { ( LibOrder.Order memory leftOrder, LibOrder.Order memory rightOrder, diff --git a/contracts/dev-utils/contracts/src/OrderTransferSimulationUtils.sol b/contracts/dev-utils/contracts/src/OrderTransferSimulationUtils.sol index b219b54983..ffef8309a5 100644 --- a/contracts/dev-utils/contracts/src/OrderTransferSimulationUtils.sol +++ b/contracts/dev-utils/contracts/src/OrderTransferSimulationUtils.sol @@ -21,8 +21,8 @@ pragma experimental ABIEncoderV2; import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; -import "@0x/contracts-exchange/contracts/src/LibExchangeRichErrors.sol"; import "@0x/contracts-exchange/contracts/src/libs/LibExchangeRichErrorDecoder.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibExchangeRichErrors.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "@0x/contracts-utils/contracts/src/LibBytes.sol"; @@ -40,9 +40,6 @@ contract OrderTransferSimulationUtils is TransfersSuccessful // All transfers in the order were successful } - // simulateDispatchTransferFromCalls(bytes[],address[],address[],uint256[]) - bytes4 constant internal _SIMULATE_DISPATCH_TRANSFER_FROM_CALLS_SELECTOR = 0xb04fbddd; - // keccak256(abi.encodeWithSignature("Error(string)", "TRANSFERS_SUCCESSFUL")); bytes32 constant internal _TRANSFERS_SUCCESSFUL_RESULT_HASH = 0xf43f26ea5a94b478394a975e856464913dc1a8a1ca70939d974aa7c238aa0ce0; @@ -101,7 +98,7 @@ contract OrderTransferSimulationUtils is // Encode data for `simulateDispatchTransferFromCalls(assetData, fromAddresses, toAddresses, amounts)` bytes memory simulateDispatchTransferFromCallsData = abi.encodeWithSelector( - _SIMULATE_DISPATCH_TRANSFER_FROM_CALLS_SELECTOR, + IExchange(address(0)).simulateDispatchTransferFromCalls.selector, assetData, fromAddresses, toAddresses, diff --git a/contracts/dev-utils/contracts/src/OrderValidationUtils.sol b/contracts/dev-utils/contracts/src/OrderValidationUtils.sol index 1875281f4f..b916cbf49d 100644 --- a/contracts/dev-utils/contracts/src/OrderValidationUtils.sol +++ b/contracts/dev-utils/contracts/src/OrderValidationUtils.sol @@ -23,14 +23,15 @@ import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; import "@0x/contracts-utils/contracts/src/LibBytes.sol"; +import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; import "./LibAssetData.sol"; contract OrderValidationUtils is - LibAssetData, - LibMath + LibAssetData { using LibBytes for bytes; + using LibSafeMath for uint256; constructor (address _exchange) public @@ -79,9 +80,9 @@ contract OrderValidationUtils is if (order.makerAssetData.equals(order.makerFeeAssetData)) { // If `makerAsset` equals `makerFeeAsset`, the % that can be filled is // transferableMakerAssetAmount / (makerAssetAmount + makerFee) - transferableTakerAssetAmount = _getPartialAmountFloor( + transferableTakerAssetAmount = LibMath.getPartialAmountFloor( transferableMakerAssetAmount, - _safeAdd(order.makerAssetAmount, makerFee), + order.makerAssetAmount.safeAdd(makerFee), takerAssetAmount ); } else { @@ -90,7 +91,7 @@ contract OrderValidationUtils is // If `makerFee` is 0, the % that can be filled is (transferableMakerAssetAmount / makerAssetAmount) if (makerFee == 0) { - transferableTakerAssetAmount = _getPartialAmountFloor( + transferableTakerAssetAmount = LibMath.getPartialAmountFloor( transferableMakerAssetAmount, order.makerAssetAmount, takerAssetAmount @@ -99,23 +100,23 @@ contract OrderValidationUtils is // If `makerAsset` does not equal `makerFeeAsset`, the % that can be filled is the lower of // (transferableMakerAssetAmount / makerAssetAmount) and (transferableMakerAssetFeeAmount / makerFee) } else { - uint256 transferableMakerToTakerAmount = _getPartialAmountFloor( + uint256 transferableMakerToTakerAmount = LibMath.getPartialAmountFloor( transferableMakerAssetAmount, order.makerAssetAmount, takerAssetAmount ); - uint256 transferableMakerFeeToTakerAmount = _getPartialAmountFloor( + uint256 transferableMakerFeeToTakerAmount = LibMath.getPartialAmountFloor( transferableMakerFeeAssetAmount, makerFee, takerAssetAmount ); - transferableTakerAssetAmount = _min256(transferableMakerToTakerAmount, transferableMakerFeeToTakerAmount); + transferableTakerAssetAmount = LibSafeMath.min256(transferableMakerToTakerAmount, transferableMakerFeeToTakerAmount); } } // `fillableTakerAssetAmount` is the lower of the order's remaining `takerAssetAmount` and the `transferableTakerAssetAmount` - fillableTakerAssetAmount = _min256( - _safeSub(takerAssetAmount, orderInfo.orderTakerAssetFilledAmount), + fillableTakerAssetAmount = LibSafeMath.min256( + takerAssetAmount.safeSub(orderInfo.orderTakerAssetFilledAmount), transferableTakerAssetAmount ); @@ -170,7 +171,7 @@ contract OrderValidationUtils is returns (uint256 transferableAssetAmount) { (uint256 balance, uint256 allowance) = getBalanceAndAssetProxyAllowance(ownerAddress, assetData); - transferableAssetAmount = _min256(balance, allowance); + transferableAssetAmount = LibSafeMath.min256(balance, allowance); return transferableAssetAmount; } } diff --git a/contracts/dev-utils/package.json b/contracts/dev-utils/package.json index 5d6d68a906..0f5e18e359 100644 --- a/contracts/dev-utils/package.json +++ b/contracts/dev-utils/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", - "pre_build": "run-s compile generate_contract_wrappers", + "pre_build": "run-s compile contracts:gen 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", @@ -34,7 +34,7 @@ "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis": "./generated-artifacts/@(DevUtils|EthBalanceChecker|LibAssetData|LibTransactionDecoder|OrderTransferSimulationUtils).json", + "abis": "./generated-artifacts/@(DevUtils|EthBalanceChecker|LibAssetData|LibTransactionDecoder|OrderTransferSimulationUtils|OrderValidationUtils).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/dev-utils/src/artifacts.ts b/contracts/dev-utils/src/artifacts.ts index 6a8002013c..6d4be86321 100644 --- a/contracts/dev-utils/src/artifacts.ts +++ b/contracts/dev-utils/src/artifacts.ts @@ -10,10 +10,12 @@ import * as EthBalanceChecker from '../generated-artifacts/EthBalanceChecker.jso import * as LibAssetData from '../generated-artifacts/LibAssetData.json'; import * as LibTransactionDecoder from '../generated-artifacts/LibTransactionDecoder.json'; import * as OrderTransferSimulationUtils from '../generated-artifacts/OrderTransferSimulationUtils.json'; +import * as OrderValidationUtils from '../generated-artifacts/OrderValidationUtils.json'; export const artifacts = { DevUtils: DevUtils as ContractArtifact, + EthBalanceChecker: EthBalanceChecker as ContractArtifact, LibAssetData: LibAssetData as ContractArtifact, LibTransactionDecoder: LibTransactionDecoder as ContractArtifact, - EthBalanceChecker: EthBalanceChecker as ContractArtifact, OrderTransferSimulationUtils: OrderTransferSimulationUtils as ContractArtifact, + OrderValidationUtils: OrderValidationUtils as ContractArtifact, }; diff --git a/contracts/dev-utils/src/wrappers.ts b/contracts/dev-utils/src/wrappers.ts index 53bf08c126..a457a7998e 100644 --- a/contracts/dev-utils/src/wrappers.ts +++ b/contracts/dev-utils/src/wrappers.ts @@ -8,3 +8,4 @@ export * from '../generated-wrappers/eth_balance_checker'; export * from '../generated-wrappers/lib_asset_data'; export * from '../generated-wrappers/lib_transaction_decoder'; export * from '../generated-wrappers/order_transfer_simulation_utils'; +export * from '../generated-wrappers/order_validation_utils'; diff --git a/contracts/dev-utils/test/lib_asset_data.ts b/contracts/dev-utils/test/lib_asset_data.ts index 03f1fab2e6..03a67999e8 100644 --- a/contracts/dev-utils/test/lib_asset_data.ts +++ b/contracts/dev-utils/test/lib_asset_data.ts @@ -465,20 +465,6 @@ describe('LibAssetData', () => { }); }); - describe('getERC721TokenOwner', async () => { - it('should return the null address when tokenId is not owned', async () => { - const nonexistentTokenId = new BigNumber(1234567890); - expect( - await libAssetData.getERC721TokenOwner.callAsync(erc721Token.address, nonexistentTokenId), - ).to.be.equal(constants.NULL_ADDRESS); - }); - it('should return the owner address when tokenId is owned', async () => { - expect( - await libAssetData.getERC721TokenOwner.callAsync(erc721Token.address, firstERC721TokenId), - ).to.be.equal(tokenOwnerAddress); - }); - }); - describe('getBalanceAndAllowance', () => { it('should query balance and allowance together, from asset data', async () => { const allowance = new BigNumber(1); diff --git a/contracts/dev-utils/tsconfig.json b/contracts/dev-utils/tsconfig.json index 9918b06d4a..d160f0c58d 100644 --- a/contracts/dev-utils/tsconfig.json +++ b/contracts/dev-utils/tsconfig.json @@ -7,7 +7,8 @@ "generated-artifacts/EthBalanceChecker.json", "generated-artifacts/LibAssetData.json", "generated-artifacts/LibTransactionDecoder.json", - "generated-artifacts/OrderTransferSimulationUtils.json" + "generated-artifacts/OrderTransferSimulationUtils.json", + "generated-artifacts/OrderValidationUtils.json" ], "exclude": ["./deploy/solc/solc_bin"] } diff --git a/contracts/erc1155/compiler.json b/contracts/erc1155/compiler.json index 1fce0b95f6..6b74c612c2 100644 --- a/contracts/erc1155/compiler.json +++ b/contracts/erc1155/compiler.json @@ -22,14 +22,5 @@ ] } } - }, - "contracts": [ - "src/ERC1155.sol", - "src/ERC1155Mintable.sol", - "src/MixinNonFungibleToken.sol", - "src/interfaces/IERC1155.sol", - "src/interfaces/IERC1155Mintable.sol", - "src/interfaces/IERC1155Receiver.sol", - "test/DummyERC1155Receiver.sol" - ] + } } diff --git a/contracts/erc1155/package.json b/contracts/erc1155/package.json index 5e9d1c9d9d..41295f7c5f 100644 --- a/contracts/erc1155/package.json +++ b/contracts/erc1155/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", - "pre_build": "run-s compile generate_contract_wrappers", + "pre_build": "run-s compile contracts:gen 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", diff --git a/contracts/erc20/compiler.json b/contracts/erc20/compiler.json index 46964b425a..ac8f2ed76d 100644 --- a/contracts/erc20/compiler.json +++ b/contracts/erc20/compiler.json @@ -23,18 +23,5 @@ ] } } - }, - "contracts": [ - "src/ERC20Token.sol", - "src/MintableERC20Token.sol", - "src/UnlimitedAllowanceERC20Token.sol", - "src/WETH9.sol", - "src/ZRXToken.sol", - "src/interfaces/IERC20Token.sol", - "src/interfaces/IEtherToken.sol", - "test/DummyERC20Token.sol", - "test/DummyMultipleReturnERC20Token.sol", - "test/DummyNoReturnERC20Token.sol", - "test/UntransferrableDummyERC20Token.sol" - ] + } } diff --git a/contracts/erc20/package.json b/contracts/erc20/package.json index 7137b8f95b..bbb5ff93e6 100644 --- a/contracts/erc20/package.json +++ b/contracts/erc20/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", - "pre_build": "run-s compile generate_contract_wrappers", + "pre_build": "run-s compile contracts:gen 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", diff --git a/contracts/erc721/compiler.json b/contracts/erc721/compiler.json index 680ca18982..6b74c612c2 100644 --- a/contracts/erc721/compiler.json +++ b/contracts/erc721/compiler.json @@ -22,14 +22,5 @@ ] } } - }, - "contracts": [ - "src/ERC721Token.sol", - "src/MintableERC721Token.sol", - "src/interfaces/IERC721Receiver.sol", - "src/interfaces/IERC721Token.sol", - "test/DummyERC721Receiver.sol", - "test/DummyERC721Token.sol", - "test/InvalidERC721Receiver.sol" - ] + } } diff --git a/contracts/erc721/package.json b/contracts/erc721/package.json index 4444ebc1cc..ac34172fd3 100644 --- a/contracts/erc721/package.json +++ b/contracts/erc721/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", - "pre_build": "run-s compile generate_contract_wrappers", + "pre_build": "run-s compile contracts:gen 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", diff --git a/contracts/exchange-forwarder/compiler.json b/contracts/exchange-forwarder/compiler.json index 0968c6246f..6b74c612c2 100644 --- a/contracts/exchange-forwarder/compiler.json +++ b/contracts/exchange-forwarder/compiler.json @@ -22,6 +22,5 @@ ] } } - }, - "contracts": ["src/Forwarder.sol"] + } } diff --git a/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol b/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol index 92e62fec09..7054d3be44 100644 --- a/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol +++ b/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol @@ -23,14 +23,13 @@ import "./libs/LibConstants.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; -import "@0x/contracts-exchange-libs/contracts/src/LibExchangeSelectors.sol"; +import "@0x/contracts/exchange/contracts/src/interfaces/IExchange.sol"; contract MixinExchangeWrapper is LibFillResults, LibMath, - LibConstants, - LibExchangeSelectors + LibConstants { /// @dev Fills the input order. /// Returns false if the transaction would otherwise revert. @@ -48,9 +47,7 @@ contract MixinExchangeWrapper is { // ABI encode calldata for `fillOrder` bytes memory fillOrderCalldata = abi.encodeWithSelector( - // bytes4(keccak256("fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes),uint256,bytes)")) - // = 0x9b44d556 - 0x9b44d556, + IExchange(address(0)).fillOrder.selector, order, takerAssetFillAmount, signature diff --git a/contracts/exchange-forwarder/package.json b/contracts/exchange-forwarder/package.json index 230b8b9ea4..b0338cbcbf 100644 --- a/contracts/exchange-forwarder/package.json +++ b/contracts/exchange-forwarder/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", - "pre_build": "run-s compile generate_contract_wrappers", + "pre_build": "run-s compile contracts:gen 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", diff --git a/contracts/exchange-libs/CHANGELOG.json b/contracts/exchange-libs/CHANGELOG.json index dfad431b04..e881cb4899 100644 --- a/contracts/exchange-libs/CHANGELOG.json +++ b/contracts/exchange-libs/CHANGELOG.json @@ -77,6 +77,30 @@ { "note": "Regenerate selectors.", "pr": 2042 + }, + { + "note": "Convert `LibFillResults`, `LibOrder`, `LibZeroExTransaction`, and `LibMath` to libraries", + "pr": 2055 + }, + { + "note": "Remove `LibExchangeSelectors`", + "pr": 2055 + }, + { + "note": "Add `LibExchangeRichErrors`", + "pr": 2055 + }, + { + "note": "Add `calculateFillResults` and `calculateMatchedFillResults` to `LibFillResults`", + "pr": 2055 + }, + { + "note": "Remove `_hashEIP712ExchangeMessage` from `LibEIP712ExchangeDomain`", + "pr": 2055 + }, + { + "note": "Compile and export all contracts, artifacts, and wrappers by default", + "pr": 2055 } ] }, diff --git a/contracts/exchange-libs/compiler.json b/contracts/exchange-libs/compiler.json index 7a3d0e687e..6b74c612c2 100644 --- a/contracts/exchange-libs/compiler.json +++ b/contracts/exchange-libs/compiler.json @@ -22,13 +22,5 @@ ] } } - }, - "contracts": [ - "src/LibEIP712ExchangeDomain.sol", - "src/LibFillResults.sol", - "src/LibMath.sol", - "src/LibOrder.sol", - "src/LibZeroExTransaction.sol", - "test/TestLibs.sol" - ] + } } diff --git a/contracts/exchange-libs/contracts/src/LibEIP712ExchangeDomain.sol b/contracts/exchange-libs/contracts/src/LibEIP712ExchangeDomain.sol index 57ba23fef7..f2782daf78 100644 --- a/contracts/exchange-libs/contracts/src/LibEIP712ExchangeDomain.sol +++ b/contracts/exchange-libs/contracts/src/LibEIP712ExchangeDomain.sol @@ -21,9 +21,8 @@ pragma solidity ^0.5.9; import "@0x/contracts-utils/contracts/src/LibEIP712.sol"; -contract LibEIP712ExchangeDomain is - LibEIP712 -{ +contract LibEIP712ExchangeDomain { + // EIP712 Exchange Domain Name value string constant public EIP712_EXCHANGE_DOMAIN_NAME = "0x Protocol"; @@ -43,23 +42,11 @@ contract LibEIP712ExchangeDomain is public { address verifyingContractAddress = verifyingContractAddressIfExists == address(0) ? address(this) : verifyingContractAddressIfExists; - EIP712_EXCHANGE_DOMAIN_HASH = _hashEIP712Domain( + EIP712_EXCHANGE_DOMAIN_HASH = LibEIP712.hashEIP712Domain( EIP712_EXCHANGE_DOMAIN_NAME, EIP712_EXCHANGE_DOMAIN_VERSION, chainId, verifyingContractAddress ); } - - /// @dev Calculates EIP712 encoding for a hash struct in the EIP712 domain - /// of the Exchange contract. - /// @param hashStruct The EIP712 hash struct. - /// @return EIP712 hash applied to the Exchange EIP712 Domain. - function _hashEIP712ExchangeMessage(bytes32 hashStruct) - internal - view - returns (bytes32 result) - { - return _hashEIP712Message(EIP712_EXCHANGE_DOMAIN_HASH, hashStruct); - } } diff --git a/contracts/exchange/contracts/src/LibExchangeRichErrors.sol b/contracts/exchange-libs/contracts/src/LibExchangeRichErrors.sol similarity index 93% rename from contracts/exchange/contracts/src/LibExchangeRichErrors.sol rename to contracts/exchange-libs/contracts/src/LibExchangeRichErrors.sol index 732ba24baf..f47db479b8 100644 --- a/contracts/exchange/contracts/src/LibExchangeRichErrors.sol +++ b/contracts/exchange-libs/contracts/src/LibExchangeRichErrors.sol @@ -19,12 +19,45 @@ pragma solidity ^0.5.9; import "@0x/contracts-utils/contracts/src/LibRichErrors.sol"; -import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; -import "./interfaces/IExchangeRichErrors.sol"; +import "./LibOrder.sol"; library LibExchangeRichErrors { + enum AssetProxyDispatchErrorCodes { + INVALID_ASSET_DATA_LENGTH, + UNKNOWN_ASSET_PROXY + } + + enum BatchMatchOrdersErrorCodes { + ZERO_LEFT_ORDERS, + ZERO_RIGHT_ORDERS, + INVALID_LENGTH_LEFT_SIGNATURES, + INVALID_LENGTH_RIGHT_SIGNATURES + } + + enum FillErrorCodes { + INVALID_TAKER_AMOUNT, + TAKER_OVERPAY, + OVERFILL, + INVALID_FILL_PRICE + } + + enum SignatureErrorCodes { + BAD_SIGNATURE, + INVALID_LENGTH, + UNSUPPORTED, + ILLEGAL, + INAPPROPRIATE_SIGNATURE_TYPE, + INVALID_SIGNER + } + + enum TransactionErrorCodes { + NO_REENTRANCY, + ALREADY_EXECUTED, + EXPIRED + } + // bytes4(keccak256("SignatureError(uint8,bytes32,address,bytes)")) bytes4 internal constant SIGNATURE_ERROR_SELECTOR = 0x7e5a2318; @@ -255,7 +288,7 @@ library LibExchangeRichErrors { } function BatchMatchOrdersError( - IExchangeRichErrors.BatchMatchOrdersErrorCodes errorCode + BatchMatchOrdersErrorCodes errorCode ) internal pure @@ -268,7 +301,7 @@ library LibExchangeRichErrors { } function SignatureError( - IExchangeRichErrors.SignatureErrorCodes errorCode, + SignatureErrorCodes errorCode, bytes32 hash, address signerAddress, bytes memory signature @@ -387,7 +420,7 @@ library LibExchangeRichErrors { } function FillError( - IExchangeRichErrors.FillErrorCodes errorCode, + FillErrorCodes errorCode, bytes32 orderHash ) internal @@ -447,7 +480,7 @@ library LibExchangeRichErrors { } function AssetProxyDispatchError( - IExchangeRichErrors.AssetProxyDispatchErrorCodes errorCode, + AssetProxyDispatchErrorCodes errorCode, bytes32 orderHash, bytes memory assetData ) @@ -496,7 +529,7 @@ library LibExchangeRichErrors { } function TransactionError( - IExchangeRichErrors.TransactionErrorCodes errorCode, + TransactionErrorCodes errorCode, bytes32 transactionHash ) internal diff --git a/contracts/exchange-libs/contracts/src/LibExchangeSelectors.sol b/contracts/exchange-libs/contracts/src/LibExchangeSelectors.sol deleted file mode 100644 index fdd759ee33..0000000000 --- a/contracts/exchange-libs/contracts/src/LibExchangeSelectors.sol +++ /dev/null @@ -1,186 +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.5.9; - - -contract LibExchangeSelectors { - // solhint-disable max-line-length - - // function allowedValidators(address,address) - bytes4 constant internal ALLOWED_VALIDATORS_SELECTOR = 0x7b8e3514; - - // function assetProxies(bytes4) - bytes4 constant internal ASSET_PROXIES_SELECTOR = 0x3fd3c997; - - // function batchCancelOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[]) - bytes4 constant internal BATCH_CANCEL_ORDERS_SELECTOR = 0xdedfc1f1; - - // function batchExecuteTransactions((uint256,uint256,address,bytes)[],bytes[]) - bytes4 constant internal BATCH_EXECUTE_TRANSACTIONS_SELECTOR = 0x3f80f0ee; - - // function batchFillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[],uint256[],bytes[]) - bytes4 constant internal BATCH_FILL_ORDERS_SELECTOR = 0x9694a402; - - // function batchFillOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[],uint256[],bytes[]) - bytes4 constant internal BATCH_FILL_ORDERS_NO_THROW_SELECTOR = 0x8ea8dfe4; - - // function batchFillOrKillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[],uint256[],bytes[]) - bytes4 constant internal BATCH_FILL_OR_KILL_ORDERS_SELECTOR = 0xbeee2e14; - - // function batchMatchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[],(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[],bytes[],bytes[]) - bytes4 constant internal BATCH_MATCH_ORDERS_SELECTOR = 0x6fcf3e9e; - - // function batchMatchOrdersWithMaximalFill((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[],(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[],bytes[],bytes[]) - bytes4 constant internal BATCH_MATCH_ORDERS_WITH_MAXIMAL_FILL_SELECTOR = 0x6a1a80fd; - - // function calculateMatchedFillResults((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes),uint256,uint256,bool) - bytes4 constant internal CALCULATE_MATCHED_FILL_RESULTS_SELECTOR = 0x38f9eb3b; - - // function cancelled(bytes32) - bytes4 constant internal CANCELLED_SELECTOR = 0x2ac12622; - - // function cancelOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)) - bytes4 constant internal CANCEL_ORDER_SELECTOR = 0x2da62987; - - // function cancelOrdersUpTo(uint256) - bytes4 constant internal CANCEL_ORDERS_UP_TO_SELECTOR = 0x4f9559b1; - - // function currentContextAddress() - bytes4 constant internal CURRENT_CONTEXT_ADDRESS_SELECTOR = 0xeea086ba; - - // function doesSignatureRequireRegularValidation(bytes32,address,bytes) - bytes4 constant internal DOES_SIGNATURE_REQUIRE_REGULAR_VALIDATION_SELECTOR = 0xc17f8ccc; - - // function EIP1271_MAGIC_VALUE() - bytes4 constant internal EIP_1271_MAGIC_VALUE_SELECTOR = 0xdd885e2d; - - // function EIP712_EXCHANGE_DOMAIN_HASH() - bytes4 constant internal EIP_712_EXCHANGE_DOMAIN_HASH_SELECTOR = 0xc26cfecd; - - // function EIP712_EXCHANGE_DOMAIN_NAME() - bytes4 constant internal EIP_712_EXCHANGE_DOMAIN_NAME_SELECTOR = 0x63c4e8cc; - - // function EIP712_EXCHANGE_DOMAIN_VERSION() - bytes4 constant internal EIP_712_EXCHANGE_DOMAIN_VERSION_SELECTOR = 0x0f01323b; - - // function EIP712_ORDER_SCHEMA_HASH() - bytes4 constant internal EIP_712_ORDER_SCHEMA_HASH_SELECTOR = 0xe4588b64; - - // function EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH() - bytes4 constant internal EIP_712_ZEROEX_TRANSACTION_SCHEMA_HASH_SELECTOR = 0xc148c58a; - - // function executeTransaction((uint256,uint256,address,bytes),bytes) - bytes4 constant internal EXECUTE_TRANSACTION_SELECTOR = 0xcba0648a; - - // function filled(bytes32) - bytes4 constant internal FILLED_SELECTOR = 0x288cdc91; - - // function fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes),uint256,bytes) - bytes4 constant internal FILL_ORDER_SELECTOR = 0x9b44d556; - - // function fillOrderNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes),uint256,bytes) - bytes4 constant internal FILL_ORDER_NO_THROW_SELECTOR = 0x01da61ae; - - // function fillOrKillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes),uint256,bytes) - bytes4 constant internal FILL_OR_KILL_ORDER_SELECTOR = 0xe14b58c4; - - // function getAssetProxy(bytes4) - bytes4 constant internal GET_ASSET_PROXY_SELECTOR = 0x60704108; - - // function getOrderHash((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)) - bytes4 constant internal GET_ORDER_HASH_SELECTOR = 0xad3449bd; - - // function getOrderInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)) - bytes4 constant internal GET_ORDER_INFO_SELECTOR = 0x9d3fa4b9; - - // function getOrdersInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[]) - bytes4 constant internal GET_ORDERS_INFO_SELECTOR = 0x9dfac06d; - - // function getTransactionHash((uint256,uint256,address,bytes)) - bytes4 constant internal GET_TRANSACTION_HASH_SELECTOR = 0xe0456690; - - // function isValidHashSignature(bytes32,address,bytes) - bytes4 constant internal IS_VALID_HASH_SIGNATURE_SELECTOR = 0x8171c407; - - // function isValidOrderSignature((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes),address,bytes) - bytes4 constant internal IS_VALID_ORDER_SIGNATURE_SELECTOR = 0xf813e384; - - // function isValidTransactionSignature((uint256,uint256,address,bytes),address,bytes) - bytes4 constant internal IS_VALID_TRANSACTION_SIGNATURE_SELECTOR = 0xfaa8b882; - - // function marketBuyOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[],uint256,bytes[]) - bytes4 constant internal MARKET_BUY_ORDERS_SELECTOR = 0xdb702a9c; - - // function marketSellOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes)[],uint256,bytes[]) - bytes4 constant internal MARKET_SELL_ORDERS_SELECTOR = 0x52b3ca9e; - - // function matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes),bytes,bytes) - bytes4 constant internal MATCH_ORDERS_SELECTOR = 0x88ec79fb; - - // function matchOrdersWithMaximalFill((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes,bytes,bytes),bytes,bytes) - bytes4 constant internal MATCH_ORDERS_WITH_MAXIMAL_FILL_SELECTOR = 0xb718e292; - - // function orderEpoch(address,address) - bytes4 constant internal ORDER_EPOCH_SELECTOR = 0xd9bfa73e; - - // function owner() - bytes4 constant internal OWNER_SELECTOR = 0x8da5cb5b; - - // function preSign(bytes32) - bytes4 constant internal PRE_SIGN_SELECTOR = 0x46c02d7a; - - // function preSigned(bytes32,address) - bytes4 constant internal PRE_SIGNED_SELECTOR = 0x82c174d0; - - // function registerAssetProxy(address) - bytes4 constant internal REGISTER_ASSET_PROXY_SELECTOR = 0xc585bb93; - - // function setSignatureValidatorApproval(address,bool) - bytes4 constant internal SET_SIGNATURE_VALIDATOR_APPROVAL_SELECTOR = 0x77fcce68; - - // function simulateDispatchTransferFromCalls(bytes[],address[],address[],uint256[]) - bytes4 constant internal SIMULATE_DISPATCH_TRANSFER_FROM_CALLS_SELECTOR = 0xb04fbddd; - - // function transactionsExecuted(bytes32) - bytes4 constant internal TRANSACTIONS_EXECUTED_SELECTOR = 0x0228e168; - - // function transferOwnership(address) - bytes4 constant internal TRANSFER_OWNERSHIP_SELECTOR = 0xf2fde38b; - - // function VERSION() - bytes4 constant internal VERSION_SELECTOR = 0xffa1ad74; - - // event AssetProxyRegistered(bytes4,address) - bytes32 constant internal EVENT_ASSET_PROXY_REGISTERED_SELECTOR = 0xd2c6b762299c609bdb96520b58a49bfb80186934d4f71a86a367571a15c03194; - - // event Cancel(address,address,address,bytes32,bytes,bytes) - bytes32 constant internal EVENT_CANCEL_SELECTOR = 0xdc47b3613d9fe400085f6dbdc99453462279057e6207385042827ed6b1a62cf7; - - // event CancelUpTo(address,address,uint256) - bytes32 constant internal EVENT_CANCEL_UP_TO_SELECTOR = 0x82af639571738f4ebd4268fb0363d8957ebe1bbb9e78dba5ebd69eed39b154f0; - - // event Fill(address,address,bytes,bytes,bytes,bytes,uint256,uint256,uint256,uint256,address,address,bytes32) - bytes32 constant internal EVENT_FILL_SELECTOR = 0xa5a8f3e79ee70e3be6330220296f9075863b936f4098d942ab107367d193a197; - - // event SignatureValidatorApproval(address,address,bool) - bytes32 constant internal EVENT_SIGNATURE_VALIDATOR_APPROVAL_SELECTOR = 0xa8656e308026eeabce8f0bc18048433252318ab80ac79da0b3d3d8697dfba891; - - // event TransactionExecution(bytes32) - bytes32 constant internal EVENT_TRANSACTION_EXECUTION_SELECTOR = 0xa4a7329f1dd821363067e07d359e347b4af9b1efe4b6cccf13240228af3c800d; -} diff --git a/contracts/exchange-libs/contracts/src/LibFillResults.sol b/contracts/exchange-libs/contracts/src/LibFillResults.sol index 13dd37283f..d3781b4841 100644 --- a/contracts/exchange-libs/contracts/src/LibFillResults.sol +++ b/contracts/exchange-libs/contracts/src/LibFillResults.sol @@ -18,12 +18,15 @@ pragma solidity ^0.5.9; -import "@0x/contracts-utils/contracts/src/SafeMath.sol"; +import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; +import "./LibMath.sol"; +import "./LibOrder.sol"; -contract LibFillResults is - SafeMath -{ +library LibFillResults { + + using LibSafeMath for uint256; + struct BatchMatchedFillResults { FillResults[] left; // Fill results for left orders FillResults[] right; // Fill results for right orders @@ -45,17 +48,358 @@ contract LibFillResults is uint256 profitInRightMakerAsset; // Profit taken from the right maker } - /// @dev Adds properties of both FillResults instances. - /// Modifies the first FillResults instance specified. - /// @param totalFillResults Fill results instance that will be added onto. - /// @param singleFillResults Fill results instance that will be added to totalFillResults. - function _addFillResults(FillResults memory totalFillResults, FillResults memory singleFillResults) + /// @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 (FillResults memory fillResults) { - totalFillResults.makerAssetFilledAmount = _safeAdd(totalFillResults.makerAssetFilledAmount, singleFillResults.makerAssetFilledAmount); - totalFillResults.takerAssetFilledAmount = _safeAdd(totalFillResults.takerAssetFilledAmount, singleFillResults.takerAssetFilledAmount); - totalFillResults.makerFeePaid = _safeAdd(totalFillResults.makerFeePaid, singleFillResults.makerFeePaid); - totalFillResults.takerFeePaid = _safeAdd(totalFillResults.takerFeePaid, singleFillResults.takerFeePaid); + // Compute proportional transfer amounts + fillResults.takerAssetFilledAmount = takerAssetFilledAmount; + fillResults.makerAssetFilledAmount = LibMath.safeGetPartialAmountFloor( + takerAssetFilledAmount, + order.takerAssetAmount, + order.makerAssetAmount + ); + fillResults.makerFeePaid = LibMath.safeGetPartialAmountFloor( + fillResults.makerAssetFilledAmount, + order.makerAssetAmount, + order.makerFee + ); + fillResults.takerFeePaid = LibMath.safeGetPartialAmountFloor( + takerAssetFilledAmount, + order.takerAssetAmount, + order.takerFee + ); + + return fillResults; + } + + /// @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 shouldMaximallyFillOrders A value that indicates whether or not this calculation should use + /// the maximal fill order matching strategy. + /// @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, + bool shouldMaximallyFillOrders + ) + internal + pure + returns (MatchedFillResults memory matchedFillResults) + { + // Derive maker asset amounts for left & right orders, given store taker assert amounts + uint256 leftTakerAssetAmountRemaining = leftOrder.takerAssetAmount.safeSub(leftOrderTakerAssetFilledAmount); + uint256 leftMakerAssetAmountRemaining = LibMath.safeGetPartialAmountFloor( + leftOrder.makerAssetAmount, + leftOrder.takerAssetAmount, + leftTakerAssetAmountRemaining + ); + uint256 rightTakerAssetAmountRemaining = rightOrder.takerAssetAmount.safeSub(rightOrderTakerAssetFilledAmount); + uint256 rightMakerAssetAmountRemaining = LibMath.safeGetPartialAmountFloor( + rightOrder.makerAssetAmount, + rightOrder.takerAssetAmount, + rightTakerAssetAmountRemaining + ); + + // Maximally fill the orders and pay out profits to the matcher in one or both of the maker assets. + if (shouldMaximallyFillOrders) { + matchedFillResults = _calculateMatchedFillResultsWithMaximalFill( + leftOrder, + rightOrder, + leftMakerAssetAmountRemaining, + leftTakerAssetAmountRemaining, + rightMakerAssetAmountRemaining, + rightTakerAssetAmountRemaining + ); + } else { + matchedFillResults = _calculateMatchedFillResults( + leftOrder, + rightOrder, + leftMakerAssetAmountRemaining, + leftTakerAssetAmountRemaining, + rightMakerAssetAmountRemaining, + rightTakerAssetAmountRemaining + ); + } + + // Compute fees for left order + matchedFillResults.left.makerFeePaid = LibMath.safeGetPartialAmountFloor( + matchedFillResults.left.makerAssetFilledAmount, + leftOrder.makerAssetAmount, + leftOrder.makerFee + ); + matchedFillResults.left.takerFeePaid = LibMath.safeGetPartialAmountFloor( + matchedFillResults.left.takerAssetFilledAmount, + leftOrder.takerAssetAmount, + leftOrder.takerFee + ); + + // Compute fees for right order + matchedFillResults.right.makerFeePaid = LibMath.safeGetPartialAmountFloor( + matchedFillResults.right.makerAssetFilledAmount, + rightOrder.makerAssetAmount, + rightOrder.makerFee + ); + matchedFillResults.right.takerFeePaid = LibMath.safeGetPartialAmountFloor( + matchedFillResults.right.takerAssetFilledAmount, + rightOrder.takerAssetAmount, + rightOrder.takerFee + ); + + // Return fill results + return matchedFillResults; + } + + /// @dev Adds properties of both FillResults instances. + /// @param fillResults1 The first FillResults. + /// @param fillResults2 The second FillResults. + /// @return The sum of both fill results. + function addFillResults( + FillResults memory fillResults1, + FillResults memory fillResults2 + ) + internal + pure + returns (FillResults memory totalFillResults) + { + totalFillResults.makerAssetFilledAmount = fillResults1.makerAssetFilledAmount.safeAdd(fillResults2.makerAssetFilledAmount); + totalFillResults.takerAssetFilledAmount = fillResults1.takerAssetFilledAmount.safeAdd(fillResults2.takerAssetFilledAmount); + totalFillResults.makerFeePaid = fillResults1.makerFeePaid.safeAdd(fillResults2.makerFeePaid); + totalFillResults.takerFeePaid = fillResults1.takerFeePaid.safeAdd(fillResults2.takerFeePaid); + + return totalFillResults; + } + + /// @dev Calculates part of the matched fill results for a given situation using the fill strategy that only + /// awards profit denominated in the left maker asset. + /// @param leftOrder The left order in the order matching situation. + /// @param rightOrder The right order in the order matching situation. + /// @param leftMakerAssetAmountRemaining The amount of the left order maker asset that can still be filled. + /// @param leftTakerAssetAmountRemaining The amount of the left order taker asset that can still be filled. + /// @param rightMakerAssetAmountRemaining The amount of the right order maker asset that can still be filled. + /// @param rightTakerAssetAmountRemaining The amount of the right order taker asset that can still be filled. + /// @return MatchFillResults struct that does not include fees paid. + function _calculateMatchedFillResults( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + uint256 leftMakerAssetAmountRemaining, + uint256 leftTakerAssetAmountRemaining, + uint256 rightMakerAssetAmountRemaining, + uint256 rightTakerAssetAmountRemaining + ) + private + pure + returns (MatchedFillResults memory matchedFillResults) + { + // 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. + // Case 3. + // If the left maker can buy exactly as much as the right maker can sell, then both orders are fully filled. + if (leftTakerAssetAmountRemaining > rightMakerAssetAmountRemaining) { + // Case 1: Right order is fully filled + matchedFillResults = _calculateCompleteRightFill( + leftOrder, + rightMakerAssetAmountRemaining, + rightTakerAssetAmountRemaining + ); + } else if (leftTakerAssetAmountRemaining < rightMakerAssetAmountRemaining) { + // Case 2: Left order is fully filled + matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining; + matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining; + matchedFillResults.right.makerAssetFilledAmount = leftTakerAssetAmountRemaining; + // 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 = LibMath.safeGetPartialAmountCeil( + rightOrder.takerAssetAmount, + rightOrder.makerAssetAmount, + leftTakerAssetAmountRemaining // matchedFillResults.right.makerAssetFilledAmount + ); + } else { + // leftTakerAssetAmountRemaining == rightMakerAssetAmountRemaining + // Case 3: Both orders are fully filled. Technically, this could be captured by the above cases, but + // this calculation will be more precise since it does not include rounding. + matchedFillResults = _calculateCompleteFillBoth( + leftMakerAssetAmountRemaining, + leftTakerAssetAmountRemaining, + rightMakerAssetAmountRemaining, + rightTakerAssetAmountRemaining + ); + } + + // Calculate amount given to taker + matchedFillResults.profitInLeftMakerAsset = matchedFillResults.left.makerAssetFilledAmount.safeSub( + matchedFillResults.right.takerAssetFilledAmount + ); + + return matchedFillResults; + } + + /// @dev Calculates part of the matched fill results for a given situation using the maximal fill order matching + /// strategy. + /// @param leftOrder The left order in the order matching situation. + /// @param rightOrder The right order in the order matching situation. + /// @param leftMakerAssetAmountRemaining The amount of the left order maker asset that can still be filled. + /// @param leftTakerAssetAmountRemaining The amount of the left order taker asset that can still be filled. + /// @param rightMakerAssetAmountRemaining The amount of the right order maker asset that can still be filled. + /// @param rightTakerAssetAmountRemaining The amount of the right order taker asset that can still be filled. + /// @return MatchFillResults struct that does not include fees paid. + function _calculateMatchedFillResultsWithMaximalFill( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + uint256 leftMakerAssetAmountRemaining, + uint256 leftTakerAssetAmountRemaining, + uint256 rightMakerAssetAmountRemaining, + uint256 rightTakerAssetAmountRemaining + ) + private + pure + returns (MatchedFillResults memory matchedFillResults) + { + // If a maker asset is greater than the opposite taker asset, than there will be a spread denominated in that maker asset. + bool doesLeftMakerAssetProfitExist = leftMakerAssetAmountRemaining > rightTakerAssetAmountRemaining; + bool doesRightMakerAssetProfitExist = rightMakerAssetAmountRemaining > leftTakerAssetAmountRemaining; + + // Calculate the maximum fill results for the maker and taker assets. At least one of the orders will be fully filled. + // + // The maximum that the left maker can possibly buy is the amount that the right order can sell. + // The maximum that the right maker can possibly buy is the amount that the left order can sell. + // + // If the left order is fully filled, profit will be paid out in the left maker asset. If the right order is fully filled, + // the profit will be out in the right maker asset. + // + // There are three cases to consider: + // Case 1. + // If the left maker can buy more than the right maker can sell, then only the right order is fully filled. + // Case 2. + // If the right maker can buy more than the left maker can sell, then only the right order is fully filled. + // Case 3. + // If the right maker can sell the max of what the left maker can buy and the left maker can sell the max of + // what the right maker can buy, then both orders are fully filled. + if (leftTakerAssetAmountRemaining > rightMakerAssetAmountRemaining) { + // Case 1: Right order is fully filled with the profit paid in the left makerAsset + matchedFillResults = _calculateCompleteRightFill( + leftOrder, + rightMakerAssetAmountRemaining, + rightTakerAssetAmountRemaining + ); + } else if (rightTakerAssetAmountRemaining > leftMakerAssetAmountRemaining) { + // Case 2: Left order is fully filled with the profit paid in the right makerAsset. + matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining; + matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining; + // Round down to ensure the right maker's exchange rate does not exceed the price specified by the order. + // We favor the right maker when the exchange rate must be rounded and the profit is being paid in the + // right maker asset. + matchedFillResults.right.makerAssetFilledAmount = LibMath.safeGetPartialAmountFloor( + rightOrder.makerAssetAmount, + rightOrder.takerAssetAmount, + leftMakerAssetAmountRemaining + ); + matchedFillResults.right.takerAssetFilledAmount = leftMakerAssetAmountRemaining; + } else { + // Case 3: The right and left orders are fully filled + matchedFillResults = _calculateCompleteFillBoth( + leftMakerAssetAmountRemaining, + leftTakerAssetAmountRemaining, + rightMakerAssetAmountRemaining, + rightTakerAssetAmountRemaining + ); + } + + // Calculate amount given to taker in the left order's maker asset if the left spread will be part of the profit. + if (doesLeftMakerAssetProfitExist) { + matchedFillResults.profitInLeftMakerAsset = matchedFillResults.left.makerAssetFilledAmount.safeSub( + matchedFillResults.right.takerAssetFilledAmount + ); + } + + // Calculate amount given to taker in the right order's maker asset if the right spread will be part of the profit. + if (doesRightMakerAssetProfitExist) { + matchedFillResults.profitInRightMakerAsset = matchedFillResults.right.makerAssetFilledAmount.safeSub( + matchedFillResults.left.takerAssetFilledAmount + ); + } + + return matchedFillResults; + } + + /// @dev Calculates the fill results for the maker and taker in the order matching and writes the results + /// to the fillResults that are being collected on the order. Both orders will be fully filled in this + /// case. + /// @param leftMakerAssetAmountRemaining The amount of the left maker asset that is remaining to be filled. + /// @param leftTakerAssetAmountRemaining The amount of the left taker asset that is remaining to be filled. + /// @param rightMakerAssetAmountRemaining The amount of the right maker asset that is remaining to be filled. + /// @param rightTakerAssetAmountRemaining The amount of the right taker asset that is remaining to be filled. + /// @return MatchFillResults struct that does not include fees paid or spreads taken. + function _calculateCompleteFillBoth( + uint256 leftMakerAssetAmountRemaining, + uint256 leftTakerAssetAmountRemaining, + uint256 rightMakerAssetAmountRemaining, + uint256 rightTakerAssetAmountRemaining + ) + private + pure + returns (MatchedFillResults memory matchedFillResults) + { + // Calculate the fully filled results for both orders. + matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining; + matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining; + matchedFillResults.right.makerAssetFilledAmount = rightMakerAssetAmountRemaining; + matchedFillResults.right.takerAssetFilledAmount = rightTakerAssetAmountRemaining; + + return matchedFillResults; + } + + /// @dev Calculates the fill results for the maker and taker in the order matching and writes the results + /// to the fillResults that are being collected on the order. + /// @param leftOrder The left order that is being maximally filled. All of the information about fill amounts + /// can be derived from this order and the right asset remaining fields. + /// @param rightMakerAssetAmountRemaining The amount of the right maker asset that is remaining to be filled. + /// @param rightTakerAssetAmountRemaining The amount of the right taker asset that is remaining to be filled. + /// @return MatchFillResults struct that does not include fees paid or spreads taken. + function _calculateCompleteRightFill( + LibOrder.Order memory leftOrder, + uint256 rightMakerAssetAmountRemaining, + uint256 rightTakerAssetAmountRemaining + ) + private + pure + returns (MatchedFillResults memory matchedFillResults) + { + matchedFillResults.right.makerAssetFilledAmount = rightMakerAssetAmountRemaining; + matchedFillResults.right.takerAssetFilledAmount = rightTakerAssetAmountRemaining; + matchedFillResults.left.takerAssetFilledAmount = rightMakerAssetAmountRemaining; + // Round down to ensure the left maker's exchange rate does not exceed the price specified by the order. + // We favor the left maker when the exchange rate must be rounded and the profit is being paid in the + // left maker asset. + matchedFillResults.left.makerAssetFilledAmount = LibMath.safeGetPartialAmountFloor( + leftOrder.makerAssetAmount, + leftOrder.takerAssetAmount, + rightMakerAssetAmountRemaining + ); + + return matchedFillResults; } } diff --git a/contracts/exchange-libs/contracts/src/LibMath.sol b/contracts/exchange-libs/contracts/src/LibMath.sol index bc145499af..e15a122cc9 100644 --- a/contracts/exchange-libs/contracts/src/LibMath.sol +++ b/contracts/exchange-libs/contracts/src/LibMath.sol @@ -18,21 +18,22 @@ pragma solidity ^0.5.9; -import "@0x/contracts-utils/contracts/src/SafeMath.sol"; +import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; import "@0x/contracts-utils/contracts/src/LibRichErrors.sol"; import "./LibMathRichErrors.sol"; -contract LibMath is - SafeMath -{ +library LibMath { + + using LibSafeMath for uint256; + /// @dev Calculates partial value given a numerator and denominator rounded down. /// 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 rounded down. - function _safeGetPartialAmountFloor( + function safeGetPartialAmountFloor( uint256 numerator, uint256 denominator, uint256 target @@ -41,22 +42,19 @@ contract LibMath is pure returns (uint256 partialAmount) { - if (_isRoundingErrorFloor( + if (isRoundingErrorFloor( numerator, denominator, target )) { - LibRichErrors._rrevert(LibMathRichErrors.RoundingError( + LibRichErrors.rrevert(LibMathRichErrors.RoundingError( numerator, denominator, target )); } - partialAmount = _safeDiv( - _safeMul(numerator, target), - denominator - ); + partialAmount = numerator.safeMul(target).safeDiv(denominator); return partialAmount; } @@ -66,7 +64,7 @@ contract LibMath is /// @param denominator Denominator. /// @param target Value to calculate partial of. /// @return Partial value of target rounded up. - function _safeGetPartialAmountCeil( + function safeGetPartialAmountCeil( uint256 numerator, uint256 denominator, uint256 target @@ -75,12 +73,12 @@ contract LibMath is pure returns (uint256 partialAmount) { - if (_isRoundingErrorCeil( + if (isRoundingErrorCeil( numerator, denominator, target )) { - LibRichErrors._rrevert(LibMathRichErrors.RoundingError( + LibRichErrors.rrevert(LibMathRichErrors.RoundingError( numerator, denominator, target @@ -90,13 +88,10 @@ contract LibMath is // _safeDiv computes `floor(a / b)`. We use the identity (a, b integer): // ceil(a / b) = floor((a + b - 1) / b) // To implement `ceil(a / b)` using _safeDiv. - partialAmount = _safeDiv( - _safeAdd( - _safeMul(numerator, target), - _safeSub(denominator, 1) - ), - denominator - ); + partialAmount = numerator.safeMul(target) + .safeAdd(denominator.safeSub(1)) + .safeDiv(denominator); + return partialAmount; } @@ -105,7 +100,7 @@ contract LibMath is /// @param denominator Denominator. /// @param target Value to calculate partial of. /// @return Partial value of target rounded down. - function _getPartialAmountFloor( + function getPartialAmountFloor( uint256 numerator, uint256 denominator, uint256 target @@ -114,10 +109,7 @@ contract LibMath is pure returns (uint256 partialAmount) { - partialAmount = _safeDiv( - _safeMul(numerator, target), - denominator - ); + partialAmount = numerator.safeMul(target).safeDiv(denominator); return partialAmount; } @@ -126,7 +118,7 @@ contract LibMath is /// @param denominator Denominator. /// @param target Value to calculate partial of. /// @return Partial value of target rounded up. - function _getPartialAmountCeil( + function getPartialAmountCeil( uint256 numerator, uint256 denominator, uint256 target @@ -138,13 +130,10 @@ contract LibMath is // _safeDiv computes `floor(a / b)`. We use the identity (a, b integer): // ceil(a / b) = floor((a + b - 1) / b) // To implement `ceil(a / b)` using _safeDiv. - partialAmount = _safeDiv( - _safeAdd( - _safeMul(numerator, target), - _safeSub(denominator, 1) - ), - denominator - ); + partialAmount = numerator.safeMul(target) + .safeAdd(denominator.safeSub(1)) + .safeDiv(denominator); + return partialAmount; } @@ -153,7 +142,7 @@ contract LibMath is /// @param denominator Denominator. /// @param target Value to multiply with numerator/denominator. /// @return Rounding error is present. - function _isRoundingErrorFloor( + function isRoundingErrorFloor( uint256 numerator, uint256 denominator, uint256 target @@ -163,7 +152,7 @@ contract LibMath is returns (bool isError) { if (denominator == 0) { - LibRichErrors._rrevert(LibMathRichErrors.DivisionByZeroError()); + LibRichErrors.rrevert(LibMathRichErrors.DivisionByZeroError()); } // The absolute rounding error is the difference between the rounded @@ -197,7 +186,7 @@ contract LibMath is numerator, denominator ); - isError = _safeMul(1000, remainder) >= _safeMul(numerator, target); + isError = remainder.safeMul(1000) >= numerator.safeMul(target); return isError; } @@ -206,7 +195,7 @@ contract LibMath is /// @param denominator Denominator. /// @param target Value to multiply with numerator/denominator. /// @return Rounding error is present. - function _isRoundingErrorCeil( + function isRoundingErrorCeil( uint256 numerator, uint256 denominator, uint256 target @@ -216,7 +205,7 @@ contract LibMath is returns (bool isError) { if (denominator == 0) { - LibRichErrors._rrevert(LibMathRichErrors.DivisionByZeroError()); + LibRichErrors.rrevert(LibMathRichErrors.DivisionByZeroError()); } // See the comments in `isRoundingError`. @@ -232,8 +221,8 @@ contract LibMath is numerator, denominator ); - remainder = _safeSub(denominator, remainder) % denominator; - isError = _safeMul(1000, remainder) >= _safeMul(numerator, target); + remainder = denominator.safeSub(remainder) % denominator; + isError = remainder.safeMul(1000) >= numerator.safeMul(target); return isError; } } diff --git a/contracts/exchange-libs/contracts/src/LibOrder.sol b/contracts/exchange-libs/contracts/src/LibOrder.sol index e2dc29c536..250aaac5a4 100644 --- a/contracts/exchange-libs/contracts/src/LibOrder.sol +++ b/contracts/exchange-libs/contracts/src/LibOrder.sol @@ -17,14 +17,14 @@ */ pragma solidity ^0.5.9; -pragma experimental ABIEncoderV2; -import "./LibEIP712ExchangeDomain.sol"; +import "@0x/contracts-utils/contracts/src/LibEIP712.sol"; -contract LibOrder is - LibEIP712ExchangeDomain -{ +library LibOrder { + + using LibOrder for Order; + // Hash for the EIP712 Order Schema: // keccak256(abi.encodePacked( // "Order(", @@ -44,11 +44,11 @@ contract LibOrder is // "bytes takerFeeAssetData", // ")" // )) - bytes32 constant public EIP712_ORDER_SCHEMA_HASH = + bytes32 constant internal _EIP712_ORDER_SCHEMA_HASH = 0xf80322eb8376aafb64eadf8f0d7623f22130fd9491a221e902b713cb984a7534; // A valid order remains fillable until it is expired, fully filled, or cancelled. - // An order's state is unaffected by external factors, like account balances. + // An order's status is unaffected by external factors, like account balances. enum OrderStatus { INVALID, // Default value INVALID_MAKER_ASSET_AMOUNT, // Order does not have a valid maker asset amount @@ -67,44 +67,47 @@ contract LibOrder is address senderAddress; // Address that is allowed to call Exchange contract methods that affect this order. If set to 0, any address is allowed to call these methods. uint256 makerAssetAmount; // Amount of makerAsset being offered by maker. Must be greater than 0. uint256 takerAssetAmount; // Amount of takerAsset being bid on by maker. Must be greater than 0. - uint256 makerFee; // Amount of ZRX paid to feeRecipient by maker when order is filled. If set to 0, no transfer of ZRX from maker to feeRecipient will be attempted. - uint256 takerFee; // Amount of ZRX paid to feeRecipient by taker when order is filled. If set to 0, no transfer of ZRX from taker to feeRecipient will be attempted. + uint256 makerFee; // Fee paid to feeRecipient by maker when order is filled. + uint256 takerFee; // Fee paid to feeRecipient by taker when order is filled. uint256 expirationTimeSeconds; // Timestamp in seconds at which order expires. uint256 salt; // Arbitrary number to facilitate uniqueness of the order's hash. bytes makerAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring makerAsset. The leading bytes4 references the id of the asset proxy. bytes takerAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring takerAsset. The leading bytes4 references the id of the asset proxy. - bytes makerFeeAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring makerAsset fees. The leading bytes4 references the id of the asset proxy. - bytes takerFeeAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring takerAsset fees. The leading bytes4 references the id of the asset proxy. + bytes makerFeeAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring makerFeeAsset. The leading bytes4 references the id of the asset proxy. + bytes takerFeeAssetData; // Encoded data that can be decoded by a specified proxy contract when transferring takerFeeAsset. The leading bytes4 references the id of the asset proxy. } // solhint-enable max-line-length struct OrderInfo { uint8 orderStatus; // Status that describes order's validity and fillability. - bytes32 orderHash; // EIP712 hash of the order (see LibOrder.getOrderHash). + bytes32 orderHash; // EIP712 typed data hash of the order (see LibOrder.getTypedDataHash). uint256 orderTakerAssetFilledAmount; // Amount of order that has already been filled. } - /// @dev Calculates Keccak-256 hash of the order. + /// @dev Calculates the EIP712 typed data hash of an order with a given domain separator. /// @param order The order structure. - /// @return Keccak-256 EIP712 hash of the order. - function getOrderHash(Order memory order) - public - view + /// @return EIP712 typed data hash of the order. + function getTypedDataHash(Order memory order, bytes32 eip712ExchangeDomainHash) + internal + pure returns (bytes32 orderHash) { - orderHash = _hashEIP712ExchangeMessage(_hashOrder(order)); + orderHash = LibEIP712.hashEIP712Message( + eip712ExchangeDomainHash, + order.getStructHash() + ); return orderHash; } - /// @dev Calculates EIP712 hash of the order. + /// @dev Calculates EIP712 hash of the order struct. /// @param order The order structure. - /// @return EIP712 hash of the order. - function _hashOrder(Order memory order) + /// @return EIP712 hash of the order struct. + function getStructHash(Order memory order) internal pure returns (bytes32 result) { - bytes32 schemaHash = EIP712_ORDER_SCHEMA_HASH; + bytes32 schemaHash = _EIP712_ORDER_SCHEMA_HASH; bytes memory makerAssetData = order.makerAssetData; bytes memory takerAssetData = order.takerAssetData; bytes memory makerFeeAssetData = order.makerFeeAssetData; @@ -113,10 +116,10 @@ contract LibOrder is // Assembly for more efficiently computing: // keccak256(abi.encodePacked( // EIP712_ORDER_SCHEMA_HASH, - // bytes32(order.makerAddress), - // bytes32(order.takerAddress), - // bytes32(order.feeRecipientAddress), - // bytes32(order.senderAddress), + // uint256(order.makerAddress), + // uint256(order.takerAddress), + // uint256(order.feeRecipientAddress), + // uint256(order.senderAddress), // order.makerAssetAmount, // order.takerAssetAmount, // order.makerFee, diff --git a/contracts/exchange-libs/contracts/src/LibZeroExTransaction.sol b/contracts/exchange-libs/contracts/src/LibZeroExTransaction.sol index a15d6f98cd..20cf3ec74d 100644 --- a/contracts/exchange-libs/contracts/src/LibZeroExTransaction.sol +++ b/contracts/exchange-libs/contracts/src/LibZeroExTransaction.sol @@ -19,12 +19,13 @@ pragma solidity ^0.5.9; pragma experimental ABIEncoderV2; -import "./LibEIP712ExchangeDomain.sol"; +import "@0x/contracts-utils/contracts/src/LibEIP712.sol"; -contract LibZeroExTransaction is - LibEIP712ExchangeDomain -{ +library LibZeroExTransaction { + + using LibZeroExTransaction for ZeroExTransaction; + // Hash for the EIP712 0x transaction schema // keccak256(abi.encodePacked( // "ZeroExTransaction(", @@ -34,7 +35,7 @@ contract LibZeroExTransaction is // "bytes data", // ")" // )); - bytes32 constant public EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = 0x6b4c70d217b44d0ff0d3bf7aeb18eb8604c5cd06f615a4b497aeefa4f01d2775; + bytes32 constant internal _EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH = 0x6b4c70d217b44d0ff0d3bf7aeb18eb8604c5cd06f615a4b497aeefa4f01d2775; struct ZeroExTransaction { uint256 salt; // Arbitrary number to ensure uniqueness of transaction hash. @@ -43,28 +44,31 @@ contract LibZeroExTransaction is bytes data; // AbiV2 encoded calldata. } - /// @dev Calculates the EIP712 hash of a 0x transaction using the domain separator of the Exchange contract. - /// @param transaction 0x transaction containing salt, signerAddress, and data. - /// @return EIP712 hash of the transaction with the domain separator of this contract. - function getTransactionHash(ZeroExTransaction memory transaction) - public - view + /// @dev Calculates the EIP712 typed data hash of a transaction with a given domain separator. + /// @param transaction 0x transaction structure. + /// @return EIP712 typed data hash of the transaction. + function getTypedDataHash(ZeroExTransaction memory transaction, bytes32 eip712ExchangeDomainHash) + internal + pure returns (bytes32 transactionHash) { // Hash the transaction with the domain separator of the Exchange contract. - transactionHash = _hashEIP712ExchangeMessage(_hashZeroExTransaction(transaction)); + transactionHash = LibEIP712.hashEIP712Message( + eip712ExchangeDomainHash, + transaction.getStructHash() + ); return transactionHash; } - /// @dev Calculates EIP712 hash of the 0x transaction with no domain separator. - /// @param transaction 0x transaction containing salt, signerAddress, and data. - /// @return EIP712 hash of the transaction with no domain separator. - function _hashZeroExTransaction(ZeroExTransaction memory transaction) + /// @dev Calculates EIP712 hash of the 0x transaction struct. + /// @param transaction 0x transaction structure. + /// @return EIP712 hash of the transaction struct. + function getStructHash(ZeroExTransaction memory transaction) internal pure returns (bytes32 result) { - bytes32 schemaHash = EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH; + bytes32 schemaHash = _EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH; bytes memory data = transaction.data; uint256 salt = transaction.salt; uint256 expirationTimeSeconds = transaction.expirationTimeSeconds; diff --git a/contracts/exchange-libs/contracts/test/TestLibEIP712ExchangeDomain.sol b/contracts/exchange-libs/contracts/test/TestLibEIP712ExchangeDomain.sol new file mode 100644 index 0000000000..c3cbd9cc11 --- /dev/null +++ b/contracts/exchange-libs/contracts/test/TestLibEIP712ExchangeDomain.sol @@ -0,0 +1,36 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "../src/LibEIP712ExchangeDomain.sol"; + + +contract TestLibEIP712ExchangeDomain is + LibEIP712ExchangeDomain +{ + + constructor( + uint256 chainId, + address verifyingContractAddressIfExists + ) + public + LibEIP712ExchangeDomain(chainId, verifyingContractAddressIfExists) + {} +} diff --git a/contracts/exchange-libs/contracts/test/TestLibFillResults.sol b/contracts/exchange-libs/contracts/test/TestLibFillResults.sol new file mode 100644 index 0000000000..714efbdfae --- /dev/null +++ b/contracts/exchange-libs/contracts/test/TestLibFillResults.sol @@ -0,0 +1,74 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "../src/LibOrder.sol"; +import "../src/LibFillResults.sol"; + + +contract TestLibFillResults { + + using LibFillResults for *; + + function calculateFillResults( + LibOrder.Order memory order, + uint256 takerAssetFilledAmount + ) + public + pure + returns (LibFillResults.FillResults memory fillResults) + { + fillResults = LibFillResults.calculateFillResults(order, takerAssetFilledAmount); + return fillResults; + } + + function calculateMatchedFillResults( + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + uint256 leftOrderTakerAssetFilledAmount, + uint256 rightOrderTakerAssetFilledAmount, + bool shouldMaximallyFillOrders + ) + public + pure + returns (LibFillResults.MatchedFillResults memory matchedFillResults) + { + matchedFillResults = LibFillResults.calculateMatchedFillResults( + leftOrder, + rightOrder, + leftOrderTakerAssetFilledAmount, + rightOrderTakerAssetFilledAmount, + shouldMaximallyFillOrders + ); + return matchedFillResults; + } + + function addFillResults( + LibFillResults.FillResults memory fillResults1, + LibFillResults.FillResults memory fillResults2 + ) + public + pure + returns (LibFillResults.FillResults memory totalFillResults) + { + totalFillResults = LibFillResults.addFillResults(fillResults1, fillResults2); + return totalFillResults; + } +} diff --git a/contracts/exchange-libs/contracts/test/TestLibMath.sol b/contracts/exchange-libs/contracts/test/TestLibMath.sol new file mode 100644 index 0000000000..593d65b73d --- /dev/null +++ b/contracts/exchange-libs/contracts/test/TestLibMath.sol @@ -0,0 +1,130 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "../src/LibMath.sol"; + + +contract TestLibMath { + + /// @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 safeGetPartialAmountFloor( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (uint256 partialAmount) + { + return LibMath.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 safeGetPartialAmountCeil( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (uint256 partialAmount) + { + return LibMath.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 getPartialAmountFloor( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (uint256 partialAmount) + { + return LibMath.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 getPartialAmountCeil( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (uint256 partialAmount) + { + return LibMath.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 isRoundingErrorFloor( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (bool isError) + { + return LibMath.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 isRoundingErrorCeil( + uint256 numerator, + uint256 denominator, + uint256 target + ) + public + pure + returns (bool isError) + { + return LibMath.isRoundingErrorCeil(numerator, denominator, target); + } +} diff --git a/contracts/exchange-libs/contracts/test/TestLibOrder.sol b/contracts/exchange-libs/contracts/test/TestLibOrder.sol new file mode 100644 index 0000000000..c1be49a49b --- /dev/null +++ b/contracts/exchange-libs/contracts/test/TestLibOrder.sol @@ -0,0 +1,44 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "../src/LibOrder.sol"; + + +contract TestLibOrder { + + function getTypedDataHash(LibOrder.Order memory order, bytes32 eip712ExchangeDomainHash) + public + pure + returns (bytes32 orderHash) + { + orderHash = LibOrder.getTypedDataHash(order, eip712ExchangeDomainHash); + return orderHash; + } + + function getStructHash(LibOrder.Order memory order) + public + pure + returns (bytes32 result) + { + result = LibOrder.getStructHash(order); + return result; + } +} diff --git a/contracts/exchange-libs/contracts/test/TestLibZeroExTransaction.sol b/contracts/exchange-libs/contracts/test/TestLibZeroExTransaction.sol new file mode 100644 index 0000000000..baaed71877 --- /dev/null +++ b/contracts/exchange-libs/contracts/test/TestLibZeroExTransaction.sol @@ -0,0 +1,44 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "../src/LibZeroExTransaction.sol"; + + +contract TestLibZeroExTransaction { + + function getTypedDataHash(LibZeroExTransaction.ZeroExTransaction memory transaction, bytes32 eip712ExchangeDomainHash) + public + pure + returns (bytes32 transactionHash) + { + transactionHash = LibZeroExTransaction.getTypedDataHash(transaction, eip712ExchangeDomainHash); + return transactionHash; + } + + function getStructHash(LibZeroExTransaction.ZeroExTransaction memory transaction) + public + pure + returns (bytes32 result) + { + result = LibZeroExTransaction.getStructHash(transaction); + return result; + } +} \ No newline at end of file diff --git a/contracts/exchange-libs/contracts/test/TestLibs.sol b/contracts/exchange-libs/contracts/test/TestLibs.sol deleted file mode 100644 index 0665793140..0000000000 --- a/contracts/exchange-libs/contracts/test/TestLibs.sol +++ /dev/null @@ -1,200 +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.5.9; -pragma experimental ABIEncoderV2; - -import "../src/LibEIP712ExchangeDomain.sol"; -import "../src/LibMath.sol"; -import "../src/LibOrder.sol"; -import "../src/LibZeroExTransaction.sol"; -import "../src/LibFillResults.sol"; - - -// solhint-disable no-empty-blocks -contract TestLibs is - LibEIP712ExchangeDomain, - LibMath, - LibOrder, - LibZeroExTransaction, - LibFillResults -{ - constructor (uint256 chainId) - public - LibEIP712ExchangeDomain(chainId, address(0)) - {} - - function getPartialAmountFloor( - uint256 numerator, - uint256 denominator, - uint256 target - ) - public - pure - returns (uint256 partialAmount) - { - partialAmount = _getPartialAmountFloor( - numerator, - denominator, - target - ); - return partialAmount; - } - - function getPartialAmountCeil( - uint256 numerator, - uint256 denominator, - uint256 target - ) - public - pure - returns (uint256 partialAmount) - { - partialAmount = _getPartialAmountCeil( - numerator, - denominator, - target - ); - return partialAmount; - } - - function safeGetPartialAmountFloor( - uint256 numerator, - uint256 denominator, - uint256 target - ) - public - pure - returns (uint256 partialAmount) - { - partialAmount = _safeGetPartialAmountFloor( - numerator, - denominator, - target - ); - return partialAmount; - } - - function safeGetPartialAmountCeil( - uint256 numerator, - uint256 denominator, - uint256 target - ) - public - pure - returns (uint256 partialAmount) - { - partialAmount = _safeGetPartialAmountCeil( - numerator, - denominator, - target - ); - return partialAmount; - } - - function isRoundingErrorFloor( - uint256 numerator, - uint256 denominator, - uint256 target - ) - public - pure - returns (bool isError) - { - isError = _isRoundingErrorFloor( - numerator, - denominator, - target - ); - return isError; - } - - function isRoundingErrorCeil( - uint256 numerator, - uint256 denominator, - uint256 target - ) - public - pure - returns (bool isError) - { - isError = _isRoundingErrorCeil( - numerator, - denominator, - target - ); - return isError; - } - - function getOrderSchemaHash() - public - pure - returns (bytes32) - { - return EIP712_ORDER_SCHEMA_HASH; - } - - function getDomainSeparatorSchemaHash() - public - pure - returns (bytes32) - { - return EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH; - } - - function getDomainSeparator() - public - view - returns (bytes32) - { - return EIP712_EXCHANGE_DOMAIN_HASH; - } - - function addFillResults(FillResults memory totalFillResults, FillResults memory singleFillResults) - public - pure - returns (FillResults memory) - { - _addFillResults(totalFillResults, singleFillResults); - return totalFillResults; - } - - function hashOrder(Order memory order) - public - pure - returns (bytes32) - { - return _hashOrder(order); - } - - function hashZeroExTransaction(ZeroExTransaction memory transaction) - public - pure - returns (bytes32) - { - return _hashZeroExTransaction(transaction); - } - - function hashEIP712ExchangeMessage(bytes32 hashStruct) - public - view - returns (bytes32) - { - return _hashEIP712ExchangeMessage(hashStruct); - } -} diff --git a/contracts/exchange-libs/package.json b/contracts/exchange-libs/package.json index cd8d1ec81c..3f093eaa5f 100644 --- a/contracts/exchange-libs/package.json +++ b/contracts/exchange-libs/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", - "pre_build": "run-s compile generate_contract_wrappers", + "pre_build": "run-s compile contracts:gen 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", @@ -31,11 +31,10 @@ "coverage:report:lcov": "istanbul report lcov", "test:circleci": "yarn test", "contracts:gen": "contracts-gen", - "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol", - "generate-exchange-selectors": "node lib/scripts/generate-exchange-selectors.js ../../../exchange/generated-artifacts/Exchange.json ./contracts/src/LibExchangeSelectors.sol" + "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis": "./generated-artifacts/@(LibEIP712ExchangeDomain|LibFillResults|LibMath|LibOrder|LibZeroExTransaction|TestLibs).json", + "abis": "./generated-artifacts/@(LibEIP712ExchangeDomain|LibExchangeRichErrors|LibFillResults|LibMath|LibMathRichErrors|LibOrder|LibZeroExTransaction|TestLibEIP712ExchangeDomain|TestLibFillResults|TestLibMath|TestLibOrder|TestLibZeroExTransaction).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/exchange-libs/scripts/generate-exchange-selectors.ts b/contracts/exchange-libs/scripts/generate-exchange-selectors.ts deleted file mode 100644 index 0ae88f962b..0000000000 --- a/contracts/exchange-libs/scripts/generate-exchange-selectors.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { AbiDefinition, ContractAbi, DataItem, EventAbi, MethodAbi } from 'ethereum-types'; -import * as ethUtil from 'ethereumjs-util'; -import * as fs from 'fs'; -import * as _ from 'lodash'; -import * as path from 'path'; -import * as process from 'process'; - -const keccak256 = ethUtil.sha3; -const ARGS = process.argv.slice(2); -const INDENT = ' '; -const LINEBREAK = '\n'; -const VISIBILITY = 'internal'; - -interface ParsedContract { - methods: { - [functionName: string]: Array<{ - selector: string; - signature: string; - }>; - }; - events: { - [eventName: string]: { - selector: string; - signature: string; - }; - }; -} - -// tslint:disable: no-console -(() => { - const [exchangeArtifactsFile, outputFile] = ARGS; - const exchangeArtifacts = require(exchangeArtifactsFile); - const contractName = path.basename(outputFile, '.sol'); - const parsedContract = parseContract(exchangeArtifacts.compilerOutput.abi); - const contractDefinition = defineContract(contractName, parsedContract); - const preamble = extractOutputFilePreamble(outputFile); - const outputFileContents = `${preamble}${contractDefinition}${LINEBREAK}`; - fs.writeFileSync(outputFile, outputFileContents); - console.log(`Wrote exchange selectors to "${path.resolve(outputFile)}."`); -})(); - -function parseContract(abi: ContractAbi): ParsedContract { - const parsedContract: ParsedContract = { - methods: {}, - events: {}, - }; - for (const abiItem of abi) { - if (isMethodAbi(abiItem)) { - const name = abiItem.name; - const signature = `${name}(${encodeMethodInputs(abiItem.inputs)})`; - const selector = `0x${keccak256(signature) - .slice(0, 4) - .toString('hex')}`; - if (parsedContract.methods[name] === undefined) { - parsedContract.methods[name] = []; - } - parsedContract.methods[name].push({ - selector, - signature, - }); - } else if (isEventAbi(abiItem)) { - const name = abiItem.name; - const signature = `${name}(${encodeMethodInputs(abiItem.inputs)})`; - const selector = `0x${keccak256(signature).toString('hex')}`; - parsedContract.events[name] = { - selector, - signature, - }; - } - } - return parsedContract; -} - -function isMethodAbi(abiItem: AbiDefinition): abiItem is MethodAbi { - return abiItem.type === 'function'; -} - -function isEventAbi(abiItem: AbiDefinition): abiItem is EventAbi { - return abiItem.type === 'event'; -} - -function defineContract(contractName: string, parsedContract: ParsedContract): string { - const constantDefinitions = []; - // Define function selectors. - const sortedMethodNames = _.sortBy(_.keys(parsedContract.methods), name => name.toLowerCase()); - for (const name of sortedMethodNames) { - const methods = parsedContract.methods[name]; - for (let idx = 0; idx < methods.length; idx++) { - const constantLines = generateFunctionSelectorConstantDefinition( - name, - methods[idx].signature, - methods[idx].selector, - idx, - methods.length, - ); - constantDefinitions.push(constantLines); - } - } - // Define event selectors. - const sortedEventNames = _.sortBy(_.keys(parsedContract.events), name => name.toLowerCase()); - for (const name of sortedEventNames) { - const event = parsedContract.events[name]; - const constantLines = generateEventSelectorConstantDefinition(name, event.signature, event.selector); - constantDefinitions.push(constantLines); - } - return [ - `contract ${contractName} {`, - `${INDENT}// solhint-disable max-line-length`, - '', - constantDefinitions - .map(lines => lines.map(line => `${INDENT}${line}`)) - .map(lines => lines.join(LINEBREAK)) - .join(`${LINEBREAK}${LINEBREAK}`), - `}`, - ].join(LINEBREAK); -} - -function extractOutputFilePreamble(outputFile: string): string { - const preambleLines = []; - const outputFileLines = fs.readFileSync(outputFile, 'utf-8').split(/\r?\n/); - for (const line of outputFileLines) { - if (/^\s*contract\s+[a-zA-Z][a-zA-Z0-9_]+/.test(line)) { - preambleLines.push(''); - break; - } - preambleLines.push(line); - } - return preambleLines.join(LINEBREAK); -} - -function generateFunctionSelectorConstantDefinition( - name: string, - signature: string, - selector: string, - idx: number, - total: number, -): string[] { - const varName = _.snakeCase(total === 1 ? name : `${name}_${idx + 1}`).toUpperCase(); - return [`// function ${signature}`, `bytes4 constant ${VISIBILITY} ${varName}_SELECTOR = ${selector};`]; -} - -function generateEventSelectorConstantDefinition(name: string, signature: string, selector: string): string[] { - const varName = _.snakeCase(name).toUpperCase(); - return [`// event ${signature}`, `bytes32 constant ${VISIBILITY} EVENT_${varName}_SELECTOR = ${selector};`]; -} - -function encodeMethodInputs(inputs?: DataItem[]): string { - if (inputs === undefined) { - throw new Error('encodeMethodInputs: inputs are undefined'); - } - const types = []; - for (const input of inputs) { - if (input.type === 'tuple') { - types.push(`(${encodeMethodInputs(input.components)})`); - } else if (input.type === 'tuple[]') { - types.push(`(${encodeMethodInputs(input.components)})[]`); - } else { - types.push(input.type); - } - } - return types.join(','); -} diff --git a/contracts/exchange-libs/src/artifacts.ts b/contracts/exchange-libs/src/artifacts.ts index fca74b4506..7bfb67a9d3 100644 --- a/contracts/exchange-libs/src/artifacts.ts +++ b/contracts/exchange-libs/src/artifacts.ts @@ -6,16 +6,28 @@ import { ContractArtifact } from 'ethereum-types'; import * as LibEIP712ExchangeDomain from '../generated-artifacts/LibEIP712ExchangeDomain.json'; +import * as LibExchangeRichErrors from '../generated-artifacts/LibExchangeRichErrors.json'; import * as LibFillResults from '../generated-artifacts/LibFillResults.json'; import * as LibMath from '../generated-artifacts/LibMath.json'; +import * as LibMathRichErrors from '../generated-artifacts/LibMathRichErrors.json'; import * as LibOrder from '../generated-artifacts/LibOrder.json'; import * as LibZeroExTransaction from '../generated-artifacts/LibZeroExTransaction.json'; -import * as TestLibs from '../generated-artifacts/TestLibs.json'; +import * as TestLibEIP712ExchangeDomain from '../generated-artifacts/TestLibEIP712ExchangeDomain.json'; +import * as TestLibFillResults from '../generated-artifacts/TestLibFillResults.json'; +import * as TestLibMath from '../generated-artifacts/TestLibMath.json'; +import * as TestLibOrder from '../generated-artifacts/TestLibOrder.json'; +import * as TestLibZeroExTransaction from '../generated-artifacts/TestLibZeroExTransaction.json'; export const artifacts = { LibEIP712ExchangeDomain: LibEIP712ExchangeDomain as ContractArtifact, + LibExchangeRichErrors: LibExchangeRichErrors as ContractArtifact, LibFillResults: LibFillResults as ContractArtifact, LibMath: LibMath as ContractArtifact, + LibMathRichErrors: LibMathRichErrors as ContractArtifact, LibOrder: LibOrder as ContractArtifact, LibZeroExTransaction: LibZeroExTransaction as ContractArtifact, - TestLibs: TestLibs as ContractArtifact, + TestLibEIP712ExchangeDomain: TestLibEIP712ExchangeDomain as ContractArtifact, + TestLibFillResults: TestLibFillResults as ContractArtifact, + TestLibMath: TestLibMath as ContractArtifact, + TestLibOrder: TestLibOrder as ContractArtifact, + TestLibZeroExTransaction: TestLibZeroExTransaction as ContractArtifact, }; diff --git a/contracts/exchange-libs/src/reference_functions.ts b/contracts/exchange-libs/src/reference_functions.ts index e3894adf13..b7ec7524a6 100644 --- a/contracts/exchange-libs/src/reference_functions.ts +++ b/contracts/exchange-libs/src/reference_functions.ts @@ -1,6 +1,6 @@ import { ReferenceFunctions } from '@0x/contracts-utils'; import { LibMathRevertErrors } from '@0x/order-utils'; -import { FillResults } from '@0x/types'; +import { FillResults, OrderWithoutDomain } from '@0x/types'; import { BigNumber } from '@0x/utils'; const { safeAdd, safeSub, safeMul, safeDiv } = ReferenceFunctions; @@ -87,3 +87,22 @@ export function addFillResults(a: FillResults, b: FillResults): FillResults { takerFeePaid: safeAdd(a.takerFeePaid, b.takerFeePaid), }; } + +/** + * Calculates amounts filled and fees paid by maker and taker. + */ +export function calculateFillResults(order: OrderWithoutDomain, takerAssetFilledAmount: BigNumber): FillResults { + const makerAssetFilledAmount = safeGetPartialAmountFloor( + takerAssetFilledAmount, + order.takerAssetAmount, + order.makerAssetAmount, + ); + const makerFeePaid = safeGetPartialAmountFloor(makerAssetFilledAmount, order.makerAssetAmount, order.makerFee); + const takerFeePaid = safeGetPartialAmountFloor(takerAssetFilledAmount, order.takerAssetAmount, order.takerFee); + return { + makerAssetFilledAmount, + takerAssetFilledAmount, + makerFeePaid, + takerFeePaid, + }; +} diff --git a/contracts/exchange-libs/src/wrappers.ts b/contracts/exchange-libs/src/wrappers.ts index 3c8fd1ef89..89a7ffc03b 100644 --- a/contracts/exchange-libs/src/wrappers.ts +++ b/contracts/exchange-libs/src/wrappers.ts @@ -4,8 +4,14 @@ * ----------------------------------------------------------------------------- */ export * from '../generated-wrappers/lib_e_i_p712_exchange_domain'; +export * from '../generated-wrappers/lib_exchange_rich_errors'; export * from '../generated-wrappers/lib_fill_results'; export * from '../generated-wrappers/lib_math'; +export * from '../generated-wrappers/lib_math_rich_errors'; export * from '../generated-wrappers/lib_order'; export * from '../generated-wrappers/lib_zero_ex_transaction'; -export * from '../generated-wrappers/test_libs'; +export * from '../generated-wrappers/test_lib_e_i_p712_exchange_domain'; +export * from '../generated-wrappers/test_lib_fill_results'; +export * from '../generated-wrappers/test_lib_math'; +export * from '../generated-wrappers/test_lib_order'; +export * from '../generated-wrappers/test_lib_zero_ex_transaction'; diff --git a/contracts/exchange-libs/test/exchange_libs.ts b/contracts/exchange-libs/test/exchange_libs.ts deleted file mode 100644 index f4f225f617..0000000000 --- a/contracts/exchange-libs/test/exchange_libs.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { addressUtils, chaiSetup, constants, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils, orderHashUtils, transactionHashUtils } from '@0x/order-utils'; -import { constants as orderConstants } from '@0x/order-utils/lib/src/constants'; -import { Order, ZeroExTransaction } from '@0x/types'; -import { BigNumber, providerUtils } from '@0x/utils'; -import * as chai from 'chai'; -import * as ethUtil from 'ethereumjs-util'; - -import { TestLibsContract } from '../src'; -import { artifacts } from '../src/artifacts'; - -import { stringifySchema } from './utils'; - -chaiSetup.configure(); -const expect = chai.expect; - -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe('Exchange libs', () => { - let chainId: number; - let order: Order; - let transaction: ZeroExTransaction; - let libs: TestLibsContract; - let libsAlternateChain: TestLibsContract; - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - before(async () => { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const [makerAddress, takerAddress, senderAddress, feeRecipientAddress, signerAddress] = accounts.slice(0, 5); - chainId = await providerUtils.getChainIdAsync(provider); - libs = await TestLibsContract.deployFrom0xArtifactAsync( - artifacts.TestLibs, - provider, - txDefaults, - new BigNumber(chainId), - ); - // Deploy a version with a different chain ID. - const alternateChainId = chainId + 1; - libsAlternateChain = await TestLibsContract.deployFrom0xArtifactAsync( - artifacts.TestLibs, - provider, - txDefaults, - new BigNumber(alternateChainId), - ); - const domain = { - verifyingContractAddress: libs.address, - chainId, - }; - order = { - ...constants.STATIC_ORDER_PARAMS, - makerAddress, - takerAddress, - senderAddress, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), - takerAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), - makerFeeAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), - takerFeeAssetData: assetDataUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), - salt: new BigNumber(0), - expirationTimeSeconds: new BigNumber(0), - domain, - }; - transaction = { - signerAddress, - salt: new BigNumber(0), - expirationTimeSeconds: new BigNumber(0), - data: constants.NULL_BYTES, - domain, - }; - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - // Note(albrow): These tests are designed to be supplemental to the - // combinatorial tests in test/exchange/internal. They test specific edge - // cases that are not covered by the combinatorial tests. - describe('LibMath', () => { - describe('isRoundingError', () => { - it('should return true if there is a rounding error of 0.1%', async () => { - const numerator = new BigNumber(20); - const denominator = new BigNumber(999); - const target = new BigNumber(50); - // rounding error = ((20*50/999) - floor(20*50/999)) / (20*50/999) = 0.1% - const isRoundingError = await libs.isRoundingErrorFloor.callAsync(numerator, denominator, target); - expect(isRoundingError).to.be.true(); - }); - it('should return false if there is a rounding of 0.09%', async () => { - const numerator = new BigNumber(20); - const denominator = new BigNumber(9991); - const target = new BigNumber(500); - // rounding error = ((20*500/9991) - floor(20*500/9991)) / (20*500/9991) = 0.09% - const isRoundingError = await libs.isRoundingErrorFloor.callAsync(numerator, denominator, target); - expect(isRoundingError).to.be.false(); - }); - it('should return true if there is a rounding error of 0.11%', async () => { - const numerator = new BigNumber(20); - const denominator = new BigNumber(9989); - const target = new BigNumber(500); - // rounding error = ((20*500/9989) - floor(20*500/9989)) / (20*500/9989) = 0.011% - const isRoundingError = await libs.isRoundingErrorFloor.callAsync(numerator, denominator, target); - expect(isRoundingError).to.be.true(); - }); - }); - describe('isRoundingErrorCeil', () => { - it('should return true if there is a rounding error of 0.1%', async () => { - const numerator = new BigNumber(20); - const denominator = new BigNumber(1001); - const target = new BigNumber(50); - // rounding error = (ceil(20*50/1001) - (20*50/1001)) / (20*50/1001) = 0.1% - const isRoundingError = await libs.isRoundingErrorCeil.callAsync(numerator, denominator, target); - expect(isRoundingError).to.be.true(); - }); - it('should return false if there is a rounding of 0.09%', async () => { - const numerator = new BigNumber(20); - const denominator = new BigNumber(10009); - const target = new BigNumber(500); - // rounding error = (ceil(20*500/10009) - (20*500/10009)) / (20*500/10009) = 0.09% - const isRoundingError = await libs.isRoundingErrorCeil.callAsync(numerator, denominator, target); - expect(isRoundingError).to.be.false(); - }); - it('should return true if there is a rounding error of 0.11%', async () => { - const numerator = new BigNumber(20); - const denominator = new BigNumber(10011); - const target = new BigNumber(500); - // rounding error = (ceil(20*500/10011) - (20*500/10011)) / (20*500/10011) = 0.11% - const isRoundingError = await libs.isRoundingErrorCeil.callAsync(numerator, denominator, target); - expect(isRoundingError).to.be.true(); - }); - }); - }); - - describe('LibOrder', () => { - describe('getOrderHash', () => { - it('should return the correct orderHash', async () => { - const orderHashHex = await libs.getOrderHash.callAsync(order); - expect(orderHashUtils.getOrderHashHex(order)).to.be.equal(orderHashHex); - }); - it('orderHash should differ if chainId is different', async () => { - const orderHashHex1 = await libsAlternateChain.getOrderHash.callAsync(order); - const orderHashHex2 = await libs.getOrderHash.callAsync(order); - expect(orderHashHex1).to.be.not.equal(orderHashHex2); - }); - }); - }); - - describe('LibZeroExTransaction', () => { - describe('EIP712ZeroExTransactionSchemaHash', () => { - it('should return the correct schema hash', async () => { - const schemaHash = await libs.EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH.callAsync(); - const schemaString = - 'ZeroExTransaction(uint256 salt,uint256 expirationTimeSeconds,address signerAddress,bytes data)'; - const expectedSchemaHash = ethUtil.addHexPrefix(ethUtil.bufferToHex(ethUtil.sha3(schemaString))); - expect(schemaHash).to.equal(expectedSchemaHash); - }); - }); - describe('getTransactionHash', () => { - it('should return the correct transactionHash', async () => { - const transactionHash = await libs.getTransactionHash.callAsync(transaction); - const expectedTransactionHash = transactionHashUtils.getTransactionHashHex(transaction); - expect(transactionHash).to.equal(expectedTransactionHash); - }); - it('transactionHash should differ if chainId is different', async () => { - const transactionHash1 = await libsAlternateChain.getTransactionHash.callAsync(transaction); - const transactionHash2 = await libs.getTransactionHash.callAsync(transaction); - expect(transactionHash1).to.not.equal(transactionHash2); - }); - }); - }); - - describe('LibEIP712', () => { - it('should return the correct domain separator schema hash', async () => { - const schema = stringifySchema(orderConstants.DEFAULT_DOMAIN_SCHEMA); - const expectedSchemaHash = ethUtil.bufferToHex(ethUtil.sha3(Buffer.from(schema))); - const actualSchemaHash = await libs.getDomainSeparatorSchemaHash.callAsync(); - expect(actualSchemaHash).to.be.equal(expectedSchemaHash); - }); - it('should return the correct order schema hash', async () => { - const schema = stringifySchema(orderConstants.EXCHANGE_ORDER_SCHEMA); - const expectedSchemaHash = ethUtil.bufferToHex(ethUtil.sha3(Buffer.from(schema))); - const actualSchemaHash = await libs.getOrderSchemaHash.callAsync(); - expect(actualSchemaHash).to.be.equal(expectedSchemaHash); - }); - it('should return the correct domain separator', async () => { - const schema = stringifySchema(orderConstants.DEFAULT_DOMAIN_SCHEMA); - const schemaHash = ethUtil.sha3(Buffer.from(schema)); - const payload = Buffer.concat([ - schemaHash, - ethUtil.sha3(Buffer.from(orderConstants.EXCHANGE_DOMAIN_NAME)), - ethUtil.sha3(Buffer.from(orderConstants.EXCHANGE_DOMAIN_VERSION)), - ethUtil.setLengthLeft(ethUtil.toBuffer(chainId), 32), - ethUtil.setLengthLeft(ethUtil.toBuffer(libs.address), 32), - ]); - const expectedDomain = ethUtil.bufferToHex(ethUtil.sha3(payload)); - const actualDomain = await libs.getDomainSeparator.callAsync(); - expect(actualDomain).to.be.equal(expectedDomain); - }); - it('should return a different domain separator if chainId is different', async () => { - const domain1 = await libsAlternateChain.getDomainSeparator.callAsync(); - const domain2 = await libs.getDomainSeparator.callAsync(); - expect(domain1).to.be.not.equal(domain2); - }); - }); -}); diff --git a/contracts/exchange-libs/test/lib_eip712_exchange_domain.ts b/contracts/exchange-libs/test/lib_eip712_exchange_domain.ts index 245c3ef3fd..e23d77cbb5 100644 --- a/contracts/exchange-libs/test/lib_eip712_exchange_domain.ts +++ b/contracts/exchange-libs/test/lib_eip712_exchange_domain.ts @@ -1,65 +1,49 @@ -import { blockchainTests, constants, describe, expect, hexRandom } from '@0x/contracts-test-utils'; +import { addressUtils, blockchainTests, constants, expect } from '@0x/contracts-test-utils'; import { BigNumber, signTypedDataUtils } from '@0x/utils'; import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; -import { artifacts, TestLibsContract } from '../src'; +import { artifacts, TestLibEIP712ExchangeDomainContract } from '../src'; blockchainTests('LibEIP712ExchangeDomain', env => { - let libsContract: TestLibsContract; - let exchangeDomainHash: string; - const CHAIN_ID = 1337; - - // Random generator functions - const randomHash = () => hexRandom(constants.WORD_LENGTH); - - /** - * Tests a specific instance of EIP712 message hashing. - * @param lib The LibEIP712 contract to call. - * @param domainHash The hash of the EIP712 domain of this instance. - * @param hashStruct The hash of the struct of this instance. - */ - async function testHashEIP712MessageAsync(hashStruct: string): Promise { - // Remove the hex-prefix from the exchangeDomainHash and the hashStruct - const unprefixedHashStruct = hashStruct.slice(2, hashStruct.length); - - // Hash the provided input to get the expected hash - const input = '0x1901'.concat(exchangeDomainHash, unprefixedHashStruct); - const expectedHash = '0x'.concat(ethUtil.sha3(input).toString('hex')); - - // Get the actual hash by calling the smart contract - const actualHash = await libsContract.hashEIP712ExchangeMessage.callAsync(hashStruct); - - // Verify that the actual hash matches the expected hash - expect(actualHash).to.be.eq(expectedHash); - } - - before(async () => { - libsContract = await TestLibsContract.deployFrom0xArtifactAsync( - artifacts.TestLibs, - env.provider, - env.txDefaults, - new BigNumber(CHAIN_ID), - ); - - // Generate the domain hash of 0x Exchange V3 - exchangeDomainHash = signTypedDataUtils - .generateDomainHash({ - name: '0x Protocol', - version: '3.0.0', - chainId: CHAIN_ID, - verifyingContractAddress: libsContract.address, - }) - .toString('hex'); - }); - - describe('hashEIP712ExchangeMessage', () => { - it('should correctly match an empty hash', async () => { - await testHashEIP712MessageAsync(constants.NULL_BYTES32); + describe('constructor', () => { + it('should calculate the correct domain hash when verifyingContractAddressIfExists is set to null', async () => { + const chainId = 1; + const libEIP712ExchangeDomainContract = await TestLibEIP712ExchangeDomainContract.deployFrom0xArtifactAsync( + artifacts.TestLibEIP712ExchangeDomain, + env.provider, + env.txDefaults, + new BigNumber(chainId), + constants.NULL_ADDRESS, + ); + const domain = { + verifyingContractAddress: libEIP712ExchangeDomainContract.address, + chainId, + name: constants.EIP712_DOMAIN_NAME, + version: constants.EIP712_DOMAIN_VERSION, + }; + const expectedDomainHash = ethUtil.bufferToHex(signTypedDataUtils.generateDomainHash(domain)); + const actualDomainHash = await libEIP712ExchangeDomainContract.EIP712_EXCHANGE_DOMAIN_HASH.callAsync(); + expect(actualDomainHash).to.be.equal(expectedDomainHash); }); - - it('should correctly match a non-empty hash', async () => { - await testHashEIP712MessageAsync(randomHash()); + it('should calculate the correct domain hash when verifyingContractAddressIfExists is set to a non-null address', async () => { + const chainId = 1; + const verifyingContractAddress = addressUtils.generatePseudoRandomAddress(); + const libEIP712ExchangeDomainContract = await TestLibEIP712ExchangeDomainContract.deployFrom0xArtifactAsync( + artifacts.TestLibEIP712ExchangeDomain, + env.provider, + env.txDefaults, + new BigNumber(chainId), + verifyingContractAddress, + ); + const domain = { + verifyingContractAddress, + chainId, + name: constants.EIP712_DOMAIN_NAME, + version: constants.EIP712_DOMAIN_VERSION, + }; + const expectedDomainHash = ethUtil.bufferToHex(signTypedDataUtils.generateDomainHash(domain)); + const actualDomainHash = await libEIP712ExchangeDomainContract.EIP712_EXCHANGE_DOMAIN_HASH.callAsync(); + expect(actualDomainHash).to.be.equal(expectedDomainHash); }); }); }); diff --git a/contracts/exchange-libs/test/lib_fill_results.ts b/contracts/exchange-libs/test/lib_fill_results.ts index a57db9bb38..9ba60471a4 100644 --- a/contracts/exchange-libs/test/lib_fill_results.ts +++ b/contracts/exchange-libs/test/lib_fill_results.ts @@ -1,23 +1,280 @@ -import { blockchainTests, constants, describe, expect } from '@0x/contracts-test-utils'; +import { + blockchainTests, + constants, + describe, + expect, + hexRandom, + testCombinatoriallyWithReferenceFunc, + uint256Values, +} from '@0x/contracts-test-utils'; +import { LibMathRevertErrors } from '@0x/order-utils'; +import { FillResults, MatchedFillResults, OrderWithoutDomain as Order } from '@0x/types'; import { BigNumber, SafeMathRevertErrors } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; import * as _ from 'lodash'; -import { artifacts, ReferenceFunctions, TestLibsContract } from '../src'; +import { artifacts, ReferenceFunctions, TestLibFillResultsContract } from '../src'; blockchainTests('LibFillResults', env => { - const CHAIN_ID = 1337; + interface PartialMatchedFillResults { + left: Partial; + right: Partial; + profitInLeftMakerAsset?: BigNumber; + profitInRightMakerAsset?: BigNumber; + } + const { ONE_ETHER, MAX_UINT256 } = constants; - let libsContract: TestLibsContract; + const EMPTY_ORDER: Order = { + senderAddress: constants.NULL_ADDRESS, + makerAddress: constants.NULL_ADDRESS, + takerAddress: constants.NULL_ADDRESS, + makerFee: constants.ZERO_AMOUNT, + takerFee: constants.ZERO_AMOUNT, + makerAssetAmount: constants.ZERO_AMOUNT, + takerAssetAmount: constants.ZERO_AMOUNT, + makerAssetData: constants.NULL_BYTES, + takerAssetData: constants.NULL_BYTES, + makerFeeAssetData: constants.NULL_BYTES, + takerFeeAssetData: constants.NULL_BYTES, + salt: constants.ZERO_AMOUNT, + feeRecipientAddress: constants.NULL_ADDRESS, + expirationTimeSeconds: constants.ZERO_AMOUNT, + }; + + const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH); + const randomAssetData = () => hexRandom(36); + const randomUint256 = () => new BigNumber(hexRandom(constants.WORD_LENGTH)); + + let libsContract: TestLibFillResultsContract; + let makerAddressLeft: string; + let makerAddressRight: string; before(async () => { - libsContract = await TestLibsContract.deployFrom0xArtifactAsync( - artifacts.TestLibs, + const accounts = await env.getAccountAddressesAsync(); + makerAddressLeft = accounts[0]; + makerAddressRight = accounts[1]; + + libsContract = await TestLibFillResultsContract.deployFrom0xArtifactAsync( + artifacts.TestLibFillResults, env.provider, env.txDefaults, - new BigNumber(CHAIN_ID), ); }); + describe('calculateFillResults', () => { + describe.optional('combinatorial tests', () => { + function makeOrder( + makerAssetAmount: BigNumber, + takerAssetAmount: BigNumber, + makerFee: BigNumber, + takerFee: BigNumber, + ): Order { + return { + ...EMPTY_ORDER, + 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. + return ReferenceFunctions.calculateFillResults( + makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount), + takerAssetFilledAmount, + ); + } + + async function testCalculateFillResultsAsync( + orderTakerAssetAmount: BigNumber, + takerAssetFilledAmount: BigNumber, + otherAmount: BigNumber, + ): Promise { + const order = makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount); + return libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount); + } + + testCombinatoriallyWithReferenceFunc( + 'calculateFillResults', + referenceCalculateFillResultsAsync, + testCalculateFillResultsAsync, + [uint256Values, uint256Values, uint256Values], + ); + }); + + describe('explicit tests', () => { + const MAX_UINT256_ROOT = constants.MAX_UINT256_ROOT; + function makeOrder(details?: Partial): Order { + return _.assign({}, EMPTY_ORDER, details); + } + + it('matches the output of the reference function', async () => { + const order = makeOrder({ + makerAssetAmount: ONE_ETHER, + takerAssetAmount: ONE_ETHER.times(2), + makerFee: ONE_ETHER.times(0.0023), + takerFee: ONE_ETHER.times(0.0025), + }); + const takerAssetFilledAmount = ONE_ETHER.dividedToIntegerBy(3); + const expected = ReferenceFunctions.calculateFillResults(order, takerAssetFilledAmount); + const actual = await libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount); + expect(actual).to.deep.eq(expected); + }); + + it('reverts if computing `fillResults.makerAssetFilledAmount` overflows', async () => { + // All values need to be large to ensure we don't trigger a RoundingError. + const order = makeOrder({ + makerAssetAmount: MAX_UINT256_ROOT.times(2), + takerAssetAmount: MAX_UINT256_ROOT, + }); + const takerAssetFilledAmount = MAX_UINT256_ROOT; + const expectedError = new SafeMathRevertErrors.SafeMathError( + SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow, + takerAssetFilledAmount, + order.makerAssetAmount, + ); + return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( + expectedError, + ); + }); + + it('reverts if computing `fillResults.makerFeePaid` overflows', async () => { + // All values need to be large to ensure we don't trigger a RoundingError. + const order = makeOrder({ + makerAssetAmount: MAX_UINT256_ROOT, + takerAssetAmount: MAX_UINT256_ROOT, + makerFee: MAX_UINT256_ROOT.times(11), + }); + const takerAssetFilledAmount = MAX_UINT256_ROOT.dividedToIntegerBy(10); + const makerAssetFilledAmount = ReferenceFunctions.getPartialAmountFloor( + takerAssetFilledAmount, + order.takerAssetAmount, + order.makerAssetAmount, + ); + const expectedError = new SafeMathRevertErrors.SafeMathError( + SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow, + makerAssetFilledAmount, + order.makerFee, + ); + return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( + expectedError, + ); + }); + + it('reverts if computing `fillResults.takerFeePaid` overflows', async () => { + // All values need to be large to ensure we don't trigger a RoundingError. + const order = makeOrder({ + makerAssetAmount: MAX_UINT256_ROOT, + takerAssetAmount: MAX_UINT256_ROOT, + takerFee: MAX_UINT256_ROOT.times(11), + }); + const takerAssetFilledAmount = MAX_UINT256_ROOT.dividedToIntegerBy(10); + const expectedError = new SafeMathRevertErrors.SafeMathError( + SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow, + takerAssetFilledAmount, + order.takerFee, + ); + return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( + expectedError, + ); + }); + + it('reverts if `order.makerAssetAmount` is 0', async () => { + const order = makeOrder({ + makerAssetAmount: constants.ZERO_AMOUNT, + takerAssetAmount: ONE_ETHER, + }); + const takerAssetFilledAmount = ONE_ETHER; + const expectedError = new LibMathRevertErrors.DivisionByZeroError(); + return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( + expectedError, + ); + }); + + it('reverts if `order.takerAssetAmount` is 0', async () => { + const order = makeOrder({ + makerAssetAmount: ONE_ETHER, + takerAssetAmount: constants.ZERO_AMOUNT, + }); + const takerAssetFilledAmount = ONE_ETHER; + const expectedError = new LibMathRevertErrors.DivisionByZeroError(); + return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( + expectedError, + ); + }); + + it('reverts if there is a rounding error computing `makerAsssetFilledAmount`', async () => { + const order = makeOrder({ + makerAssetAmount: new BigNumber(100), + takerAssetAmount: ONE_ETHER, + }); + const takerAssetFilledAmount = order.takerAssetAmount.dividedToIntegerBy(3); + const expectedError = new LibMathRevertErrors.RoundingError( + takerAssetFilledAmount, + order.takerAssetAmount, + order.makerAssetAmount, + ); + return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( + expectedError, + ); + }); + + it('reverts if there is a rounding error computing `makerFeePaid`', async () => { + const order = makeOrder({ + makerAssetAmount: ONE_ETHER, + takerAssetAmount: ONE_ETHER, + makerFee: new BigNumber(100), + }); + const takerAssetFilledAmount = order.takerAssetAmount.dividedToIntegerBy(3); + const makerAssetFilledAmount = ReferenceFunctions.getPartialAmountFloor( + takerAssetFilledAmount, + order.takerAssetAmount, + order.makerAssetAmount, + ); + const expectedError = new LibMathRevertErrors.RoundingError( + makerAssetFilledAmount, + order.makerAssetAmount, + order.makerFee, + ); + return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( + expectedError, + ); + }); + + it('reverts if there is a rounding error computing `takerFeePaid`', async () => { + const order = makeOrder({ + makerAssetAmount: ONE_ETHER, + takerAssetAmount: ONE_ETHER, + takerFee: new BigNumber(100), + }); + const takerAssetFilledAmount = order.takerAssetAmount.dividedToIntegerBy(3); + const makerAssetFilledAmount = ReferenceFunctions.getPartialAmountFloor( + takerAssetFilledAmount, + order.takerAssetAmount, + order.makerAssetAmount, + ); + const expectedError = new LibMathRevertErrors.RoundingError( + makerAssetFilledAmount, + order.makerAssetAmount, + order.takerFee, + ); + return expect(libsContract.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( + expectedError, + ); + }); + }); + }); + describe('addFillResults', () => { describe('explicit tests', () => { const DEFAULT_FILL_RESULTS = [ @@ -87,4 +344,1320 @@ blockchainTests('LibFillResults', env => { }); }); }); + + const EMPTY_FILL_RESULTS: FillResults = { + makerAssetFilledAmount: constants.ZERO_AMOUNT, + takerAssetFilledAmount: constants.ZERO_AMOUNT, + makerFeePaid: constants.ZERO_AMOUNT, + takerFeePaid: constants.ZERO_AMOUNT, + }; + + const EMPTY_MATCHED_FILL_RESULTS: MatchedFillResults = { + left: EMPTY_FILL_RESULTS, + right: EMPTY_FILL_RESULTS, + profitInLeftMakerAsset: constants.ZERO_AMOUNT, + profitInRightMakerAsset: constants.ZERO_AMOUNT, + }; + + const COMMON_MATCHED_FILL_RESULTS = { + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 18), + profitInRightMakerAsset: constants.ZERO_AMOUNT, + }; + + function createMatchedFillResults(partialMatchedFillResults: PartialMatchedFillResults): MatchedFillResults { + const matchedFillResults = EMPTY_MATCHED_FILL_RESULTS; + matchedFillResults.left = _.assign({}, EMPTY_FILL_RESULTS, partialMatchedFillResults.left); + matchedFillResults.right = _.assign({}, EMPTY_FILL_RESULTS, partialMatchedFillResults.right); + matchedFillResults.profitInLeftMakerAsset = + partialMatchedFillResults.profitInLeftMakerAsset || constants.ZERO_AMOUNT; + matchedFillResults.profitInRightMakerAsset = + partialMatchedFillResults.profitInRightMakerAsset || constants.ZERO_AMOUNT; + return matchedFillResults; + } + + blockchainTests('calculateMatchedFillResults', async () => { + /** + * Asserts that the results of calling `calculateMatchedFillResults()` is consistent with the results that are expected. + */ + async function assertCalculateMatchedFillResultsAsync( + expectedMatchedFillResults: MatchedFillResults, + leftOrder: Order, + rightOrder: Order, + leftOrderTakerAssetFilledAmount: BigNumber, + rightOrderTakerAssetFilledAmount: BigNumber, + from?: string, + ): Promise { + const actualMatchedFillResults = await libsContract.calculateMatchedFillResults.callAsync( + leftOrder, + rightOrder, + leftOrderTakerAssetFilledAmount, + rightOrderTakerAssetFilledAmount, + false, + { from }, + ); + expect(actualMatchedFillResults).to.be.deep.eq(expectedMatchedFillResults); + } + + const ORDER_DEFAULTS = { + ...constants.STATIC_ORDER_PARAMS, + makerAddress: randomAddress(), + takerAddress: randomAddress(), + senderAddress: randomAddress(), + makerAssetData: randomAssetData(), + takerAssetData: randomAssetData(), + makerFeeAssetData: randomAssetData(), + takerFeeAssetData: randomAssetData(), + feeRecipientAddress: randomAddress(), + expirationTimeSeconds: randomUint256(), + salt: randomUint256(), + domain: { + verifyingContractAddress: constants.NULL_ADDRESS, + chainId: 1337, // The chain id for the isolated exchange + }, + }; + + function makeOrder(details?: Partial): Order { + return _.assign({}, ORDER_DEFAULTS, details); + } + + before(async () => { + ORDER_DEFAULTS.domain.verifyingContractAddress = libsContract.address; + }); + + it('should correctly calculate the results when only the right order is fully filled', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(17, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(98, 0), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(75, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(13, 0), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(75, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(75, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + }); + await assertCalculateMatchedFillResultsAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should correctly calculate the results when only the left order is fully filled', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(15, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(90, 0), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(14, 0), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(15, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(90, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(90, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('92.7835051546391752'), 16), // 92.85% + takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('92.8571428571428571'), 16), // 92.85% + }, + profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(2, 0), + }); + await assertCalculateMatchedFillResultsAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should give right maker a better price when rounding', async () => { + const leftOrder = makeOrder({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(16, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(22, 0), + }); + const rightOrder = makeOrder({ + makerAddress: makerAddressRight, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(83, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(49, 0), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(16, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(22, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(22, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('26.5060240963855421'), 16), // 26.506% + takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('26.5306122448979591'), 16), // 26.531% + }, + profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 0), + }); + await assertCalculateMatchedFillResultsAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should give left maker a better sell price when rounding', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(12, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), + makerAddress: makerAddressLeft, + }); + const rightOrder = makeOrder({ + makerAddress: makerAddressRight, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(89, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 0), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(11, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.6666666666666666'), 16), // 91.6% + takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.7525773195876288'), 16), // 91.75% + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(10, 0), + }); + await assertCalculateMatchedFillResultsAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('Should give right maker and right taker a favorable fee price when rounding', async () => { + const leftOrder = makeOrder({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(16, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(22, 0), + }); + const rightOrder = makeOrder({ + makerAddress: makerAddressRight, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(83, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(49, 0), + makerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), + takerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(16, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(22, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(22, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(2650, 0), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(2653, 0), + }, + profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 0), + }); + await assertCalculateMatchedFillResultsAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('Should give left maker and left taker a favorable fee price when rounding', async () => { + // Create orders to match + const leftOrder = makeOrder({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(12, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), + makerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), + takerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), + }); + const rightOrder = makeOrder({ + makerAddress: makerAddressRight, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(89, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 0), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(11, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(9166, 0), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(9175, 0), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(10, 0), + }); + await assertCalculateMatchedFillResultsAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('Should transfer correct amounts when right order fill amount deviates from amount derived by `Exchange.fillOrder`', async () => { + const leftOrder = makeOrder({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(1000, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1005, 0), + }); + const rightOrder = makeOrder({ + makerAddress: makerAddressRight, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(2126, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1063, 0), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1000, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1005, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1005, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(503, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('47.2718720602069614'), 16), // 47.27% + takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('47.3189087488240827'), 16), // 47.31% + }, + profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(497, 0), + }); + await assertCalculateMatchedFillResultsAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should transfer the correct amounts when orders completely fill each other', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 18), + }); + await assertCalculateMatchedFillResultsAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + }); + await assertCalculateMatchedFillResultsAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should transfer the correct amounts when left order is completely filled and right order is partially filled', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(20, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(4, 18), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(50, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(50, 16), + }, + profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 18), + }); + await assertCalculateMatchedFillResultsAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should transfer the correct amounts when right order is completely filled and left order is partially filled', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(10, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(10, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 18), + }); + await assertCalculateMatchedFillResultsAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + it('should transfer the correct amounts if fee recipient is the same across both matched orders', async () => { + const feeRecipientAddress = randomAddress(); + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + feeRecipientAddress, + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + feeRecipientAddress, + }); + await assertCalculateMatchedFillResultsAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should transfer the correct amounts if taker == leftMaker', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + }); + await assertCalculateMatchedFillResultsAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + leftOrder.makerAddress, + ); + }); + + it('should transfer the correct amounts if taker == leftMaker', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + }); + await assertCalculateMatchedFillResultsAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + rightOrder.makerAddress, + ); + }); + + it('should transfer the correct amounts if taker == leftFeeRecipient', async () => { + const feeRecipientAddressLeft = randomAddress(); + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + feeRecipientAddress: feeRecipientAddressLeft, + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + }); + await assertCalculateMatchedFillResultsAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + feeRecipientAddressLeft, + ); + }); + + it('should transfer the correct amounts if taker == rightFeeRecipient', async () => { + const feeRecipientAddressRight = randomAddress(); + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + feeRecipientAddress: feeRecipientAddressRight, + }); + await assertCalculateMatchedFillResultsAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + feeRecipientAddressRight, + ); + }); + + it('should transfer the correct amounts if leftMaker == leftFeeRecipient && rightMaker == rightFeeRecipient', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + feeRecipientAddress: makerAddressLeft, + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + feeRecipientAddress: makerAddressRight, + }); + await assertCalculateMatchedFillResultsAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should transfer the correct amounts if leftMaker == leftFeeRecipient && leftMakerFeeAsset == leftTakerAsset', async () => { + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + }); + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + makerFeeAssetData: rightOrder.makerAssetData, + feeRecipientAddress: makerAddressLeft, + }); + await assertCalculateMatchedFillResultsAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should transfer the correct amounts if rightMaker == rightFeeRecipient && rightMakerFeeAsset == rightTakerAsset', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + makerFeeAssetData: leftOrder.makerAssetData, + feeRecipientAddress: makerAddressRight, + }); + await assertCalculateMatchedFillResultsAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should transfer the correct amounts if rightMaker == rightFeeRecipient && rightTakerAsset == rightMakerFeeAsset && leftMaker == leftFeeRecipient && leftTakerAsset == leftMakerFeeAsset', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + feeRecipientAddress: makerAddressLeft, + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + makerFeeAssetData: leftOrder.makerAssetData, + feeRecipientAddress: makerAddressRight, + }); + await assertCalculateMatchedFillResultsAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + }); + + blockchainTests('calculateMatchedFillResultsWithMaximalFill', async () => { + /** + * Asserts that the results of calling `calculateMatchedFillResults()` is consistent with the results that are expected. + */ + async function assertCalculateMatchedFillResultsWithMaximalFillAsync( + expectedMatchedFillResults: MatchedFillResults, + leftOrder: Order, + rightOrder: Order, + leftOrderTakerAssetFilledAmount: BigNumber, + rightOrderTakerAssetFilledAmount: BigNumber, + from?: string, + ): Promise { + const actualMatchedFillResults = await libsContract.calculateMatchedFillResults.callAsync( + leftOrder, + rightOrder, + leftOrderTakerAssetFilledAmount, + rightOrderTakerAssetFilledAmount, + true, + { from }, + ); + expect(actualMatchedFillResults).to.be.deep.eq(expectedMatchedFillResults); + } + + const ORDER_DEFAULTS = { + ...constants.STATIC_ORDER_PARAMS, + makerAddress: randomAddress(), + takerAddress: randomAddress(), + senderAddress: randomAddress(), + makerAssetData: randomAssetData(), + takerAssetData: randomAssetData(), + makerFeeAssetData: randomAssetData(), + takerFeeAssetData: randomAssetData(), + feeRecipientAddress: randomAddress(), + expirationTimeSeconds: randomUint256(), + salt: randomUint256(), + domain: { + verifyingContractAddress: constants.NULL_ADDRESS, + chainId: 1337, // The chain id for the isolated exchange + }, + }; + + function makeOrder(details?: Partial): Order { + return _.assign({}, ORDER_DEFAULTS, details); + } + + before(async () => { + ORDER_DEFAULTS.domain.verifyingContractAddress = libsContract.address; + }); + + it('should transfer correct amounts when right order is fully filled', async () => { + const leftOrder = makeOrder({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(17, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(98, 0), + }); + const rightOrder = makeOrder({ + makerAddress: makerAddressRight, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(75, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(13, 0), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(75, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), // 76.47% + takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(75, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('Should transfer correct amounts when left order is fully filled', async () => { + const leftOrder = makeOrder({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(15, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(90, 0), + }); + const rightOrder = makeOrder({ + makerAddress: makerAddressRight, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(196, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(28, 0), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(15, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(90, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(105, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(15, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('53.5714285714285714'), 16), // 53.57% + takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('53.5714285714285714'), 16), // 53.57% + }, + profitInLeftMakerAsset: constants.ZERO_AMOUNT, + profitInRightMakerAsset: Web3Wrapper.toBaseUnitAmount(15, 0), + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('Should transfer correct amounts when left order is fully filled', async () => { + const leftOrder = makeOrder({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(16, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(22, 0), + }); + const rightOrder = makeOrder({ + makerAddress: makerAddressRight, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(87, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(48, 0), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(16, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(22, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(29, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(16, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('33.3333333333333333'), 16), // 33.33% + takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('33.3333333333333333'), 16), // 33.33% + }, + profitInLeftMakerAsset: constants.ZERO_AMOUNT, + profitInRightMakerAsset: Web3Wrapper.toBaseUnitAmount(7, 0), + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should fully fill both orders and pay out profit in both maker assets', async () => { + const leftOrder = makeOrder({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(7, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(4, 0), + }); + const rightOrder = makeOrder({ + makerAddress: makerAddressRight, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(8, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(6, 0), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(7, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(4, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(8, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(6, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(1, 0), + profitInRightMakerAsset: Web3Wrapper.toBaseUnitAmount(4, 0), + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('Should give left maker a better sell price when rounding', async () => { + const leftOrder = makeOrder({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(12, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), + }); + const rightOrder = makeOrder({ + makerAddress: makerAddressRight, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(89, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 0), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(11, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.6666666666666666'), 16), // 91.6% + takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.7525773195876288'), 16), // 91.75% + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(10, 0), + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('Should give right maker and right taker a favorable fee price when rounding', async () => { + const leftOrder = makeOrder({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(16, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(22, 0), + }); + const rightOrder = makeOrder({ + makerAddress: makerAddressRight, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(87, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(48, 0), + makerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), + takerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(16, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(22, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(29, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(16, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(3333, 0), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(3333, 0), + }, + profitInRightMakerAsset: Web3Wrapper.toBaseUnitAmount(7, 0), + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('Should give left maker and left taker a favorable fee price when rounding', async () => { + const leftOrder = makeOrder({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(12, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), + makerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), + takerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), + }); + const rightOrder = makeOrder({ + makerAddress: makerAddressRight, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(89, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 0), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(11, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(9166, 0), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(9175, 0), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(10, 0), + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should transfer the correct amounts when consecutive calls are used to completely fill the left order', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + }); + const expectedMatchedFillResults = { + ...COMMON_MATCHED_FILL_RESULTS, + left: { + ...COMMON_MATCHED_FILL_RESULTS.left, + makerFeePaid: Web3Wrapper.toBaseUnitAmount(10, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(10, 16), + }, + }; + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + const rightOrder2 = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), + }); + const expectedMatchedFillResults2 = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(45, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(90, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(90, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(45, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), + }, + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + expectedMatchedFillResults2, + leftOrder, + rightOrder2, + Web3Wrapper.toBaseUnitAmount(10, 18), + constants.ZERO_AMOUNT, + ); + }); + + it('Should transfer correct amounts when right order fill amount deviates from amount derived by `Exchange.fillOrder`', async () => { + const leftOrder = makeOrder({ + makerAddress: makerAddressLeft, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(1000, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1005, 0), + }); + const rightOrder = makeOrder({ + makerAddress: makerAddressRight, + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(2126, 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1063, 0), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1000, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1005, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2000, 0), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1000, 0), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('94.0733772342427093'), 16), // 94.07% + takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('94.0733772342427093'), 16), // 94.07% + }, + profitInRightMakerAsset: Web3Wrapper.toBaseUnitAmount(995, 0), + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should transfer the correct amounts when consecutive calls are used to completely fill the right order', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + }); + + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), + }); + const expectedMatchedFillResults = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(10, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(10, 16), + }, + profitInRightMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 18), + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + expectedMatchedFillResults, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + // 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 leftOrder2 = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), + }); + const expectedMatchedFillResults2 = createMatchedFillResults({ + left: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(90, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(45, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), + }, + right: { + makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(45, 18), + takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(90, 18), + makerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), + takerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), + }, + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + expectedMatchedFillResults2, + leftOrder2, + rightOrder, + constants.ZERO_AMOUNT, + Web3Wrapper.toBaseUnitAmount(10, 18), + ); + }); + + it('should transfer the correct amounts if fee recipient is the same across both matched orders', async () => { + const feeRecipientAddress = randomAddress(); + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + feeRecipientAddress, + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + feeRecipientAddress, + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should transfer the correct amounts if taker == leftMaker', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + leftOrder.makerAddress, + ); + }); + + it('should transfer the correct amounts if taker == rightMaker', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + rightOrder.makerAddress, + ); + }); + + it('should transfer the correct amounts if taker == leftFeeRecipient', async () => { + const feeRecipientAddress = randomAddress(); + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + feeRecipientAddress, + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + feeRecipientAddress, + ); + }); + + it('should transfer the correct amounts if taker == rightFeeRecipient', async () => { + const feeRecipientAddress = randomAddress(); + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + feeRecipientAddress, + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + feeRecipientAddress, + ); + }); + + it('should transfer the correct amounts if leftMaker == leftFeeRecipient && rightMaker == rightFeeRecipient', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + feeRecipientAddress: makerAddressLeft, + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + feeRecipientAddress: makerAddressRight, + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should transfer the correct amounts if leftMaker == leftFeeRecipient && leftMakerFeeAsset == leftTakerAsset', async () => { + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + }); + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + makerFeeAssetData: rightOrder.makerAssetData, + feeRecipientAddress: makerAddressLeft, + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should transfer the correct amounts if rightMaker == rightFeeRecipient && rightMakerFeeAsset == rightTakerAsset', async () => { + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + makerFeeAssetData: leftOrder.makerAssetData, + feeRecipientAddress: makerAddressRight, + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + + it('should transfer the correct amounts if rightMaker == rightFeeRecipient && rightTakerAsset == rightMakerFeeAsset && leftMaker == leftFeeRecipient && leftTakerAsset == leftMakerFeeAsset', async () => { + const makerFeeAssetData = randomAssetData(); + const leftOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + makerFeeAssetData, + feeRecipientAddress: makerAddressLeft, + }); + const rightOrder = makeOrder({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), + makerFeeAssetData: leftOrder.makerAssetData, + feeRecipientAddress: makerAddressRight, + }); + await assertCalculateMatchedFillResultsWithMaximalFillAsync( + COMMON_MATCHED_FILL_RESULTS, + leftOrder, + rightOrder, + constants.ZERO_AMOUNT, + constants.ZERO_AMOUNT, + ); + }); + }); }); +// tslint:disable-line:max-file-line-count diff --git a/contracts/exchange-libs/test/lib_math.ts b/contracts/exchange-libs/test/lib_math.ts index a25d23fa6f..0c54ad5bf3 100644 --- a/contracts/exchange-libs/test/lib_math.ts +++ b/contracts/exchange-libs/test/lib_math.ts @@ -9,19 +9,17 @@ import { import { LibMathRevertErrors } from '@0x/order-utils'; import { BigNumber, SafeMathRevertErrors } from '@0x/utils'; -import { artifacts, ReferenceFunctions, TestLibsContract } from '../src'; +import { artifacts, ReferenceFunctions, TestLibMathContract } from '../src'; blockchainTests('LibMath', env => { - const CHAIN_ID = 1337; const { ONE_ETHER, MAX_UINT256, MAX_UINT256_ROOT, ZERO_AMOUNT } = constants; - let libsContract: TestLibsContract; + let libsContract: TestLibMathContract; before(async () => { - libsContract = await TestLibsContract.deployFrom0xArtifactAsync( - artifacts.TestLibs, + libsContract = await TestLibMathContract.deployFrom0xArtifactAsync( + artifacts.TestLibMath, env.provider, env.txDefaults, - new BigNumber(CHAIN_ID), ); }); diff --git a/contracts/exchange-libs/test/lib_order.ts b/contracts/exchange-libs/test/lib_order.ts index 61a42b4b4f..364c017305 100644 --- a/contracts/exchange-libs/test/lib_order.ts +++ b/contracts/exchange-libs/test/lib_order.ts @@ -2,13 +2,13 @@ import { blockchainTests, constants, describe, expect, hexRandom } from '@0x/con import { eip712Utils, orderHashUtils } from '@0x/order-utils'; import { Order } from '@0x/types'; import { BigNumber, signTypedDataUtils } from '@0x/utils'; +import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { artifacts, TestLibsContract } from '../src'; +import { artifacts, TestLibOrderContract } from '../src'; blockchainTests('LibOrder', env => { - const CHAIN_ID = 1337; - let libsContract: TestLibsContract; + let libOrderContract: TestLibOrderContract; const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH); const randomHash = () => hexRandom(constants.WORD_LENGTH); @@ -37,38 +37,44 @@ blockchainTests('LibOrder', env => { }; before(async () => { - libsContract = await TestLibsContract.deployFrom0xArtifactAsync( - artifacts.TestLibs, + libOrderContract = await TestLibOrderContract.deployFrom0xArtifactAsync( + artifacts.TestLibOrder, env.provider, env.txDefaults, - new BigNumber(CHAIN_ID), ); }); /** - * Tests the `getOrderHash()` function against a reference hash. + * Tests the `getTypedDataHash()` function against a reference hash. */ - async function testGetOrderHashAsync(order: Order): Promise { + async function testGetTypedDataHashAsync(order: Order): Promise { const expectedHash = orderHashUtils.getOrderHashHex(order); - const actualHash = await libsContract.getOrderHash.callAsync(order); + const domainHash = ethUtil.bufferToHex( + signTypedDataUtils.generateDomainHash({ + ...order.domain, + name: constants.EIP712_DOMAIN_NAME, + version: constants.EIP712_DOMAIN_VERSION, + }), + ); + const actualHash = await libOrderContract.getTypedDataHash.callAsync(order, domainHash); expect(actualHash).to.be.eq(expectedHash); } - describe('getOrderHash', () => { + describe('getTypedDataHash', () => { it('should correctly hash an empty order', async () => { - await testGetOrderHashAsync({ + await testGetTypedDataHashAsync({ ...EMPTY_ORDER, domain: { - verifyingContractAddress: libsContract.address, - chainId: 1337, + ...EMPTY_ORDER.domain, + verifyingContractAddress: libOrderContract.address, }, }); }); it('should correctly hash a non-empty order', async () => { - await testGetOrderHashAsync({ + await testGetTypedDataHashAsync({ domain: { - verifyingContractAddress: libsContract.address, + verifyingContractAddress: libOrderContract.address, chainId: 1337, }, senderAddress: randomAddress(), @@ -87,27 +93,46 @@ blockchainTests('LibOrder', env => { expirationTimeSeconds: randomUint256(), }); }); + + it('orderHash should differ if the domain hash is different', async () => { + const domainHash1 = ethUtil.bufferToHex( + signTypedDataUtils.generateDomainHash({ + ...EMPTY_ORDER.domain, + name: constants.EIP712_DOMAIN_NAME, + version: constants.EIP712_DOMAIN_VERSION, + }), + ); + const domainHash2 = ethUtil.bufferToHex( + signTypedDataUtils.generateDomainHash({ + ...EMPTY_ORDER.domain, + name: constants.EIP712_DOMAIN_NAME, + version: constants.EIP712_DOMAIN_VERSION, + chainId: 1337, + }), + ); + const orderHashHex1 = await libOrderContract.getTypedDataHash.callAsync(EMPTY_ORDER, domainHash1); + const orderHashHex2 = await libOrderContract.getTypedDataHash.callAsync(EMPTY_ORDER, domainHash2); + expect(orderHashHex1).to.be.not.equal(orderHashHex2); + }); }); /** - * Tests the `_hashOrder()` function against a reference hash. + * Tests the `getStructHash()` function against a reference hash. */ - async function testHashOrderAsync(order: Order): Promise { + async function testGetStructHashAsync(order: Order): Promise { const typedData = eip712Utils.createOrderTypedData(order); - const expectedHash = '0x'.concat( - signTypedDataUtils.generateTypedDataHashWithoutDomain(typedData).toString('hex'), - ); - const actualHash = await libsContract.hashOrder.callAsync(order); + const expectedHash = ethUtil.bufferToHex(signTypedDataUtils.generateTypedDataHashWithoutDomain(typedData)); + const actualHash = await libOrderContract.getStructHash.callAsync(order); expect(actualHash).to.be.eq(expectedHash); } - describe('hashOrder', () => { + describe('getStructHash', () => { it('should correctly hash an empty order', async () => { - await testHashOrderAsync(EMPTY_ORDER); + await testGetStructHashAsync(EMPTY_ORDER); }); it('should correctly hash a non-empty order', async () => { - await testHashOrderAsync({ + await testGetStructHashAsync({ // The domain is not used in this test, so it's okay if it is left empty. domain: { verifyingContractAddress: constants.NULL_ADDRESS, diff --git a/contracts/exchange-libs/test/lib_zero_ex_transaction.ts b/contracts/exchange-libs/test/lib_zero_ex_transaction.ts index f6685e4dc3..f2b02d315d 100644 --- a/contracts/exchange-libs/test/lib_zero_ex_transaction.ts +++ b/contracts/exchange-libs/test/lib_zero_ex_transaction.ts @@ -1,14 +1,14 @@ import { blockchainTests, constants, describe, expect, hexRandom } from '@0x/contracts-test-utils'; -import { eip712Utils } from '@0x/order-utils'; +import { eip712Utils, transactionHashUtils } from '@0x/order-utils'; import { ZeroExTransaction } from '@0x/types'; import { BigNumber, signTypedDataUtils } from '@0x/utils'; +import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; -import { artifacts, TestLibsContract } from '../src'; +import { artifacts, TestLibZeroExTransactionContract } from '../src'; blockchainTests('LibZeroExTransaction', env => { - const CHAIN_ID = 1337; - let libsContract: TestLibsContract; + let libZeroExTransactionContract: TestLibZeroExTransactionContract; const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH); const randomHash = () => hexRandom(constants.WORD_LENGTH); @@ -27,68 +27,97 @@ blockchainTests('LibZeroExTransaction', env => { }; before(async () => { - libsContract = await TestLibsContract.deployFrom0xArtifactAsync( - artifacts.TestLibs, + libZeroExTransactionContract = await TestLibZeroExTransactionContract.deployFrom0xArtifactAsync( + artifacts.TestLibZeroExTransaction, env.provider, env.txDefaults, - new BigNumber(CHAIN_ID), ); }); /** - * Tests the `getTransactionHash()` function against a reference hash. + * Tests the `getTypedDataHash()` function against a reference hash. */ - async function testGetTransactionHashAsync(transaction: ZeroExTransaction): Promise { - const typedData = eip712Utils.createZeroExTransactionTypedData(transaction); - const expectedHash = '0x'.concat(signTypedDataUtils.generateTypedDataHash(typedData).toString('hex')); - const actualHash = await libsContract.getTransactionHash.callAsync(transaction); + async function testGetTypedDataHashAsync(transaction: ZeroExTransaction): Promise { + const expectedHash = transactionHashUtils.getTransactionHashHex(transaction); + const domainHash = ethUtil.bufferToHex( + signTypedDataUtils.generateDomainHash({ + ...transaction.domain, + name: constants.EIP712_DOMAIN_NAME, + version: constants.EIP712_DOMAIN_VERSION, + }), + ); + const actualHash = await libZeroExTransactionContract.getTypedDataHash.callAsync(transaction, domainHash); expect(actualHash).to.be.eq(expectedHash); } - describe('getTransactionHash', () => { + describe('getTypedDataHash', () => { it('should correctly hash an empty transaction', async () => { - await testGetTransactionHashAsync({ + await testGetTypedDataHashAsync({ ...EMPTY_TRANSACTION, domain: { - verifyingContractAddress: libsContract.address, - chainId: 1337, + ...EMPTY_TRANSACTION.domain, + verifyingContractAddress: libZeroExTransactionContract.address, }, }); }); it('should correctly hash a non-empty transaction', async () => { - await testGetTransactionHashAsync({ + await testGetTypedDataHashAsync({ salt: randomUint256(), expirationTimeSeconds: randomUint256(), signerAddress: randomAddress(), data: randomAssetData(), domain: { - verifyingContractAddress: libsContract.address, - chainId: 1337, + ...EMPTY_TRANSACTION.domain, + verifyingContractAddress: libZeroExTransactionContract.address, }, }); }); + it('transactionHash should differ if the domain hash is different', async () => { + const domainHash1 = ethUtil.bufferToHex( + signTypedDataUtils.generateDomainHash({ + ...EMPTY_TRANSACTION.domain, + name: constants.EIP712_DOMAIN_NAME, + version: constants.EIP712_DOMAIN_VERSION, + }), + ); + const domainHash2 = ethUtil.bufferToHex( + signTypedDataUtils.generateDomainHash({ + ...EMPTY_TRANSACTION.domain, + name: constants.EIP712_DOMAIN_NAME, + version: constants.EIP712_DOMAIN_VERSION, + chainId: 1337, + }), + ); + const transactionHashHex1 = await libZeroExTransactionContract.getTypedDataHash.callAsync( + EMPTY_TRANSACTION, + domainHash1, + ); + const transactionHashHex2 = await libZeroExTransactionContract.getTypedDataHash.callAsync( + EMPTY_TRANSACTION, + domainHash2, + ); + expect(transactionHashHex1).to.be.not.equal(transactionHashHex2); + }); }); /** - * Tests the `_hashZeroExTransaction()` function against a reference hash. + * Tests the `getStructHash()` function against a reference hash. */ - async function testHashZeroExTransactionAsync(transaction: ZeroExTransaction): Promise { + async function testGetStructHashAsync(transaction: ZeroExTransaction): Promise { const typedData = eip712Utils.createZeroExTransactionTypedData(transaction); - const expectedHash = '0x'.concat( - signTypedDataUtils.generateTypedDataHashWithoutDomain(typedData).toString('hex'), - ); - const actualHash = await libsContract.hashZeroExTransaction.callAsync(transaction); + const expectedHash = ethUtil.bufferToHex(signTypedDataUtils.generateTypedDataHashWithoutDomain(typedData)); + const actualHash = await libZeroExTransactionContract.getStructHash.callAsync(transaction); expect(actualHash).to.be.eq(expectedHash); } - describe('hashOrder', () => { + describe('getStructHash', () => { it('should correctly hash an empty transaction', async () => { - await testHashZeroExTransactionAsync(EMPTY_TRANSACTION); + await testGetStructHashAsync(EMPTY_TRANSACTION); }); it('should correctly hash a non-empty transaction', async () => { - await testHashZeroExTransactionAsync({ + await testGetStructHashAsync({ salt: randomUint256(), expirationTimeSeconds: randomUint256(), signerAddress: randomAddress(), diff --git a/contracts/exchange-libs/tsconfig.json b/contracts/exchange-libs/tsconfig.json index 3c11b6bdb8..7e46a4852c 100644 --- a/contracts/exchange-libs/tsconfig.json +++ b/contracts/exchange-libs/tsconfig.json @@ -4,11 +4,17 @@ "include": ["./src/**/*", "./test/**/*", "./scripts/**/*", "./generated-wrappers/**/*"], "files": [ "generated-artifacts/LibEIP712ExchangeDomain.json", + "generated-artifacts/LibExchangeRichErrors.json", "generated-artifacts/LibFillResults.json", "generated-artifacts/LibMath.json", + "generated-artifacts/LibMathRichErrors.json", "generated-artifacts/LibOrder.json", "generated-artifacts/LibZeroExTransaction.json", - "generated-artifacts/TestLibs.json" + "generated-artifacts/TestLibEIP712ExchangeDomain.json", + "generated-artifacts/TestLibFillResults.json", + "generated-artifacts/TestLibMath.json", + "generated-artifacts/TestLibOrder.json", + "generated-artifacts/TestLibZeroExTransaction.json" ], "exclude": ["./deploy/solc/solc_bin"] } diff --git a/contracts/exchange/CHANGELOG.json b/contracts/exchange/CHANGELOG.json index 228c6fb43a..ecc5948702 100644 --- a/contracts/exchange/CHANGELOG.json +++ b/contracts/exchange/CHANGELOG.json @@ -153,6 +153,26 @@ { "note": "Add (semi) automated reentrancy tests and remove manual ones", "pr": 2042 + }, + { + "note": "Refactor to use new `LibFillResults`, `LibOrder`, `LibZeroExTransaction`, and `LibMath` to libraries", + "pr": 2055 + }, + { + "note": "Remove `LibExchangeRichErrors` and `IExchangeRichErrors`", + "pr": 2055 + }, + { + "note": "Use built in selectors instead of `LibExchangeSelectors` constants", + "pr": 2055 + }, + { + "note": "Move `calculateFillResults` and `calculateMatchedFillResults` to `LibFillResults` in `exchange-libs` package", + "pr": 2055 + }, + { + "note": "Compile and export all contracts, artifacts, and wrappers by default", + "pr": 2055 } ] }, diff --git a/contracts/exchange/compiler.json b/contracts/exchange/compiler.json index c5d0606d2d..6b74c612c2 100644 --- a/contracts/exchange/compiler.json +++ b/contracts/exchange/compiler.json @@ -22,27 +22,5 @@ ] } } - }, - "contracts": [ - "examples/ExchangeWrapper.sol", - "examples/Whitelist.sol", - "src/Exchange.sol", - "src/interfaces/IAssetProxyDispatcher.sol", - "src/interfaces/IEIP1271Wallet.sol", - "src/interfaces/IExchange.sol", - "src/interfaces/IExchangeCore.sol", - "src/interfaces/IMatchOrders.sol", - "src/interfaces/ISignatureValidator.sol", - "src/interfaces/ITransactions.sol", - "src/interfaces/IWallet.sol", - "src/interfaces/IWrapperFunctions.sol", - "test/IsolatedExchange.sol", - "test/ReentrancyTester.sol", - "test/TestAssetProxyDispatcher.sol", - "test/TestExchangeInternals.sol", - "test/TestLibExchangeRichErrorDecoder.sol", - "test/TestSignatureValidator.sol", - "test/TestValidatorWallet.sol", - "test/TestWrapperFunctions.sol" - ] + } } diff --git a/contracts/exchange/contracts/src/Exchange.sol b/contracts/exchange/contracts/src/Exchange.sol index 337ca7055d..c13e0f0d05 100644 --- a/contracts/exchange/contracts/src/Exchange.sol +++ b/contracts/exchange/contracts/src/Exchange.sol @@ -19,18 +19,18 @@ pragma solidity ^0.5.9; pragma experimental ABIEncoderV2; +import "@0x/contracts-exchange-libs/contracts/src/LibEIP712ExchangeDomain.sol"; import "./MixinMatchOrders.sol"; -import "./MixinSignatureValidator.sol"; import "./MixinWrapperFunctions.sol"; import "./MixinTransferSimulator.sol"; // solhint-disable no-empty-blocks -// MixinAssetProxyDispatcher, MixinExchangeCore, MixinExchangeRichErrors, +// MixinAssetProxyDispatcher, MixinExchangeCore, MixinSignatureValidator, // and MixinTransactions are all inherited via the other Mixins that are // used. contract Exchange is - MixinSignatureValidator, + LibEIP712ExchangeDomain, MixinMatchOrders, MixinWrapperFunctions, MixinTransferSimulator @@ -42,12 +42,5 @@ contract Exchange is constructor (uint256 chainId) public LibEIP712ExchangeDomain(chainId, address(0)) - MixinExchangeCore() - MixinMatchOrders() - MixinSignatureValidator() - MixinTransactions() - MixinAssetProxyDispatcher() - MixinTransferSimulator() - MixinWrapperFunctions() {} } diff --git a/contracts/exchange/contracts/src/MixinAssetProxyDispatcher.sol b/contracts/exchange/contracts/src/MixinAssetProxyDispatcher.sol index 5accd6e49a..26d018c603 100644 --- a/contracts/exchange/contracts/src/MixinAssetProxyDispatcher.sol +++ b/contracts/exchange/contracts/src/MixinAssetProxyDispatcher.sol @@ -21,10 +21,9 @@ pragma solidity ^0.5.9; import "@0x/contracts-utils/contracts/src/Ownable.sol"; import "@0x/contracts-utils/contracts/src/LibBytes.sol"; import "@0x/contracts-utils/contracts/src/LibRichErrors.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibExchangeRichErrors.sol"; import "./interfaces/IAssetProxy.sol"; import "./interfaces/IAssetProxyDispatcher.sol"; -import "./interfaces/IExchangeRichErrors.sol"; -import "./LibExchangeRichErrors.sol"; contract MixinAssetProxyDispatcher is @@ -47,7 +46,7 @@ contract MixinAssetProxyDispatcher is bytes4 assetProxyId = IAssetProxy(assetProxy).getProxyId(); address currentAssetProxy = assetProxies[assetProxyId]; if (currentAssetProxy != address(0)) { - LibRichErrors._rrevert(LibExchangeRichErrors.AssetProxyExistsError(currentAssetProxy)); + LibRichErrors.rrevert(LibExchangeRichErrors.AssetProxyExistsError(currentAssetProxy)); } // Add asset proxy and log registration. @@ -88,8 +87,8 @@ contract MixinAssetProxyDispatcher is if (amount > 0 && from != to) { // Ensure assetData length is valid if (assetData.length <= 3) { - LibRichErrors._rrevert(LibExchangeRichErrors.AssetProxyDispatchError( - IExchangeRichErrors.AssetProxyDispatchErrorCodes.INVALID_ASSET_DATA_LENGTH, + LibRichErrors.rrevert(LibExchangeRichErrors.AssetProxyDispatchError( + LibExchangeRichErrors.AssetProxyDispatchErrorCodes.INVALID_ASSET_DATA_LENGTH, orderHash, assetData )); @@ -101,8 +100,8 @@ contract MixinAssetProxyDispatcher is // Ensure that assetProxy exists if (assetProxy == address(0)) { - LibRichErrors._rrevert(LibExchangeRichErrors.AssetProxyDispatchError( - IExchangeRichErrors.AssetProxyDispatchErrorCodes.UNKNOWN_ASSET_PROXY, + LibRichErrors.rrevert(LibExchangeRichErrors.AssetProxyDispatchError( + LibExchangeRichErrors.AssetProxyDispatchErrorCodes.UNKNOWN_ASSET_PROXY, orderHash, assetData )); @@ -122,7 +121,7 @@ contract MixinAssetProxyDispatcher is // If the transaction did not succeed, revert with the returned data. if (!didSucceed) { - LibRichErrors._rrevert(LibExchangeRichErrors.AssetProxyTransferError( + LibRichErrors.rrevert(LibExchangeRichErrors.AssetProxyTransferError( orderHash, assetData, revertData diff --git a/contracts/exchange/contracts/src/MixinExchangeCore.sol b/contracts/exchange/contracts/src/MixinExchangeCore.sol index 890cbfc3ba..20806658c1 100644 --- a/contracts/exchange/contracts/src/MixinExchangeCore.sol +++ b/contracts/exchange/contracts/src/MixinExchangeCore.sol @@ -18,27 +18,26 @@ pragma solidity ^0.5.9; pragma experimental ABIEncoderV2; -import "@0x/contracts-utils/contracts/src/LibBytes.sol"; import "@0x/contracts-utils/contracts/src/LibRichErrors.sol"; +import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibEIP712ExchangeDomain.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibExchangeRichErrors.sol"; import "./interfaces/IExchangeCore.sol"; -import "./interfaces/IExchangeRichErrors.sol"; -import "./LibExchangeRichErrors.sol"; import "./MixinAssetProxyDispatcher.sol"; import "./MixinSignatureValidator.sol"; contract MixinExchangeCore is + LibEIP712ExchangeDomain, IExchangeCore, - IExchangeRichErrors, - LibMath, - LibFillResults, MixinAssetProxyDispatcher, MixinSignatureValidator { - using LibBytes for bytes; + using LibOrder for LibOrder.Order; + using LibSafeMath for uint256; // Mapping of orderHash => amount of takerAsset already bought by maker mapping (bytes32 => uint256) public filled; @@ -68,7 +67,7 @@ contract MixinExchangeCore is // Ensure orderEpoch is monotonically increasing if (newOrderEpoch <= oldOrderEpoch) { - LibRichErrors._rrevert(LibExchangeRichErrors.OrderEpochError( + LibRichErrors.rrevert(LibExchangeRichErrors.OrderEpochError( makerAddress, orderSenderAddress, oldOrderEpoch @@ -90,13 +89,13 @@ contract MixinExchangeCore is /// @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, + LibOrder.Order memory order, uint256 takerAssetFillAmount, bytes memory signature ) public nonReentrant - returns (FillResults memory fillResults) + returns (LibFillResults.FillResults memory fillResults) { fillResults = _fillOrder( order, @@ -109,7 +108,7 @@ contract MixinExchangeCore is /// @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) + function cancelOrder(LibOrder.Order memory order) public nonReentrant { @@ -120,13 +119,13 @@ contract MixinExchangeCore is /// @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) + function getOrderInfo(LibOrder.Order memory order) public view - returns (OrderInfo memory orderInfo) + returns (LibOrder.OrderInfo memory orderInfo) { // Compute the order hash - orderInfo.orderHash = getOrderHash(order); + orderInfo.orderHash = order.getTypedDataHash(EIP712_EXCHANGE_DOMAIN_HASH); // Fetch filled amount orderInfo.orderTakerAssetFilledAmount = filled[orderInfo.orderHash]; @@ -136,7 +135,7 @@ contract MixinExchangeCore is // 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); + orderInfo.orderStatus = uint8(LibOrder.OrderStatus.INVALID_MAKER_ASSET_AMOUNT); return orderInfo; } @@ -145,35 +144,35 @@ contract MixinExchangeCore is // 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); + orderInfo.orderStatus = uint8(LibOrder.OrderStatus.INVALID_TAKER_ASSET_AMOUNT); return orderInfo; } // Validate order availability if (orderInfo.orderTakerAssetFilledAmount >= order.takerAssetAmount) { - orderInfo.orderStatus = uint8(OrderStatus.FULLY_FILLED); + orderInfo.orderStatus = uint8(LibOrder.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); + orderInfo.orderStatus = uint8(LibOrder.OrderStatus.EXPIRED); return orderInfo; } // Check if order has been cancelled if (cancelled[orderInfo.orderHash]) { - orderInfo.orderStatus = uint8(OrderStatus.CANCELLED); + orderInfo.orderStatus = uint8(LibOrder.OrderStatus.CANCELLED); return orderInfo; } if (orderEpoch[order.makerAddress][order.senderAddress] > order.salt) { - orderInfo.orderStatus = uint8(OrderStatus.CANCELLED); + orderInfo.orderStatus = uint8(LibOrder.OrderStatus.CANCELLED); return orderInfo; } // All other statuses are ruled out: order is Fillable - orderInfo.orderStatus = uint8(OrderStatus.FILLABLE); + orderInfo.orderStatus = uint8(LibOrder.OrderStatus.FILLABLE); return orderInfo; } @@ -183,15 +182,15 @@ contract MixinExchangeCore is /// @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, + LibOrder.Order memory order, uint256 takerAssetFillAmount, bytes memory signature ) internal - returns (FillResults memory fillResults) + returns (LibFillResults.FillResults memory fillResults) { // Fetch order info - OrderInfo memory orderInfo = getOrderInfo(order); + LibOrder.OrderInfo memory orderInfo = getOrderInfo(order); // Fetch taker address address takerAddress = _getCurrentContextAddress(); @@ -205,11 +204,11 @@ contract MixinExchangeCore is ); // Get amount of takerAsset to fill - uint256 remainingTakerAssetAmount = _safeSub(order.takerAssetAmount, orderInfo.orderTakerAssetFilledAmount); - uint256 takerAssetFilledAmount = _min256(takerAssetFillAmount, remainingTakerAssetAmount); + uint256 remainingTakerAssetAmount = order.takerAssetAmount.safeSub(orderInfo.orderTakerAssetFilledAmount); + uint256 takerAssetFilledAmount = LibSafeMath.min256(takerAssetFillAmount, remainingTakerAssetAmount); // Compute proportional fill amounts - fillResults = _calculateFillResults(order, takerAssetFilledAmount); + fillResults = LibFillResults.calculateFillResults(order, takerAssetFilledAmount); bytes32 orderHash = orderInfo.orderHash; @@ -236,17 +235,17 @@ contract MixinExchangeCore is /// @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) + function _cancelOrder(LibOrder.Order memory order) internal { // Fetch current order status - OrderInfo memory orderInfo = getOrderInfo(order); + LibOrder.OrderInfo memory orderInfo = getOrderInfo(order); // Validate context _assertValidCancel(order, orderInfo); // Noop if order is already unfillable - if (orderInfo.orderStatus != uint8(OrderStatus.FILLABLE)) { + if (orderInfo.orderStatus != uint8(LibOrder.OrderStatus.FILLABLE)) { return; } @@ -259,16 +258,16 @@ contract MixinExchangeCore is /// @param takerAddress Address of taker who filled the order. /// @param orderTakerAssetFilledAmount Amount of order already filled. function _updateFilledState( - Order memory order, + LibOrder.Order memory order, address takerAddress, bytes32 orderHash, uint256 orderTakerAssetFilledAmount, - FillResults memory fillResults + LibFillResults.FillResults memory fillResults ) internal { // Update state - filled[orderHash] = _safeAdd(orderTakerAssetFilledAmount, fillResults.takerAssetFilledAmount); + filled[orderHash] = orderTakerAssetFilledAmount.safeAdd(fillResults.takerAssetFilledAmount); // Emit a Fill() event THE HARD WAY to avoid a stack overflow. // All this logic is equivalent to: @@ -295,7 +294,7 @@ contract MixinExchangeCore is /// @param order that was cancelled. /// @param orderHash Hash of order that was cancelled. function _updateCancelledState( - Order memory order, + LibOrder.Order memory order, bytes32 orderHash ) internal @@ -320,8 +319,8 @@ contract MixinExchangeCore is /// @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, + LibOrder.Order memory order, + LibOrder.OrderInfo memory orderInfo, address takerAddress, bytes memory signature ) @@ -329,17 +328,17 @@ contract MixinExchangeCore is view { // An order can only be filled if its status is FILLABLE. - if (orderInfo.orderStatus != uint8(OrderStatus.FILLABLE)) { - LibRichErrors._rrevert(LibExchangeRichErrors.OrderStatusError( + if (orderInfo.orderStatus != uint8(LibOrder.OrderStatus.FILLABLE)) { + LibRichErrors.rrevert(LibExchangeRichErrors.OrderStatusError( orderInfo.orderHash, - OrderStatus(orderInfo.orderStatus) + LibOrder.OrderStatus(orderInfo.orderStatus) )); } // Validate sender is allowed to fill this order if (order.senderAddress != address(0)) { if (order.senderAddress != msg.sender) { - LibRichErrors._rrevert(LibExchangeRichErrors.InvalidSenderError( + LibRichErrors.rrevert(LibExchangeRichErrors.InvalidSenderError( orderInfo.orderHash, msg.sender )); } @@ -348,7 +347,7 @@ contract MixinExchangeCore is // Validate taker is allowed to fill this order if (order.takerAddress != address(0)) { if (order.takerAddress != takerAddress) { - LibRichErrors._rrevert(LibExchangeRichErrors.InvalidTakerError( + LibRichErrors.rrevert(LibExchangeRichErrors.InvalidTakerError( orderInfo.orderHash, takerAddress )); } @@ -367,8 +366,8 @@ contract MixinExchangeCore is order, orderInfo.orderHash, signature)) { - LibRichErrors._rrevert(LibExchangeRichErrors.SignatureError( - SignatureErrorCodes.BAD_SIGNATURE, + LibRichErrors.rrevert(LibExchangeRichErrors.SignatureError( + LibExchangeRichErrors.SignatureErrorCodes.BAD_SIGNATURE, orderInfo.orderHash, makerAddress, signature @@ -381,8 +380,8 @@ contract MixinExchangeCore is /// @param order to be cancelled. /// @param orderInfo OrderStatus, orderHash, and amount already filled of order. function _assertValidCancel( - Order memory order, - OrderInfo memory orderInfo + LibOrder.Order memory order, + LibOrder.OrderInfo memory orderInfo ) internal view @@ -390,50 +389,17 @@ contract MixinExchangeCore is // Validate sender is allowed to cancel this order if (order.senderAddress != address(0)) { if (order.senderAddress != msg.sender) { - LibRichErrors._rrevert(LibExchangeRichErrors.InvalidSenderError(orderInfo.orderHash, msg.sender)); + LibRichErrors.rrevert(LibExchangeRichErrors.InvalidSenderError(orderInfo.orderHash, msg.sender)); } } // Validate transaction signed by maker address makerAddress = _getCurrentContextAddress(); if (order.makerAddress != makerAddress) { - LibRichErrors._rrevert(LibExchangeRichErrors.InvalidMakerError(orderInfo.orderHash, makerAddress)); + LibRichErrors.rrevert(LibExchangeRichErrors.InvalidMakerError(orderInfo.orderHash, makerAddress)); } } - /// @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 orderHash The order hash. /// @param order Order struct containing order specifications. diff --git a/contracts/exchange/contracts/src/MixinMatchOrders.sol b/contracts/exchange/contracts/src/MixinMatchOrders.sol index 674623a0d6..9ec79700ac 100644 --- a/contracts/exchange/contracts/src/MixinMatchOrders.sol +++ b/contracts/exchange/contracts/src/MixinMatchOrders.sol @@ -16,14 +16,10 @@ pragma experimental ABIEncoderV2; import "@0x/contracts-utils/contracts/src/LibBytes.sol"; import "@0x/contracts-utils/contracts/src/LibRichErrors.sol"; -import "@0x/contracts-utils/contracts/src/ReentrancyGuard.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol"; -import "./interfaces/IAssetProxyDispatcher.sol"; -import "./interfaces/IExchangeRichErrors.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibExchangeRichErrors.sol"; import "./interfaces/IMatchOrders.sol"; -import "./interfaces/ITransactions.sol"; -import "./LibExchangeRichErrors.sol"; import "./MixinExchangeCore.sol"; @@ -32,6 +28,7 @@ contract MixinMatchOrders is IMatchOrders { using LibBytes for bytes; + using LibSafeMath for uint256; /// @dev Match complementary orders that have a profitable spread. /// Each order is filled at their respective price point, and @@ -88,93 +85,6 @@ contract MixinMatchOrders is ); } - /// @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 shouldMaximallyFillOrders A value that indicates whether or not this calculation should use - /// the maximal fill order matching strategy. - /// @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, - bool shouldMaximallyFillOrders - ) - public - 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 - ); - - // Maximally fill the orders and pay out profits to the matcher in one or both of the maker assets. - if (shouldMaximallyFillOrders) { - _calculateMatchedFillResultsWithMaximalFill( - matchedFillResults, - leftOrder, - rightOrder, - leftMakerAssetAmountRemaining, - leftTakerAssetAmountRemaining, - rightMakerAssetAmountRemaining, - rightTakerAssetAmountRemaining - ); - } else { - _calculateMatchedFillResults( - matchedFillResults, - leftOrder, - rightOrder, - leftMakerAssetAmountRemaining, - leftTakerAssetAmountRemaining, - rightMakerAssetAmountRemaining, - rightTakerAssetAmountRemaining - ); - } - - // 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 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. @@ -234,9 +144,13 @@ contract MixinMatchOrders is /// @dev Validates context for matchOrders. Succeeds or throws. /// @param leftOrder First order to match. /// @param rightOrder Second order to match. + /// @param leftOrderInfo OrderStatus, orderHash, and amount already filled of leftOrder. + /// @param rightOrderInfo OrderStatus, orderHash, and amount already filled of rightOrder. function _assertValidMatch( LibOrder.Order memory leftOrder, - LibOrder.Order memory rightOrder + LibOrder.Order memory rightOrder, + LibOrder.OrderInfo memory leftOrderInfo, + LibOrder.OrderInfo memory rightOrderInfo ) internal view @@ -249,340 +163,15 @@ contract MixinMatchOrders is // AND // / >= / // These equations can be combined to get the following: - if (_safeMul(leftOrder.makerAssetAmount, rightOrder.makerAssetAmount) < - _safeMul(leftOrder.takerAssetAmount, rightOrder.takerAssetAmount)) { - LibRichErrors._rrevert(LibExchangeRichErrors.NegativeSpreadError( - getOrderHash(leftOrder), - getOrderHash(rightOrder) + if (leftOrder.makerAssetAmount.safeMul(rightOrder.makerAssetAmount) < + leftOrder.takerAssetAmount.safeMul(rightOrder.takerAssetAmount)) { + LibRichErrors.rrevert(LibExchangeRichErrors.NegativeSpreadError( + leftOrderInfo.orderHash, + rightOrderInfo.orderHash )); } } - /// @dev Calculates part of the matched fill results for a given situation using the fill strategy that only - /// awards profit denominated in the left maker asset. - /// @param matchedFillResults The MatchedFillResults struct to update with fill result calculations. - /// @param leftOrder The left order in the order matching situation. - /// @param rightOrder The right order in the order matching situation. - /// @param leftMakerAssetAmountRemaining The amount of the left order maker asset that can still be filled. - /// @param leftTakerAssetAmountRemaining The amount of the left order taker asset that can still be filled. - /// @param rightMakerAssetAmountRemaining The amount of the right order maker asset that can still be filled. - /// @param rightTakerAssetAmountRemaining The amount of the right order taker asset that can still be filled. - function _calculateMatchedFillResults( - MatchedFillResults memory matchedFillResults, - LibOrder.Order memory leftOrder, - LibOrder.Order memory rightOrder, - uint256 leftMakerAssetAmountRemaining, - uint256 leftTakerAssetAmountRemaining, - uint256 rightMakerAssetAmountRemaining, - uint256 rightTakerAssetAmountRemaining - ) - internal - pure - { - // 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. - // Case 3. - // If the left maker can buy exactly as much as the right maker can sell, then both orders are fully filled. - if (leftTakerAssetAmountRemaining > rightMakerAssetAmountRemaining) { - // Case 1: Right order is fully filled - _calculateCompleteRightFill( - matchedFillResults, - leftOrder, - rightMakerAssetAmountRemaining, - rightTakerAssetAmountRemaining - ); - } else if (leftTakerAssetAmountRemaining < rightMakerAssetAmountRemaining) { - // Case 2: Left order is fully filled - matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining; - matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining; - matchedFillResults.right.makerAssetFilledAmount = leftTakerAssetAmountRemaining; - // 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, - leftTakerAssetAmountRemaining // matchedFillResults.right.makerAssetFilledAmount - ); - } else { // leftTakerAssetAmountRemaining == rightMakerAssetAmountRemaining - // Case 3: Both orders are fully filled. Technically, this could be captured by the above cases, but - // this calculation will be more precise since it does not include rounding. - _calculateCompleteFillBoth( - matchedFillResults, - leftMakerAssetAmountRemaining, - leftTakerAssetAmountRemaining, - rightMakerAssetAmountRemaining, - rightTakerAssetAmountRemaining - ); - } - - // Calculate amount given to taker - matchedFillResults.profitInLeftMakerAsset = _safeSub( - matchedFillResults.left.makerAssetFilledAmount, - matchedFillResults.right.takerAssetFilledAmount - ); - } - - /// @dev Calculates part of the matched fill results for a given situation using the maximal fill order matching - /// strategy. - /// @param matchedFillResults The MatchedFillResults struct to update with fill result calculations. - /// @param leftOrder The left order in the order matching situation. - /// @param rightOrder The right order in the order matching situation. - /// @param leftMakerAssetAmountRemaining The amount of the left order maker asset that can still be filled. - /// @param leftTakerAssetAmountRemaining The amount of the left order taker asset that can still be filled. - /// @param rightMakerAssetAmountRemaining The amount of the right order maker asset that can still be filled. - /// @param rightTakerAssetAmountRemaining The amount of the right order taker asset that can still be filled. - function _calculateMatchedFillResultsWithMaximalFill( - MatchedFillResults memory matchedFillResults, - LibOrder.Order memory leftOrder, - LibOrder.Order memory rightOrder, - uint256 leftMakerAssetAmountRemaining, - uint256 leftTakerAssetAmountRemaining, - uint256 rightMakerAssetAmountRemaining, - uint256 rightTakerAssetAmountRemaining - ) - internal - pure - { - // If a maker asset is greater than the opposite taker asset, than there will be a spread denominated in that maker asset. - bool doesLeftMakerAssetProfitExist = leftMakerAssetAmountRemaining > rightTakerAssetAmountRemaining; - bool doesRightMakerAssetProfitExist = rightMakerAssetAmountRemaining > leftTakerAssetAmountRemaining; - - // Calculate the maximum fill results for the maker and taker assets. At least one of the orders will be fully filled. - // - // The maximum that the left maker can possibly buy is the amount that the right order can sell. - // The maximum that the right maker can possibly buy is the amount that the left order can sell. - // - // If the left order is fully filled, profit will be paid out in the left maker asset. If the right order is fully filled, - // the profit will be out in the right maker asset. - // - // There are three cases to consider: - // Case 1. - // If the left maker can buy more than the right maker can sell, then only the right order is fully filled. - // Case 2. - // If the right maker can buy more than the left maker can sell, then only the right order is fully filled. - // Case 3. - // If the right maker can sell the max of what the left maker can buy and the left maker can sell the max of - // what the right maker can buy, then both orders are fully filled. - if (leftTakerAssetAmountRemaining > rightMakerAssetAmountRemaining) { - // Case 1: Right order is fully filled with the profit paid in the left makerAsset - _calculateCompleteRightFill( - matchedFillResults, - leftOrder, - rightMakerAssetAmountRemaining, - rightTakerAssetAmountRemaining - ); - } else if (rightTakerAssetAmountRemaining > leftMakerAssetAmountRemaining) { - // Case 2: Left order is fully filled with the profit paid in the right makerAsset. - matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining; - matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining; - // Round down to ensure the right maker's exchange rate does not exceed the price specified by the order. - // We favor the right maker when the exchange rate must be rounded and the profit is being paid in the - // right maker asset. - matchedFillResults.right.makerAssetFilledAmount = _safeGetPartialAmountFloor( - rightOrder.makerAssetAmount, - rightOrder.takerAssetAmount, - leftMakerAssetAmountRemaining - ); - matchedFillResults.right.takerAssetFilledAmount = leftMakerAssetAmountRemaining; - } else { - // Case 3: The right and left orders are fully filled - _calculateCompleteFillBoth( - matchedFillResults, - leftMakerAssetAmountRemaining, - leftTakerAssetAmountRemaining, - rightMakerAssetAmountRemaining, - rightTakerAssetAmountRemaining - ); - } - - // Calculate amount given to taker in the left order's maker asset if the left spread will be part of the profit. - if (doesLeftMakerAssetProfitExist) { - matchedFillResults.profitInLeftMakerAsset = _safeSub( - matchedFillResults.left.makerAssetFilledAmount, - matchedFillResults.right.takerAssetFilledAmount - ); - } - - // Calculate amount given to taker in the right order's maker asset if the right spread will be part of the profit. - if (doesRightMakerAssetProfitExist) { - matchedFillResults.profitInRightMakerAsset = _safeSub( - matchedFillResults.right.makerAssetFilledAmount, - matchedFillResults.left.takerAssetFilledAmount - ); - } - } - - /// @dev Calculates the fill results for the maker and taker in the order matching and writes the results - /// to the fillResults that are being collected on the order. Both orders will be fully filled in this - /// case. - /// @param matchedFillResults The fill results object to populate with calculations. - /// @param leftMakerAssetAmountRemaining The amount of the left maker asset that is remaining to be filled. - /// @param leftTakerAssetAmountRemaining The amount of the left taker asset that is remaining to be filled. - /// @param rightMakerAssetAmountRemaining The amount of the right maker asset that is remaining to be filled. - /// @param rightTakerAssetAmountRemaining The amount of the right taker asset that is remaining to be filled. - function _calculateCompleteFillBoth( - MatchedFillResults memory matchedFillResults, - uint256 leftMakerAssetAmountRemaining, - uint256 leftTakerAssetAmountRemaining, - uint256 rightMakerAssetAmountRemaining, - uint256 rightTakerAssetAmountRemaining - ) - internal - pure - { - // Calculate the fully filled results for both orders. - matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining; - matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining; - matchedFillResults.right.makerAssetFilledAmount = rightMakerAssetAmountRemaining; - matchedFillResults.right.takerAssetFilledAmount = rightTakerAssetAmountRemaining; - } - - /// @dev Calculates the fill results for the maker and taker in the order matching and writes the results - /// to the fillResults that are being collected on the order. - /// @param matchedFillResults The fill results object to populate with calculations. - /// @param leftOrder The left order that is being maximally filled. All of the information about fill amounts - /// can be derived from this order and the right asset remaining fields. - /// @param rightMakerAssetAmountRemaining The amount of the right maker asset that is remaining to be filled. - /// @param rightTakerAssetAmountRemaining The amount of the right taker asset that is remaining to be filled. - function _calculateCompleteRightFill( - MatchedFillResults memory matchedFillResults, - LibOrder.Order memory leftOrder, - uint256 rightMakerAssetAmountRemaining, - uint256 rightTakerAssetAmountRemaining - ) - internal - pure - { - matchedFillResults.right.makerAssetFilledAmount = rightMakerAssetAmountRemaining; - matchedFillResults.right.takerAssetFilledAmount = rightTakerAssetAmountRemaining; - matchedFillResults.left.takerAssetFilledAmount = rightMakerAssetAmountRemaining; - // Round down to ensure the left maker's exchange rate does not exceed the price specified by the order. - // We favor the left maker when the exchange rate must be rounded and the profit is being paid in the - // left maker asset. - matchedFillResults.left.makerAssetFilledAmount = _safeGetPartialAmountFloor( - leftOrder.makerAssetAmount, - leftOrder.takerAssetAmount, - rightMakerAssetAmountRemaining - ); - } - - /// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient. - /// @param leftOrderHash First matched order hash. - /// @param rightOrderHash Second matched order hash. - /// @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( - bytes32 leftOrderHash, - bytes32 rightOrderHash, - LibOrder.Order memory leftOrder, - LibOrder.Order memory rightOrder, - address takerAddress, - LibFillResults.MatchedFillResults memory matchedFillResults - ) - internal - { - address leftFeeRecipientAddress = leftOrder.feeRecipientAddress; - address rightFeeRecipientAddress = rightOrder.feeRecipientAddress; - - // Right maker asset -> left maker - _dispatchTransferFrom( - rightOrderHash, - rightOrder.makerAssetData, - rightOrder.makerAddress, - leftOrder.makerAddress, - matchedFillResults.left.takerAssetFilledAmount - ); - - // Left maker asset -> right maker - _dispatchTransferFrom( - leftOrderHash, - leftOrder.makerAssetData, - leftOrder.makerAddress, - rightOrder.makerAddress, - matchedFillResults.right.takerAssetFilledAmount - ); - - // Right maker fee -> right fee recipient - _dispatchTransferFrom( - rightOrderHash, - rightOrder.makerFeeAssetData, - rightOrder.makerAddress, - rightFeeRecipientAddress, - matchedFillResults.right.makerFeePaid - ); - - // Left maker fee -> left fee recipient - _dispatchTransferFrom( - leftOrderHash, - leftOrder.makerFeeAssetData, - leftOrder.makerAddress, - leftFeeRecipientAddress, - matchedFillResults.left.makerFeePaid - ); - - // Settle taker profits. - _dispatchTransferFrom( - leftOrderHash, - leftOrder.makerAssetData, - leftOrder.makerAddress, - takerAddress, - matchedFillResults.profitInLeftMakerAsset - ); - _dispatchTransferFrom( - rightOrderHash, - rightOrder.makerAssetData, - rightOrder.makerAddress, - takerAddress, - matchedFillResults.profitInRightMakerAsset - ); - - // Settle taker fees. - if ( - leftFeeRecipientAddress == rightFeeRecipientAddress && - leftOrder.takerFeeAssetData.equals(rightOrder.takerFeeAssetData) - ) { - // Fee recipients and taker fee assets are identical, so we can - // transfer them in one go. - _dispatchTransferFrom( - leftOrderHash, - leftOrder.takerFeeAssetData, - takerAddress, - leftFeeRecipientAddress, - _safeAdd( - matchedFillResults.left.takerFeePaid, - matchedFillResults.right.takerFeePaid - ) - ); - } else { - // Right taker fee -> right fee recipient - _dispatchTransferFrom( - rightOrderHash, - rightOrder.takerFeeAssetData, - takerAddress, - rightFeeRecipientAddress, - matchedFillResults.right.takerFeePaid - ); - - // Left taker fee -> left fee recipient - _dispatchTransferFrom( - leftOrderHash, - leftOrder.takerFeeAssetData, - takerAddress, - leftFeeRecipientAddress, - matchedFillResults.left.takerFeePaid - ); - } - } - /// @dev Match complementary orders that have a profitable spread. /// Each order is filled at their respective price point, and /// the matcher receives a profit denominated in the left maker asset. @@ -606,25 +195,25 @@ contract MixinMatchOrders is { // Ensure that the left and right orders have nonzero lengths. if (leftOrders.length == 0) { - LibRichErrors._rrevert(LibExchangeRichErrors.BatchMatchOrdersError( - BatchMatchOrdersErrorCodes.ZERO_LEFT_ORDERS + LibRichErrors.rrevert(LibExchangeRichErrors.BatchMatchOrdersError( + LibExchangeRichErrors.BatchMatchOrdersErrorCodes.ZERO_LEFT_ORDERS )); } if (rightOrders.length == 0) { - LibRichErrors._rrevert(LibExchangeRichErrors.BatchMatchOrdersError( - BatchMatchOrdersErrorCodes.ZERO_RIGHT_ORDERS + LibRichErrors.rrevert(LibExchangeRichErrors.BatchMatchOrdersError( + LibExchangeRichErrors.BatchMatchOrdersErrorCodes.ZERO_RIGHT_ORDERS )); } // Ensure that the left and right arrays are compatible. if (leftOrders.length != leftSignatures.length) { - LibRichErrors._rrevert(LibExchangeRichErrors.BatchMatchOrdersError( - BatchMatchOrdersErrorCodes.INVALID_LENGTH_LEFT_SIGNATURES + LibRichErrors.rrevert(LibExchangeRichErrors.BatchMatchOrdersError( + LibExchangeRichErrors.BatchMatchOrdersErrorCodes.INVALID_LENGTH_LEFT_SIGNATURES )); } if (rightOrders.length != rightSignatures.length) { - LibRichErrors._rrevert(LibExchangeRichErrors.BatchMatchOrdersError( - BatchMatchOrdersErrorCodes.INVALID_LENGTH_RIGHT_SIGNATURES + LibRichErrors.rrevert(LibExchangeRichErrors.BatchMatchOrdersError( + LibExchangeRichErrors.BatchMatchOrdersErrorCodes.INVALID_LENGTH_RIGHT_SIGNATURES )); } @@ -656,33 +245,29 @@ contract MixinMatchOrders is ); // Update the orderInfo structs with the updated takerAssetFilledAmount - leftOrderInfo.orderTakerAssetFilledAmount = _safeAdd( - leftOrderInfo.orderTakerAssetFilledAmount, + leftOrderInfo.orderTakerAssetFilledAmount = leftOrderInfo.orderTakerAssetFilledAmount.safeAdd( matchResults.left.takerAssetFilledAmount ); - rightOrderInfo.orderTakerAssetFilledAmount = _safeAdd( - rightOrderInfo.orderTakerAssetFilledAmount, + rightOrderInfo.orderTakerAssetFilledAmount = rightOrderInfo.orderTakerAssetFilledAmount.safeAdd( matchResults.right.takerAssetFilledAmount ); // Aggregate the new fill results with the previous fill results for the current orders. - _addFillResults( + leftFillResults = LibFillResults.addFillResults( leftFillResults, matchResults.left ); - _addFillResults( + rightFillResults = LibFillResults.addFillResults( rightFillResults, matchResults.right ); // Update the profit in the left and right maker assets using the profits from // the match. - batchMatchedFillResults.profitInLeftMakerAsset = _safeAdd( - batchMatchedFillResults.profitInLeftMakerAsset, + batchMatchedFillResults.profitInLeftMakerAsset = batchMatchedFillResults.profitInLeftMakerAsset.safeAdd( matchResults.profitInLeftMakerAsset ); - batchMatchedFillResults.profitInRightMakerAsset = _safeAdd( - batchMatchedFillResults.profitInRightMakerAsset, + batchMatchedFillResults.profitInRightMakerAsset = batchMatchedFillResults.profitInRightMakerAsset.safeAdd( matchResults.profitInRightMakerAsset ); @@ -779,10 +364,15 @@ contract MixinMatchOrders is takerAddress, rightSignature ); - _assertValidMatch(leftOrder, rightOrder); + _assertValidMatch( + leftOrder, + rightOrder, + leftOrderInfo, + rightOrderInfo + ); // Compute proportional fill amounts - matchedFillResults = calculateMatchedFillResults( + matchedFillResults = LibFillResults.calculateMatchedFillResults( leftOrder, rightOrder, leftOrderInfo.orderTakerAssetFilledAmount, @@ -818,4 +408,112 @@ contract MixinMatchOrders is return matchedFillResults; } + + /// @dev Settles matched order by transferring appropriate funds between order makers, taker, and fee recipient. + /// @param leftOrderHash First matched order hash. + /// @param rightOrderHash Second matched order hash. + /// @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( + bytes32 leftOrderHash, + bytes32 rightOrderHash, + LibOrder.Order memory leftOrder, + LibOrder.Order memory rightOrder, + address takerAddress, + LibFillResults.MatchedFillResults memory matchedFillResults + ) + internal + { + address leftFeeRecipientAddress = leftOrder.feeRecipientAddress; + address rightFeeRecipientAddress = rightOrder.feeRecipientAddress; + + // Right maker asset -> left maker + _dispatchTransferFrom( + rightOrderHash, + rightOrder.makerAssetData, + rightOrder.makerAddress, + leftOrder.makerAddress, + matchedFillResults.left.takerAssetFilledAmount + ); + + // Left maker asset -> right maker + _dispatchTransferFrom( + leftOrderHash, + leftOrder.makerAssetData, + leftOrder.makerAddress, + rightOrder.makerAddress, + matchedFillResults.right.takerAssetFilledAmount + ); + + // Right maker fee -> right fee recipient + _dispatchTransferFrom( + rightOrderHash, + rightOrder.makerFeeAssetData, + rightOrder.makerAddress, + rightFeeRecipientAddress, + matchedFillResults.right.makerFeePaid + ); + + // Left maker fee -> left fee recipient + _dispatchTransferFrom( + leftOrderHash, + leftOrder.makerFeeAssetData, + leftOrder.makerAddress, + leftFeeRecipientAddress, + matchedFillResults.left.makerFeePaid + ); + + // Settle taker profits. + _dispatchTransferFrom( + leftOrderHash, + leftOrder.makerAssetData, + leftOrder.makerAddress, + takerAddress, + matchedFillResults.profitInLeftMakerAsset + ); + + _dispatchTransferFrom( + rightOrderHash, + rightOrder.makerAssetData, + rightOrder.makerAddress, + takerAddress, + matchedFillResults.profitInRightMakerAsset + ); + + // Settle taker fees. + if ( + leftFeeRecipientAddress == rightFeeRecipientAddress && + leftOrder.takerFeeAssetData.equals(rightOrder.takerFeeAssetData) + ) { + // Fee recipients and taker fee assets are identical, so we can + // transfer them in one go. + _dispatchTransferFrom( + leftOrderHash, + leftOrder.takerFeeAssetData, + takerAddress, + leftFeeRecipientAddress, + matchedFillResults.left.takerFeePaid.safeAdd(matchedFillResults.right.takerFeePaid) + ); + } else { + // Right taker fee -> right fee recipient + _dispatchTransferFrom( + rightOrderHash, + rightOrder.takerFeeAssetData, + takerAddress, + rightFeeRecipientAddress, + matchedFillResults.right.takerFeePaid + ); + + // Left taker fee -> left fee recipient + _dispatchTransferFrom( + leftOrderHash, + leftOrder.takerFeeAssetData, + takerAddress, + leftFeeRecipientAddress, + matchedFillResults.left.takerFeePaid + ); + } + } } diff --git a/contracts/exchange/contracts/src/MixinSignatureValidator.sol b/contracts/exchange/contracts/src/MixinSignatureValidator.sol index a83987330a..d33ea8a406 100644 --- a/contracts/exchange/contracts/src/MixinSignatureValidator.sol +++ b/contracts/exchange/contracts/src/MixinSignatureValidator.sol @@ -25,23 +25,24 @@ import "@0x/contracts-utils/contracts/src/LibRichErrors.sol"; import "@0x/contracts-utils/contracts/src/ReentrancyGuard.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibEIP712ExchangeDomain.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibExchangeRichErrors.sol"; import "./interfaces/IWallet.sol"; import "./interfaces/IEIP1271Wallet.sol"; -import "./interfaces/IExchangeRichErrors.sol"; import "./interfaces/ISignatureValidator.sol"; -import "./LibExchangeRichErrors.sol"; import "./MixinTransactions.sol"; contract MixinSignatureValidator is ReentrancyGuard, + LibEIP712ExchangeDomain, LibEIP1271, - LibOrder, - LibZeroExTransaction, ISignatureValidator, MixinTransactions { using LibBytes for bytes; + using LibOrder for LibOrder.Order; + using LibZeroExTransaction for LibZeroExTransaction.ZeroExTransaction; // Magic bytes to be returned by `Wallet` signature type validators. // bytes4(keccak256("isValidWalletSignature(bytes32,address,bytes)")) @@ -109,8 +110,8 @@ contract MixinSignatureValidator is signatureType == SignatureType.Validator || signatureType == SignatureType.EIP1271Wallet ) { - LibRichErrors._rrevert(LibExchangeRichErrors.SignatureError( - IExchangeRichErrors.SignatureErrorCodes.INAPPROPRIATE_SIGNATURE_TYPE, + LibRichErrors.rrevert(LibExchangeRichErrors.SignatureError( + LibExchangeRichErrors.SignatureErrorCodes.INAPPROPRIATE_SIGNATURE_TYPE, hash, signerAddress, signature @@ -129,14 +130,14 @@ contract MixinSignatureValidator is /// @param signature Proof that the order has been signed by signer. /// @return isValid `true` if the signature is valid for the given order and signer. function isValidOrderSignature( - Order memory order, + LibOrder.Order memory order, bytes memory signature ) public view returns (bool isValid) { - bytes32 orderHash = getOrderHash(order); + bytes32 orderHash = order.getTypedDataHash(EIP712_EXCHANGE_DOMAIN_HASH); return _isValidOrderWithHashSignature( order, orderHash, @@ -149,14 +150,14 @@ contract MixinSignatureValidator is /// @param signature Proof that the order has been signed by signer. /// @return isValid `true` if the signature is valid for the given transaction and signer. function isValidTransactionSignature( - ZeroExTransaction memory transaction, + LibZeroExTransaction.ZeroExTransaction memory transaction, bytes memory signature ) public view returns (bool isValid) { - bytes32 transactionHash = getTransactionHash(transaction); + bytes32 transactionHash = transaction.getTypedDataHash(EIP712_EXCHANGE_DOMAIN_HASH); isValid = _isValidTransactionWithHashSignature( transaction, transactionHash, @@ -198,7 +199,7 @@ contract MixinSignatureValidator is /// @param signature Proof that the hash has been signed by signer. /// @return isValid True if the signature is valid for the given order and signer. function _isValidOrderWithHashSignature( - Order memory order, + LibOrder.Order memory order, bytes32 orderHash, bytes memory signature ) @@ -246,7 +247,7 @@ contract MixinSignatureValidator is /// @param signature Proof that the hash has been signed by signer. /// @return isValid True if the signature is valid for the given transaction and signer. function _isValidTransactionWithHashSignature( - ZeroExTransaction memory transaction, + LibZeroExTransaction.ZeroExTransaction memory transaction, bytes32 transactionHash, bytes memory signature ) @@ -305,8 +306,8 @@ contract MixinSignatureValidator is // a correctly formatted but incorrect signature. if (signatureType == SignatureType.Invalid) { if (signature.length != 1) { - LibRichErrors._rrevert(LibExchangeRichErrors.SignatureError( - IExchangeRichErrors.SignatureErrorCodes.INVALID_LENGTH, + LibRichErrors.rrevert(LibExchangeRichErrors.SignatureError( + LibExchangeRichErrors.SignatureErrorCodes.INVALID_LENGTH, hash, signerAddress, signature @@ -317,8 +318,8 @@ contract MixinSignatureValidator is // Signature using EIP712 } else if (signatureType == SignatureType.EIP712) { if (signature.length != 66) { - LibRichErrors._rrevert(LibExchangeRichErrors.SignatureError( - IExchangeRichErrors.SignatureErrorCodes.INVALID_LENGTH, + LibRichErrors.rrevert(LibExchangeRichErrors.SignatureError( + LibExchangeRichErrors.SignatureErrorCodes.INVALID_LENGTH, hash, signerAddress, signature @@ -338,8 +339,8 @@ contract MixinSignatureValidator is // Signed using web3.eth_sign } else if (signatureType == SignatureType.EthSign) { if (signature.length != 66) { - LibRichErrors._rrevert(LibExchangeRichErrors.SignatureError( - IExchangeRichErrors.SignatureErrorCodes.INVALID_LENGTH, + LibRichErrors.rrevert(LibExchangeRichErrors.SignatureError( + LibExchangeRichErrors.SignatureErrorCodes.INVALID_LENGTH, hash, signerAddress, signature @@ -388,8 +389,8 @@ contract MixinSignatureValidator is // Disallow address zero because it is ecrecover() returns zero on // failure. if (signerAddress == address(0)) { - LibRichErrors._rrevert(LibExchangeRichErrors.SignatureError( - IExchangeRichErrors.SignatureErrorCodes.INVALID_SIGNER, + LibRichErrors.rrevert(LibExchangeRichErrors.SignatureError( + LibExchangeRichErrors.SignatureErrorCodes.INVALID_SIGNER, hash, signerAddress, signature @@ -397,8 +398,8 @@ contract MixinSignatureValidator is } if (signature.length == 0) { - LibRichErrors._rrevert(LibExchangeRichErrors.SignatureError( - IExchangeRichErrors.SignatureErrorCodes.INVALID_LENGTH, + LibRichErrors.rrevert(LibExchangeRichErrors.SignatureError( + LibExchangeRichErrors.SignatureErrorCodes.INVALID_LENGTH, hash, signerAddress, signature @@ -410,8 +411,8 @@ contract MixinSignatureValidator is // Ensure signature is supported if (signatureTypeRaw >= uint8(SignatureType.NSignatureTypes)) { - LibRichErrors._rrevert(LibExchangeRichErrors.SignatureError( - IExchangeRichErrors.SignatureErrorCodes.UNSUPPORTED, + LibRichErrors.rrevert(LibExchangeRichErrors.SignatureError( + LibExchangeRichErrors.SignatureErrorCodes.UNSUPPORTED, hash, signerAddress, signature @@ -424,8 +425,8 @@ contract MixinSignatureValidator is // it an explicit option. This aids testing and analysis. It is // also the initialization value for the enum type. if (SignatureType(signatureTypeRaw) == SignatureType.Illegal) { - LibRichErrors._rrevert(LibExchangeRichErrors.SignatureError( - IExchangeRichErrors.SignatureErrorCodes.ILLEGAL, + LibRichErrors.rrevert(LibExchangeRichErrors.SignatureError( + LibExchangeRichErrors.SignatureErrorCodes.ILLEGAL, hash, signerAddress, signature @@ -479,7 +480,7 @@ contract MixinSignatureValidator is return returnData.readBytes4(0) == LEGACY_WALLET_MAGIC_VALUE; } // Static call to verifier failed. - LibRichErrors._rrevert(LibExchangeRichErrors.SignatureWalletError( + LibRichErrors.rrevert(LibExchangeRichErrors.SignatureWalletError( hash, walletAddress, signature, @@ -533,7 +534,7 @@ contract MixinSignatureValidator is return returnData.readBytes4(0) == EIP1271_MAGIC_VALUE; } // Static call to verifier failed. - LibRichErrors._rrevert(LibExchangeRichErrors.SignatureWalletError( + LibRichErrors.rrevert(LibExchangeRichErrors.SignatureWalletError( hash, walletAddress, signature, @@ -569,7 +570,7 @@ contract MixinSignatureValidator is address validatorAddress = signature.readAddress(signatureLength - 21); // Ensure signer has approved validator. if (!allowedValidators[signerAddress][validatorAddress]) { - LibRichErrors._rrevert(LibExchangeRichErrors.SignatureValidatorNotApprovedError( + LibRichErrors.rrevert(LibExchangeRichErrors.SignatureValidatorNotApprovedError( signerAddress, validatorAddress )); @@ -597,7 +598,7 @@ contract MixinSignatureValidator is return returnData.readBytes4(0) == EIP1271_MAGIC_VALUE; } // Static call to verifier failed. - LibRichErrors._rrevert(LibExchangeRichErrors.SignatureValidatorError( + LibRichErrors.rrevert(LibExchangeRichErrors.SignatureValidatorError( hash, signerAddress, validatorAddress, diff --git a/contracts/exchange/contracts/src/MixinTransactions.sol b/contracts/exchange/contracts/src/MixinTransactions.sol index f882ae3a98..9958dd6c5b 100644 --- a/contracts/exchange/contracts/src/MixinTransactions.sol +++ b/contracts/exchange/contracts/src/MixinTransactions.sol @@ -20,18 +20,20 @@ pragma solidity ^0.5.9; pragma experimental ABIEncoderV2; import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibEIP712ExchangeDomain.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibExchangeRichErrors.sol"; import "@0x/contracts-utils/contracts/src/LibRichErrors.sol"; -import "./interfaces/IExchangeRichErrors.sol"; import "./interfaces/ITransactions.sol"; import "./interfaces/ISignatureValidator.sol"; -import "./LibExchangeRichErrors.sol"; contract MixinTransactions is - LibZeroExTransaction, + LibEIP712ExchangeDomain, ISignatureValidator, ITransactions { + using LibZeroExTransaction for LibZeroExTransaction.ZeroExTransaction; + // Mapping of transaction hash => executed // This prevents transactions from being executed more than once. mapping (bytes32 => bool) public transactionsExecuted; @@ -44,7 +46,7 @@ contract MixinTransactions is /// @param signature Proof that transaction has been signed by signer. /// @return ABI encoded return data of the underlying Exchange function call. function executeTransaction( - ZeroExTransaction memory transaction, + LibZeroExTransaction.ZeroExTransaction memory transaction, bytes memory signature ) public @@ -58,7 +60,7 @@ contract MixinTransactions is /// @param signatures Array of proofs that transactions have been signed by signer(s). /// @return Array containing ABI encoded return data for each of the underlying Exchange function calls. function batchExecuteTransactions( - ZeroExTransaction[] memory transactions, + LibZeroExTransaction.ZeroExTransaction[] memory transactions, bytes[] memory signatures ) public @@ -77,35 +79,35 @@ contract MixinTransactions is /// @param signature Proof that transaction has been signed by signer. /// @return ABI encoded return data of the underlying Exchange function call. function _executeTransaction( - ZeroExTransaction memory transaction, + LibZeroExTransaction.ZeroExTransaction memory transaction, bytes memory signature ) internal returns (bytes memory) { - bytes32 transactionHash = getTransactionHash(transaction); + bytes32 transactionHash = transaction.getTypedDataHash(EIP712_EXCHANGE_DOMAIN_HASH); // Check transaction is not expired // solhint-disable-next-line not-rely-on-time if (block.timestamp >= transaction.expirationTimeSeconds) { - LibRichErrors._rrevert(LibExchangeRichErrors.TransactionError( - IExchangeRichErrors.TransactionErrorCodes.EXPIRED, + LibRichErrors.rrevert(LibExchangeRichErrors.TransactionError( + LibExchangeRichErrors.TransactionErrorCodes.EXPIRED, transactionHash )); } // Prevent reentrancy if (currentContextAddress != address(0)) { - LibRichErrors._rrevert(LibExchangeRichErrors.TransactionError( - IExchangeRichErrors.TransactionErrorCodes.NO_REENTRANCY, + LibRichErrors.rrevert(LibExchangeRichErrors.TransactionError( + LibExchangeRichErrors.TransactionErrorCodes.NO_REENTRANCY, transactionHash )); } // Validate transaction has not been executed if (transactionsExecuted[transactionHash]) { - LibRichErrors._rrevert(LibExchangeRichErrors.TransactionError( - IExchangeRichErrors.TransactionErrorCodes.ALREADY_EXECUTED, + LibRichErrors.rrevert(LibExchangeRichErrors.TransactionError( + LibExchangeRichErrors.TransactionErrorCodes.ALREADY_EXECUTED, transactionHash )); } @@ -118,7 +120,7 @@ contract MixinTransactions is transaction, transactionHash, signature)) { - LibRichErrors._rrevert(LibExchangeRichErrors.TransactionSignatureError( + LibRichErrors.rrevert(LibExchangeRichErrors.TransactionSignatureError( transactionHash, signerAddress, signature @@ -133,7 +135,7 @@ contract MixinTransactions is transactionsExecuted[transactionHash] = true; (bool didSucceed, bytes memory returnData) = address(this).delegatecall(transaction.data); if (!didSucceed) { - LibRichErrors._rrevert(LibExchangeRichErrors.TransactionExecutionError( + LibRichErrors.rrevert(LibExchangeRichErrors.TransactionExecutionError( transactionHash, returnData )); diff --git a/contracts/exchange/contracts/src/MixinWrapperFunctions.sol b/contracts/exchange/contracts/src/MixinWrapperFunctions.sol index 94e7bae8f3..cd2381f966 100644 --- a/contracts/exchange/contracts/src/MixinWrapperFunctions.sol +++ b/contracts/exchange/contracts/src/MixinWrapperFunctions.sol @@ -19,15 +19,14 @@ pragma solidity ^0.5.9; pragma experimental ABIEncoderV2; -import "@0x/contracts-utils/contracts/src/ReentrancyGuard.sol"; +import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; import "@0x/contracts-utils/contracts/src/LibRichErrors.sol"; -import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibExchangeRichErrors.sol"; import "./interfaces/IExchangeCore.sol"; -import "./interfaces/IExchangeRichErrors.sol"; import "./interfaces/IWrapperFunctions.sol"; -import "./LibExchangeRichErrors.sol"; import "./MixinExchangeCore.sol"; @@ -35,6 +34,8 @@ contract MixinWrapperFunctions is IWrapperFunctions, MixinExchangeCore { + using LibSafeMath for uint256; + /// @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. @@ -46,7 +47,7 @@ contract MixinWrapperFunctions is ) public nonReentrant - returns (FillResults memory fillResults) + returns (LibFillResults.FillResults memory fillResults) { fillResults = _fillOrKillOrder( order, @@ -68,11 +69,11 @@ contract MixinWrapperFunctions is bytes memory signature ) public - returns (FillResults memory fillResults) + returns (LibFillResults.FillResults memory fillResults) { // ABI encode calldata for `fillOrder` bytes memory fillOrderCalldata = abi.encodeWithSelector( - IExchangeCore(0).fillOrder.selector, + IExchangeCore(address(0)).fillOrder.selector, order, takerAssetFillAmount, signature @@ -81,7 +82,7 @@ contract MixinWrapperFunctions is (bool didSucceed, bytes memory returnData) = address(this).delegatecall(fillOrderCalldata); if (didSucceed) { assert(returnData.length == 128); - fillResults = abi.decode(returnData, (FillResults)); + fillResults = abi.decode(returnData, (LibFillResults.FillResults)); } // fillResults values will be 0 by default if call was unsuccessful return fillResults; @@ -99,10 +100,10 @@ contract MixinWrapperFunctions is ) public nonReentrant - returns (FillResults[] memory fillResults) + returns (LibFillResults.FillResults[] memory fillResults) { uint256 ordersLength = orders.length; - fillResults = new FillResults[](ordersLength); + fillResults = new LibFillResults.FillResults[](ordersLength); for (uint256 i = 0; i != ordersLength; i++) { fillResults[i] = _fillOrder( orders[i], @@ -125,10 +126,10 @@ contract MixinWrapperFunctions is ) public nonReentrant - returns (FillResults[] memory fillResults) + returns (LibFillResults.FillResults[] memory fillResults) { uint256 ordersLength = orders.length; - fillResults = new FillResults[](ordersLength); + fillResults = new LibFillResults.FillResults[](ordersLength); for (uint256 i = 0; i != ordersLength; i++) { fillResults[i] = _fillOrKillOrder( orders[i], @@ -150,10 +151,10 @@ contract MixinWrapperFunctions is bytes[] memory signatures ) public - returns (FillResults[] memory fillResults) + returns (LibFillResults.FillResults[] memory fillResults) { uint256 ordersLength = orders.length; - fillResults = new FillResults[](ordersLength); + fillResults = new LibFillResults.FillResults[](ordersLength); for (uint256 i = 0; i != ordersLength; i++) { fillResults[i] = fillOrderNoThrow( orders[i], @@ -175,7 +176,7 @@ contract MixinWrapperFunctions is bytes[] memory signatures ) public - returns (FillResults memory fillResults) + returns (LibFillResults.FillResults memory fillResults) { bytes memory takerAssetData = orders[0].takerAssetData; @@ -188,17 +189,17 @@ contract MixinWrapperFunctions is orders[i].takerAssetData = takerAssetData; // Calculate the remaining amount of takerAsset to sell - uint256 remainingTakerAssetFillAmount = _safeSub(takerAssetFillAmount, fillResults.takerAssetFilledAmount); + uint256 remainingTakerAssetFillAmount = takerAssetFillAmount.safeSub(fillResults.takerAssetFilledAmount); // Attempt to sell the remaining amount of takerAsset - FillResults memory singleFillResults = fillOrderNoThrow( + LibFillResults.FillResults memory singleFillResults = fillOrderNoThrow( orders[i], remainingTakerAssetFillAmount, signatures[i] ); // Update amounts filled and fees paid by maker and taker - _addFillResults(fillResults, singleFillResults); + fillResults = LibFillResults.addFillResults(fillResults, singleFillResults); // Stop execution if the entire amount of takerAsset has been sold if (fillResults.takerAssetFilledAmount >= takerAssetFillAmount) { @@ -219,7 +220,7 @@ contract MixinWrapperFunctions is bytes[] memory signatures ) public - returns (FillResults memory fillResults) + returns (LibFillResults.FillResults memory fillResults) { bytes memory makerAssetData = orders[0].makerAssetData; @@ -232,25 +233,25 @@ contract MixinWrapperFunctions is orders[i].makerAssetData = makerAssetData; // Calculate the remaining amount of makerAsset to buy - uint256 remainingMakerAssetFillAmount = _safeSub(makerAssetFillAmount, fillResults.makerAssetFilledAmount); + uint256 remainingMakerAssetFillAmount = makerAssetFillAmount.safeSub(fillResults.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( + uint256 remainingTakerAssetFillAmount = LibMath.getPartialAmountFloor( orders[i].takerAssetAmount, orders[i].makerAssetAmount, remainingMakerAssetFillAmount ); // Attempt to sell the remaining amount of takerAsset - FillResults memory singleFillResults = fillOrderNoThrow( + LibFillResults.FillResults memory singleFillResults = fillOrderNoThrow( orders[i], remainingTakerAssetFillAmount, signatures[i] ); // Update amounts filled and fees paid by maker and taker - _addFillResults(fillResults, singleFillResults); + fillResults = LibFillResults.addFillResults(fillResults, singleFillResults); // Stop execution if the entire amount of makerAsset has been bought if (fillResults.makerAssetFilledAmount >= makerAssetFillAmount) { @@ -298,7 +299,7 @@ contract MixinWrapperFunctions is bytes memory signature ) internal - returns (FillResults memory fillResults) + returns (LibFillResults.FillResults memory fillResults) { fillResults = _fillOrder( order, @@ -306,7 +307,7 @@ contract MixinWrapperFunctions is signature ); if (fillResults.takerAssetFilledAmount != takerAssetFillAmount) { - LibRichErrors._rrevert(LibExchangeRichErrors.IncompleteFillError( + LibRichErrors.rrevert(LibExchangeRichErrors.IncompleteFillError( getOrderInfo(order).orderHash )); } diff --git a/contracts/exchange/contracts/src/interfaces/IExchangeCore.sol b/contracts/exchange/contracts/src/interfaces/IExchangeCore.sol index 085154a7b5..5381623f27 100644 --- a/contracts/exchange/contracts/src/interfaces/IExchangeCore.sol +++ b/contracts/exchange/contracts/src/interfaces/IExchangeCore.sol @@ -32,14 +32,14 @@ contract IExchangeCore { bytes makerAssetData, // Encoded data specific to makerAsset. bytes takerAssetData, // Encoded data specific to takerAsset. bytes makerFeeAssetData, // Encoded data specific to makerFeeAsset. - bytes takerFeeAssetData, // Encoded data specific to takerFeeAsset. + bytes takerFeeAssetData, // Encoded data specific to takerFeeAsset. 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 makerFeeAssetData paid to feeRecipient by maker. uint256 takerFeePaid, // Amount of takerFeeAssetData paid to feeRecipient by taker. address takerAddress, // Address that filled the order. address senderAddress, // Address that called the Exchange contract (msg.sender). - bytes32 indexed orderHash // EIP712 hash of order (see LibOrder.getOrderHash). + bytes32 indexed orderHash // EIP712 hash of order (see LibOrder.getTypedDataHash). ); // Cancel event is emitted whenever an individual order is cancelled. @@ -47,7 +47,7 @@ contract IExchangeCore { 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). + bytes32 indexed orderHash, // EIP712 hash of order (see LibOrder.getTypedDataHash). bytes makerAssetData, // Encoded data specific to makerAsset. bytes takerAssetData // Encoded data specific to takerAsset. ); diff --git a/contracts/exchange/contracts/src/interfaces/IMatchOrders.sol b/contracts/exchange/contracts/src/interfaces/IMatchOrders.sol index fd8b67373f..9bcfcfd37d 100644 --- a/contracts/exchange/contracts/src/interfaces/IMatchOrders.sol +++ b/contracts/exchange/contracts/src/interfaces/IMatchOrders.sol @@ -60,26 +60,6 @@ contract IMatchOrders { public returns (LibFillResults.BatchMatchedFillResults memory batchMatchedFillResults); - /// @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, - bool shouldMaximallyFillOrders - ) - public - pure - returns (LibFillResults.MatchedFillResults memory matchedFillResults); - /// @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. diff --git a/contracts/exchange/contracts/src/libs/LibExchangeRichErrorDecoder.sol b/contracts/exchange/contracts/src/libs/LibExchangeRichErrorDecoder.sol index b56fcd7420..eb171b34ad 100644 --- a/contracts/exchange/contracts/src/libs/LibExchangeRichErrorDecoder.sol +++ b/contracts/exchange/contracts/src/libs/LibExchangeRichErrorDecoder.sol @@ -19,14 +19,12 @@ pragma solidity ^0.5.9; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibExchangeRichErrors.sol"; import "@0x/contracts-utils/contracts/src/LibBytes.sol"; -import "../interfaces/IExchangeRichErrors.sol"; -import "../LibExchangeRichErrors.sol"; -contract LibExchangeRichErrorDecoder is - IExchangeRichErrors -{ +contract LibExchangeRichErrorDecoder { + /// @dev Decompose an ABI-encoded SignatureError. /// @param encoded ABI-encoded revert error. /// @return errorCode The error code. @@ -36,14 +34,14 @@ contract LibExchangeRichErrorDecoder is public pure returns ( - SignatureErrorCodes errorCode, + LibExchangeRichErrors.SignatureErrorCodes errorCode, bytes32 hash, address signerAddress, bytes memory signature ) { _assertSelectorBytes(encoded, LibExchangeRichErrors.SignatureErrorSelector()); - errorCode = SignatureErrorCodes(_readErrorParameterAsUint256(encoded, 0)); + errorCode = LibExchangeRichErrors.SignatureErrorCodes(_readErrorParameterAsUint256(encoded, 0)); hash = _readErrorParameterAsBytes32(encoded, 1); signerAddress = _readErrorParameterAsAddress(encoded, 2); signature = _readErrorParameterAsBytes(encoded, 3); @@ -189,12 +187,12 @@ contract LibExchangeRichErrorDecoder is public pure returns ( - FillErrorCodes errorCode, + LibExchangeRichErrors.FillErrorCodes errorCode, bytes32 orderHash ) { _assertSelectorBytes(encoded, LibExchangeRichErrors.FillErrorSelector()); - errorCode = FillErrorCodes(_readErrorParameterAsUint256(encoded, 0)); + errorCode = LibExchangeRichErrors.FillErrorCodes(_readErrorParameterAsUint256(encoded, 0)); orderHash = _readErrorParameterAsBytes32(encoded, 1); } @@ -239,13 +237,13 @@ contract LibExchangeRichErrorDecoder is public pure returns ( - AssetProxyDispatchErrorCodes errorCode, + LibExchangeRichErrors.AssetProxyDispatchErrorCodes errorCode, bytes32 orderHash, bytes memory assetData ) { _assertSelectorBytes(encoded, LibExchangeRichErrors.AssetProxyDispatchErrorSelector()); - errorCode = AssetProxyDispatchErrorCodes(_readErrorParameterAsUint256(encoded, 0)); + errorCode = LibExchangeRichErrors.AssetProxyDispatchErrorCodes(_readErrorParameterAsUint256(encoded, 0)); orderHash = _readErrorParameterAsBytes32(encoded, 1); assetData = _readErrorParameterAsBytes(encoded, 2); } @@ -295,12 +293,12 @@ contract LibExchangeRichErrorDecoder is public pure returns ( - TransactionErrorCodes errorCode, + LibExchangeRichErrors.TransactionErrorCodes errorCode, bytes32 transactionHash ) { _assertSelectorBytes(encoded, LibExchangeRichErrors.TransactionErrorSelector()); - errorCode = TransactionErrorCodes(_readErrorParameterAsUint256(encoded, 0)); + errorCode = LibExchangeRichErrors.TransactionErrorCodes(_readErrorParameterAsUint256(encoded, 0)); transactionHash = _readErrorParameterAsBytes32(encoded, 1); } diff --git a/contracts/exchange/contracts/test/IsolatedExchange.sol b/contracts/exchange/contracts/test/IsolatedExchange.sol index 90853cd537..7590b8d32c 100644 --- a/contracts/exchange/contracts/test/IsolatedExchange.sol +++ b/contracts/exchange/contracts/test/IsolatedExchange.sol @@ -19,6 +19,7 @@ pragma solidity ^0.5.5; pragma experimental ABIEncoderV2; +import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "../src/Exchange.sol"; @@ -71,7 +72,7 @@ contract IsolatedExchange is /// @dev Overriden to simplify signature validation. /// Unfortunately, this is `view`, so it can't log arguments. function _isValidOrderWithHashSignature( - Order memory order, + LibOrder.Order memory order, bytes32 orderHash, bytes memory signature ) diff --git a/contracts/exchange/contracts/test/ReentrancyTester.sol b/contracts/exchange/contracts/test/ReentrancyTester.sol index c50dec6662..5eb1d5d113 100644 --- a/contracts/exchange/contracts/test/ReentrancyTester.sol +++ b/contracts/exchange/contracts/test/ReentrancyTester.sol @@ -20,6 +20,9 @@ pragma solidity ^0.5.5; pragma experimental ABIEncoderV2; import "@0x/contracts-utils/contracts/src/LibReentrancyGuardRichErrors.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol"; import "../src/Exchange.sol"; @@ -71,27 +74,27 @@ contract ReentrancyTester is /// @dev Overriden to do nothing. function _fillOrder( - Order memory order, + LibOrder.Order memory order, uint256 takerAssetFillAmount, bytes memory signature ) internal - returns (FillResults memory fillResults) + returns (LibFillResults.FillResults memory fillResults) {} /// @dev Overriden to do nothing. function _fillOrKillOrder( - Order memory order, + LibOrder.Order memory order, uint256 takerAssetFillAmount, bytes memory signature ) internal - returns (FillResults memory fillResults) + returns (LibFillResults.FillResults memory fillResults) {} /// @dev Overridden to do nothing. function _executeTransaction( - ZeroExTransaction memory transaction, + LibZeroExTransaction.ZeroExTransaction memory transaction, bytes memory signature ) internal @@ -126,7 +129,7 @@ contract ReentrancyTester is {} /// @dev Overriden to do nothing. - function _cancelOrder(Order memory order) + function _cancelOrder(LibOrder.Order memory order) internal {} } diff --git a/contracts/exchange/contracts/test/TestExchangeInternals.sol b/contracts/exchange/contracts/test/TestExchangeInternals.sol index b233404272..48a657227a 100644 --- a/contracts/exchange/contracts/test/TestExchangeInternals.sol +++ b/contracts/exchange/contracts/test/TestExchangeInternals.sol @@ -19,6 +19,8 @@ pragma solidity ^0.5.5; pragma experimental ABIEncoderV2; +import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol"; import "../src/Exchange.sol"; @@ -46,69 +48,26 @@ contract TestExchangeInternals is public view { - _assertValidMatch(leftOrder, rightOrder); - } - - function calculateFillResults( - Order memory order, - uint256 takerAssetFilledAmount - ) - public - pure - returns (FillResults memory fillResults) - { - return _calculateFillResults(order, takerAssetFilledAmount); - } - - function calculateCompleteFillBoth( - uint256 leftMakerAssetAmountRemaining, - uint256 leftTakerAssetAmountRemaining, - uint256 rightMakerAssetAmountRemaining, - uint256 rightTakerAssetAmountRemaining - ) - public - pure - returns (MatchedFillResults memory fillResults) - { - _calculateCompleteFillBoth( - fillResults, - leftMakerAssetAmountRemaining, - leftTakerAssetAmountRemaining, - rightMakerAssetAmountRemaining, - rightTakerAssetAmountRemaining - ); - return fillResults; - } - - function calculateCompleteRightFill( - LibOrder.Order memory leftOrder, - uint256 rightMakerAssetAmountRemaining, - uint256 rightTakerAssetAmountRemaining - ) - public - pure - returns (MatchedFillResults memory fillResults) - { - _calculateCompleteRightFill( - fillResults, + _assertValidMatch( leftOrder, - rightMakerAssetAmountRemaining, - rightTakerAssetAmountRemaining + rightOrder, + getOrderInfo(leftOrder), + getOrderInfo(rightOrder) ); } /// @dev Call `_updateFilledState()` but first set `filled[order]` to /// `orderTakerAssetFilledAmount`. function testUpdateFilledState( - Order memory order, + LibOrder.Order memory order, address takerAddress, bytes32 orderHash, uint256 orderTakerAssetFilledAmount, - FillResults memory fillResults + LibFillResults.FillResults memory fillResults ) public { - filled[getOrderHash(order)] = orderTakerAssetFilledAmount; + filled[LibOrder.getTypedDataHash(order, EIP712_EXCHANGE_DOMAIN_HASH)] = orderTakerAssetFilledAmount; _updateFilledState( order, takerAddress, diff --git a/contracts/exchange/contracts/test/TestSignatureValidator.sol b/contracts/exchange/contracts/test/TestSignatureValidator.sol index 0b0314cdf1..9705a4a298 100644 --- a/contracts/exchange/contracts/test/TestSignatureValidator.sol +++ b/contracts/exchange/contracts/test/TestSignatureValidator.sol @@ -20,14 +20,12 @@ pragma solidity ^0.5.5; pragma experimental ABIEncoderV2; import "@0x/contracts-exchange-libs/contracts/src/LibEIP712ExchangeDomain.sol"; -import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "../src/MixinSignatureValidator.sol"; import "../src/MixinTransactions.sol"; contract TestSignatureValidator is LibEIP712ExchangeDomain, - LibOrder, MixinSignatureValidator { diff --git a/contracts/exchange/contracts/test/TestValidatorWallet.sol b/contracts/exchange/contracts/test/TestValidatorWallet.sol index b33d2f6ce2..7e0472023e 100644 --- a/contracts/exchange/contracts/test/TestValidatorWallet.sol +++ b/contracts/exchange/contracts/test/TestValidatorWallet.sol @@ -21,24 +21,12 @@ pragma experimental ABIEncoderV2; import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibEIP712ExchangeDomain.sol"; import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol"; import "@0x/contracts-utils/contracts/src/LibBytes.sol"; import "@0x/contracts-utils/contracts/src/LibEIP1271.sol"; -interface ISimplifiedExchange { - function getOrderHash(LibOrder.Order calldata order) - external - view - returns (bytes32 orderHash); - - function getTransactionHash(LibZeroExTransaction.ZeroExTransaction calldata transaction) - external - view - returns (bytes32 transactionHash); -} - - // solhint-disable no-unused-vars contract TestValidatorWallet is LibEIP1271 @@ -80,8 +68,8 @@ contract TestValidatorWallet is NTypes } - /// @dev The Exchange contract. - ISimplifiedExchange internal _exchange; + /// @dev The Exchange domain hash.. + LibEIP712ExchangeDomain internal _exchange; /// @dev Internal state to modify. uint256 internal _state = 1; /// @dev What action to execute when a hash is validated . @@ -92,7 +80,7 @@ contract TestValidatorWallet is mapping (bytes32 => bytes32) internal _hashSignatureHashes; constructor(address exchange) public { - _exchange = ISimplifiedExchange(exchange); + _exchange = LibEIP712ExchangeDomain(exchange); } /// @dev Approves an ERC20 token to spend tokens from this address. @@ -239,7 +227,7 @@ contract TestValidatorWallet is // Use the Exchange to calculate the hash of the order and assert // that it matches the one we extracted previously. require( - _exchange.getOrderHash(order) == hash, + LibOrder.getTypedDataHash(order, _exchange.EIP712_EXCHANGE_DOMAIN_HASH()) == hash, "UNEXPECTED_ORDER_HASH" ); } else { @@ -250,7 +238,7 @@ contract TestValidatorWallet is // Use the Exchange to calculate the hash of the transaction and assert // that it matches the one we extracted previously. require( - _exchange.getTransactionHash(transaction) == hash, + LibZeroExTransaction.getTypedDataHash(transaction, _exchange.EIP712_EXCHANGE_DOMAIN_HASH()) == hash, "UNEXPECTED_TRANSACTION_HASH" ); } diff --git a/contracts/exchange/contracts/test/TestWrapperFunctions.sol b/contracts/exchange/contracts/test/TestWrapperFunctions.sol index f360f641a0..598202b12f 100644 --- a/contracts/exchange/contracts/test/TestWrapperFunctions.sol +++ b/contracts/exchange/contracts/test/TestWrapperFunctions.sol @@ -19,6 +19,8 @@ pragma solidity ^0.5.5; pragma experimental ABIEncoderV2; +import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibFillResults.sol"; import "../src/Exchange.sol"; @@ -28,19 +30,19 @@ import "../src/Exchange.sol"; contract TestWrapperFunctions is Exchange { - uint8 internal constant MAX_ORDER_STATUS = uint8(OrderStatus.CANCELLED); + uint8 internal constant MAX_ORDER_STATUS = uint8(LibOrder.OrderStatus.CANCELLED); uint256 internal constant ALWAYS_FAILING_SALT = uint256(-1); string internal constant ALWAYS_FAILING_SALT_REVERT_REASON = "ALWAYS_FAILING_SALT"; // solhint-disable no-unused-vars event FillOrderCalled( - Order order, + LibOrder.Order order, uint256 takerAssetFillAmount, bytes signature ); event CancelOrderCalled( - Order order + LibOrder.Order order ); // solhint-disable no-empty-blocks @@ -51,26 +53,26 @@ contract TestWrapperFunctions is {} /// @dev Overridden to be deterministic and simplified. - function getOrderInfo(Order memory order) + function getOrderInfo(LibOrder.Order memory order) public view - returns (OrderInfo memory orderInfo) + returns (LibOrder.OrderInfo memory orderInfo) { // Lower uint128 of `order.salt` is the `orderTakerAssetFilledAmount`. orderInfo.orderTakerAssetFilledAmount = uint128(order.salt); // High byte of `order.salt` is the `orderStatus`. orderInfo.orderStatus = uint8(order.salt >> 248) % (MAX_ORDER_STATUS + 1); - orderInfo.orderHash = _getOrderHash(order); + orderInfo.orderHash = _getTypedDataHash(order); } /// @dev Overridden to log arguments, be deterministic, and revert with certain inputs. function _fillOrder( - Order memory order, + LibOrder.Order memory order, uint256 takerAssetFillAmount, bytes memory signature ) internal - returns (FillResults memory fillResults) + returns (LibFillResults.FillResults memory fillResults) { emit FillOrderCalled( order, @@ -94,7 +96,7 @@ contract TestWrapperFunctions is /// @dev Overridden to only log arguments and revert with certain inputs. function _cancelOrder( - Order memory order + LibOrder.Order memory order ) internal { @@ -109,7 +111,7 @@ contract TestWrapperFunctions is } /// @dev Simplified order hashing. - function _getOrderHash(Order memory order) + function _getTypedDataHash(LibOrder.Order memory order) internal pure returns (bytes32 hash) diff --git a/contracts/exchange/package.json b/contracts/exchange/package.json index a761d63079..5246e81385 100644 --- a/contracts/exchange/package.json +++ b/contracts/exchange/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", - "pre_build": "run-s compile generate_contract_wrappers", + "pre_build": "run-s compile contracts:gen 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", @@ -34,8 +34,8 @@ "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./generated-artifacts/@(Exchange|ExchangeWrapper|IAssetProxyDispatcher|IEIP1271Wallet|IExchange|IExchangeCore|IMatchOrders|ISignatureValidator|ITransactions|IWallet|IWrapperFunctions|IsolatedExchange|ReentrancyTester|TestAssetProxyDispatcher|TestExchangeInternals|TestLibExchangeRichErrorDecoder|TestSignatureValidator|TestValidatorWallet|TestWrapperFunctions|Whitelist).json" + "abis": "./generated-artifacts/@(Exchange|ExchangeWrapper|IAssetProxy|IAssetProxyDispatcher|IEIP1271Wallet|IExchange|IExchangeCore|IExchangeRichErrors|IMatchOrders|ISignatureValidator|ITransactions|ITransferSimulator|IWallet|IWrapperFunctions|IsolatedExchange|LibExchangeRichErrorDecoder|MixinAssetProxyDispatcher|MixinExchangeCore|MixinMatchOrders|MixinSignatureValidator|MixinTransactions|MixinTransferSimulator|MixinWrapperFunctions|ReentrancyTester|TestAssetProxyDispatcher|TestExchangeInternals|TestLibExchangeRichErrorDecoder|TestSignatureValidator|TestValidatorWallet|TestWrapperFunctions|Whitelist).json", + "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { "type": "git", diff --git a/contracts/exchange/src/artifacts.ts b/contracts/exchange/src/artifacts.ts index ef5184ade7..03f27aafc6 100644 --- a/contracts/exchange/src/artifacts.ts +++ b/contracts/exchange/src/artifacts.ts @@ -7,16 +7,27 @@ import { ContractArtifact } from 'ethereum-types'; import * as Exchange from '../generated-artifacts/Exchange.json'; import * as ExchangeWrapper from '../generated-artifacts/ExchangeWrapper.json'; +import * as IAssetProxy from '../generated-artifacts/IAssetProxy.json'; import * as IAssetProxyDispatcher from '../generated-artifacts/IAssetProxyDispatcher.json'; import * as IEIP1271Wallet from '../generated-artifacts/IEIP1271Wallet.json'; import * as IExchange from '../generated-artifacts/IExchange.json'; import * as IExchangeCore from '../generated-artifacts/IExchangeCore.json'; +import * as IExchangeRichErrors from '../generated-artifacts/IExchangeRichErrors.json'; import * as IMatchOrders from '../generated-artifacts/IMatchOrders.json'; import * as ISignatureValidator from '../generated-artifacts/ISignatureValidator.json'; import * as ITransactions from '../generated-artifacts/ITransactions.json'; +import * as ITransferSimulator from '../generated-artifacts/ITransferSimulator.json'; import * as IWallet from '../generated-artifacts/IWallet.json'; import * as IWrapperFunctions from '../generated-artifacts/IWrapperFunctions.json'; import * as IsolatedExchange from '../generated-artifacts/IsolatedExchange.json'; +import * as LibExchangeRichErrorDecoder from '../generated-artifacts/LibExchangeRichErrorDecoder.json'; +import * as MixinAssetProxyDispatcher from '../generated-artifacts/MixinAssetProxyDispatcher.json'; +import * as MixinExchangeCore from '../generated-artifacts/MixinExchangeCore.json'; +import * as MixinMatchOrders from '../generated-artifacts/MixinMatchOrders.json'; +import * as MixinSignatureValidator from '../generated-artifacts/MixinSignatureValidator.json'; +import * as MixinTransactions from '../generated-artifacts/MixinTransactions.json'; +import * as MixinTransferSimulator from '../generated-artifacts/MixinTransferSimulator.json'; +import * as MixinWrapperFunctions from '../generated-artifacts/MixinWrapperFunctions.json'; import * as ReentrancyTester from '../generated-artifacts/ReentrancyTester.json'; import * as TestAssetProxyDispatcher from '../generated-artifacts/TestAssetProxyDispatcher.json'; import * as TestExchangeInternals from '../generated-artifacts/TestExchangeInternals.json'; @@ -29,15 +40,26 @@ export const artifacts = { ExchangeWrapper: ExchangeWrapper as ContractArtifact, Whitelist: Whitelist as ContractArtifact, Exchange: Exchange as ContractArtifact, + MixinAssetProxyDispatcher: MixinAssetProxyDispatcher as ContractArtifact, + MixinExchangeCore: MixinExchangeCore as ContractArtifact, + MixinMatchOrders: MixinMatchOrders as ContractArtifact, + MixinSignatureValidator: MixinSignatureValidator as ContractArtifact, + MixinTransactions: MixinTransactions as ContractArtifact, + MixinTransferSimulator: MixinTransferSimulator as ContractArtifact, + MixinWrapperFunctions: MixinWrapperFunctions as ContractArtifact, + IAssetProxy: IAssetProxy as ContractArtifact, IAssetProxyDispatcher: IAssetProxyDispatcher as ContractArtifact, IEIP1271Wallet: IEIP1271Wallet as ContractArtifact, IExchange: IExchange as ContractArtifact, IExchangeCore: IExchangeCore as ContractArtifact, + IExchangeRichErrors: IExchangeRichErrors as ContractArtifact, IMatchOrders: IMatchOrders as ContractArtifact, ISignatureValidator: ISignatureValidator as ContractArtifact, ITransactions: ITransactions as ContractArtifact, + ITransferSimulator: ITransferSimulator as ContractArtifact, IWallet: IWallet as ContractArtifact, IWrapperFunctions: IWrapperFunctions as ContractArtifact, + LibExchangeRichErrorDecoder: LibExchangeRichErrorDecoder as ContractArtifact, IsolatedExchange: IsolatedExchange as ContractArtifact, ReentrancyTester: ReentrancyTester as ContractArtifact, TestAssetProxyDispatcher: TestAssetProxyDispatcher as ContractArtifact, diff --git a/contracts/exchange/src/wrappers.ts b/contracts/exchange/src/wrappers.ts index 63165e9a40..2efb88a2ed 100644 --- a/contracts/exchange/src/wrappers.ts +++ b/contracts/exchange/src/wrappers.ts @@ -5,16 +5,27 @@ */ export * from '../generated-wrappers/exchange'; export * from '../generated-wrappers/exchange_wrapper'; +export * from '../generated-wrappers/i_asset_proxy'; export * from '../generated-wrappers/i_asset_proxy_dispatcher'; export * from '../generated-wrappers/i_e_i_p1271_wallet'; export * from '../generated-wrappers/i_exchange'; export * from '../generated-wrappers/i_exchange_core'; +export * from '../generated-wrappers/i_exchange_rich_errors'; export * from '../generated-wrappers/i_match_orders'; export * from '../generated-wrappers/i_signature_validator'; export * from '../generated-wrappers/i_transactions'; +export * from '../generated-wrappers/i_transfer_simulator'; export * from '../generated-wrappers/i_wallet'; export * from '../generated-wrappers/i_wrapper_functions'; export * from '../generated-wrappers/isolated_exchange'; +export * from '../generated-wrappers/lib_exchange_rich_error_decoder'; +export * from '../generated-wrappers/mixin_asset_proxy_dispatcher'; +export * from '../generated-wrappers/mixin_exchange_core'; +export * from '../generated-wrappers/mixin_match_orders'; +export * from '../generated-wrappers/mixin_signature_validator'; +export * from '../generated-wrappers/mixin_transactions'; +export * from '../generated-wrappers/mixin_transfer_simulator'; +export * from '../generated-wrappers/mixin_wrapper_functions'; export * from '../generated-wrappers/reentrancy_tester'; export * from '../generated-wrappers/test_asset_proxy_dispatcher'; export * from '../generated-wrappers/test_exchange_internals'; diff --git a/contracts/exchange/test/internal.ts b/contracts/exchange/test/internal.ts index 8357233f8c..e7fc187446 100644 --- a/contracts/exchange/test/internal.ts +++ b/contracts/exchange/test/internal.ts @@ -1,16 +1,7 @@ import { ReferenceFunctions as LibReferenceFunctions } from '@0x/contracts-exchange-libs'; -import { - blockchainTests, - constants, - describe, - expect, - hexRandom, - LogDecoder, - testCombinatoriallyWithReferenceFunc, - uint256Values, -} from '@0x/contracts-test-utils'; -import { ExchangeRevertErrors, LibMathRevertErrors, orderHashUtils } from '@0x/order-utils'; -import { FillResults, MatchedFillResults, Order, OrderWithoutDomain } from '@0x/types'; +import { blockchainTests, constants, expect, hexRandom, LogDecoder } from '@0x/contracts-test-utils'; +import { ExchangeRevertErrors, orderHashUtils } from '@0x/order-utils'; +import { Order, OrderWithoutDomain } from '@0x/types'; import { BigNumber, SafeMathRevertErrors } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import { LogWithDecodedArgs } from 'ethereum-types'; @@ -18,7 +9,6 @@ import * as _ from 'lodash'; import { artifacts, - ReferenceFunctions, TestExchangeInternalsContract, TestExchangeInternalsDispatchTransferFromCalledEventArgs, TestExchangeInternalsFillEventArgs, @@ -27,83 +17,16 @@ import { blockchainTests('Exchange core internal functions', env => { const CHAIN_ID = 1337; const ONE_ETHER = constants.ONE_ETHER; - const EMPTY_ORDER_WITHOUT_DOMAIN: OrderWithoutDomain = { - senderAddress: constants.NULL_ADDRESS, - makerAddress: constants.NULL_ADDRESS, - takerAddress: constants.NULL_ADDRESS, - makerFee: constants.ZERO_AMOUNT, - takerFee: constants.ZERO_AMOUNT, - makerAssetAmount: constants.ZERO_AMOUNT, - takerAssetAmount: constants.ZERO_AMOUNT, - makerAssetData: constants.NULL_BYTES, - takerAssetData: constants.NULL_BYTES, - makerFeeAssetData: constants.NULL_BYTES, - takerFeeAssetData: constants.NULL_BYTES, - salt: constants.ZERO_AMOUNT, - feeRecipientAddress: constants.NULL_ADDRESS, - expirationTimeSeconds: constants.ZERO_AMOUNT, - }; - const EMPTY_FILL_RESULTS: FillResults = { - makerAssetFilledAmount: constants.ZERO_AMOUNT, - takerAssetFilledAmount: constants.ZERO_AMOUNT, - makerFeePaid: constants.ZERO_AMOUNT, - takerFeePaid: constants.ZERO_AMOUNT, - }; - const EMPTY_MATCHED_FILL_RESULTS: MatchedFillResults = { - left: EMPTY_FILL_RESULTS, - right: EMPTY_FILL_RESULTS, - profitInLeftMakerAsset: constants.ZERO_AMOUNT, - profitInRightMakerAsset: constants.ZERO_AMOUNT, - }; - const COMMON_MATCHED_FILL_RESULTS = { - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 18), - profitInRightMakerAsset: constants.ZERO_AMOUNT, - }; const randomAddress = () => hexRandom(constants.ADDRESS_LENGTH); const randomHash = () => hexRandom(constants.WORD_LENGTH); const randomAssetData = () => hexRandom(36); - const randomUint256 = () => new BigNumber(hexRandom(constants.WORD_LENGTH)); let testExchange: TestExchangeInternalsContract; let logDecoder: LogDecoder; let senderAddress: string; - let makerAddressLeft: string; - let makerAddressRight: string; - - interface PartialMatchedFillResults { - left: Partial; - right: Partial; - profitInLeftMakerAsset?: BigNumber; - profitInRightMakerAsset?: BigNumber; - } - - function createMatchedFillResults(partialMatchedFillResults: PartialMatchedFillResults): MatchedFillResults { - const matchedFillResults = EMPTY_MATCHED_FILL_RESULTS; - matchedFillResults.left = _.assign({}, EMPTY_FILL_RESULTS, partialMatchedFillResults.left); - matchedFillResults.right = _.assign({}, EMPTY_FILL_RESULTS, partialMatchedFillResults.right); - matchedFillResults.profitInLeftMakerAsset = - partialMatchedFillResults.profitInLeftMakerAsset || constants.ZERO_AMOUNT; - matchedFillResults.profitInRightMakerAsset = - partialMatchedFillResults.profitInRightMakerAsset || constants.ZERO_AMOUNT; - return matchedFillResults; - } before(async () => { const accounts = await env.getAccountAddressesAsync(); senderAddress = accounts[0]; - makerAddressLeft = accounts[1]; - makerAddressRight = accounts[2]; testExchange = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( artifacts.TestExchangeInternals, @@ -218,1723 +141,6 @@ blockchainTests('Exchange core internal functions', env => { }); }); - blockchainTests('calculateCompleteFillBoth', () => { - /** - * Asserts that the actual result of calling `calculateCompleteFillBoth()` is as expected. - */ - async function assertCalculateCompleteFillBothAsync( - expectedMatchedFillResults: MatchedFillResults, - leftMakerAssetAmountRemaining: BigNumber, - leftTakerAssetAmountRemaining: BigNumber, - rightMakerAssetAmountRemaining: BigNumber, - rightTakerAssetAmountRemaining: BigNumber, - ): Promise { - const actualMatchedFillResults = await testExchange.calculateCompleteFillBoth.callAsync( - leftMakerAssetAmountRemaining, - leftTakerAssetAmountRemaining, - rightMakerAssetAmountRemaining, - rightTakerAssetAmountRemaining, - ); - expect(actualMatchedFillResults).to.be.deep.eq(expectedMatchedFillResults); - } - - it('should assign everything to zero if all inputs are zero', async () => { - await assertCalculateCompleteFillBothAsync( - EMPTY_MATCHED_FILL_RESULTS, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - // Note: This call would never be able to be made through our contracts, since these orders will not actually be - // fully filled. This is just a test case to make sure. - it('should correctly update the fillResults with nonzero input', async () => { - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(17, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(98, 0), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(75, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), - }, - }); - await assertCalculateCompleteFillBothAsync( - expectedMatchedFillResults, - new BigNumber(17), - new BigNumber(98), - new BigNumber(75), - new BigNumber(13), - ); - }); - - it('should correctly update the fillResults with nonzero input', async () => { - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 0), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 0), - }, - }); - await assertCalculateCompleteFillBothAsync( - expectedMatchedFillResults, - new BigNumber(5), - new BigNumber(10), - new BigNumber(10), - new BigNumber(5), - ); - }); - - it('should correctly update the fillResults with nonzero input', async () => { - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - }, - }); - await assertCalculateCompleteFillBothAsync( - expectedMatchedFillResults, - Web3Wrapper.toBaseUnitAmount(5, 18), - Web3Wrapper.toBaseUnitAmount(10, 18), - Web3Wrapper.toBaseUnitAmount(10, 18), - Web3Wrapper.toBaseUnitAmount(5, 18), - ); - }); - - it('should correctly update the fillResults with nonzero input', async () => { - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - }, - }); - await assertCalculateCompleteFillBothAsync( - expectedMatchedFillResults, - Web3Wrapper.toBaseUnitAmount(5, 18), - Web3Wrapper.toBaseUnitAmount(10, 18), - Web3Wrapper.toBaseUnitAmount(10, 18), - Web3Wrapper.toBaseUnitAmount(2, 18), - ); - }); - }); - - blockchainTests('calculateCompleteRightFill', () => { - /** - * Asserts that the results of calling `calculateCompleteRightFill()` are consistent with the expected results. - */ - async function assertCalculateCompleteRightFillAsync( - expectedMatchedFillResults: MatchedFillResults, - leftOrder: Order, - rightMakerAssetAmountRemaining: BigNumber, - rightTakerAssetAmountRemaining: BigNumber, - ): Promise { - const actualMatchedFillResults = await testExchange.calculateCompleteRightFill.callAsync( - leftOrder, - rightMakerAssetAmountRemaining, - rightTakerAssetAmountRemaining, - ); - expect(actualMatchedFillResults).to.be.deep.eq(expectedMatchedFillResults); - } - - const ORDER_DEFAULTS = { - senderAddress: randomAddress(), - makerAddress: randomAddress(), - takerAddress: randomAddress(), - makerFee: ONE_ETHER.times(0.001), - takerFee: ONE_ETHER.times(0.003), - makerAssetAmount: ONE_ETHER, - takerAssetAmount: ONE_ETHER.times(0.5), - makerAssetData: randomAssetData(), - takerAssetData: randomAssetData(), - makerFeeAssetData: randomAssetData(), - takerFeeAssetData: randomAssetData(), - salt: new BigNumber(_.random(0, 1e8)), - feeRecipientAddress: randomAddress(), - expirationTimeSeconds: new BigNumber(_.random(0, 1e8)), - domain: { - verifyingContractAddress: constants.NULL_ADDRESS, - chainId: 1337, // The chain id for the isolated exchange - }, - }; - - function makeOrder(details?: Partial): Order { - return _.assign({}, ORDER_DEFAULTS, details); - } - - before(async () => { - ORDER_DEFAULTS.domain.verifyingContractAddress = testExchange.address; - }); - - it('should correctly calculate the complete right fill', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(17, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(98, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(75, 0), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(75, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), - }, - }); - await assertCalculateCompleteRightFillAsync( - expectedMatchedFillResults, - leftOrder, - Web3Wrapper.toBaseUnitAmount(75, 0), - Web3Wrapper.toBaseUnitAmount(13, 0), - ); - }); - - it('should correctly calculate the complete right fill', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(12, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(11, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1, 0), - }, - }); - await assertCalculateCompleteRightFillAsync( - expectedMatchedFillResults, - leftOrder, - Web3Wrapper.toBaseUnitAmount(89, 0), - Web3Wrapper.toBaseUnitAmount(1, 0), - ); - }); - - it('should correctly calculate the complete right fill', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - }, - }); - await assertCalculateCompleteRightFillAsync( - expectedMatchedFillResults, - leftOrder, - Web3Wrapper.toBaseUnitAmount(10, 18), - Web3Wrapper.toBaseUnitAmount(2, 18), - ); - }); - }); - - blockchainTests('calculateFillResults', () => { - describe.optional('combinatorial tests', () => { - function makeOrder( - makerAssetAmount: BigNumber, - takerAssetAmount: BigNumber, - makerFee: BigNumber, - takerFee: BigNumber, - ): OrderWithoutDomain { - return { - ...EMPTY_ORDER_WITHOUT_DOMAIN, - 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. - return ReferenceFunctions.calculateFillResults( - makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount), - takerAssetFilledAmount, - ); - } - - async function testCalculateFillResultsAsync( - orderTakerAssetAmount: BigNumber, - takerAssetFilledAmount: BigNumber, - otherAmount: BigNumber, - ): Promise { - const order = makeOrder(otherAmount, orderTakerAssetAmount, otherAmount, otherAmount); - return testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount); - } - - testCombinatoriallyWithReferenceFunc( - 'calculateFillResults', - referenceCalculateFillResultsAsync, - testCalculateFillResultsAsync, - [uint256Values, uint256Values, uint256Values], - ); - }); - - describe('explicit tests', () => { - const MAX_UINT256_ROOT = constants.MAX_UINT256_ROOT; - function makeOrder(details?: Partial): OrderWithoutDomain { - return _.assign({}, EMPTY_ORDER_WITHOUT_DOMAIN, details); - } - - it('matches the output of the reference function', async () => { - const order = makeOrder({ - makerAssetAmount: ONE_ETHER, - takerAssetAmount: ONE_ETHER.times(2), - makerFee: ONE_ETHER.times(0.0023), - takerFee: ONE_ETHER.times(0.0025), - }); - const takerAssetFilledAmount = ONE_ETHER.dividedToIntegerBy(3); - const expected = ReferenceFunctions.calculateFillResults(order, takerAssetFilledAmount); - const actual = await testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount); - expect(actual).to.deep.eq(expected); - }); - - it('reverts if computing `fillResults.makerAssetFilledAmount` overflows', async () => { - // All values need to be large to ensure we don't trigger a RoundingError. - const order = makeOrder({ - makerAssetAmount: MAX_UINT256_ROOT.times(2), - takerAssetAmount: MAX_UINT256_ROOT, - }); - const takerAssetFilledAmount = MAX_UINT256_ROOT; - const expectedError = new SafeMathRevertErrors.SafeMathError( - SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow, - takerAssetFilledAmount, - order.makerAssetAmount, - ); - return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( - expectedError, - ); - }); - - it('reverts if computing `fillResults.makerFeePaid` overflows', async () => { - // All values need to be large to ensure we don't trigger a RoundingError. - const order = makeOrder({ - makerAssetAmount: MAX_UINT256_ROOT, - takerAssetAmount: MAX_UINT256_ROOT, - makerFee: MAX_UINT256_ROOT.times(11), - }); - const takerAssetFilledAmount = MAX_UINT256_ROOT.dividedToIntegerBy(10); - const makerAssetFilledAmount = LibReferenceFunctions.getPartialAmountFloor( - takerAssetFilledAmount, - order.takerAssetAmount, - order.makerAssetAmount, - ); - const expectedError = new SafeMathRevertErrors.SafeMathError( - SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow, - makerAssetFilledAmount, - order.makerFee, - ); - return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( - expectedError, - ); - }); - - it('reverts if computing `fillResults.takerFeePaid` overflows', async () => { - // All values need to be large to ensure we don't trigger a RoundingError. - const order = makeOrder({ - makerAssetAmount: MAX_UINT256_ROOT, - takerAssetAmount: MAX_UINT256_ROOT, - takerFee: MAX_UINT256_ROOT.times(11), - }); - const takerAssetFilledAmount = MAX_UINT256_ROOT.dividedToIntegerBy(10); - const expectedError = new SafeMathRevertErrors.SafeMathError( - SafeMathRevertErrors.SafeMathErrorCodes.Uint256MultiplicationOverflow, - takerAssetFilledAmount, - order.takerFee, - ); - return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( - expectedError, - ); - }); - - it('reverts if `order.makerAssetAmount` is 0', async () => { - const order = makeOrder({ - makerAssetAmount: constants.ZERO_AMOUNT, - takerAssetAmount: ONE_ETHER, - }); - const takerAssetFilledAmount = ONE_ETHER; - const expectedError = new LibMathRevertErrors.DivisionByZeroError(); - return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( - expectedError, - ); - }); - - it('reverts if `order.takerAssetAmount` is 0', async () => { - const order = makeOrder({ - makerAssetAmount: ONE_ETHER, - takerAssetAmount: constants.ZERO_AMOUNT, - }); - const takerAssetFilledAmount = ONE_ETHER; - const expectedError = new LibMathRevertErrors.DivisionByZeroError(); - return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( - expectedError, - ); - }); - - it('reverts if there is a rounding error computing `makerAsssetFilledAmount`', async () => { - const order = makeOrder({ - makerAssetAmount: new BigNumber(100), - takerAssetAmount: ONE_ETHER, - }); - const takerAssetFilledAmount = order.takerAssetAmount.dividedToIntegerBy(3); - const expectedError = new LibMathRevertErrors.RoundingError( - takerAssetFilledAmount, - order.takerAssetAmount, - order.makerAssetAmount, - ); - return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( - expectedError, - ); - }); - - it('reverts if there is a rounding error computing `makerFeePaid`', async () => { - const order = makeOrder({ - makerAssetAmount: ONE_ETHER, - takerAssetAmount: ONE_ETHER, - makerFee: new BigNumber(100), - }); - const takerAssetFilledAmount = order.takerAssetAmount.dividedToIntegerBy(3); - const makerAssetFilledAmount = LibReferenceFunctions.getPartialAmountFloor( - takerAssetFilledAmount, - order.takerAssetAmount, - order.makerAssetAmount, - ); - const expectedError = new LibMathRevertErrors.RoundingError( - makerAssetFilledAmount, - order.makerAssetAmount, - order.makerFee, - ); - return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( - expectedError, - ); - }); - - it('reverts if there is a rounding error computing `takerFeePaid`', async () => { - const order = makeOrder({ - makerAssetAmount: ONE_ETHER, - takerAssetAmount: ONE_ETHER, - takerFee: new BigNumber(100), - }); - const takerAssetFilledAmount = order.takerAssetAmount.dividedToIntegerBy(3); - const makerAssetFilledAmount = LibReferenceFunctions.getPartialAmountFloor( - takerAssetFilledAmount, - order.takerAssetAmount, - order.makerAssetAmount, - ); - const expectedError = new LibMathRevertErrors.RoundingError( - makerAssetFilledAmount, - order.makerAssetAmount, - order.takerFee, - ); - return expect(testExchange.calculateFillResults.callAsync(order, takerAssetFilledAmount)).to.revertWith( - expectedError, - ); - }); - }); - }); - - blockchainTests('calculateMatchedFillResults', async () => { - /** - * Asserts that the results of calling `calculateMatchedFillResults()` is consistent with the results that are expected. - */ - async function assertCalculateMatchedFillResultsAsync( - expectedMatchedFillResults: MatchedFillResults, - leftOrder: Order, - rightOrder: Order, - leftOrderTakerAssetFilledAmount: BigNumber, - rightOrderTakerAssetFilledAmount: BigNumber, - from?: string, - ): Promise { - const actualMatchedFillResults = await testExchange.calculateMatchedFillResults.callAsync( - leftOrder, - rightOrder, - leftOrderTakerAssetFilledAmount, - rightOrderTakerAssetFilledAmount, - false, - { from }, - ); - expect(actualMatchedFillResults).to.be.deep.eq(expectedMatchedFillResults); - } - - const ORDER_DEFAULTS = { - ...constants.STATIC_ORDER_PARAMS, - makerAddress: randomAddress(), - takerAddress: randomAddress(), - senderAddress: randomAddress(), - makerAssetData: randomAssetData(), - takerAssetData: randomAssetData(), - makerFeeAssetData: randomAssetData(), - takerFeeAssetData: randomAssetData(), - feeRecipientAddress: randomAddress(), - expirationTimeSeconds: randomUint256(), - salt: randomUint256(), - domain: { - verifyingContractAddress: constants.NULL_ADDRESS, - chainId: 1337, // The chain id for the isolated exchange - }, - }; - - function makeOrder(details?: Partial): Order { - return _.assign({}, ORDER_DEFAULTS, details); - } - - before(async () => { - ORDER_DEFAULTS.domain.verifyingContractAddress = testExchange.address; - }); - - it('should correctly calculate the results when only the right order is fully filled', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(17, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(98, 0), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(75, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(13, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(75, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(75, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - }); - await assertCalculateMatchedFillResultsAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should correctly calculate the results when only the left order is fully filled', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(15, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(90, 0), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(14, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(15, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(90, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(90, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('92.7835051546391752'), 16), // 92.85% - takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('92.8571428571428571'), 16), // 92.85% - }, - profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(2, 0), - }); - await assertCalculateMatchedFillResultsAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should give right maker a better price when rounding', async () => { - const leftOrder = makeOrder({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(16, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(22, 0), - }); - const rightOrder = makeOrder({ - makerAddress: makerAddressRight, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(83, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(49, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(16, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(22, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(22, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('26.5060240963855421'), 16), // 26.506% - takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('26.5306122448979591'), 16), // 26.531% - }, - profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 0), - }); - await assertCalculateMatchedFillResultsAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should give left maker a better sell price when rounding', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(12, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), - makerAddress: makerAddressLeft, - }); - const rightOrder = makeOrder({ - makerAddress: makerAddressRight, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(89, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(11, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.6666666666666666'), 16), // 91.6% - takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.7525773195876288'), 16), // 91.75% - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(10, 0), - }); - await assertCalculateMatchedFillResultsAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('Should give right maker and right taker a favorable fee price when rounding', async () => { - const leftOrder = makeOrder({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(16, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(22, 0), - }); - const rightOrder = makeOrder({ - makerAddress: makerAddressRight, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(83, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(49, 0), - makerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), - takerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(16, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(22, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(22, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(2650, 0), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(2653, 0), - }, - profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 0), - }); - await assertCalculateMatchedFillResultsAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('Should give left maker and left taker a favorable fee price when rounding', async () => { - // Create orders to match - const leftOrder = makeOrder({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(12, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), - makerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), - takerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), - }); - const rightOrder = makeOrder({ - makerAddress: makerAddressRight, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(89, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(11, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(9166, 0), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(9175, 0), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(10, 0), - }); - await assertCalculateMatchedFillResultsAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('Should transfer correct amounts when right order fill amount deviates from amount derived by `Exchange.fillOrder`', async () => { - const leftOrder = makeOrder({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(1000, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1005, 0), - }); - const rightOrder = makeOrder({ - makerAddress: makerAddressRight, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(2126, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1063, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1000, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1005, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1005, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(503, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('47.2718720602069614'), 16), // 47.27% - takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('47.3189087488240827'), 16), // 47.31% - }, - profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(497, 0), - }); - await assertCalculateMatchedFillResultsAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should transfer the correct amounts when orders completely fill each other', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 18), - }); - await assertCalculateMatchedFillResultsAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - }); - await assertCalculateMatchedFillResultsAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should transfer the correct amounts when left order is completely filled and right order is partially filled', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(20, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(4, 18), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(50, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(50, 16), - }, - profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 18), - }); - await assertCalculateMatchedFillResultsAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should transfer the correct amounts when right order is completely filled and left order is partially filled', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(10, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(10, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 18), - }); - await assertCalculateMatchedFillResultsAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - it('should transfer the correct amounts if fee recipient is the same across both matched orders', async () => { - const feeRecipientAddress = randomAddress(); - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - feeRecipientAddress, - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - feeRecipientAddress, - }); - await assertCalculateMatchedFillResultsAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should transfer the correct amounts if taker == leftMaker', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - }); - await assertCalculateMatchedFillResultsAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - leftOrder.makerAddress, - ); - }); - - it('should transfer the correct amounts if taker == leftMaker', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - }); - await assertCalculateMatchedFillResultsAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - rightOrder.makerAddress, - ); - }); - - it('should transfer the correct amounts if taker == leftFeeRecipient', async () => { - const feeRecipientAddressLeft = randomAddress(); - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - feeRecipientAddress: feeRecipientAddressLeft, - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - }); - await assertCalculateMatchedFillResultsAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - feeRecipientAddressLeft, - ); - }); - - it('should transfer the correct amounts if taker == rightFeeRecipient', async () => { - const feeRecipientAddressRight = randomAddress(); - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - feeRecipientAddress: feeRecipientAddressRight, - }); - await assertCalculateMatchedFillResultsAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - feeRecipientAddressRight, - ); - }); - - it('should transfer the correct amounts if leftMaker == leftFeeRecipient && rightMaker == rightFeeRecipient', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - feeRecipientAddress: makerAddressLeft, - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - feeRecipientAddress: makerAddressRight, - }); - await assertCalculateMatchedFillResultsAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should transfer the correct amounts if leftMaker == leftFeeRecipient && leftMakerFeeAsset == leftTakerAsset', async () => { - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - }); - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - makerFeeAssetData: rightOrder.makerAssetData, - feeRecipientAddress: makerAddressLeft, - }); - await assertCalculateMatchedFillResultsAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should transfer the correct amounts if rightMaker == rightFeeRecipient && rightMakerFeeAsset == rightTakerAsset', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - makerFeeAssetData: leftOrder.makerAssetData, - feeRecipientAddress: makerAddressRight, - }); - await assertCalculateMatchedFillResultsAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should transfer the correct amounts if rightMaker == rightFeeRecipient && rightTakerAsset == rightMakerFeeAsset && leftMaker == leftFeeRecipient && leftTakerAsset == leftMakerFeeAsset', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - feeRecipientAddress: makerAddressLeft, - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - makerFeeAssetData: leftOrder.makerAssetData, - feeRecipientAddress: makerAddressRight, - }); - await assertCalculateMatchedFillResultsAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - }); - - blockchainTests('calculateMatchedFillResultsWithMaximalFill', async () => { - /** - * Asserts that the results of calling `calculateMatchedFillResults()` is consistent with the results that are expected. - */ - async function assertCalculateMatchedFillResultsWithMaximalFillAsync( - expectedMatchedFillResults: MatchedFillResults, - leftOrder: Order, - rightOrder: Order, - leftOrderTakerAssetFilledAmount: BigNumber, - rightOrderTakerAssetFilledAmount: BigNumber, - from?: string, - ): Promise { - const actualMatchedFillResults = await testExchange.calculateMatchedFillResults.callAsync( - leftOrder, - rightOrder, - leftOrderTakerAssetFilledAmount, - rightOrderTakerAssetFilledAmount, - true, - { from }, - ); - expect(actualMatchedFillResults).to.be.deep.eq(expectedMatchedFillResults); - } - - const ORDER_DEFAULTS = { - ...constants.STATIC_ORDER_PARAMS, - makerAddress: randomAddress(), - takerAddress: randomAddress(), - senderAddress: randomAddress(), - makerAssetData: randomAssetData(), - takerAssetData: randomAssetData(), - makerFeeAssetData: randomAssetData(), - takerFeeAssetData: randomAssetData(), - feeRecipientAddress: randomAddress(), - expirationTimeSeconds: randomUint256(), - salt: randomUint256(), - domain: { - verifyingContractAddress: constants.NULL_ADDRESS, - chainId: 1337, // The chain id for the isolated exchange - }, - }; - - function makeOrder(details?: Partial): Order { - return _.assign({}, ORDER_DEFAULTS, details); - } - - before(async () => { - ORDER_DEFAULTS.domain.verifyingContractAddress = testExchange.address; - }); - - it('should transfer correct amounts when right order is fully filled', async () => { - const leftOrder = makeOrder({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(17, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(98, 0), - }); - const rightOrder = makeOrder({ - makerAddress: makerAddressRight, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(75, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(13, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(75, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), // 76.47% - takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(75, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(13, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('Should transfer correct amounts when left order is fully filled', async () => { - const leftOrder = makeOrder({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(15, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(90, 0), - }); - const rightOrder = makeOrder({ - makerAddress: makerAddressRight, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(196, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(28, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(15, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(90, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(105, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(15, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('53.5714285714285714'), 16), // 53.57% - takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('53.5714285714285714'), 16), // 53.57% - }, - profitInLeftMakerAsset: constants.ZERO_AMOUNT, - profitInRightMakerAsset: Web3Wrapper.toBaseUnitAmount(15, 0), - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('Should transfer correct amounts when left order is fully filled', async () => { - const leftOrder = makeOrder({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(16, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(22, 0), - }); - const rightOrder = makeOrder({ - makerAddress: makerAddressRight, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(87, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(48, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(16, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(22, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(29, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(16, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('33.3333333333333333'), 16), // 33.33% - takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('33.3333333333333333'), 16), // 33.33% - }, - profitInLeftMakerAsset: constants.ZERO_AMOUNT, - profitInRightMakerAsset: Web3Wrapper.toBaseUnitAmount(7, 0), - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should fully fill both orders and pay out profit in both maker assets', async () => { - const leftOrder = makeOrder({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(7, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(4, 0), - }); - const rightOrder = makeOrder({ - makerAddress: makerAddressRight, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(8, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(6, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(7, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(4, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(8, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(6, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(1, 0), - profitInRightMakerAsset: Web3Wrapper.toBaseUnitAmount(4, 0), - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('Should give left maker a better sell price when rounding', async () => { - const leftOrder = makeOrder({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(12, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), - }); - const rightOrder = makeOrder({ - makerAddress: makerAddressRight, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(89, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(11, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.6666666666666666'), 16), // 91.6% - takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('91.7525773195876288'), 16), // 91.75% - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(10, 0), - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('Should give right maker and right taker a favorable fee price when rounding', async () => { - const leftOrder = makeOrder({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(16, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(22, 0), - }); - const rightOrder = makeOrder({ - makerAddress: makerAddressRight, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(87, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(48, 0), - makerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), - takerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(16, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(22, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(29, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(16, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(3333, 0), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(3333, 0), - }, - profitInRightMakerAsset: Web3Wrapper.toBaseUnitAmount(7, 0), - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('Should give left maker and left taker a favorable fee price when rounding', async () => { - const leftOrder = makeOrder({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(12, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(97, 0), - makerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), - takerFee: Web3Wrapper.toBaseUnitAmount(10000, 0), - }); - const rightOrder = makeOrder({ - makerAddress: makerAddressRight, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(89, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(11, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(9166, 0), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(9175, 0), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(89, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - profitInLeftMakerAsset: Web3Wrapper.toBaseUnitAmount(10, 0), - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should transfer the correct amounts when consecutive calls are used to completely fill the left order', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - }); - const expectedMatchedFillResults = { - ...COMMON_MATCHED_FILL_RESULTS, - left: { - ...COMMON_MATCHED_FILL_RESULTS.left, - makerFeePaid: Web3Wrapper.toBaseUnitAmount(10, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(10, 16), - }, - }; - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - const rightOrder2 = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), - }); - const expectedMatchedFillResults2 = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(45, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(90, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(90, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(45, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), - }, - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - expectedMatchedFillResults2, - leftOrder, - rightOrder2, - Web3Wrapper.toBaseUnitAmount(10, 18), - constants.ZERO_AMOUNT, - ); - }); - - it('Should transfer correct amounts when right order fill amount deviates from amount derived by `Exchange.fillOrder`', async () => { - const leftOrder = makeOrder({ - makerAddress: makerAddressLeft, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(1000, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1005, 0), - }); - const rightOrder = makeOrder({ - makerAddress: makerAddressRight, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(2126, 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(1063, 0), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1000, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1005, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2000, 0), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(1000, 0), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('94.0733772342427093'), 16), // 94.07% - takerFeePaid: Web3Wrapper.toBaseUnitAmount(new BigNumber('94.0733772342427093'), 16), // 94.07% - }, - profitInRightMakerAsset: Web3Wrapper.toBaseUnitAmount(995, 0), - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should transfer the correct amounts when orders completely fill each other and taker doesnt take a profit', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should transfer the correct amounts when consecutive calls are used to completely fill the right order', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - }); - - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), - }); - const expectedMatchedFillResults = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(100, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(10, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(10, 16), - }, - profitInRightMakerAsset: Web3Wrapper.toBaseUnitAmount(3, 18), - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - expectedMatchedFillResults, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - // 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 leftOrder2 = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(100, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(50, 18), - }); - const expectedMatchedFillResults2 = createMatchedFillResults({ - left: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(90, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(45, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), - }, - right: { - makerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(45, 18), - takerAssetFilledAmount: Web3Wrapper.toBaseUnitAmount(90, 18), - makerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), - takerFeePaid: Web3Wrapper.toBaseUnitAmount(90, 16), - }, - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - expectedMatchedFillResults2, - leftOrder2, - rightOrder, - constants.ZERO_AMOUNT, - Web3Wrapper.toBaseUnitAmount(10, 18), - ); - }); - - it('should transfer the correct amounts if fee recipient is the same across both matched orders', async () => { - const feeRecipientAddress = randomAddress(); - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - feeRecipientAddress, - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - feeRecipientAddress, - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should transfer the correct amounts if taker == leftMaker', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - leftOrder.makerAddress, - ); - }); - - it('should transfer the correct amounts if taker == rightMaker', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - rightOrder.makerAddress, - ); - }); - - it('should transfer the correct amounts if taker == leftFeeRecipient', async () => { - const feeRecipientAddress = randomAddress(); - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - feeRecipientAddress, - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - feeRecipientAddress, - ); - }); - - it('should transfer the correct amounts if taker == rightFeeRecipient', async () => { - const feeRecipientAddress = randomAddress(); - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - feeRecipientAddress, - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - feeRecipientAddress, - ); - }); - - it('should transfer the correct amounts if leftMaker == leftFeeRecipient && rightMaker == rightFeeRecipient', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - feeRecipientAddress: makerAddressLeft, - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - feeRecipientAddress: makerAddressRight, - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should transfer the correct amounts if leftMaker == leftFeeRecipient && leftMakerFeeAsset == leftTakerAsset', async () => { - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - }); - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - makerFeeAssetData: rightOrder.makerAssetData, - feeRecipientAddress: makerAddressLeft, - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should transfer the correct amounts if rightMaker == rightFeeRecipient && rightMakerFeeAsset == rightTakerAsset', async () => { - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - makerFeeAssetData: leftOrder.makerAssetData, - feeRecipientAddress: makerAddressRight, - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - - it('should transfer the correct amounts if rightMaker == rightFeeRecipient && rightTakerAsset == rightMakerFeeAsset && leftMaker == leftFeeRecipient && leftTakerAsset == leftMakerFeeAsset', async () => { - const makerFeeAssetData = randomAssetData(); - const leftOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(5, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - makerFeeAssetData, - feeRecipientAddress: makerAddressLeft, - }); - const rightOrder = makeOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(10, 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(2, 18), - makerFeeAssetData: leftOrder.makerAssetData, - feeRecipientAddress: makerAddressRight, - }); - await assertCalculateMatchedFillResultsWithMaximalFillAsync( - COMMON_MATCHED_FILL_RESULTS, - leftOrder, - rightOrder, - constants.ZERO_AMOUNT, - constants.ZERO_AMOUNT, - ); - }); - }); - blockchainTests.resets('updateFilledState', async () => { const ORDER_DEFAULTS = { senderAddress: randomAddress(), @@ -1964,7 +170,7 @@ blockchainTests('Exchange core internal functions', env => { takerAssetFillAmount: BigNumber, ): Promise { const orderHash = randomHash(); - const fillResults = ReferenceFunctions.calculateFillResults(order, takerAssetFillAmount); + const fillResults = LibReferenceFunctions.calculateFillResults(order, takerAssetFillAmount); const expectedFilledState = orderTakerAssetFilledAmount.plus(takerAssetFillAmount); // CAll `testUpdateFilledState()`, which will set the `filled` // state for this order to `orderTakerAssetFilledAmount` before diff --git a/contracts/exchange/tsconfig.json b/contracts/exchange/tsconfig.json index c2cb29514d..ee982946bd 100644 --- a/contracts/exchange/tsconfig.json +++ b/contracts/exchange/tsconfig.json @@ -5,16 +5,27 @@ "files": [ "generated-artifacts/Exchange.json", "generated-artifacts/ExchangeWrapper.json", + "generated-artifacts/IAssetProxy.json", "generated-artifacts/IAssetProxyDispatcher.json", "generated-artifacts/IEIP1271Wallet.json", "generated-artifacts/IExchange.json", "generated-artifacts/IExchangeCore.json", + "generated-artifacts/IExchangeRichErrors.json", "generated-artifacts/IMatchOrders.json", "generated-artifacts/ISignatureValidator.json", "generated-artifacts/ITransactions.json", + "generated-artifacts/ITransferSimulator.json", "generated-artifacts/IWallet.json", "generated-artifacts/IWrapperFunctions.json", "generated-artifacts/IsolatedExchange.json", + "generated-artifacts/LibExchangeRichErrorDecoder.json", + "generated-artifacts/MixinAssetProxyDispatcher.json", + "generated-artifacts/MixinExchangeCore.json", + "generated-artifacts/MixinMatchOrders.json", + "generated-artifacts/MixinSignatureValidator.json", + "generated-artifacts/MixinTransactions.json", + "generated-artifacts/MixinTransferSimulator.json", + "generated-artifacts/MixinWrapperFunctions.json", "generated-artifacts/ReentrancyTester.json", "generated-artifacts/TestAssetProxyDispatcher.json", "generated-artifacts/TestExchangeInternals.json", diff --git a/contracts/extensions/compiler.json b/contracts/extensions/compiler.json index c0b4437500..6b74c612c2 100644 --- a/contracts/extensions/compiler.json +++ b/contracts/extensions/compiler.json @@ -22,13 +22,5 @@ ] } } - }, - "contracts": [ - "@0x/contracts-erc20/contracts/src/WETH9.sol", - "@0x/contracts-exchange/contracts/examples/ExchangeWrapper.sol", - "@0x/contracts-exchange/contracts/src/Exchange.sol", - "src/BalanceThresholdFilter/BalanceThresholdFilter.sol", - "src/DutchAuction/DutchAuction.sol", - "src/OrderMatcher/OrderMatcher.sol" - ] + } } diff --git a/contracts/extensions/contracts/src/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/contracts/extensions/contracts/src/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 7c7712307f..6c0674786c 100644 --- a/contracts/extensions/contracts/src/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/contracts/extensions/contracts/src/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -19,16 +19,15 @@ pragma solidity ^0.5.9; pragma experimental ABIEncoderV2; -import "@0x/contracts-exchange-libs/contracts/src/LibExchangeSelectors.sol"; import "@0x/contracts-exchange-libs/contracts/src/LibZeroExTransaction.sol"; +import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; import "./MixinExchangeCalldata.sol"; import "./interfaces/IBalanceThresholdFilterCore.sol"; contract MixinBalanceThresholdFilterCore is IBalanceThresholdFilterCore, - MixinExchangeCalldata, - LibExchangeSelectors + MixinExchangeCalldata { /// @dev Executes an Exchange transaction iff the maker and taker meet @@ -100,34 +99,34 @@ contract MixinBalanceThresholdFilterCore is bytes4 exchangeFunctionSelector = bytes4(_exchangeCalldataload(0)); // solhint-disable expression-indent if ( - exchangeFunctionSelector == BATCH_FILL_ORDERS_SELECTOR || - exchangeFunctionSelector == BATCH_FILL_ORDERS_NO_THROW_SELECTOR || - exchangeFunctionSelector == BATCH_FILL_OR_KILL_ORDERS_SELECTOR || - exchangeFunctionSelector == MARKET_BUY_ORDERS_SELECTOR || - exchangeFunctionSelector == MARKET_BUY_ORDERS_NO_THROW_SELECTOR || - exchangeFunctionSelector == MARKET_SELL_ORDERS_SELECTOR || - exchangeFunctionSelector == MARKET_SELL_ORDERS_NO_THROW_SELECTOR + exchangeFunctionSelector == IExchange(address(0)).batchFillOrders.selector || + exchangeFunctionSelector == IExchange(address(0)).batchFillOrdersNoThrow.selector || + exchangeFunctionSelector == IExchange(address(0)).batchFillOrKillOrders.selector || + exchangeFunctionSelector == IExchange(address(0)).marketBuyOrders.selector || + exchangeFunctionSelector == IExchange(address(0)).marketBuyOrdersNoThrow.selector || + exchangeFunctionSelector == IExchange(address(0)).marketSellOrders.selector || + exchangeFunctionSelector == IExchange(address(0)).marketSellOrdersNoThrow.selector ) { addressesToValidate = _loadMakerAddressesFromOrderArray(0); addressesToValidate = addressesToValidate.append(signerAddress); } else if ( - exchangeFunctionSelector == FILL_ORDER_SELECTOR || - exchangeFunctionSelector == FILL_ORDER_NO_THROW_SELECTOR || - exchangeFunctionSelector == FILL_OR_KILL_ORDER_SELECTOR + exchangeFunctionSelector == IExchange(address(0)).fillOrder.selector || + exchangeFunctionSelector == IExchange(address(0)).fillOrderNoThrow.selector || + exchangeFunctionSelector == IExchange(address(0)).fillOrKillOrder.selector ) { address makerAddress = _loadMakerAddressFromOrder(0); addressesToValidate = addressesToValidate.append(makerAddress); addressesToValidate = addressesToValidate.append(signerAddress); - } else if (exchangeFunctionSelector == MATCH_ORDERS_SELECTOR) { + } else if (exchangeFunctionSelector == IExchange(address(0)).matchOrders.selector) { address leftMakerAddress = _loadMakerAddressFromOrder(0); addressesToValidate = addressesToValidate.append(leftMakerAddress); address rightMakerAddress = _loadMakerAddressFromOrder(1); addressesToValidate = addressesToValidate.append(rightMakerAddress); addressesToValidate = addressesToValidate.append(signerAddress); } else if ( - exchangeFunctionSelector != CANCEL_ORDER_SELECTOR && - exchangeFunctionSelector != BATCH_CANCEL_ORDERS_SELECTOR && - exchangeFunctionSelector != CANCEL_ORDERS_UP_TO_SELECTOR + exchangeFunctionSelector != IExchange(address(0)).cancelOrder.selector && + exchangeFunctionSelector != IExchange(address(0)).batchCancelOrders.selector && + exchangeFunctionSelector != IExchange(address(0)).cancelOrdersUpTo.selector ) { revert("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR"); } diff --git a/contracts/extensions/package.json b/contracts/extensions/package.json index 72293c9d02..b541141ede 100644 --- a/contracts/extensions/package.json +++ b/contracts/extensions/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", - "pre_build": "run-s compile generate_contract_wrappers", + "pre_build": "run-s compile contracts:gen 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", diff --git a/contracts/multisig/compiler.json b/contracts/multisig/compiler.json index 4a7bf2d3bf..6b74c612c2 100644 --- a/contracts/multisig/compiler.json +++ b/contracts/multisig/compiler.json @@ -22,12 +22,5 @@ ] } } - }, - "contracts": [ - "src/AssetProxyOwner.sol", - "src/MultiSigWallet.sol", - "src/MultiSigWalletWithTimeLock.sol", - "test/TestAssetProxyOwner.sol", - "test/TestRejectEther.sol" - ] + } } diff --git a/contracts/multisig/package.json b/contracts/multisig/package.json index 864618515c..a5652fe4eb 100644 --- a/contracts/multisig/package.json +++ b/contracts/multisig/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", - "pre_build": "run-s compile generate_contract_wrappers", + "pre_build": "run-s compile contracts:gen 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", diff --git a/contracts/staking/compiler.json b/contracts/staking/compiler.json index 31b20970a6..f432301d36 100644 --- a/contracts/staking/compiler.json +++ b/contracts/staking/compiler.json @@ -21,6 +21,5 @@ ] } } - }, - "contracts": ["src/IStaking.sol", "src/Staking.sol"] + } } diff --git a/contracts/staking/package.json b/contracts/staking/package.json index 1273b0c1c9..f016f820bf 100644 --- a/contracts/staking/package.json +++ b/contracts/staking/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", - "pre_build": "run-s compile generate_contract_wrappers", + "pre_build": "run-s compile contracts:gen 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", diff --git a/contracts/test-utils/src/constants.ts b/contracts/test-utils/src/constants.ts index 7a2740da2a..82ddf44ed8 100644 --- a/contracts/test-utils/src/constants.ts +++ b/contracts/test-utils/src/constants.ts @@ -66,4 +66,6 @@ export const constants = { KECCAK256_NULL: ethUtil.addHexPrefix(ethUtil.bufferToHex(ethUtil.SHA3_NULL)), MAX_UINT256_ROOT: new BigNumber('340282366920938463463374607431768211456'), ONE_ETHER: new BigNumber(1e18), + EIP712_DOMAIN_NAME: '0x Protocol', + EIP712_DOMAIN_VERSION: '3.0.0', }; diff --git a/contracts/utils/CHANGELOG.json b/contracts/utils/CHANGELOG.json index ff9ced6e96..3b56d6271e 100644 --- a/contracts/utils/CHANGELOG.json +++ b/contracts/utils/CHANGELOG.json @@ -41,6 +41,18 @@ { "note": "Throw a `SafeMathError` in `SafeMath._safeDiv()` when denominator is zero.", "pr": 2031 + }, + { + "note": "Create `LibSafeMath`", + "pr": 2055 + }, + { + "note": "Rename `_rrevert` to `rrevert` in `LibRichErrors` contract", + "pr": 2055 + }, + { + "note": "Compile and export all contracts, artifacts, and wrappers by default", + "pr": 2055 } ] }, diff --git a/contracts/utils/compiler.json b/contracts/utils/compiler.json index ebd1f5898c..6b74c612c2 100644 --- a/contracts/utils/compiler.json +++ b/contracts/utils/compiler.json @@ -22,26 +22,5 @@ ] } } - }, - "contracts": [ - "src/Authorizable.sol", - "src/LibAddress.sol", - "src/LibBytes.sol", - "src/LibEIP1271.sol", - "src/LibEIP712.sol", - "src/Ownable.sol", - "src/ReentrancyGuard.sol", - "src/SafeMath.sol", - "src/interfaces/IAuthorizable.sol", - "src/interfaces/IOwnable.sol", - "test/TestConstants.sol", - "test/TestLibAddress.sol", - "test/TestLibAddressArray.sol", - "test/TestLibBytes.sol", - "test/TestLibEIP712.sol", - "test/TestLibRichErrors.sol", - "test/TestOwnable.sol", - "test/TestReentrancyGuard.sol", - "test/TestSafeMath.sol" - ] + } } diff --git a/contracts/utils/contracts/src/Authorizable.sol b/contracts/utils/contracts/src/Authorizable.sol index cbd8ac475c..e14832d48b 100644 --- a/contracts/utils/contracts/src/Authorizable.sol +++ b/contracts/utils/contracts/src/Authorizable.sol @@ -31,7 +31,7 @@ contract Authorizable is /// @dev Only authorized addresses can invoke functions with this modifier. modifier onlyAuthorized { if (!authorized[msg.sender]) { - LibRichErrors._rrevert(LibAuthorizableRichErrors.SenderNotAuthorizedError(msg.sender)); + LibRichErrors.rrevert(LibAuthorizableRichErrors.SenderNotAuthorizedError(msg.sender)); } _; } @@ -47,12 +47,12 @@ contract Authorizable is { // Ensure that the target is not the zero address. if (target == address(0)) { - LibRichErrors._rrevert(LibAuthorizableRichErrors.ZeroCantBeAuthorizedError()); + LibRichErrors.rrevert(LibAuthorizableRichErrors.ZeroCantBeAuthorizedError()); } // Ensure that the target is not already authorized. if (authorized[target]) { - LibRichErrors._rrevert(LibAuthorizableRichErrors.TargetAlreadyAuthorizedError(target)); + LibRichErrors.rrevert(LibAuthorizableRichErrors.TargetAlreadyAuthorizedError(target)); } authorized[target] = true; @@ -67,7 +67,7 @@ contract Authorizable is onlyOwner { if (!authorized[target]) { - LibRichErrors._rrevert(LibAuthorizableRichErrors.TargetNotAuthorizedError(target)); + LibRichErrors.rrevert(LibAuthorizableRichErrors.TargetNotAuthorizedError(target)); } delete authorized[target]; @@ -92,16 +92,16 @@ contract Authorizable is onlyOwner { if (!authorized[target]) { - LibRichErrors._rrevert(LibAuthorizableRichErrors.TargetNotAuthorizedError(target)); + LibRichErrors.rrevert(LibAuthorizableRichErrors.TargetNotAuthorizedError(target)); } if (index >= authorities.length) { - LibRichErrors._rrevert(LibAuthorizableRichErrors.IndexOutOfBoundsError( + LibRichErrors.rrevert(LibAuthorizableRichErrors.IndexOutOfBoundsError( index, authorities.length )); } if (authorities[index] != target) { - LibRichErrors._rrevert(LibAuthorizableRichErrors.AuthorizedAddressMismatchError( + LibRichErrors.rrevert(LibAuthorizableRichErrors.AuthorizedAddressMismatchError( authorities[index], target )); diff --git a/contracts/utils/contracts/src/LibAddressArray.sol b/contracts/utils/contracts/src/LibAddressArray.sol index 70b7686df9..05d4af2611 100644 --- a/contracts/utils/contracts/src/LibAddressArray.sol +++ b/contracts/utils/contracts/src/LibAddressArray.sol @@ -54,7 +54,7 @@ library LibAddressArray { // `freeMemPtr` > `addressArrayEndPtr`: Some value occupies memory after `addressArray` // `freeMemPtr` < `addressArrayEndPtr`: Memory has not been managed properly. if (freeMemPtr < addressArrayEndPtr) { - LibRichErrors._rrevert(LibAddressArrayRichErrors.MismanagedMemoryError( + LibRichErrors.rrevert(LibAddressArrayRichErrors.MismanagedMemoryError( freeMemPtr, addressArrayEndPtr )); diff --git a/contracts/utils/contracts/src/LibBytes.sol b/contracts/utils/contracts/src/LibBytes.sol index 7dc5a6dc8a..6caefb8bac 100644 --- a/contracts/utils/contracts/src/LibBytes.sol +++ b/contracts/utils/contracts/src/LibBytes.sol @@ -180,14 +180,14 @@ library LibBytes { // Ensure that the from and to positions are valid positions for a slice within // the byte array that is being used. if (from > to) { - LibRichErrors._rrevert(LibBytesRichErrors.InvalidByteOperationError( + LibRichErrors.rrevert(LibBytesRichErrors.InvalidByteOperationError( LibBytesRichErrors.InvalidByteOperationErrorCodes.FromLessThanOrEqualsToRequired, from, to )); } if (to > b.length) { - LibRichErrors._rrevert(LibBytesRichErrors.InvalidByteOperationError( + LibRichErrors.rrevert(LibBytesRichErrors.InvalidByteOperationError( LibBytesRichErrors.InvalidByteOperationErrorCodes.ToLessThanOrEqualsLengthRequired, to, b.length @@ -222,14 +222,14 @@ library LibBytes { // Ensure that the from and to positions are valid positions for a slice within // the byte array that is being used. if (from > to) { - LibRichErrors._rrevert(LibBytesRichErrors.InvalidByteOperationError( + LibRichErrors.rrevert(LibBytesRichErrors.InvalidByteOperationError( LibBytesRichErrors.InvalidByteOperationErrorCodes.FromLessThanOrEqualsToRequired, from, to )); } if (to > b.length) { - LibRichErrors._rrevert(LibBytesRichErrors.InvalidByteOperationError( + LibRichErrors.rrevert(LibBytesRichErrors.InvalidByteOperationError( LibBytesRichErrors.InvalidByteOperationErrorCodes.ToLessThanOrEqualsLengthRequired, to, b.length @@ -253,7 +253,7 @@ library LibBytes { returns (bytes1 result) { if (b.length == 0) { - LibRichErrors._rrevert(LibBytesRichErrors.InvalidByteOperationError( + LibRichErrors.rrevert(LibBytesRichErrors.InvalidByteOperationError( LibBytesRichErrors.InvalidByteOperationErrorCodes.LengthGreaterThanZeroRequired, b.length, 0 @@ -280,7 +280,7 @@ library LibBytes { returns (address result) { if (b.length < 20) { - LibRichErrors._rrevert(LibBytesRichErrors.InvalidByteOperationError( + LibRichErrors.rrevert(LibBytesRichErrors.InvalidByteOperationError( LibBytesRichErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsTwentyRequired, b.length, 20 // 20 is length of address @@ -329,7 +329,7 @@ library LibBytes { returns (address result) { if (b.length < index + 20) { - LibRichErrors._rrevert(LibBytesRichErrors.InvalidByteOperationError( + LibRichErrors.rrevert(LibBytesRichErrors.InvalidByteOperationError( LibBytesRichErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsTwentyRequired, b.length, index + 20 // 20 is length of address @@ -364,7 +364,7 @@ library LibBytes { pure { if (b.length < index + 20) { - LibRichErrors._rrevert(LibBytesRichErrors.InvalidByteOperationError( + LibRichErrors.rrevert(LibBytesRichErrors.InvalidByteOperationError( LibBytesRichErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsTwentyRequired, b.length, index + 20 // 20 is length of address @@ -413,7 +413,7 @@ library LibBytes { returns (bytes32 result) { if (b.length < index + 32) { - LibRichErrors._rrevert(LibBytesRichErrors.InvalidByteOperationError( + LibRichErrors.rrevert(LibBytesRichErrors.InvalidByteOperationError( LibBytesRichErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired, b.length, index + 32 @@ -443,7 +443,7 @@ library LibBytes { pure { if (b.length < index + 32) { - LibRichErrors._rrevert(LibBytesRichErrors.InvalidByteOperationError( + LibRichErrors.rrevert(LibBytesRichErrors.InvalidByteOperationError( LibBytesRichErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsThirtyTwoRequired, b.length, index + 32 @@ -503,7 +503,7 @@ library LibBytes { returns (bytes4 result) { if (b.length < index + 4) { - LibRichErrors._rrevert(LibBytesRichErrors.InvalidByteOperationError( + LibRichErrors.rrevert(LibBytesRichErrors.InvalidByteOperationError( LibBytesRichErrors.InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsFourRequired, b.length, index + 4 @@ -544,7 +544,7 @@ library LibBytes { // Assert length of is valid, given // length of nested bytes if (b.length < index + nestedBytesLength) { - LibRichErrors._rrevert(LibBytesRichErrors.InvalidByteOperationError( + LibRichErrors.rrevert(LibBytesRichErrors.InvalidByteOperationError( LibBytesRichErrors .InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsNestedBytesLengthRequired, b.length, @@ -574,7 +574,7 @@ library LibBytes { // Assert length of is valid, given // length of input if (b.length < index + 32 + input.length) { - LibRichErrors._rrevert(LibBytesRichErrors.InvalidByteOperationError( + LibRichErrors.rrevert(LibBytesRichErrors.InvalidByteOperationError( LibBytesRichErrors .InvalidByteOperationErrorCodes.LengthGreaterThanOrEqualsNestedBytesLengthRequired, b.length, @@ -603,7 +603,7 @@ library LibBytes { uint256 sourceLen = source.length; // Dest length must be >= source length, or some bytes would not be copied. if (dest.length < sourceLen) { - LibRichErrors._rrevert(LibBytesRichErrors.InvalidByteOperationError( + LibRichErrors.rrevert(LibBytesRichErrors.InvalidByteOperationError( LibBytesRichErrors .InvalidByteOperationErrorCodes.DestinationLengthGreaterThanOrEqualSourceLengthRequired, dest.length, diff --git a/contracts/utils/contracts/src/LibEIP712.sol b/contracts/utils/contracts/src/LibEIP712.sol index 32ca6ff117..06e2a7daa6 100644 --- a/contracts/utils/contracts/src/LibEIP712.sol +++ b/contracts/utils/contracts/src/LibEIP712.sol @@ -19,24 +19,25 @@ pragma solidity ^0.5.9; -contract LibEIP712 { +library LibEIP712 { // Hash of the EIP712 Domain Separator Schema - bytes32 constant internal EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked( - "EIP712Domain(", - "string name,", - "string version,", - "uint256 chainId,", - "address verifyingContractAddress", - ")" - )); - + // keccak256(abi.encodePacked( + // "EIP712Domain(", + // "string name,", + // "string version,", + // "uint256 chainId,", + // "address verifyingContractAddress", + // ")" + // )) + bytes32 constant internal _EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = 0xb1b295f2c1ed6b459ddeb95701466e4e0b385527a6cfa3873ae72a63c08466b6; + /// @dev Calculates a EIP712 domain separator. /// @param name The EIP712 domain name. /// @param version The EIP712 domain version. /// @param verifyingContractAddress The EIP712 verifying contract. /// @return EIP712 domain separator. - function _hashEIP712Domain( + function hashEIP712Domain( string memory name, string memory version, uint256 chainId, @@ -46,13 +47,36 @@ contract LibEIP712 { pure returns (bytes32 result) { - return keccak256(abi.encodePacked( - EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH, - keccak256(bytes(name)), - keccak256(bytes(version)), - chainId, - uint256(verifyingContractAddress) - )); + bytes32 schemaHash = _EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH; + + // Assembly for more efficient computing: + // keccak256(abi.encodePacked( + // _EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH, + // keccak256(bytes(name)), + // keccak256(bytes(version)), + // chainId, + // uint256(verifyingContractAddress) + // )) + + assembly { + // Calculate hashes of dynamic data + let nameHash := keccak256(add(name, 32), mload(name)) + let versionHash := keccak256(add(version, 32), mload(version)) + + // Load free memory pointer + let memPtr := mload(64) + + // Store params in memory + mstore(memPtr, schemaHash) + mstore(add(memPtr, 32), nameHash) + mstore(add(memPtr, 64), versionHash) + mstore(add(memPtr, 96), chainId) + mstore(add(memPtr, 128), verifyingContractAddress) + + // Compute hash + result := keccak256(memPtr, 160) + } + return result; } /// @dev Calculates EIP712 encoding for a hash struct with a given domain hash. @@ -60,7 +84,7 @@ contract LibEIP712 { /// with getDomainHash(). /// @param hashStruct The EIP712 hash struct. /// @return EIP712 hash applied to the given EIP712 Domain. - function _hashEIP712Message(bytes32 eip712DomainHash, bytes32 hashStruct) + function hashEIP712Message(bytes32 eip712DomainHash, bytes32 hashStruct) internal pure returns (bytes32 result) diff --git a/contracts/utils/contracts/src/LibRichErrors.sol b/contracts/utils/contracts/src/LibRichErrors.sol index 5b1f89b630..8c4837c5d0 100644 --- a/contracts/utils/contracts/src/LibRichErrors.sol +++ b/contracts/utils/contracts/src/LibRichErrors.sol @@ -47,7 +47,7 @@ library LibRichErrors { /// @dev Reverts an encoded rich revert reason `errorData`. /// @param errorData ABI encoded error data. - function _rrevert(bytes memory errorData) + function rrevert(bytes memory errorData) internal pure { diff --git a/contracts/utils/contracts/src/LibSafeMath.sol b/contracts/utils/contracts/src/LibSafeMath.sol new file mode 100644 index 0000000000..8aa6b09495 --- /dev/null +++ b/contracts/utils/contracts/src/LibSafeMath.sol @@ -0,0 +1,90 @@ +pragma solidity ^0.5.9; + +import "./LibRichErrors.sol"; +import "./LibSafeMathRichErrors.sol"; + + +library LibSafeMath { + + function safeMul(uint256 a, uint256 b) + internal + pure + returns (uint256) + { + if (a == 0) { + return 0; + } + uint256 c = a * b; + if (c / a != b) { + LibRichErrors.rrevert(LibSafeMathRichErrors.SafeMathError( + LibSafeMathRichErrors.SafeMathErrorCodes.UINT256_MULTIPLICATION_OVERFLOW, + a, + b + )); + } + return c; + } + + function safeDiv(uint256 a, uint256 b) + internal + pure + returns (uint256) + { + if (b == 0) { + LibRichErrors.rrevert(LibSafeMathRichErrors.SafeMathError( + LibSafeMathRichErrors.SafeMathErrorCodes.UINT256_DIVISION_BY_ZERO, + a, + b + )); + } + uint256 c = a / b; + return c; + } + + function safeSub(uint256 a, uint256 b) + internal + pure + returns (uint256) + { + if (b > a) { + LibRichErrors.rrevert(LibSafeMathRichErrors.SafeMathError( + LibSafeMathRichErrors.SafeMathErrorCodes.UINT256_SUBTRACTION_UNDERFLOW, + a, + b + )); + } + return a - b; + } + + function safeAdd(uint256 a, uint256 b) + internal + pure + returns (uint256) + { + uint256 c = a + b; + if (c < a) { + LibRichErrors.rrevert(LibSafeMathRichErrors.SafeMathError( + LibSafeMathRichErrors.SafeMathErrorCodes.UINT256_ADDITION_OVERFLOW, + a, + b + )); + } + return c; + } + + function max256(uint256 a, uint256 b) + internal + pure + returns (uint256) + { + return a >= b ? a : b; + } + + function min256(uint256 a, uint256 b) + internal + pure + returns (uint256) + { + return a < b ? a : b; + } +} diff --git a/contracts/utils/contracts/src/Ownable.sol b/contracts/utils/contracts/src/Ownable.sol index 63a10e5473..05aed815a4 100644 --- a/contracts/utils/contracts/src/Ownable.sol +++ b/contracts/utils/contracts/src/Ownable.sol @@ -18,7 +18,7 @@ contract Ownable is modifier onlyOwner() { if (msg.sender != owner) { - LibRichErrors._rrevert(LibOwnableRichErrors.OnlyOwnerError( + LibRichErrors.rrevert(LibOwnableRichErrors.OnlyOwnerError( msg.sender, owner )); @@ -31,7 +31,7 @@ contract Ownable is onlyOwner { if (newOwner == address(0)) { - LibRichErrors._rrevert(LibOwnableRichErrors.TransferOwnerToZeroError()); + LibRichErrors.rrevert(LibOwnableRichErrors.TransferOwnerToZeroError()); } else { owner = newOwner; } diff --git a/contracts/utils/contracts/src/ReentrancyGuard.sol b/contracts/utils/contracts/src/ReentrancyGuard.sol index 99b1ced1cb..c0b7e4750a 100644 --- a/contracts/utils/contracts/src/ReentrancyGuard.sol +++ b/contracts/utils/contracts/src/ReentrancyGuard.sol @@ -37,7 +37,7 @@ contract ReentrancyGuard { // If the counter value is different from what we remember, the function // was called more than once and an illegal reentrancy occured. if (localCounter != reentrancyGuardCounter) { - LibRichErrors._rrevert( + LibRichErrors.rrevert( LibReentrancyGuardRichErrors.IllegalReentrancyError() ); } diff --git a/contracts/utils/contracts/src/SafeMath.sol b/contracts/utils/contracts/src/SafeMath.sol index 1edc1bac93..8a690e0be5 100644 --- a/contracts/utils/contracts/src/SafeMath.sol +++ b/contracts/utils/contracts/src/SafeMath.sol @@ -16,7 +16,7 @@ contract SafeMath { } uint256 c = a * b; if (c / a != b) { - LibRichErrors._rrevert(LibSafeMathRichErrors.SafeMathError( + LibRichErrors.rrevert(LibSafeMathRichErrors.SafeMathError( LibSafeMathRichErrors.SafeMathErrorCodes.UINT256_MULTIPLICATION_OVERFLOW, a, b @@ -31,7 +31,7 @@ contract SafeMath { returns (uint256) { if (b == 0) { - LibRichErrors._rrevert(LibSafeMathRichErrors.SafeMathError( + LibRichErrors.rrevert(LibSafeMathRichErrors.SafeMathError( LibSafeMathRichErrors.SafeMathErrorCodes.UINT256_DIVISION_BY_ZERO, a, b @@ -47,7 +47,7 @@ contract SafeMath { returns (uint256) { if (b > a) { - LibRichErrors._rrevert(LibSafeMathRichErrors.SafeMathError( + LibRichErrors.rrevert(LibSafeMathRichErrors.SafeMathError( LibSafeMathRichErrors.SafeMathErrorCodes.UINT256_SUBTRACTION_UNDERFLOW, a, b @@ -63,7 +63,7 @@ contract SafeMath { { uint256 c = a + b; if (c < a) { - LibRichErrors._rrevert(LibSafeMathRichErrors.SafeMathError( + LibRichErrors.rrevert(LibSafeMathRichErrors.SafeMathError( LibSafeMathRichErrors.SafeMathErrorCodes.UINT256_ADDITION_OVERFLOW, a, b diff --git a/contracts/utils/contracts/test/TestConstants.sol b/contracts/utils/contracts/test/TestConstants.sol deleted file mode 100644 index b44fc019fb..0000000000 --- a/contracts/utils/contracts/test/TestConstants.sol +++ /dev/null @@ -1,57 +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.5.5; - -import "../src/LibBytes.sol"; - - -// solhint-disable max-line-length -contract TestConstants { - - using LibBytes for bytes; - - bytes4 constant internal ERC20_PROXY_ID = bytes4(keccak256("ERC20Token(address)")); - - address constant internal KOVAN_ZRX_ADDRESS = 0x6Ff6C0Ff1d68b964901F986d4C9FA3ac68346570; - bytes constant internal KOVAN_ZRX_ASSET_DATA = "\xf4\x72\x61\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x6f\xf6\xc0\xff\x1d\x68\xb9\x64\x90\x1f\x98\x6d\x4c\x9f\xa3\xac\x68\x34\x65\x70"; - - address constant internal MAINNET_ZRX_ADDRESS = 0xE41d2489571d322189246DaFA5ebDe1F4699F498; - bytes constant public MAINNET_ZRX_ASSET_DATA = "\xf4\x72\x61\xb0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe4\x1d\x24\x89\x57\x1d\x32\x21\x89\x24\x6d\xaf\xa5\xeb\xde\x1f\x46\x99\xf4\x98"; - - function assertValidZrxAssetData() - public - pure - returns (bool) - { - bytes memory kovanZrxAssetData = abi.encodeWithSelector(ERC20_PROXY_ID, KOVAN_ZRX_ADDRESS); - require( - kovanZrxAssetData.equals(KOVAN_ZRX_ASSET_DATA), - "INVALID_KOVAN_ZRX_ASSET_DATA" - ); - - bytes memory mainetZrxAssetData = abi.encodeWithSelector(ERC20_PROXY_ID, MAINNET_ZRX_ADDRESS); - require( - mainetZrxAssetData.equals(MAINNET_ZRX_ASSET_DATA), - "INVALID_MAINNET_ZRX_ASSET_DATA" - ); - - return true; - } -} -// solhint-enable max-line-length \ No newline at end of file diff --git a/contracts/utils/contracts/test/TestLibEIP712.sol b/contracts/utils/contracts/test/TestLibEIP712.sol index 807651e5b3..adf3da412d 100644 --- a/contracts/utils/contracts/test/TestLibEIP712.sol +++ b/contracts/utils/contracts/test/TestLibEIP712.sol @@ -21,9 +21,8 @@ pragma solidity ^0.5.9; import "../src/LibEIP712.sol"; -contract TestLibEIP712 is - LibEIP712 -{ +contract TestLibEIP712 { + function externalHashEIP712DomainSeperator( string calldata name, string calldata version, @@ -34,7 +33,7 @@ contract TestLibEIP712 is pure returns (bytes32) { - return _hashEIP712Domain( + return LibEIP712.hashEIP712Domain( name, version, chainid, @@ -47,6 +46,6 @@ contract TestLibEIP712 is pure returns (bytes32) { - return _hashEIP712Message(eip712DomainHash, hashStruct); + return LibEIP712.hashEIP712Message(eip712DomainHash, hashStruct); } } diff --git a/contracts/utils/contracts/test/TestLibRichErrors.sol b/contracts/utils/contracts/test/TestLibRichErrors.sol index fe0909c18a..53b1ae04d0 100644 --- a/contracts/utils/contracts/test/TestLibRichErrors.sol +++ b/contracts/utils/contracts/test/TestLibRichErrors.sol @@ -27,6 +27,6 @@ contract TestLibRichErrors { external pure { - LibRichErrors._rrevert(errorData); + LibRichErrors.rrevert(errorData); } } diff --git a/contracts/utils/package.json b/contracts/utils/package.json index a369e1cd41..a80aa90c4b 100644 --- a/contracts/utils/package.json +++ b/contracts/utils/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "yarn pre_build && tsc -b", "build:ci": "yarn build", - "pre_build": "run-s compile generate_contract_wrappers", + "pre_build": "run-s compile contracts:gen 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", @@ -34,7 +34,7 @@ "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis": "./generated-artifacts/@(Authorizable|IAuthorizable|IOwnable|LibAddress|LibBytes|LibEIP1271|LibEIP712|Ownable|ReentrancyGuard|SafeMath|TestConstants|TestLibAddress|TestLibAddressArray|TestLibBytes|TestLibEIP712|TestLibRichErrors|TestOwnable|TestReentrancyGuard|TestSafeMath).json", + "abis": "./generated-artifacts/@(Authorizable|IAuthorizable|IOwnable|LibAddress|LibAddressArray|LibAddressArrayRichErrors|LibAuthorizableRichErrors|LibBytes|LibBytesRichErrors|LibEIP1271|LibEIP712|LibOwnableRichErrors|LibReentrancyGuardRichErrors|LibRichErrors|LibSafeMath|LibSafeMathRichErrors|Ownable|ReentrancyGuard|SafeMath|TestLibAddress|TestLibAddressArray|TestLibBytes|TestLibEIP712|TestLibRichErrors|TestOwnable|TestReentrancyGuard|TestSafeMath).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/utils/src/artifacts.ts b/contracts/utils/src/artifacts.ts index c9ae81ca55..8c91c30ca1 100644 --- a/contracts/utils/src/artifacts.ts +++ b/contracts/utils/src/artifacts.ts @@ -9,13 +9,21 @@ import * as Authorizable from '../generated-artifacts/Authorizable.json'; import * as IAuthorizable from '../generated-artifacts/IAuthorizable.json'; import * as IOwnable from '../generated-artifacts/IOwnable.json'; import * as LibAddress from '../generated-artifacts/LibAddress.json'; +import * as LibAddressArray from '../generated-artifacts/LibAddressArray.json'; +import * as LibAddressArrayRichErrors from '../generated-artifacts/LibAddressArrayRichErrors.json'; +import * as LibAuthorizableRichErrors from '../generated-artifacts/LibAuthorizableRichErrors.json'; import * as LibBytes from '../generated-artifacts/LibBytes.json'; +import * as LibBytesRichErrors from '../generated-artifacts/LibBytesRichErrors.json'; import * as LibEIP1271 from '../generated-artifacts/LibEIP1271.json'; import * as LibEIP712 from '../generated-artifacts/LibEIP712.json'; +import * as LibOwnableRichErrors from '../generated-artifacts/LibOwnableRichErrors.json'; +import * as LibReentrancyGuardRichErrors from '../generated-artifacts/LibReentrancyGuardRichErrors.json'; +import * as LibRichErrors from '../generated-artifacts/LibRichErrors.json'; +import * as LibSafeMath from '../generated-artifacts/LibSafeMath.json'; +import * as LibSafeMathRichErrors from '../generated-artifacts/LibSafeMathRichErrors.json'; import * as Ownable from '../generated-artifacts/Ownable.json'; import * as ReentrancyGuard from '../generated-artifacts/ReentrancyGuard.json'; import * as SafeMath from '../generated-artifacts/SafeMath.json'; -import * as TestConstants from '../generated-artifacts/TestConstants.json'; import * as TestLibAddress from '../generated-artifacts/TestLibAddress.json'; import * as TestLibAddressArray from '../generated-artifacts/TestLibAddressArray.json'; import * as TestLibBytes from '../generated-artifacts/TestLibBytes.json'; @@ -27,15 +35,23 @@ import * as TestSafeMath from '../generated-artifacts/TestSafeMath.json'; export const artifacts = { Authorizable: Authorizable as ContractArtifact, LibAddress: LibAddress as ContractArtifact, + LibAddressArray: LibAddressArray as ContractArtifact, + LibAddressArrayRichErrors: LibAddressArrayRichErrors as ContractArtifact, + LibAuthorizableRichErrors: LibAuthorizableRichErrors as ContractArtifact, LibBytes: LibBytes as ContractArtifact, + LibBytesRichErrors: LibBytesRichErrors as ContractArtifact, LibEIP1271: LibEIP1271 as ContractArtifact, LibEIP712: LibEIP712 as ContractArtifact, + LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact, + LibReentrancyGuardRichErrors: LibReentrancyGuardRichErrors as ContractArtifact, + LibRichErrors: LibRichErrors as ContractArtifact, + LibSafeMath: LibSafeMath as ContractArtifact, + LibSafeMathRichErrors: LibSafeMathRichErrors as ContractArtifact, Ownable: Ownable as ContractArtifact, ReentrancyGuard: ReentrancyGuard as ContractArtifact, SafeMath: SafeMath as ContractArtifact, IAuthorizable: IAuthorizable as ContractArtifact, IOwnable: IOwnable as ContractArtifact, - TestConstants: TestConstants as ContractArtifact, TestLibAddress: TestLibAddress as ContractArtifact, TestLibAddressArray: TestLibAddressArray as ContractArtifact, TestLibBytes: TestLibBytes as ContractArtifact, diff --git a/contracts/utils/src/wrappers.ts b/contracts/utils/src/wrappers.ts index daf082ed59..f2562b9d7e 100644 --- a/contracts/utils/src/wrappers.ts +++ b/contracts/utils/src/wrappers.ts @@ -7,13 +7,21 @@ export * from '../generated-wrappers/authorizable'; export * from '../generated-wrappers/i_authorizable'; export * from '../generated-wrappers/i_ownable'; export * from '../generated-wrappers/lib_address'; +export * from '../generated-wrappers/lib_address_array'; +export * from '../generated-wrappers/lib_address_array_rich_errors'; +export * from '../generated-wrappers/lib_authorizable_rich_errors'; export * from '../generated-wrappers/lib_bytes'; +export * from '../generated-wrappers/lib_bytes_rich_errors'; export * from '../generated-wrappers/lib_e_i_p1271'; export * from '../generated-wrappers/lib_e_i_p712'; +export * from '../generated-wrappers/lib_ownable_rich_errors'; +export * from '../generated-wrappers/lib_reentrancy_guard_rich_errors'; +export * from '../generated-wrappers/lib_rich_errors'; +export * from '../generated-wrappers/lib_safe_math'; +export * from '../generated-wrappers/lib_safe_math_rich_errors'; export * from '../generated-wrappers/ownable'; export * from '../generated-wrappers/reentrancy_guard'; export * from '../generated-wrappers/safe_math'; -export * from '../generated-wrappers/test_constants'; export * from '../generated-wrappers/test_lib_address'; export * from '../generated-wrappers/test_lib_address_array'; export * from '../generated-wrappers/test_lib_bytes'; diff --git a/contracts/utils/test/libs.ts b/contracts/utils/test/libs.ts deleted file mode 100644 index 77dc6e2ba5..0000000000 --- a/contracts/utils/test/libs.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { chaiSetup, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import * as chai from 'chai'; - -import { artifacts, TestConstantsContract } from '../src'; - -chaiSetup.configure(); -const expect = chai.expect; - -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe('Libs', () => { - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - - describe('LibConstants', () => { - describe('ZRX_ASSET_DATA', () => { - it('should have the correct ZRX_ASSET_DATA', async () => { - const testConstants = await TestConstantsContract.deployFrom0xArtifactAsync( - artifacts.TestConstants, - provider, - txDefaults, - ); - const isValid = await testConstants.assertValidZrxAssetData.callAsync(); - expect(isValid).to.be.equal(true); - }); - }); - }); -}); diff --git a/contracts/utils/tsconfig.json b/contracts/utils/tsconfig.json index 35e669ea77..61768eaa2f 100644 --- a/contracts/utils/tsconfig.json +++ b/contracts/utils/tsconfig.json @@ -7,13 +7,21 @@ "generated-artifacts/IAuthorizable.json", "generated-artifacts/IOwnable.json", "generated-artifacts/LibAddress.json", + "generated-artifacts/LibAddressArray.json", + "generated-artifacts/LibAddressArrayRichErrors.json", + "generated-artifacts/LibAuthorizableRichErrors.json", "generated-artifacts/LibBytes.json", + "generated-artifacts/LibBytesRichErrors.json", "generated-artifacts/LibEIP1271.json", "generated-artifacts/LibEIP712.json", + "generated-artifacts/LibOwnableRichErrors.json", + "generated-artifacts/LibReentrancyGuardRichErrors.json", + "generated-artifacts/LibRichErrors.json", + "generated-artifacts/LibSafeMath.json", + "generated-artifacts/LibSafeMathRichErrors.json", "generated-artifacts/Ownable.json", "generated-artifacts/ReentrancyGuard.json", "generated-artifacts/SafeMath.json", - "generated-artifacts/TestConstants.json", "generated-artifacts/TestLibAddress.json", "generated-artifacts/TestLibAddressArray.json", "generated-artifacts/TestLibBytes.json", diff --git a/package.json b/package.json index c3108f9ba0..38a19a0aa9 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "lint:contracts": "wsrun lint -p ${npm_package_config_contractsPackages} -c --fast-exit --stages --exclude-missing" }, "config": { - "contractsPackages": "@0x/contracts-asset-proxy @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-exchange @0x/contracts-exchange-libs @0x/contracts-multisig @0x/contracts-test-utils @0x/contracts-utils @0x/contracts-coordinator @0x/contracts-dev-utils @0x/contracts-staking", + "contractsPackages": "@0x/contracts-asset-proxy @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-exchange @0x/contracts-exchange-libs @0x/contracts-multisig @0x/contracts-test-utils @0x/contracts-utils @0x/contracts-dev-utils @0x/contracts-staking", "mnemonic": "concert load couple harbor equip island argue ramp clarify fence smart topic", "packagesWithDocPages": "0x.js connect json-schemas subproviders web3-wrapper contract-wrappers order-utils order-watcher sol-compiler sol-coverage sol-profiler sol-trace ethereum-types asset-buyer migrations", "ignoreDependencyVersions": "@types/styled-components @types/node", diff --git a/packages/contracts-gen/CHANGELOG.json b/packages/contracts-gen/CHANGELOG.json index 631831c1ed..6d77252e41 100644 --- a/packages/contracts-gen/CHANGELOG.json +++ b/packages/contracts-gen/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "1.1.0", + "changes": [ + { + "note": "Generate boilerplate for all contracts if none are specified or if all contracts identifier is used", + "pr": 2055 + } + ] + }, { "timestamp": 1563006338, "version": "1.0.10", diff --git a/packages/contracts-gen/package.json b/packages/contracts-gen/package.json index 72ba090f98..7d4036b906 100644 --- a/packages/contracts-gen/package.json +++ b/packages/contracts-gen/package.json @@ -28,6 +28,7 @@ "homepage": "https://github.com/0xProject/0x-monorepo/packages/contracts-gen/README.md", "dependencies": { "@0x/sol-resolver": "^2.0.8", + "@0x/sol-compiler": "^3.1.9", "@0x/types": "^2.4.0", "@0x/typescript-typings": "^4.2.3", "@0x/utils": "^4.4.0", diff --git a/packages/contracts-gen/src/contracts-gen.ts b/packages/contracts-gen/src/contracts-gen.ts index 0160a82046..a72448e285 100644 --- a/packages/contracts-gen/src/contracts-gen.ts +++ b/packages/contracts-gen/src/contracts-gen.ts @@ -1,5 +1,6 @@ #!/usr/bin/env node +import { Compiler } from '@0x/sol-compiler'; import { NameResolver } from '@0x/sol-resolver'; import { PackageJSON } from '@0x/types'; import { logUtils } from '@0x/utils'; @@ -20,11 +21,13 @@ const AUTO_GENERATED_BANNER = `/* * ----------------------------------------------------------------------------- */`; const AUTO_GENERATED_BANNER_FOR_LISTS = `This list is auto-generated by contracts-gen. Don't edit manually.`; +const ALL_CONTRACTS_IDENTIFIER = '*'; (async () => { const packageDir = process.cwd(); const compilerJSON = readJSONFile('compiler.json'); - const contracts = compilerJSON.contracts; + const compiler = new Compiler(compilerJSON); + const contracts = compiler.getContractNamesToCompile(); const contractsDir = compilerJSON.contractsDir || DEFAULT_CONTRACTS_DIR; const artifactsDir = compilerJSON.artifactsDir || DEFAULT_ARTIFACTS_DIR; const wrappersDir = DEFAULT_WRAPPERS_DIR; @@ -50,16 +53,18 @@ function generateCompilerJSONContractsList( ): void { const COMPILER_JSON_FILE_PATH = 'compiler.json'; const compilerJSON = readJSONFile(COMPILER_JSON_FILE_PATH); - compilerJSON.contracts = _.map(contracts, contract => { - if (contract.endsWith(SOLIDITY_EXTENSION)) { - // If it's already a relative path - NO-OP. - return contract; - } else { - // If it's just a contract name - resolve it and rewrite. - return new NameResolver(contractsDir).resolve(contract).path; - } - }); - compilerJSON.contracts = _.sortBy(compilerJSON.contracts); + if (compilerJSON.contracts !== undefined && compilerJSON.contracts !== ALL_CONTRACTS_IDENTIFIER) { + compilerJSON.contracts = _.map(contracts, contract => { + if (contract.endsWith(SOLIDITY_EXTENSION)) { + // If it's already a relative path - NO-OP. + return contract; + } else { + // If it's just a contract name - resolve it and rewrite. + return new NameResolver(contractsDir).resolve(contract).path; + } + }); + compilerJSON.contracts = _.sortBy(compilerJSON.contracts); + } const compilerJSONString = JSON.stringify(compilerJSON); const formattedCompilerJSON = prettier.format(compilerJSONString, { ...prettierConfig, diff --git a/packages/sol-compiler/CHANGELOG.json b/packages/sol-compiler/CHANGELOG.json index 3062927a69..c13fdb9a47 100644 --- a/packages/sol-compiler/CHANGELOG.json +++ b/packages/sol-compiler/CHANGELOG.json @@ -1,10 +1,14 @@ [ { - "version": "3.1.10", + "version": "3.2.0", "changes": [ { "note": "re-export new ethereum-types types, TupleDataItem", "pr": 1919 + }, + { + "note": "Convert `getContractNamesToCompile` to public function of `Compiler` class", + "pr": 1919 } ] }, diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts index 1d716c0547..919f9909bc 100644 --- a/packages/sol-compiler/src/compiler.ts +++ b/packages/sol-compiler/src/compiler.ts @@ -132,7 +132,7 @@ export class Compiler { public async compileAsync(): Promise { await createDirIfDoesNotExistAsync(this._artifactsDir); await createDirIfDoesNotExistAsync(constants.SOLC_BIN_DIR); - await this._compileContractsAsync(this._getContractNamesToCompile(), true); + await this._compileContractsAsync(this.getContractNamesToCompile(), true); } /** * Compiles Solidity files specified during instantiation, and returns the @@ -143,7 +143,7 @@ export class Compiler { * that version. */ public async getCompilerOutputsAsync(): Promise { - const promisedOutputs = this._compileContractsAsync(this._getContractNamesToCompile(), false); + const promisedOutputs = this._compileContractsAsync(this.getContractNamesToCompile(), false); return promisedOutputs; } public async watchAsync(): Promise { @@ -180,8 +180,23 @@ export class Compiler { onFileChangedAsync(); // tslint:disable-line no-floating-promises }); } + /** + * Gets a list of contracts to compile. + */ + public getContractNamesToCompile(): string[] { + let contractNamesToCompile; + if (this._specifiedContracts === ALL_CONTRACTS_IDENTIFIER) { + const allContracts = this._nameResolver.getAll(); + contractNamesToCompile = _.map(allContracts, contractSource => + path.basename(contractSource.path, constants.SOLIDITY_FILE_EXTENSION), + ); + } else { + return this._specifiedContracts; + } + return contractNamesToCompile; + } private _getPathsToWatch(): string[] { - const contractNames = this._getContractNamesToCompile(); + const contractNames = this.getContractNamesToCompile(); const spyResolver = new SpyResolver(this._resolver); for (const contractName of contractNames) { const contractSource = spyResolver.resolve(contractName); @@ -194,18 +209,6 @@ export class Compiler { const pathsToWatch = _.uniq(spyResolver.resolvedContractSources.map(cs => cs.absolutePath)); return pathsToWatch; } - private _getContractNamesToCompile(): string[] { - let contractNamesToCompile; - if (this._specifiedContracts === ALL_CONTRACTS_IDENTIFIER) { - const allContracts = this._nameResolver.getAll(); - contractNamesToCompile = _.map(allContracts, contractSource => - path.basename(contractSource.path, constants.SOLIDITY_FILE_EXTENSION), - ); - } else { - return this._specifiedContracts; - } - return contractNamesToCompile; - } /** * Compiles contracts, and, if `shouldPersist` is true, saves artifacts to artifactsDir. * @param fileName Name of contract with '.sol' extension.