diff --git a/.circleci/config.yml b/.circleci/config.yml index 58e16383f8..9c1046dc0b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -58,6 +58,7 @@ jobs: - run: yarn wsrun test:circleci @0x/contracts-exchange - run: yarn wsrun test:circleci @0x/contracts-exchange-forwarder - run: yarn wsrun test:circleci @0x/contracts-coordinator + - run: yarn wsrun test:circleci @0x/contracts-dev-utils test-contracts-geth: docker: - image: circleci/node:9-browsers @@ -80,6 +81,7 @@ jobs: - run: TEST_PROVIDER=geth yarn wsrun test:circleci @0x/contracts-exchange - run: TEST_PROVIDER=geth yarn wsrun test:circleci @0x/contracts-exchange-forwarder - run: TEST_PROVIDER=geth yarn wsrun test:circleci @0x/contracts-coordinator + - run: TEST_PROVIDER=geth yarn wsrun test:circleci @0x/contracts-dev-utils test-publish: resource_class: medium+ docker: diff --git a/.gitignore b/.gitignore index 203d8ad811..78d357c298 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,7 @@ contracts/erc721/generated-artifacts/ contracts/erc1155/generated-artifacts/ contracts/extensions/generated-artifacts/ contracts/exchange-forwarder/generated-artifacts/ +contracts/dev-utils/generated-artifacts/ packages/sol-tracing-utils/test/fixtures/artifacts/ packages/metacoin/artifacts/ @@ -110,6 +111,7 @@ contracts/erc721/generated-wrappers/ contracts/erc1155/generated-wrappers/ contracts/extensions/generated-wrappers/ contracts/exchange-forwarder/generated-wrappers/ +contracts/dev-utils/generated-wrappers/ packages/metacoin/src/contract_wrappers # solc-bin in sol-compiler diff --git a/.prettierignore b/.prettierignore index 6614aec2a7..2c92af5de2 100644 --- a/.prettierignore +++ b/.prettierignore @@ -22,6 +22,8 @@ lib /contracts/extensions/generated-artifacts /contracts/exchange-forwarder/generated-wrappers /contracts/exchange-forwarder/generated-artifacts +/contracts/dev-utils/generated-wrappers +/contracts/dev-utils/generated-artifacts /packages/abi-gen-wrappers/src/generated-wrappers /packages/contract-artifacts/artifacts /python-packages/contract_artifacts/src/zero_ex/contract_artifacts/artifacts diff --git a/README.md b/README.md index f6299bf43e..5c080b63c2 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ These packages are all under development. See [/contracts/README.md](/contracts/ | [`@0x/contracts-test-utils`](/contracts/test-utils) | [![npm](https://img.shields.io/npm/v/@0x/contracts-test-utils.svg)](https://www.npmjs.com/package/@0x/contracts-test-utils) | Typescript/Javascript shared utilities used for testing contracts | | [`@0x/contracts-utils`](/contracts/utils) | [![npm](https://img.shields.io/npm/v/@0x/contracts-utils.svg)](https://www.npmjs.com/package/@0x/contracts-utils) | Generic libraries and utilities used throughout all of the contracts | | [`@0x/contracts-coordinator`](/contracts/coordinator) | [![npm](https://img.shields.io/npm/v/@0x/contracts-coordinator.svg)](https://www.npmjs.com/package/@0x/contracts-coordinator) | A contract that allows users to execute 0x transactions with permission from a Coordinator | +| [`@0x/contracts-dev-utils`](/contracts/dev-utils) | [![npm](https://img.shields.io/npm/v/@0x/contracts-dev-utils.svg)](https://www.npmjs.com/package/@0x/contracts-dev-utils) | A contract contains utility functions for developers (such as validating many orders using a single eth_call) | ### Typescript/Javascript Packages diff --git a/contracts/asset-proxy/CHANGELOG.json b/contracts/asset-proxy/CHANGELOG.json index f383c96a97..4f2e5ce78a 100644 --- a/contracts/asset-proxy/CHANGELOG.json +++ b/contracts/asset-proxy/CHANGELOG.json @@ -12,7 +12,7 @@ "version": "2.2.0", "changes": [ { - "note": "Add `LibAssetProxyIds` and `LibAssetData` contracts", + "note": "Add `LibAssetProxyIds` contract", "pr": 1835 } ] diff --git a/contracts/asset-proxy/compiler.json b/contracts/asset-proxy/compiler.json index ddb26ea0e3..c09706f307 100644 --- a/contracts/asset-proxy/compiler.json +++ b/contracts/asset-proxy/compiler.json @@ -30,7 +30,6 @@ "src/MultiAssetProxy.sol", "src/interfaces/IAssetData.sol", "src/interfaces/IAssetProxy.sol", - "src/interfaces/IAuthorizable.sol", - "src/libs/LibAssetData.sol" + "src/interfaces/IAuthorizable.sol" ] } diff --git a/contracts/asset-proxy/contracts/src/libs/LibAssetData.sol b/contracts/asset-proxy/contracts/src/libs/LibAssetData.sol deleted file mode 100644 index 0b223cde77..0000000000 --- a/contracts/asset-proxy/contracts/src/libs/LibAssetData.sol +++ /dev/null @@ -1,484 +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; -pragma experimental ABIEncoderV2; - -import "@0x/contracts-utils/contracts/src/LibBytes.sol"; -import "@0x/contracts-erc1155/contracts/src/interfaces/IERC1155.sol"; -import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol"; -import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol"; -import "./LibAssetProxyIds.sol"; - - -contract LibAssetData is - LibAssetProxyIds -{ - uint256 constant internal _MAX_UINT256 = uint256(-1); - - using LibBytes for bytes; - - /// @dev Returns the owner's balance of the token(s) specified in - /// assetData. When the asset data contains multiple tokens (eg in - /// ERC1155 or Multi-Asset), the return value indicates how many - /// complete "baskets" of those tokens are owned by owner. - /// @param owner Owner of the tokens specified by assetData. - /// @param assetData Description of tokens, per the AssetProxy contract - /// specification. - /// @return Number of tokens (or token baskets) held by owner. - function getBalance(address owner, bytes memory assetData) - public - view - returns (uint256 balance) - { - bytes4 proxyId = assetData.readBytes4(0); - - if (proxyId == ERC20_PROXY_ID) { - address tokenAddress = assetData.readAddress(16); - balance = IERC20Token(tokenAddress).balanceOf(owner); - } else if (proxyId == ERC721_PROXY_ID) { - (, address tokenAddress, uint256 tokenId) = decodeERC721AssetData(assetData); - balance = getERC721TokenOwner(tokenAddress, tokenId) == owner ? 1 : 0; - } else if (proxyId == ERC1155_PROXY_ID) { - ( - , - address tokenAddress, - uint256[] memory tokenIds, - uint256[] memory tokenValues, - ) = decodeERC1155AssetData(assetData); - for (uint256 i = 0; i < tokenIds.length; i++) { - uint256 totalBalance = IERC1155(tokenAddress).balanceOf(owner, tokenIds[i]); - uint256 scaledBalance = totalBalance / tokenValues[i]; - if (scaledBalance < balance || balance == 0) { - balance = scaledBalance; - } - } - } else if (proxyId == MULTI_ASSET_PROXY_ID) { - ( - , - uint256[] memory assetAmounts, - bytes[] memory nestedAssetData - ) = decodeMultiAssetData(assetData); - for (uint256 i = 0; i < nestedAssetData.length; i++) { - uint256 totalBalance = getBalance(owner, nestedAssetData[i]); - uint256 scaledBalance = totalBalance / assetAmounts[i]; - if (scaledBalance < balance || balance == 0) { - balance = scaledBalance; - } - } - } else { - revert("UNSUPPORTED_PROXY_ID"); - } - - return balance; - } - - /// @dev Calls getBalance() for each element of assetData. - /// @param owner Owner of the tokens specified by assetData. - /// @param assetData Array of token descriptors, each encoded per the - /// AssetProxy contract specification. - /// @return Array of token balances from getBalance(), with each element - /// corresponding to the same-indexed element in the assetData input. - function getBatchBalances(address owner, bytes[] memory assetData) - public - view - returns (uint256[] memory balances) - { - uint256 length = assetData.length; - balances = new uint256[](length); - for (uint256 i = 0; i != length; i++) { - balances[i] = getBalance(owner, assetData[i]); - } - return balances; - } - - /// @dev Returns the number of token(s) (described by assetData) that - /// spender is authorized to spend. When the asset data contains - /// multiple tokens (eg for Multi-Asset), the return value indicates - /// how many complete "baskets" of those tokens may be spent by spender. - /// @param owner Owner of the tokens specified by assetData. - /// @param spender Address whose authority to spend is in question. - /// @param assetData Description of tokens, per the AssetProxy contract - /// specification. - /// @return Number of tokens (or token baskets) that the spender is - /// authorized to spend. - function getAllowance( - address owner, - address spender, - bytes memory assetData - ) - public - view - returns (uint256 allowance) - { - bytes4 proxyId = assetData.readBytes4(0); - - if (proxyId == ERC20_PROXY_ID) { - address tokenAddress = assetData.readAddress(16); - allowance = IERC20Token(tokenAddress).allowance(owner, spender); - } else if (proxyId == ERC721_PROXY_ID) { - (, address tokenAddress, uint256 tokenId) = decodeERC721AssetData(assetData); - IERC721Token token = IERC721Token(tokenAddress); - if (token.isApprovedForAll(owner, spender)) { - allowance = _MAX_UINT256; - } else if (token.getApproved(tokenId) == spender) { - allowance = 1; - } - } else if (proxyId == ERC1155_PROXY_ID) { - (, address tokenAddress, , , ) = decodeERC1155AssetData(assetData); - allowance = IERC1155(tokenAddress).isApprovedForAll(owner, spender) ? _MAX_UINT256 : 0; - } else if (proxyId == MULTI_ASSET_PROXY_ID) { - ( - , - uint256[] memory amounts, - bytes[] memory nestedAssetData - ) = decodeMultiAssetData(assetData); - for (uint256 i = 0; i < nestedAssetData.length; i++) { - uint256 totalAllowance = getAllowance( - owner, - spender, - nestedAssetData[i] - ); - uint256 scaledAllowance = totalAllowance / amounts[i]; - if (scaledAllowance < allowance || allowance == 0) { - allowance = scaledAllowance; - } - } - } else { - revert("UNSUPPORTED_PROXY_ID"); - } - - return allowance; - } - - /// @dev Calls getAllowance() for each element of assetData. - /// @param owner Owner of the tokens specified by assetData. - /// @param spenders Array of addresses whose authority to spend is in question for each corresponding assetData. - /// @param assetData Description of tokens, per the AssetProxy contract - /// specification. - /// @return An array of token allowances from getAllowance(), with each - /// element corresponding to the same-indexed element in the assetData - /// input. - function getBatchAllowances( - address owner, - address[] memory spenders, - bytes[] memory assetData - ) - public - view - returns (uint256[] memory allowances) - { - uint256 length = assetData.length; - allowances = new uint256[](length); - for (uint256 i = 0; i != length; i++) { - allowances[i] = getAllowance(owner, spenders[i], assetData[i]); - } - return allowances; - } - - /// @dev Calls getBalance() and getAllowance() for assetData. - /// @param owner Owner of the tokens specified by assetData. - /// @param spender Address whose authority to spend is in question. - /// @param assetData Description of tokens, per the AssetProxy contract - /// specification. - /// @return Number of tokens (or token baskets) held by owner, and number - /// of tokens (or token baskets) that the spender is authorized to - /// spend. - function getBalanceAndAllowance( - address owner, - address spender, - bytes memory assetData - ) - public - view - returns (uint256 balance, uint256 allowance) - { - balance = getBalance(owner, assetData); - allowance = getAllowance(owner, spender, assetData); - return (balance, allowance); - } - - /// @dev Calls getBatchBalances() and getBatchAllowances() for each element - /// of assetData. - /// @param owner Owner of the tokens specified by assetData. - /// @param spenders Array of addresses whose authority to spend is in question for each corresponding assetData. - /// @param assetData Description of tokens, per the AssetProxy contract - /// specification. - /// @return An array of token balances from getBalance(), and an array of - /// token allowances from getAllowance(), with each element - /// corresponding to the same-indexed element in the assetData input. - function getBatchBalancesAndAllowances( - address owner, - address[] memory spenders, - bytes[] memory assetData - ) - public - view - returns ( - uint256[] memory balances, - uint256[] memory allowances - ) - { - balances = getBatchBalances(owner, assetData); - allowances = getBatchAllowances(owner, spenders, assetData); - return (balances, allowances); - } - - /// @dev Encode ERC-20 asset data into the format described in the - /// AssetProxy contract specification. - /// @param tokenAddress The address of the ERC-20 contract hosting the - /// token to be traded. - /// @return AssetProxy-compliant data describing the asset. - function encodeERC20AssetData(address tokenAddress) - public - pure - returns (bytes memory assetData) - { - assetData = abi.encodeWithSelector(ERC20_PROXY_ID, tokenAddress); - return assetData; - } - - /// @dev Decode ERC-20 asset data from the format described in the - /// AssetProxy contract specification. - /// @param assetData AssetProxy-compliant asset data describing an ERC-20 - /// asset. - /// @return The ERC-20 AssetProxy identifier, and the address of the ERC-20 - /// contract hosting this asset. - function decodeERC20AssetData(bytes memory assetData) - public - pure - returns ( - bytes4 proxyId, - address tokenAddress - ) - { - proxyId = assetData.readBytes4(0); - - require( - proxyId == ERC20_PROXY_ID, - "WRONG_PROXY_ID" - ); - - tokenAddress = assetData.readAddress(16); - return (proxyId, tokenAddress); - } - - /// @dev Encode ERC-721 asset data into the format described in the - /// AssetProxy specification. - /// @param tokenAddress The address of the ERC-721 contract hosting the - /// token to be traded. - /// @param tokenId The identifier of the specific token to be traded. - /// @return AssetProxy-compliant asset data describing the asset. - function encodeERC721AssetData( - address tokenAddress, - uint256 tokenId - ) - public - pure - returns (bytes memory assetData) - { - assetData = abi.encodeWithSelector( - ERC721_PROXY_ID, - tokenAddress, - tokenId - ); - return assetData; - } - - /// @dev Decode ERC-721 asset data from the format described in the - /// AssetProxy contract specification. - /// @param assetData AssetProxy-compliant asset data describing an ERC-721 - /// asset. - /// @return The ERC-721 AssetProxy identifier, the address of the ERC-721 - /// contract hosting this asset, and the identifier of the specific - /// token to be traded. - function decodeERC721AssetData(bytes memory assetData) - public - pure - returns ( - bytes4 proxyId, - address tokenAddress, - uint256 tokenId - ) - { - proxyId = assetData.readBytes4(0); - - require( - proxyId == ERC721_PROXY_ID, - "WRONG_PROXY_ID" - ); - - tokenAddress = assetData.readAddress(16); - tokenId = assetData.readUint256(36); - return (proxyId, tokenAddress, tokenId); - } - - /// @dev Encode ERC-1155 asset data into the format described in the - /// AssetProxy contract specification. - /// @param tokenAddress The address of the ERC-1155 contract hosting the - /// token(s) to be traded. - /// @param tokenIds The identifiers of the specific tokens to be traded. - /// @param tokenValues The amounts of each token to be traded. - /// @param callbackData ... - /// @return AssetProxy-compliant asset data describing the set of assets. - function encodeERC1155AssetData( - address tokenAddress, - uint256[] memory tokenIds, - uint256[] memory tokenValues, - bytes memory callbackData - ) - public - pure - returns (bytes memory assetData) - { - assetData = abi.encodeWithSelector( - ERC1155_PROXY_ID, - tokenAddress, - tokenIds, - tokenValues, - callbackData - ); - return assetData; - } - - /// @dev Decode ERC-1155 asset data from the format described in the - /// AssetProxy contract specification. - /// @param assetData AssetProxy-compliant asset data describing an ERC-1155 - /// set of assets. - /// @return The ERC-1155 AssetProxy identifier, the address of the ERC-1155 - /// contract hosting the assets, an array of the identifiers of the - /// tokens to be traded, an array of token amounts to be traded, and - /// callback data. Each element of the arrays corresponds to the - /// same-indexed element of the other array. Return values specified as - /// `memory` are returned as pointers to locations within the memory of - /// the input parameter `assetData`. - function decodeERC1155AssetData(bytes memory assetData) - public - pure - returns ( - bytes4 proxyId, - address tokenAddress, - uint256[] memory tokenIds, - uint256[] memory tokenValues, - bytes memory callbackData - ) - { - proxyId = assetData.readBytes4(0); - - require( - proxyId == ERC1155_PROXY_ID, - "WRONG_PROXY_ID" - ); - - assembly { - // Skip selector and length to get to the first parameter: - assetData := add(assetData, 36) - // Read the value of the first parameter: - tokenAddress := mload(assetData) - // Point to the next parameter's data: - tokenIds := add(assetData, mload(add(assetData, 32))) - // Point to the next parameter's data: - tokenValues := add(assetData, mload(add(assetData, 64))) - // Point to the next parameter's data: - callbackData := add(assetData, mload(add(assetData, 96))) - } - - return ( - proxyId, - tokenAddress, - tokenIds, - tokenValues, - callbackData - ); - } - - /// @dev Encode data for multiple assets, per the AssetProxy contract - /// specification. - /// @param amounts The amounts of each asset to be traded. - /// @param nestedAssetData AssetProxy-compliant data describing each asset - /// to be traded. - /// @return AssetProxy-compliant data describing the set of assets. - function encodeMultiAssetData( - uint256[] memory amounts, - bytes[] memory nestedAssetData - ) - public - pure - returns (bytes memory assetData) - { - assetData = abi.encodeWithSelector( - MULTI_ASSET_PROXY_ID, - amounts, - nestedAssetData - ); - return assetData; - } - - /// @dev Decode multi-asset data from the format described in the - /// AssetProxy contract specification. - /// @param assetData AssetProxy-compliant data describing a multi-asset - /// basket. - /// @return The Multi-Asset AssetProxy identifier, an array of the amounts - /// of the assets to be traded, and an array of the - /// AssetProxy-compliant data describing each asset to be traded. Each - /// element of the arrays corresponds to the same-indexed element of - /// the other array. - function decodeMultiAssetData(bytes memory assetData) - public - pure - returns ( - bytes4 proxyId, - uint256[] memory amounts, - bytes[] memory nestedAssetData - ) - { - proxyId = assetData.readBytes4(0); - - require( - proxyId == MULTI_ASSET_PROXY_ID, - "WRONG_PROXY_ID" - ); - - // solhint-disable indent - (amounts, nestedAssetData) = abi.decode( - assetData.slice(4, assetData.length), - (uint256[], bytes[]) - ); - // solhint-enable indent - } - - /// @dev Calls `token.ownerOf(tokenId)`, but returns a null owner instead of reverting on an unowned token. - /// @param tokenAddress Address of ERC721 token. - /// @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 owner) - { - bytes memory ownerOfCalldata = abi.encodeWithSelector( - 0x6352211e, - tokenId - ); - - (bool success, bytes memory returnData) = tokenAddress.staticcall(ownerOfCalldata); - - owner = (success && returnData.length == 32) ? returnData.readAddress(12) : address(0); - return owner; - } -} diff --git a/contracts/asset-proxy/package.json b/contracts/asset-proxy/package.json index 78a10f0a49..2bdba923c3 100644 --- a/contracts/asset-proxy/package.json +++ b/contracts/asset-proxy/package.json @@ -34,7 +34,7 @@ "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis": "./generated-artifacts/@(ERC1155Proxy|ERC20Proxy|ERC721Proxy|IAssetData|IAssetProxy|IAuthorizable|LibAssetData|MixinAuthorizable|MultiAssetProxy).json", + "abis": "./generated-artifacts/@(ERC1155Proxy|ERC20Proxy|ERC721Proxy|IAssetData|IAssetProxy|IAuthorizable|MixinAuthorizable|MultiAssetProxy).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 9c82fe54ff..7fc2b38cda 100644 --- a/contracts/asset-proxy/src/artifacts.ts +++ b/contracts/asset-proxy/src/artifacts.ts @@ -11,11 +11,9 @@ 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 IAuthorizable from '../generated-artifacts/IAuthorizable.json'; -import * as LibAssetData from '../generated-artifacts/LibAssetData.json'; import * as MixinAuthorizable from '../generated-artifacts/MixinAuthorizable.json'; import * as MultiAssetProxy from '../generated-artifacts/MultiAssetProxy.json'; export const artifacts = { - LibAssetData: LibAssetData as ContractArtifact, ERC1155Proxy: ERC1155Proxy as ContractArtifact, ERC20Proxy: ERC20Proxy as ContractArtifact, ERC721Proxy: ERC721Proxy as ContractArtifact, diff --git a/contracts/asset-proxy/src/wrappers.ts b/contracts/asset-proxy/src/wrappers.ts index 2b7bf8bbde..3412cb0266 100644 --- a/contracts/asset-proxy/src/wrappers.ts +++ b/contracts/asset-proxy/src/wrappers.ts @@ -9,6 +9,5 @@ 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_authorizable'; -export * from '../generated-wrappers/lib_asset_data'; export * from '../generated-wrappers/mixin_authorizable'; export * from '../generated-wrappers/multi_asset_proxy'; diff --git a/contracts/asset-proxy/test/lib_asset_data.ts b/contracts/asset-proxy/test/lib_asset_data.ts deleted file mode 100644 index d744ba3769..0000000000 --- a/contracts/asset-proxy/test/lib_asset_data.ts +++ /dev/null @@ -1,432 +0,0 @@ -// TODO: change test titles to say "... from asset data" -import * as chai from 'chai'; -import { LogWithDecodedArgs } from 'ethereum-types'; - -import { - artifacts as erc1155Artifacts, - ERC1155MintableContract, - ERC1155TransferSingleEventArgs, - IERC1155MintableContract, -} from '@0x/contracts-erc1155'; -import { artifacts as erc20Artifacts, DummyERC20TokenContract, IERC20TokenContract } from '@0x/contracts-erc20'; -import { artifacts as erc721Artifacts, DummyERC721TokenContract, IERC721TokenContract } from '@0x/contracts-erc721'; -import { chaiSetup, constants, LogDecoder, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { AssetProxyId } from '@0x/types'; -import { BigNumber } from '@0x/utils'; - -import { artifacts, LibAssetDataContract } from '../src'; - -chaiSetup.configure(); -const expect = chai.expect; - -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -const KNOWN_ERC20_ENCODING = { - address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48', - assetData: '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48', -}; -const KNOWN_ERC721_ENCODING = { - address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48', - tokenId: new BigNumber(1), - assetData: - '0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001', -}; -const KNOWN_ERC1155_ENCODING = { - tokenAddress: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48', - tokenIds: [new BigNumber(100), new BigNumber(1001), new BigNumber(10001)], - tokenValues: [new BigNumber(200), new BigNumber(2001), new BigNumber(20001)], - callbackData: - '0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001', - assetData: - '0xa7cb5fb70000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000002711000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000007d10000000000000000000000000000000000000000000000000000000000004e210000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000', -}; -const KNOWN_MULTI_ASSET_ENCODING = { - amounts: [new BigNumber(70), new BigNumber(1), new BigNumber(18)], - nestedAssetData: [ - KNOWN_ERC20_ENCODING.assetData, - KNOWN_ERC721_ENCODING.assetData, - KNOWN_ERC1155_ENCODING.assetData, - ], - assetData: - '0x94cfcdd7000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000204a7cb5fb70000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000002711000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000007d10000000000000000000000000000000000000000000000000000000000004e210000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c4800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', -}; - -describe('LibAssetData', () => { - let libAssetData: LibAssetDataContract; - - let tokenOwnerAddress: string; - let approvedSpenderAddress: string; - let anotherApprovedSpenderAddress: string; - - let erc20TokenAddress: string; - const erc20TokenTotalSupply = new BigNumber(1); - - let erc721TokenAddress: string; - const firstERC721TokenId = new BigNumber(1); - const numberOfERC721Tokens = 10; - - let erc1155MintableAddress: string; - let erc1155TokenId: BigNumber; - - before(async () => { - await blockchainLifecycle.startAsync(); - - libAssetData = await LibAssetDataContract.deployFrom0xArtifactAsync( - artifacts.LibAssetData, - provider, - txDefaults, - ); - - [ - tokenOwnerAddress, - approvedSpenderAddress, - anotherApprovedSpenderAddress, - ] = await web3Wrapper.getAvailableAddressesAsync(); - - erc20TokenAddress = (await DummyERC20TokenContract.deployFrom0xArtifactAsync( - erc20Artifacts.DummyERC20Token, - provider, - txDefaults, - 'Dummy', - 'DUM', - new BigNumber(1), - erc20TokenTotalSupply, - )).address; - - const erc721TokenContract = await DummyERC721TokenContract.deployFrom0xArtifactAsync( - erc721Artifacts.DummyERC721Token, - provider, - txDefaults, - 'Dummy', - 'DUM', - ); - erc721TokenAddress = erc721TokenContract.address; - // mint `numberOfERC721Tokens` tokens - const transactionMinedPromises = []; - for (let i = 0; i < numberOfERC721Tokens; i++) { - transactionMinedPromises.push( - web3Wrapper.awaitTransactionSuccessAsync( - await erc721TokenContract.mint.sendTransactionAsync( - tokenOwnerAddress, - firstERC721TokenId.plus(i - 1), - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ), - ); - } - await Promise.all(transactionMinedPromises); - - const erc1155MintableContract = await ERC1155MintableContract.deployFrom0xArtifactAsync( - erc1155Artifacts.ERC1155Mintable, - provider, - txDefaults, - ); - erc1155MintableAddress = erc1155MintableContract.address; - // Somewhat re-inventing the wheel here, but the prior art currently - // exists only as an unexported test util in the erc1155 package - // (Erc1155Wrapper.mintFungibleTokensAsync() in erc1155/test/utils/). - // This is concise enough to justify duplication, but it sure is ugly. - // tslint:disable-next-line no-unnecessary-type-assertion - erc1155TokenId = ((await new LogDecoder(web3Wrapper, erc1155Artifacts).getTxWithDecodedLogsAsync( - await erc1155MintableContract.create.sendTransactionAsync('uri:Dummy', /*isNonFungible:*/ false), - )).logs[0] as LogWithDecodedArgs).args.id; - await web3Wrapper.awaitTransactionSuccessAsync( - await erc1155MintableContract.mintFungible.sendTransactionAsync( - erc1155TokenId, - [tokenOwnerAddress], - [new BigNumber(1)], - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - }); - - async function setERC20AllowanceAsync(): Promise { - return web3Wrapper.awaitTransactionSuccessAsync( - await new IERC20TokenContract( - erc20Artifacts.IERC20Token.compilerOutput.abi, - erc20TokenAddress, - provider, - ).approve.sendTransactionAsync(approvedSpenderAddress, new BigNumber(1), { from: tokenOwnerAddress }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - } - - async function setERC721AllowanceAsync(): Promise { - return web3Wrapper.awaitTransactionSuccessAsync( - await new IERC721TokenContract( - erc721Artifacts.IERC721Token.compilerOutput.abi, - erc721TokenAddress, - provider, - ).approve.sendTransactionAsync(approvedSpenderAddress, new BigNumber(1), { from: tokenOwnerAddress }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - } - - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - - it('should have a deployed-to address', () => { - expect(libAssetData.address.slice(0, 2)).to.equal('0x'); - }); - - it('should encode ERC20 asset data', async () => { - expect(await libAssetData.encodeERC20AssetData.callAsync(KNOWN_ERC20_ENCODING.address)).to.equal( - KNOWN_ERC20_ENCODING.assetData, - ); - }); - - it('should decode ERC20 asset data', async () => { - expect(await libAssetData.decodeERC20AssetData.callAsync(KNOWN_ERC20_ENCODING.assetData)).to.deep.equal([ - AssetProxyId.ERC20, - KNOWN_ERC20_ENCODING.address, - ]); - }); - - it('should encode ERC721 asset data', async () => { - expect( - await libAssetData.encodeERC721AssetData.callAsync( - KNOWN_ERC721_ENCODING.address, - KNOWN_ERC721_ENCODING.tokenId, - ), - ).to.equal(KNOWN_ERC721_ENCODING.assetData); - }); - - it('should decode ERC721 asset data', async () => { - expect(await libAssetData.decodeERC721AssetData.callAsync(KNOWN_ERC721_ENCODING.assetData)).to.deep.equal([ - AssetProxyId.ERC721, - KNOWN_ERC721_ENCODING.address, - KNOWN_ERC721_ENCODING.tokenId, - ]); - }); - - it('should encode ERC1155 asset data', async () => { - expect( - await libAssetData.encodeERC1155AssetData.callAsync( - KNOWN_ERC1155_ENCODING.tokenAddress, - KNOWN_ERC1155_ENCODING.tokenIds, - KNOWN_ERC1155_ENCODING.tokenValues, - KNOWN_ERC1155_ENCODING.callbackData, - ), - ).to.equal(KNOWN_ERC1155_ENCODING.assetData); - }); - - it('should decode ERC1155 asset data', async () => { - expect(await libAssetData.decodeERC1155AssetData.callAsync(KNOWN_ERC1155_ENCODING.assetData)).to.deep.equal([ - AssetProxyId.ERC1155, - KNOWN_ERC1155_ENCODING.tokenAddress, - KNOWN_ERC1155_ENCODING.tokenIds, - KNOWN_ERC1155_ENCODING.tokenValues, - KNOWN_ERC1155_ENCODING.callbackData, - ]); - }); - - it('should encode multiasset data', async () => { - expect( - await libAssetData.encodeMultiAssetData.callAsync( - KNOWN_MULTI_ASSET_ENCODING.amounts, - KNOWN_MULTI_ASSET_ENCODING.nestedAssetData, - ), - ).to.equal(KNOWN_MULTI_ASSET_ENCODING.assetData); - }); - - it('should decode multiasset data', async () => { - expect(await libAssetData.decodeMultiAssetData.callAsync(KNOWN_MULTI_ASSET_ENCODING.assetData)).to.deep.equal([ - AssetProxyId.MultiAsset, - KNOWN_MULTI_ASSET_ENCODING.amounts, - KNOWN_MULTI_ASSET_ENCODING.nestedAssetData, - ]); - }); - - it('should query ERC20 balance by asset data', async () => { - expect( - await libAssetData.getBalance.callAsync( - tokenOwnerAddress, - await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress), - ), - ).to.bignumber.equal(erc20TokenTotalSupply); - }); - - it('should query ERC721 balance by asset data', async () => { - expect( - await libAssetData.getBalance.callAsync( - tokenOwnerAddress, - await libAssetData.encodeERC721AssetData.callAsync(erc721TokenAddress, firstERC721TokenId), - ), - ).to.bignumber.equal(1); - }); - - it('should query ERC1155 balances by asset data', async () => { - expect( - await libAssetData.getBalance.callAsync( - tokenOwnerAddress, - await libAssetData.encodeERC1155AssetData.callAsync( - erc1155MintableAddress, - [erc1155TokenId], - [new BigNumber(1)], // token values - '0x', // callback data - ), - ), - ).to.bignumber.equal(1); - }); - - it('should query multi-asset batch balance by asset data', async () => { - expect( - await libAssetData.getBalance.callAsync( - tokenOwnerAddress, - await libAssetData.encodeMultiAssetData.callAsync( - [new BigNumber(1), new BigNumber(1)], - [ - await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress), - await libAssetData.encodeERC721AssetData.callAsync(erc721TokenAddress, firstERC721TokenId), - ], - ), - ), - ).to.bignumber.equal(Math.min(erc20TokenTotalSupply.toNumber(), numberOfERC721Tokens)); - }); - - it('should query ERC20 allowances by asset data', async () => { - await setERC20AllowanceAsync(); - expect( - await libAssetData.getAllowance.callAsync( - tokenOwnerAddress, - approvedSpenderAddress, - await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress), - ), - ).to.bignumber.equal(1); - }); - - it('should query ERC721 approval by asset data', async () => { - await setERC721AllowanceAsync(); - expect( - await libAssetData.getAllowance.callAsync( - tokenOwnerAddress, - approvedSpenderAddress, - await libAssetData.encodeERC721AssetData.callAsync(erc721TokenAddress, firstERC721TokenId), - ), - ).to.bignumber.equal(1); - }); - - it('should query ERC721 approvalForAll by assetData', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await new IERC721TokenContract( - erc721Artifacts.IERC721Token.compilerOutput.abi, - erc721TokenAddress, - provider, - ).setApprovalForAll.sendTransactionAsync(anotherApprovedSpenderAddress, true, { - from: tokenOwnerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - expect( - await libAssetData.getAllowance.callAsync( - tokenOwnerAddress, - anotherApprovedSpenderAddress, - await libAssetData.encodeERC721AssetData.callAsync(erc721TokenAddress, firstERC721TokenId), - ), - ).to.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - - it('should query ERC1155 allowances by asset data', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await new IERC1155MintableContract( - erc1155Artifacts.IERC1155Mintable.compilerOutput.abi, - erc1155MintableAddress, - provider, - ).setApprovalForAll.sendTransactionAsync(approvedSpenderAddress, true, { from: tokenOwnerAddress }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - expect( - await libAssetData.getAllowance.callAsync( - tokenOwnerAddress, - approvedSpenderAddress, - await libAssetData.encodeERC1155AssetData.callAsync( - erc1155MintableAddress, - [erc1155TokenId], - [new BigNumber(1)], - '0x', - ), - ), - ).to.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - - it('should query multi-asset allowances by asset data', async () => { - await setERC20AllowanceAsync(); - await setERC721AllowanceAsync(); - expect( - await libAssetData.getAllowance.callAsync( - tokenOwnerAddress, - approvedSpenderAddress, - await libAssetData.encodeMultiAssetData.callAsync( - [new BigNumber(1), new BigNumber(1)], - [ - await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress), - await libAssetData.encodeERC721AssetData.callAsync(erc721TokenAddress, firstERC721TokenId), - ], - ), - ), - ).to.bignumber.equal(1); - return; - }); - - it('should query balances for a batch of asset data strings', async () => { - expect( - await libAssetData.getBatchBalances.callAsync(tokenOwnerAddress, [ - await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress), - await libAssetData.encodeERC721AssetData.callAsync(erc721TokenAddress, firstERC721TokenId), - ]), - ).to.deep.equal([new BigNumber(erc20TokenTotalSupply), new BigNumber(1)]); - }); - - it('should query allowances for a batch of asset data strings', async () => { - await setERC20AllowanceAsync(); - await setERC721AllowanceAsync(); - expect( - await libAssetData.getBatchAllowances.callAsync( - tokenOwnerAddress, - [approvedSpenderAddress, approvedSpenderAddress], - [ - await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress), - await libAssetData.encodeERC721AssetData.callAsync(erc721TokenAddress, firstERC721TokenId), - ], - ), - ).to.deep.equal([new BigNumber(1), new BigNumber(1)]); - }); - - describe('getERC721TokenOwner', async () => { - it('should return the null address when tokenId is not owned', async () => { - const nonexistentTokenId = new BigNumber(1234567890); - expect( - await libAssetData.getERC721TokenOwner.callAsync(erc721TokenAddress, nonexistentTokenId), - ).to.be.equal(constants.NULL_ADDRESS); - }); - it('should return the owner address when tokenId is owned', async () => { - expect( - await libAssetData.getERC721TokenOwner.callAsync(erc721TokenAddress, firstERC721TokenId), - ).to.be.equal(tokenOwnerAddress); - }); - }); - - it('should query balance and allowance together, from asset data', async () => { - await setERC20AllowanceAsync(); - expect( - await libAssetData.getBalanceAndAllowance.callAsync( - tokenOwnerAddress, - approvedSpenderAddress, - await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress), - ), - ).to.deep.equal([new BigNumber(erc20TokenTotalSupply), new BigNumber(1)]); - }); - - it('should query balances and allowances together, from an asset data array', async () => { - await setERC20AllowanceAsync(); - expect( - await libAssetData.getBatchBalancesAndAllowances.callAsync( - tokenOwnerAddress, - [approvedSpenderAddress, approvedSpenderAddress], - [await libAssetData.encodeERC20AssetData.callAsync(erc20TokenAddress)], - ), - ).to.deep.equal([[new BigNumber(erc20TokenTotalSupply)], [new BigNumber(1)]]); - }); -}); diff --git a/contracts/asset-proxy/tsconfig.json b/contracts/asset-proxy/tsconfig.json index 809a8ed0da..31358217c9 100644 --- a/contracts/asset-proxy/tsconfig.json +++ b/contracts/asset-proxy/tsconfig.json @@ -9,7 +9,6 @@ "generated-artifacts/IAssetData.json", "generated-artifacts/IAssetProxy.json", "generated-artifacts/IAuthorizable.json", - "generated-artifacts/LibAssetData.json", "generated-artifacts/MixinAuthorizable.json", "generated-artifacts/MultiAssetProxy.json" ], diff --git a/contracts/dev-utils/CHANGELOG.json b/contracts/dev-utils/CHANGELOG.json new file mode 100644 index 0000000000..37287903d7 --- /dev/null +++ b/contracts/dev-utils/CHANGELOG.json @@ -0,0 +1,27 @@ +[ + { + "version": "0.0.1", + "changes": [ + { + "note": "Create dev-utils package", + "pr": 1848 + }, + { + "note": "Add `LibAssetData` and `LibTransactionDecoder` contracts", + "pr": 1848 + }, + { + "note": "Refactor `LibAssetData` to only check 0x-specific allowances", + "pr": 1848 + }, + { + "note": "Refactor `LibAssetData` balance/allowance checks to never revert", + "pr": 1848 + }, + { + "note": "Refactor `OrderValidationUtils` to calculate `fillableTakerAssetAmount`", + "pr": 1848 + } + ] + } +] diff --git a/contracts/dev-utils/DEPLOYS.json b/contracts/dev-utils/DEPLOYS.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/contracts/dev-utils/DEPLOYS.json @@ -0,0 +1 @@ +[] diff --git a/contracts/dev-utils/README.md b/contracts/dev-utils/README.md new file mode 100644 index 0000000000..8c62bce579 --- /dev/null +++ b/contracts/dev-utils/README.md @@ -0,0 +1,73 @@ +## Dev-Utils + +This package implements various utilities for developers. For example, the `DevUtils` contract can query batches of balances or allowances given some `assetData`, can validate batches of orders, and can decode 0x-specific calldata. Addresses of the deployed contracts can be found in the 0x [wiki](https://0xproject.com/wiki#Deployed-Addresses) or the [DEPLOYS](./DEPLOYS.json) file within this package. + +## Installation + +**Install** + +```bash +npm install @0x/contracts-dev-utils --save +``` + +## Bug bounty + +A bug bounty for the 2.0.0 contracts is ongoing! Instructions can be found [here](https://0xproject.com/wiki#Bug-Bounty). + +## Contributing + +We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. + +For proposals regarding the 0x protocol's smart contract architecture, message format, or additional functionality, go to the [0x Improvement Proposals (ZEIPs)](https://github.com/0xProject/ZEIPs) repository and follow the contribution guidelines provided therein. + +Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started. + +### Install Dependencies + +If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them: + +```bash +yarn config set workspaces-experimental true +``` + +Then install dependencies + +```bash +yarn install +``` + +### Build + +To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory: + +```bash +PKG=@0x/contracts-extensions yarn build +``` + +Or continuously rebuild on change: + +```bash +PKG=@0x/contracts-extensions yarn watch +``` + +### Clean + +```bash +yarn clean +``` + +### Lint + +```bash +yarn lint +``` + +### Run Tests + +```bash +yarn test +``` + +#### Testing options + +Contracts testing options like coverage, profiling, revert traces or backing node choosing - are described [here](../TESTING.md). diff --git a/contracts/dev-utils/compiler.json b/contracts/dev-utils/compiler.json new file mode 100644 index 0000000000..eccc11fef8 --- /dev/null +++ b/contracts/dev-utils/compiler.json @@ -0,0 +1,26 @@ +{ + "artifactsDir": "./generated-artifacts", + "contractsDir": "./contracts", + "useDockerisedSolc": false, + "isOfflineMode": false, + "compilerSettings": { + "evmVersion": "constantinople", + "optimizer": { + "enabled": true, + "runs": 1000000, + "details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true } + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + } + }, + "contracts": ["src/DevUtils.sol", "src/LibAssetData.sol", "src/LibTransactionDecoder.sol"] +} diff --git a/contracts/extensions/contracts/src/DevUtils/DevUtils.sol b/contracts/dev-utils/contracts/src/DevUtils.sol similarity index 92% rename from contracts/extensions/contracts/src/DevUtils/DevUtils.sol rename to contracts/dev-utils/contracts/src/DevUtils.sol index b3827051b5..16de2404cb 100644 --- a/contracts/extensions/contracts/src/DevUtils/DevUtils.sol +++ b/contracts/dev-utils/contracts/src/DevUtils.sol @@ -20,7 +20,7 @@ pragma solidity ^0.5.5; pragma experimental ABIEncoderV2; import "./OrderValidationUtils.sol"; -import "@0x/contracts-exchange-libs/contracts/src/LibTransactionDecoder.sol"; +import "./LibTransactionDecoder.sol"; // solhint-disable no-empty-blocks diff --git a/contracts/dev-utils/contracts/src/LibAssetData.sol b/contracts/dev-utils/contracts/src/LibAssetData.sol new file mode 100644 index 0000000000..dc6f2929fd --- /dev/null +++ b/contracts/dev-utils/contracts/src/LibAssetData.sol @@ -0,0 +1,518 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/src/LibBytes.sol"; +import "@0x/contracts-asset-proxy/contracts/src/libs/LibAssetProxyIds.sol"; +import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; + + +contract LibAssetData is + LibAssetProxyIds +{ + // 2^256 - 1 + uint256 constant internal _MAX_UINT256 = uint256(-1); + + // ERC20 selectors + bytes4 constant internal _ERC20_BALANCE_OF_SELECTOR = 0x70a08231; + bytes4 constant internal _ERC20_ALLOWANCE_SELECTOR = 0xdd62ed3e; + + // ERC721 selectors + bytes4 constant internal _ERC721_OWNER_OF_SELECTOR = 0x6352211e; + bytes4 constant internal _ERC721_IS_APPROVED_FOR_ALL_SELECTOR = 0xe985e9c5; + bytes4 constant internal _ERC721_GET_APPROVED_SELECTOR = 0x081812fc; + + // ERC1155 selectors + bytes4 constant internal _ERC1155_BALANCE_OF_SELECTOR = 0x00fdd58e; + bytes4 constant internal _ERC1155_IS_APPROVED_FOR_ALL_SELECTOR = 0xe985e9c5; + + using LibBytes for bytes; + + // solhint-disable var-name-mixedcase + IExchange internal _EXCHANGE; + address internal _ERC20_PROXY_ADDRESS; + address internal _ERC721_PROXY_ADDRESS; + address internal _ERC1155_PROXY_ADDRESS; + // solhint-enable var-name-mixedcase + + constructor (address _exchange) + public + { + _EXCHANGE = IExchange(_exchange); + _ERC20_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(ERC20_PROXY_ID); + _ERC721_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(ERC721_PROXY_ID); + _ERC1155_PROXY_ADDRESS = _EXCHANGE.getAssetProxy(ERC1155_PROXY_ID); + } + + /// @dev Returns the owner's balance of the assets(s) specified in + /// assetData. When the asset data contains multiple assets (eg in + /// ERC1155 or Multi-Asset), the return value indicates how many + /// complete "baskets" of those assets are owned by owner. + /// @param ownerAddress Owner of the assets specified by assetData. + /// @param assetData Details of asset, encoded per the AssetProxy contract specification. + /// @return Number of assets (or asset baskets) held by owner. + function getBalance(address ownerAddress, bytes memory assetData) + public + view + returns (uint256 balance) + { + // Get id of AssetProxy contract + bytes4 assetProxyId = assetData.readBytes4(0); + + if (assetProxyId == ERC20_PROXY_ID) { + // Get ERC20 token address + address tokenAddress = assetData.readAddress(16); + + // Encode data for `balanceOf(ownerAddress)` + bytes memory balanceOfData = abi.encodeWithSelector(_ERC20_BALANCE_OF_SELECTOR, ownerAddress); + + // Query balance + (bool success, bytes memory returnData) = tokenAddress.staticcall(balanceOfData); + balance = success && returnData.length == 32 ? returnData.readUint256(0) : 0; + } else if (assetProxyId == ERC721_PROXY_ID) { + // Get ERC721 token address and id + (, address tokenAddress, uint256 tokenId) = decodeERC721AssetData(assetData); + + // Check if id is owned by ownerAddress + balance = getERC721TokenOwner(tokenAddress, tokenId) == ownerAddress ? 1 : 0; + } else if (assetProxyId == ERC1155_PROXY_ID) { + // Get ERC1155 token address, array of ids, and array of values + (, address tokenAddress, uint256[] memory tokenIds, uint256[] memory tokenValues,) = decodeERC1155AssetData(assetData); + + uint256 length = tokenIds.length; + for (uint256 i = 0; i != length; i++) { + // Encode data for `balanceOf(ownerAddress, tokenIds[i]) + bytes memory balanceOfData = abi.encodeWithSelector( + _ERC1155_BALANCE_OF_SELECTOR, + ownerAddress, + tokenIds[i] + ); + + // Query balance + (bool success, bytes memory returnData) = tokenAddress.staticcall(balanceOfData); + uint256 totalBalance = success && returnData.length == 32 ? returnData.readUint256(0) : 0; + + // Scale total balance down by corresponding value in assetData + uint256 scaledBalance = totalBalance / tokenValues[i]; + if (scaledBalance < balance || balance == 0) { + balance = scaledBalance; + } + } + } else if (assetProxyId == MULTI_ASSET_PROXY_ID) { + // Get array of values and array of assetDatas + (, uint256[] memory assetAmounts, bytes[] memory nestedAssetData) = decodeMultiAssetData(assetData); + + uint256 length = nestedAssetData.length; + for (uint256 i = 0; i != length; i++) { + // Query balance of individual assetData + uint256 totalBalance = getBalance(ownerAddress, nestedAssetData[i]); + + // Scale total balance down by corresponding value in assetData + uint256 scaledBalance = totalBalance / assetAmounts[i]; + if (scaledBalance < balance || balance == 0) { + balance = scaledBalance; + } + } + } + + // Balance will be 0 if assetProxyId is unknown + return balance; + } + + /// @dev Calls getBalance() for each element of assetData. + /// @param ownerAddress Owner of the assets specified by assetData. + /// @param assetData Array of asset details, each encoded per the AssetProxy contract specification. + /// @return Array of asset balances from getBalance(), with each element + /// corresponding to the same-indexed element in the assetData input. + function getBatchBalances(address ownerAddress, bytes[] memory assetData) + public + view + returns (uint256[] memory balances) + { + uint256 length = assetData.length; + balances = new uint256[](length); + for (uint256 i = 0; i != length; i++) { + balances[i] = getBalance(ownerAddress, assetData[i]); + } + return balances; + } + + /// @dev Returns the number of asset(s) (described by assetData) that + /// the corresponding AssetProxy contract is authorized to spend. When the asset data contains + /// multiple assets (eg for Multi-Asset), the return value indicates + /// how many complete "baskets" of those assets may be spent by all of the corresponding + /// AssetProxy contracts. + /// @param ownerAddress Owner of the assets specified by assetData. + /// @param assetData Details of asset, encoded per the AssetProxy contract specification. + /// @return Number of assets (or asset baskets) that the corresponding AssetProxy is authorized to spend. + function getAssetProxyAllowance(address ownerAddress, bytes memory assetData) + public + view + returns (uint256 allowance) + { + // Get id of AssetProxy contract + bytes4 assetProxyId = assetData.readBytes4(0); + + if (assetProxyId == MULTI_ASSET_PROXY_ID) { + // Get array of values and array of assetDatas + (, uint256[] memory amounts, bytes[] memory nestedAssetData) = decodeMultiAssetData(assetData); + + uint256 length = nestedAssetData.length; + for (uint256 i = 0; i != length; i++) { + // Query allowance of individual assetData + uint256 totalAllowance = getAssetProxyAllowance(ownerAddress, nestedAssetData[i]); + + // Scale total allowance down by corresponding value in assetData + uint256 scaledAllowance = totalAllowance / amounts[i]; + if (scaledAllowance < allowance || allowance == 0) { + allowance = scaledAllowance; + } + } + return allowance; + } + + if (assetProxyId == ERC20_PROXY_ID) { + // Get ERC20 token address + address tokenAddress = assetData.readAddress(16); + + // Encode data for `allowance(ownerAddress, _ERC20_PROXY_ADDRESS)` + bytes memory allowanceData = abi.encodeWithSelector( + _ERC20_ALLOWANCE_SELECTOR, + ownerAddress, + _ERC20_PROXY_ADDRESS + ); + + // Query allowance + (bool success, bytes memory returnData) = tokenAddress.staticcall(allowanceData); + allowance = success && returnData.length == 32 ? returnData.readUint256(0) : 0; + } else if (assetProxyId == ERC721_PROXY_ID) { + // Get ERC721 token address and id + (, address tokenAddress, uint256 tokenId) = decodeERC721AssetData(assetData); + + // Encode data for `isApprovedForAll(ownerAddress, _ERC721_PROXY_ADDRESS)` + bytes memory isApprovedForAllData = abi.encodeWithSelector( + _ERC721_IS_APPROVED_FOR_ALL_SELECTOR, + ownerAddress, + _ERC721_PROXY_ADDRESS + ); + + (bool success, bytes memory returnData) = tokenAddress.staticcall(isApprovedForAllData); + + // If not approved for all, call `getApproved(tokenId)` + if (!success || returnData.length != 32 || returnData.readUint256(0) != 1) { + // Encode data for `getApproved(tokenId)` + bytes memory getApprovedData = abi.encodeWithSelector(_ERC721_GET_APPROVED_SELECTOR, tokenId); + (success, returnData) = tokenAddress.staticcall(getApprovedData); + + // Allowance is 1 if successful and the approved address is the ERC721Proxy + allowance = success && returnData.length == 32 && returnData.readAddress(12) == _ERC721_PROXY_ADDRESS ? 1 : 0; + } else { + // Allowance is 2^256 - 1 if `isApprovedForAll` returned true + allowance = _MAX_UINT256; + } + } else if (assetProxyId == ERC1155_PROXY_ID) { + // Get ERC1155 token address + (, address tokenAddress, , , ) = decodeERC1155AssetData(assetData); + + // Encode data for `isApprovedForAll(ownerAddress, _ERC1155_PROXY_ADDRESS)` + bytes memory isApprovedForAllData = abi.encodeWithSelector( + _ERC1155_IS_APPROVED_FOR_ALL_SELECTOR, + ownerAddress, + _ERC1155_PROXY_ADDRESS + ); + + // Query allowance + (bool success, bytes memory returnData) = tokenAddress.staticcall(isApprovedForAllData); + allowance = success && returnData.length == 32 && returnData.readUint256(0) == 1 ? _MAX_UINT256 : 0; + } + + // Allowance will be 0 if the assetProxyId is unknown + return allowance; + } + + /// @dev Calls getAssetProxyAllowance() for each element of assetData. + /// @param ownerAddress Owner of the assets specified by assetData. + /// @param assetData Array of asset details, each encoded per the AssetProxy contract specification. + /// @return An array of asset allowances from getAllowance(), with each + /// element corresponding to the same-indexed element in the assetData input. + function getBatchAssetProxyAllowances(address ownerAddress, bytes[] memory assetData) + public + view + returns (uint256[] memory allowances) + { + uint256 length = assetData.length; + allowances = new uint256[](length); + for (uint256 i = 0; i != length; i++) { + allowances[i] = getAssetProxyAllowance(ownerAddress, assetData[i]); + } + return allowances; + } + + /// @dev Calls getBalance() and getAllowance() for assetData. + /// @param ownerAddress Owner of the assets specified by assetData. + /// @param assetData Details of asset, encoded per the AssetProxy contract specification. + /// @return Number of assets (or asset baskets) held by owner, and number + /// of assets (or asset baskets) that the corresponding AssetProxy is authorized to spend. + function getBalanceAndAssetProxyAllowance(address ownerAddress, bytes memory assetData) + public + view + returns (uint256 balance, uint256 allowance) + { + balance = getBalance(ownerAddress, assetData); + allowance = getAssetProxyAllowance(ownerAddress, assetData); + return (balance, allowance); + } + + /// @dev Calls getBatchBalances() and getBatchAllowances() for each element of assetData. + /// @param ownerAddress Owner of the assets specified by assetData. + /// @param assetData Array of asset details, each encoded per the AssetProxy contract specification. + /// @return An array of asset balances from getBalance(), and an array of + /// asset allowances from getAllowance(), with each element + /// corresponding to the same-indexed element in the assetData input. + function getBatchBalancesAndAssetProxyAllowances(address ownerAddress, bytes[] memory assetData) + public + view + returns (uint256[] memory balances, uint256[] memory allowances) + { + balances = getBatchBalances(ownerAddress, assetData); + allowances = getBatchAssetProxyAllowances(ownerAddress, assetData); + return (balances, allowances); + } + + /// @dev Encode ERC-20 asset data into the format described in the AssetProxy contract specification. + /// @param tokenAddress The address of the ERC-20 contract hosting the asset to be traded. + /// @return AssetProxy-compliant data describing the asset. + function encodeERC20AssetData(address tokenAddress) + public + pure + returns (bytes memory assetData) + { + assetData = abi.encodeWithSelector(ERC20_PROXY_ID, tokenAddress); + return assetData; + } + + /// @dev Decode ERC-20 asset data from the format described in the AssetProxy contract specification. + /// @param assetData AssetProxy-compliant asset data describing an ERC-20 asset. + /// @return The ERC-20 AssetProxy identifier, and the address of the ERC-20 + /// contract hosting this asset. + function decodeERC20AssetData(bytes memory assetData) + public + pure + returns ( + bytes4 assetProxyId, + address tokenAddress + ) + { + assetProxyId = assetData.readBytes4(0); + + require( + assetProxyId == ERC20_PROXY_ID, + "WRONG_PROXY_ID" + ); + + tokenAddress = assetData.readAddress(16); + return (assetProxyId, tokenAddress); + } + + /// @dev Encode ERC-721 asset data into the format described in the AssetProxy specification. + /// @param tokenAddress The address of the ERC-721 contract hosting the asset to be traded. + /// @param tokenId The identifier of the specific asset to be traded. + /// @return AssetProxy-compliant asset data describing the asset. + function encodeERC721AssetData(address tokenAddress, uint256 tokenId) + public + pure + returns (bytes memory assetData) + { + assetData = abi.encodeWithSelector( + ERC721_PROXY_ID, + tokenAddress, + tokenId + ); + return assetData; + } + + /// @dev Decode ERC-721 asset data from the format described in the AssetProxy contract specification. + /// @param assetData AssetProxy-compliant asset data describing an ERC-721 asset. + /// @return The ERC-721 AssetProxy identifier, the address of the ERC-721 + /// contract hosting this asset, and the identifier of the specific + /// asset to be traded. + function decodeERC721AssetData(bytes memory assetData) + public + pure + returns ( + bytes4 assetProxyId, + address tokenAddress, + uint256 tokenId + ) + { + assetProxyId = assetData.readBytes4(0); + + require( + assetProxyId == ERC721_PROXY_ID, + "WRONG_PROXY_ID" + ); + + tokenAddress = assetData.readAddress(16); + tokenId = assetData.readUint256(36); + return (assetProxyId, tokenAddress, tokenId); + } + + /// @dev Encode ERC-1155 asset data into the format described in the AssetProxy contract specification. + /// @param tokenAddress The address of the ERC-1155 contract hosting the asset(s) to be traded. + /// @param tokenIds The identifiers of the specific assets to be traded. + /// @param tokenValues The amounts of each asset to be traded. + /// @param callbackData Data to be passed to receiving contracts when a transfer is performed. + /// @return AssetProxy-compliant asset data describing the set of assets. + function encodeERC1155AssetData( + address tokenAddress, + uint256[] memory tokenIds, + uint256[] memory tokenValues, + bytes memory callbackData + ) + public + pure + returns (bytes memory assetData) + { + assetData = abi.encodeWithSelector( + ERC1155_PROXY_ID, + tokenAddress, + tokenIds, + tokenValues, + callbackData + ); + return assetData; + } + + /// @dev Decode ERC-1155 asset data from the format described in the AssetProxy contract specification. + /// @param assetData AssetProxy-compliant asset data describing an ERC-1155 set of assets. + /// @return The ERC-1155 AssetProxy identifier, the address of the ERC-1155 + /// contract hosting the assets, an array of the identifiers of the + /// assets to be traded, an array of asset amounts to be traded, and + /// callback data. Each element of the arrays corresponds to the + /// same-indexed element of the other array. Return values specified as + /// `memory` are returned as pointers to locations within the memory of + /// the input parameter `assetData`. + function decodeERC1155AssetData(bytes memory assetData) + public + pure + returns ( + bytes4 assetProxyId, + address tokenAddress, + uint256[] memory tokenIds, + uint256[] memory tokenValues, + bytes memory callbackData + ) + { + assetProxyId = assetData.readBytes4(0); + + require( + assetProxyId == ERC1155_PROXY_ID, + "WRONG_PROXY_ID" + ); + + assembly { + // Skip selector and length to get to the first parameter: + assetData := add(assetData, 36) + // Read the value of the first parameter: + tokenAddress := mload(assetData) + // Point to the next parameter's data: + tokenIds := add(assetData, mload(add(assetData, 32))) + // Point to the next parameter's data: + tokenValues := add(assetData, mload(add(assetData, 64))) + // Point to the next parameter's data: + callbackData := add(assetData, mload(add(assetData, 96))) + } + + return ( + assetProxyId, + tokenAddress, + tokenIds, + tokenValues, + callbackData + ); + } + + /// @dev Encode data for multiple assets, per the AssetProxy contract specification. + /// @param amounts The amounts of each asset to be traded. + /// @param nestedAssetData AssetProxy-compliant data describing each asset to be traded. + /// @return AssetProxy-compliant data describing the set of assets. + function encodeMultiAssetData(uint256[] memory amounts, bytes[] memory nestedAssetData) + public + pure + returns (bytes memory assetData) + { + assetData = abi.encodeWithSelector( + MULTI_ASSET_PROXY_ID, + amounts, + nestedAssetData + ); + return assetData; + } + + /// @dev Decode multi-asset data from the format described in the AssetProxy contract specification. + /// @param assetData AssetProxy-compliant data describing a multi-asset basket. + /// @return The Multi-Asset AssetProxy identifier, an array of the amounts + /// of the assets to be traded, and an array of the + /// AssetProxy-compliant data describing each asset to be traded. Each + /// element of the arrays corresponds to the same-indexed element of the other array. + function decodeMultiAssetData(bytes memory assetData) + public + pure + returns ( + bytes4 assetProxyId, + uint256[] memory amounts, + bytes[] memory nestedAssetData + ) + { + assetProxyId = assetData.readBytes4(0); + + require( + assetProxyId == MULTI_ASSET_PROXY_ID, + "WRONG_PROXY_ID" + ); + + // solhint-disable indent + (amounts, nestedAssetData) = abi.decode( + assetData.slice(4, assetData.length), + (uint256[], bytes[]) + ); + // solhint-enable indent + } + + /// @dev Calls `asset.ownerOf(tokenId)`, but returns a null owner instead of reverting on an unowned asset. + /// @param tokenAddress Address of ERC721 asset. + /// @param tokenId The identifier for the specific NFT. + /// @return Owner of tokenId or null address if unowned. + function getERC721TokenOwner(address tokenAddress, uint256 tokenId) + public + view + returns (address ownerAddress) + { + bytes memory ownerOfCalldata = abi.encodeWithSelector( + _ERC721_OWNER_OF_SELECTOR, + tokenId + ); + + (bool success, bytes memory returnData) = tokenAddress.staticcall(ownerOfCalldata); + + ownerAddress = (success && returnData.length == 32) ? returnData.readAddress(12) : address(0); + return ownerAddress; + } +} diff --git a/contracts/exchange-libs/contracts/src/LibTransactionDecoder.sol b/contracts/dev-utils/contracts/src/LibTransactionDecoder.sol similarity index 100% rename from contracts/exchange-libs/contracts/src/LibTransactionDecoder.sol rename to contracts/dev-utils/contracts/src/LibTransactionDecoder.sol diff --git a/contracts/dev-utils/contracts/src/OrderValidationUtils.sol b/contracts/dev-utils/contracts/src/OrderValidationUtils.sol new file mode 100644 index 0000000000..0b04ff5804 --- /dev/null +++ b/contracts/dev-utils/contracts/src/OrderValidationUtils.sol @@ -0,0 +1,184 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol"; +import "@0x/contracts-exchange-libs/contracts/src/LibMath.sol"; +import "@0x/contracts-utils/contracts/src/LibBytes.sol"; +import "./LibAssetData.sol"; + + +contract OrderValidationUtils is + LibAssetData, + LibMath +{ + using LibBytes for bytes; + + // solhint-disable var-name-mixedcase + bytes internal _ZRX_ASSET_DATA; + // solhint-enable var-name-mixedcase + + constructor (address _exchange, bytes memory _zrxAssetData) + public + LibAssetData(_exchange) + { + _ZRX_ASSET_DATA = _zrxAssetData; + } + + /// @dev Fetches all order-relevant information needed to validate if the supplied order is fillable. + /// @param order The order structure. + /// @param signature Signature provided by maker that proves the order's authenticity. + /// `0x01` can always be provided if the signature does not need to be validated. + /// @return The orderInfo (hash, status, and `takerAssetAmount` already filled for the given order), + /// fillableTakerAssetAmount (amount of the order's `takerAssetAmount` that is fillable given all on-chain state), + /// and isValidSignature (validity of the provided signature). + /// NOTE: If the `takerAssetData` encodes data for multiple assets, `fillableTakerAssetAmount` will represent a "scaled" + /// amount, meaning it must be multiplied by all the individual asset amounts within the `takerAssetData` to get the final + /// amount of each asset that can be filled. + function getOrderRelevantState(LibOrder.Order memory order, bytes memory signature) + public + view + returns ( + LibOrder.OrderInfo memory orderInfo, + uint256 fillableTakerAssetAmount, + bool isValidSignature + ) + { + // Get info specific to order + orderInfo = _EXCHANGE.getOrderInfo(order); + + // Validate the maker's signature + address makerAddress = order.makerAddress; + isValidSignature = _EXCHANGE.isValidSignature( + orderInfo.orderHash, + makerAddress, + signature + ); + + // Get the transferable amount of the `makerAsset` + uint256 transferableMakerAssetAmount = getTransferableAssetAmount(makerAddress, order.makerAssetData); + + // Assign to stack variables to reduce redundant mloads/sloads + uint256 takerAssetAmount = order.takerAssetAmount; + uint256 makerFee = order.makerFee; + bytes memory zrxAssetData = _ZRX_ASSET_DATA; + + // Get the amount of `takerAsset` that is transferable to maker given the transferability of `makerAsset`, `makerFeeAsset`, + // and the total amounts specified in the order + uint256 transferableTakerAssetAmount; + if (order.makerAssetData.equals(zrxAssetData)) { + // If `makerAsset` equals `makerFeeAsset`, the % that can be filled is + // transferableMakerAssetAmount / (makerAssetAmount + makerFee) + transferableTakerAssetAmount = getPartialAmountFloor( + transferableMakerAssetAmount, + safeAdd(order.makerAssetAmount, makerFee), + takerAssetAmount + ); + } else { + // Get the transferable amount of the `makerFeeAsset` + uint256 transferableMakerFeeAssetAmount = getTransferableAssetAmount(makerAddress, zrxAssetData); + + // If `makerFee` is 0, the % that can be filled is (transferableMakerAssetAmount / makerAssetAmount) + if (makerFee == 0) { + transferableTakerAssetAmount = getPartialAmountFloor( + transferableMakerAssetAmount, + order.makerAssetAmount, + takerAssetAmount + ); + + // If `makerAsset` does not equal `makerFeeAsset`, the % that can be filled is the lower of + // (transferableMakerAssetAmount / makerAssetAmount) and (transferableMakerAssetFeeAmount / makerFee) + } else { + uint256 transferableMakerToTakerAmount = getPartialAmountFloor( + transferableMakerAssetAmount, + order.makerAssetAmount, + takerAssetAmount + ); + uint256 transferableMakerFeeToTakerAmount = getPartialAmountFloor( + transferableMakerFeeAssetAmount, + makerFee, + takerAssetAmount + ); + transferableTakerAssetAmount = min256(transferableMakerToTakerAmount, transferableMakerFeeToTakerAmount); + } + } + + // `fillableTakerAssetAmount` is the lower of the order's remaining `takerAssetAmount` and the `transferableTakerAssetAmount` + fillableTakerAssetAmount = min256( + safeSub(takerAssetAmount, orderInfo.orderTakerAssetFilledAmount), + transferableTakerAssetAmount + ); + + return (orderInfo, fillableTakerAssetAmount, isValidSignature); + } + + /// @dev Fetches all order-relevant information needed to validate if the supplied orders are fillable. + /// @param orders Array of order structures. + /// @param signatures Array of signatures provided by makers that prove the authenticity of the orders. + /// `0x01` can always be provided if a signature does not need to be validated. + /// @return The ordersInfo (array of the hash, status, and `takerAssetAmount` already filled for each order), + /// fillableTakerAssetAmounts (array of amounts for each order's `takerAssetAmount` that is fillable given all on-chain state), + /// and isValidSignature (array containing the validity of each provided signature). + /// NOTE: If the `takerAssetData` encodes data for multiple assets, each element of `fillableTakerAssetAmounts` + /// will represent a "scaled" amount, meaning it must be multiplied by all the individual asset amounts within + /// the `takerAssetData` to get the final amount of each asset that can be filled. + function getOrderRelevantStates(LibOrder.Order[] memory orders, bytes[] memory signatures) + public + view + returns ( + LibOrder.OrderInfo[] memory ordersInfo, + uint256[] memory fillableTakerAssetAmounts, + bool[] memory isValidSignature + ) + { + uint256 length = orders.length; + ordersInfo = new LibOrder.OrderInfo[](length); + fillableTakerAssetAmounts = new uint256[](length); + isValidSignature = new bool[](length); + + for (uint256 i = 0; i != length; i++) { + (ordersInfo[i], fillableTakerAssetAmounts[i], isValidSignature[i]) = getOrderRelevantState( + orders[i], + signatures[i] + ); + } + + return (ordersInfo, fillableTakerAssetAmounts, isValidSignature); + } + + /// @dev Gets the amount of an asset transferable by the owner. + /// @param ownerAddress Address of the owner of the asset. + /// @param assetData Description of tokens, per the AssetProxy contract specification. + /// @return The amount of the asset tranferable by the owner. + /// NOTE: If the `assetData` encodes data for multiple assets, the `transferableAssetAmount` + /// will represent the amount of times the entire `assetData` can be transferred. To calculate + /// the total individual transferable amounts, this scaled `transferableAmount` must be multiplied by + /// the individual asset amounts located within the `assetData`. + function getTransferableAssetAmount(address ownerAddress, bytes memory assetData) + public + view + returns (uint256 transferableAssetAmount) + { + (uint256 balance, uint256 allowance) = getBalanceAndAssetProxyAllowance(ownerAddress, assetData); + transferableAssetAmount = min256(balance, allowance); + return transferableAssetAmount; + } +} diff --git a/contracts/dev-utils/package.json b/contracts/dev-utils/package.json new file mode 100644 index 0000000000..10958e8a06 --- /dev/null +++ b/contracts/dev-utils/package.json @@ -0,0 +1,90 @@ +{ + "name": "@0x/contracts-dev-utils", + "version": "0.0.1", + "engines": { + "node": ">=6.12" + }, + "description": "0x protocol specific utility contracts", + "main": "lib/src/index.js", + "directories": { + "test": "test" + }, + "scripts": { + "build": "yarn pre_build && tsc -b", + "build:ci": "yarn build", + "pre_build": "run-s compile generate_contract_wrappers", + "test": "yarn run_mocha", + "rebuild_and_test": "run-s build test", + "test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov", + "test:profiler": "SOLIDITY_PROFILER=true run-s build run_mocha profiler:report:html", + "test:trace": "SOLIDITY_REVERT_TRACE=true run-s build run_mocha", + "run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/**/*.js' --timeout 100000 --bail --exit", + "compile": "sol-compiler", + "watch": "sol-compiler -w", + "clean": "shx rm -rf lib generated-artifacts generated-wrappers", + "generate_contract_wrappers": "abi-gen --abis ${npm_package_config_abis} --template ../../node_modules/@0x/abi-gen-templates/contract.handlebars --partials '../../node_modules/@0x/abi-gen-templates/partials/**/*.handlebars' --output generated-wrappers --backend ethers", + "lint": "tslint --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts", + "fix": "tslint --fix --format stylish --project . --exclude ./generated-wrappers/**/* --exclude ./generated-artifacts/**/* --exclude **/lib/**/* && yarn lint-contracts", + "coverage:report:text": "istanbul report text", + "coverage:report:html": "istanbul report html && open coverage/index.html", + "profiler:report:html": "istanbul report html && open coverage/index.html", + "coverage:report:lcov": "istanbul report lcov", + "test:circleci": "yarn test", + "contracts:gen": "contracts-gen", + "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" + }, + "config": { + "abis": "./generated-artifacts/@(DevUtils|LibAssetData|LibTransactionDecoder).json", + "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." + }, + "repository": { + "type": "git", + "url": "https://github.com/0xProject/0x-monorepo.git" + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/0xProject/0x-monorepo/issues" + }, + "homepage": "https://github.com/0xProject/0x-monorepo/contracts/dev-utils/README.md", + "devDependencies": { + "@0x/abi-gen": "^2.0.10", + "@0x/contract-wrappers": "^9.1.4", + "@0x/contracts-gen": "^1.0.9", + "@0x/contracts-test-utils": "^3.1.7", + "@0x/dev-utils": "^2.2.3", + "@0x/sol-compiler": "^3.1.8", + "@0x/tslint-config": "^3.0.1", + "@types/lodash": "4.14.104", + "@types/node": "*", + "chai": "^4.0.1", + "chai-as-promised": "^7.1.0", + "chai-bignumber": "^3.0.0", + "dirty-chai": "^2.0.1", + "make-promises-safe": "^1.1.0", + "mocha": "^4.1.0", + "npm-run-all": "^4.1.2", + "shx": "^0.2.2", + "solhint": "^1.4.1", + "tslint": "5.11.0", + "typescript": "3.0.1" + }, + "dependencies": { + "@0x/base-contract": "^5.1.0", + "@0x/contracts-asset-proxy": "^2.1.5", + "@0x/contracts-erc20": "^2.2.5", + "@0x/contracts-erc721": "^2.1.6", + "@0x/contracts-erc1155": "^1.1.6", + "@0x/contracts-exchange": "^2.1.5", + "@0x/contracts-exchange-libs": "^2.1.6", + "@0x/contracts-utils": "^3.1.6", + "@0x/order-utils": "^8.1.1", + "@0x/types": "^2.2.2", + "@0x/typescript-typings": "^4.2.2", + "@0x/utils": "^4.3.3", + "@0x/web3-wrapper": "^6.0.6", + "ethereum-types": "^2.1.2" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/contracts/dev-utils/src/artifacts.ts b/contracts/dev-utils/src/artifacts.ts new file mode 100644 index 0000000000..022784e6dc --- /dev/null +++ b/contracts/dev-utils/src/artifacts.ts @@ -0,0 +1,15 @@ +/* + * ----------------------------------------------------------------------------- + * Warning: This file is auto-generated by contracts-gen. Don't edit manually. + * ----------------------------------------------------------------------------- + */ +import { ContractArtifact } from 'ethereum-types'; + +import * as DevUtils from '../generated-artifacts/DevUtils.json'; +import * as LibAssetData from '../generated-artifacts/LibAssetData.json'; +import * as LibTransactionDecoder from '../generated-artifacts/LibTransactionDecoder.json'; +export const artifacts = { + DevUtils: DevUtils as ContractArtifact, + LibTransactionDecoder: LibTransactionDecoder as ContractArtifact, + LibAssetData: LibAssetData as ContractArtifact, +}; diff --git a/contracts/dev-utils/src/index.ts b/contracts/dev-utils/src/index.ts new file mode 100644 index 0000000000..d55f08ea2d --- /dev/null +++ b/contracts/dev-utils/src/index.ts @@ -0,0 +1,2 @@ +export * from './artifacts'; +export * from './wrappers'; diff --git a/contracts/dev-utils/src/wrappers.ts b/contracts/dev-utils/src/wrappers.ts new file mode 100644 index 0000000000..e07c65436d --- /dev/null +++ b/contracts/dev-utils/src/wrappers.ts @@ -0,0 +1,8 @@ +/* + * ----------------------------------------------------------------------------- + * Warning: This file is auto-generated by contracts-gen. Don't edit manually. + * ----------------------------------------------------------------------------- + */ +export * from '../generated-wrappers/dev_utils'; +export * from '../generated-wrappers/lib_asset_data'; +export * from '../generated-wrappers/lib_transaction_decoder'; diff --git a/contracts/dev-utils/test/global_hooks.ts b/contracts/dev-utils/test/global_hooks.ts new file mode 100644 index 0000000000..2ca47d433b --- /dev/null +++ b/contracts/dev-utils/test/global_hooks.ts @@ -0,0 +1,19 @@ +import { env, EnvVars } from '@0x/dev-utils'; + +import { coverage, profiler, provider } from '@0x/contracts-test-utils'; +import { providerUtils } from '@0x/utils'; + +before('start web3 provider', () => { + providerUtils.startProviderEngine(provider); +}); +after('generate coverage report', async () => { + if (env.parseBoolean(EnvVars.SolidityCoverage)) { + const coverageSubprovider = coverage.getCoverageSubproviderSingleton(); + await coverageSubprovider.writeCoverageAsync(); + } + if (env.parseBoolean(EnvVars.SolidityProfiler)) { + const profilerSubprovider = profiler.getProfilerSubproviderSingleton(); + await profilerSubprovider.writeProfilerOutputAsync(); + } + provider.stop(); +}); diff --git a/contracts/dev-utils/test/lib_asset_data.ts b/contracts/dev-utils/test/lib_asset_data.ts new file mode 100644 index 0000000000..d9708f3f4f --- /dev/null +++ b/contracts/dev-utils/test/lib_asset_data.ts @@ -0,0 +1,472 @@ +import * as chai from 'chai'; +import { LogWithDecodedArgs } from 'ethereum-types'; + +import { + artifacts as proxyArtifacts, + ERC1155ProxyContract, + ERC20ProxyContract, + ERC721ProxyContract, + MultiAssetProxyContract, +} from '@0x/contracts-asset-proxy'; +import { + artifacts as erc1155Artifacts, + ERC1155MintableContract, + ERC1155TransferSingleEventArgs, +} from '@0x/contracts-erc1155'; +import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20'; +import { artifacts as erc721Artifacts, DummyERC721TokenContract } from '@0x/contracts-erc721'; +import { artifacts as exchangeArtifacts, ExchangeContract } from '@0x/contracts-exchange'; +import { chaiSetup, constants, LogDecoder, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils } from '@0x/order-utils'; +import { AssetProxyId } from '@0x/types'; +import { BigNumber } from '@0x/utils'; + +import { artifacts, LibAssetDataContract } from '../src'; + +chaiSetup.configure(); +const expect = chai.expect; + +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +const KNOWN_ERC20_ENCODING = { + address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48', + assetData: '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48', +}; +const KNOWN_ERC721_ENCODING = { + address: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48', + tokenId: new BigNumber(1), + assetData: + '0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001', +}; +const KNOWN_ERC1155_ENCODING = { + tokenAddress: '0x1dc4c1cefef38a777b15aa20260a54e584b16c48', + tokenIds: [new BigNumber(100), new BigNumber(1001), new BigNumber(10001)], + tokenValues: [new BigNumber(200), new BigNumber(2001), new BigNumber(20001)], + callbackData: + '0x025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001', + assetData: + '0xa7cb5fb70000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000002711000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000007d10000000000000000000000000000000000000000000000000000000000004e210000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000', +}; +const KNOWN_MULTI_ASSET_ENCODING = { + amounts: [new BigNumber(70), new BigNumber(1), new BigNumber(18)], + nestedAssetData: [ + KNOWN_ERC20_ENCODING.assetData, + KNOWN_ERC721_ENCODING.assetData, + KNOWN_ERC1155_ENCODING.assetData, + ], + assetData: + '0x94cfcdd7000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000024f47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000204a7cb5fb70000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c480000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000003e90000000000000000000000000000000000000000000000000000000000002711000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000007d10000000000000000000000000000000000000000000000000000000000004e210000000000000000000000000000000000000000000000000000000000000044025717920000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c4800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', +}; + +describe('LibAssetData', () => { + let exchange: ExchangeContract; + let erc20Proxy: ERC20ProxyContract; + let erc721Proxy: ERC721ProxyContract; + let erc1155Proxy: ERC1155ProxyContract; + let multiAssetProxy: MultiAssetProxyContract; + let libAssetData: LibAssetDataContract; + + let tokenOwnerAddress: string; + + let erc20Token: DummyERC20TokenContract; + let erc721Token: DummyERC721TokenContract; + let erc1155Token: ERC1155MintableContract; + + const erc20TokenTotalSupply = new BigNumber(1); + + const firstERC721TokenId = new BigNumber(1); + const numberOfERC721Tokens = 10; + + let erc1155TokenId: BigNumber; + + before(async () => { + await blockchainLifecycle.startAsync(); + + exchange = await ExchangeContract.deployFrom0xArtifactAsync( + exchangeArtifacts.Exchange, + provider, + txDefaults, + constants.NULL_BYTES, + ); + + erc20Proxy = await ERC20ProxyContract.deployFrom0xArtifactAsync( + proxyArtifacts.ERC20Proxy, + provider, + txDefaults, + ); + erc721Proxy = await ERC721ProxyContract.deployFrom0xArtifactAsync( + proxyArtifacts.ERC721Proxy, + provider, + txDefaults, + ); + erc1155Proxy = await ERC1155ProxyContract.deployFrom0xArtifactAsync( + proxyArtifacts.ERC1155Proxy, + provider, + txDefaults, + ); + multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync( + proxyArtifacts.MultiAssetProxy, + provider, + txDefaults, + ); + + await exchange.registerAssetProxy.awaitTransactionSuccessAsync(erc20Proxy.address); + await exchange.registerAssetProxy.awaitTransactionSuccessAsync(erc721Proxy.address); + await exchange.registerAssetProxy.awaitTransactionSuccessAsync(erc1155Proxy.address); + await exchange.registerAssetProxy.awaitTransactionSuccessAsync(multiAssetProxy.address); + + libAssetData = await LibAssetDataContract.deployFrom0xArtifactAsync( + artifacts.LibAssetData, + provider, + txDefaults, + exchange.address, + ); + + [tokenOwnerAddress] = await web3Wrapper.getAvailableAddressesAsync(); + + erc20Token = await DummyERC20TokenContract.deployFrom0xArtifactAsync( + erc20Artifacts.DummyERC20Token, + provider, + txDefaults, + 'Dummy', + 'DUM', + new BigNumber(1), + erc20TokenTotalSupply, + ); + + erc721Token = await DummyERC721TokenContract.deployFrom0xArtifactAsync( + erc721Artifacts.DummyERC721Token, + provider, + txDefaults, + 'Dummy', + 'DUM', + ); + // mint `numberOfERC721Tokens` tokens + const transactionMinedPromises = []; + for (let i = 0; i < numberOfERC721Tokens; i++) { + transactionMinedPromises.push( + erc721Token.mint.awaitTransactionSuccessAsync(tokenOwnerAddress, firstERC721TokenId.plus(i - 1)), + ); + } + await Promise.all(transactionMinedPromises); + + erc1155Token = await ERC1155MintableContract.deployFrom0xArtifactAsync( + erc1155Artifacts.ERC1155Mintable, + provider, + txDefaults, + ); + + const logDecoder = new LogDecoder(web3Wrapper, erc1155Artifacts); + const transactionReceipt = await logDecoder.getTxWithDecodedLogsAsync( + await erc1155Token.create.sendTransactionAsync('uri:Dummy', /*isNonFungible:*/ false), + ); + + // tslint:disable-next-line no-unnecessary-type-assertion + erc1155TokenId = (transactionReceipt.logs[0] as LogWithDecodedArgs).args.id; + await erc1155Token.mintFungible.awaitTransactionSuccessAsync( + erc1155TokenId, + [tokenOwnerAddress], + [new BigNumber(1)], + ); + }); + + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + + it('should have a deployed-to address', () => { + expect(libAssetData.address.slice(0, 2)).to.equal('0x'); + }); + + describe('encoding and decoding', () => { + it('should encode ERC20 asset data', async () => { + expect(await libAssetData.encodeERC20AssetData.callAsync(KNOWN_ERC20_ENCODING.address)).to.equal( + KNOWN_ERC20_ENCODING.assetData, + ); + }); + + it('should decode ERC20 asset data', async () => { + expect(await libAssetData.decodeERC20AssetData.callAsync(KNOWN_ERC20_ENCODING.assetData)).to.deep.equal([ + AssetProxyId.ERC20, + KNOWN_ERC20_ENCODING.address, + ]); + }); + + it('should encode ERC721 asset data', async () => { + expect( + await libAssetData.encodeERC721AssetData.callAsync( + KNOWN_ERC721_ENCODING.address, + KNOWN_ERC721_ENCODING.tokenId, + ), + ).to.equal(KNOWN_ERC721_ENCODING.assetData); + }); + + it('should decode ERC721 asset data', async () => { + expect(await libAssetData.decodeERC721AssetData.callAsync(KNOWN_ERC721_ENCODING.assetData)).to.deep.equal([ + AssetProxyId.ERC721, + KNOWN_ERC721_ENCODING.address, + KNOWN_ERC721_ENCODING.tokenId, + ]); + }); + + it('should encode ERC1155 asset data', async () => { + expect( + await libAssetData.encodeERC1155AssetData.callAsync( + KNOWN_ERC1155_ENCODING.tokenAddress, + KNOWN_ERC1155_ENCODING.tokenIds, + KNOWN_ERC1155_ENCODING.tokenValues, + KNOWN_ERC1155_ENCODING.callbackData, + ), + ).to.equal(KNOWN_ERC1155_ENCODING.assetData); + }); + + it('should decode ERC1155 asset data', async () => { + expect(await libAssetData.decodeERC1155AssetData.callAsync(KNOWN_ERC1155_ENCODING.assetData)).to.deep.equal( + [ + AssetProxyId.ERC1155, + KNOWN_ERC1155_ENCODING.tokenAddress, + KNOWN_ERC1155_ENCODING.tokenIds, + KNOWN_ERC1155_ENCODING.tokenValues, + KNOWN_ERC1155_ENCODING.callbackData, + ], + ); + }); + + it('should encode multiasset data', async () => { + expect( + await libAssetData.encodeMultiAssetData.callAsync( + KNOWN_MULTI_ASSET_ENCODING.amounts, + KNOWN_MULTI_ASSET_ENCODING.nestedAssetData, + ), + ).to.equal(KNOWN_MULTI_ASSET_ENCODING.assetData); + }); + + it('should decode multiasset data', async () => { + expect( + await libAssetData.decodeMultiAssetData.callAsync(KNOWN_MULTI_ASSET_ENCODING.assetData), + ).to.deep.equal([ + AssetProxyId.MultiAsset, + KNOWN_MULTI_ASSET_ENCODING.amounts, + KNOWN_MULTI_ASSET_ENCODING.nestedAssetData, + ]); + }); + }); + + describe('getBalance', () => { + it('should query ERC20 balance by asset data', async () => { + const assetData = assetDataUtils.encodeERC20AssetData(erc20Token.address); + expect(await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData)).to.bignumber.equal( + erc20TokenTotalSupply, + ); + }); + + it('should return 0 if ERC20 token does not exist', async () => { + const assetData = assetDataUtils.encodeERC20AssetData(constants.NULL_ADDRESS); + const balance = await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData); + expect(balance).to.bignumber.equal(constants.ZERO_AMOUNT); + }); + + it('should query ERC721 balance by asset data', async () => { + const assetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, firstERC721TokenId); + expect(await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData)).to.bignumber.equal(1); + }); + + it('should return 0 if ERC721 token does not exist', async () => { + const assetData = assetDataUtils.encodeERC721AssetData(constants.NULL_ADDRESS, firstERC721TokenId); + const balance = await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData); + expect(balance).to.bignumber.equal(constants.ZERO_AMOUNT); + }); + + it('should query ERC1155 balances by asset data', async () => { + const assetData = assetDataUtils.encodeERC1155AssetData( + erc1155Token.address, + [erc1155TokenId], + [new BigNumber(1)], + constants.NULL_BYTES, + ); + expect(await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData)).to.bignumber.equal(1); + }); + + it('should return 0 if ERC1155 token does not exist', async () => { + const assetData = assetDataUtils.encodeERC1155AssetData( + constants.NULL_ADDRESS, + [erc1155TokenId], + [new BigNumber(1)], + constants.NULL_BYTES, + ); + const balance = await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData); + expect(balance).to.bignumber.equal(constants.ZERO_AMOUNT); + }); + + it('should query multi-asset batch balance by asset data', async () => { + const assetData = assetDataUtils.encodeMultiAssetData( + [new BigNumber(1), new BigNumber(1)], + [ + assetDataUtils.encodeERC20AssetData(erc20Token.address), + assetDataUtils.encodeERC721AssetData(erc721Token.address, firstERC721TokenId), + ], + ); + expect(await libAssetData.getBalance.callAsync(tokenOwnerAddress, assetData)).to.bignumber.equal( + Math.min(erc20TokenTotalSupply.toNumber(), numberOfERC721Tokens), + ); + }); + + it('should return a balance of 0 if the assetData does not correspond to an AssetProxy contract', async () => { + const fakeAssetData = '0x01020304'; + const balance = await libAssetData.getBalance.callAsync(tokenOwnerAddress, fakeAssetData); + expect(balance).to.bignumber.equal(constants.ZERO_AMOUNT); + }); + }); + + describe('getAssetProxyAllowance', () => { + it('should query ERC20 allowances by asset data', async () => { + const allowance = new BigNumber(1); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, allowance, { + from: tokenOwnerAddress, + }); + const assetData = assetDataUtils.encodeERC20AssetData(erc20Token.address); + expect( + await libAssetData.getAssetProxyAllowance.callAsync(tokenOwnerAddress, assetData), + ).to.bignumber.equal(allowance); + }); + + it('should query ERC721 approval by asset data', async () => { + await erc721Token.approve.awaitTransactionSuccessAsync(erc721Proxy.address, firstERC721TokenId, { + from: tokenOwnerAddress, + }); + const assetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, firstERC721TokenId); + expect( + await libAssetData.getAssetProxyAllowance.callAsync(tokenOwnerAddress, assetData), + ).to.bignumber.equal(1); + }); + + it('should query ERC721 approvalForAll by assetData', async () => { + await erc721Token.setApprovalForAll.awaitTransactionSuccessAsync(erc721Proxy.address, true, { + from: tokenOwnerAddress, + }); + const assetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, firstERC721TokenId); + expect( + await libAssetData.getAssetProxyAllowance.callAsync(tokenOwnerAddress, assetData), + ).to.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + }); + + it('should query ERC1155 allowances by asset data', async () => { + await erc1155Token.setApprovalForAll.awaitTransactionSuccessAsync(erc1155Proxy.address, true, { + from: tokenOwnerAddress, + }); + const assetData = assetDataUtils.encodeERC1155AssetData( + erc1155Token.address, + [erc1155TokenId], + [new BigNumber(1)], + constants.NULL_BYTES, + ); + expect( + await libAssetData.getAssetProxyAllowance.callAsync(tokenOwnerAddress, assetData), + ).to.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); + }); + + it('should query multi-asset allowances by asset data', async () => { + const allowance = new BigNumber(1); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, allowance, { + from: tokenOwnerAddress, + }); + await erc721Token.approve.awaitTransactionSuccessAsync(erc721Proxy.address, firstERC721TokenId, { + from: tokenOwnerAddress, + }); + const assetData = assetDataUtils.encodeMultiAssetData( + [new BigNumber(1), new BigNumber(1)], + [ + assetDataUtils.encodeERC20AssetData(erc20Token.address), + assetDataUtils.encodeERC721AssetData(erc721Token.address, firstERC721TokenId), + ], + ); + expect( + await libAssetData.getAssetProxyAllowance.callAsync(tokenOwnerAddress, assetData), + ).to.bignumber.equal(1); + return; + }); + + it('should return an allowance of 0 if the assetData does not correspond to an AssetProxy contract', async () => { + const fakeAssetData = '0x01020304'; + const allowance = await libAssetData.getAssetProxyAllowance.callAsync(tokenOwnerAddress, fakeAssetData); + expect(allowance).to.bignumber.equal(constants.ZERO_AMOUNT); + }); + }); + + describe('getBatchBalances', () => { + it('should query balances for a batch of asset data strings', async () => { + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20Token.address); + const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, firstERC721TokenId); + expect( + await libAssetData.getBatchBalances.callAsync(tokenOwnerAddress, [erc20AssetData, erc721AssetData]), + ).to.deep.equal([new BigNumber(erc20TokenTotalSupply), new BigNumber(1)]); + }); + }); + + describe('getERC721TokenOwner', async () => { + it('should return the null address when tokenId is not owned', async () => { + const nonexistentTokenId = new BigNumber(1234567890); + expect( + await libAssetData.getERC721TokenOwner.callAsync(erc721Token.address, nonexistentTokenId), + ).to.be.equal(constants.NULL_ADDRESS); + }); + it('should return the owner address when tokenId is owned', async () => { + expect( + await libAssetData.getERC721TokenOwner.callAsync(erc721Token.address, firstERC721TokenId), + ).to.be.equal(tokenOwnerAddress); + }); + }); + + describe('getBalanceAndAllowance', () => { + it('should query balance and allowance together, from asset data', async () => { + const allowance = new BigNumber(1); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, allowance, { + from: tokenOwnerAddress, + }); + const assetData = assetDataUtils.encodeERC20AssetData(erc20Token.address); + expect( + await libAssetData.getBalanceAndAssetProxyAllowance.callAsync(tokenOwnerAddress, assetData), + ).to.deep.equal([new BigNumber(erc20TokenTotalSupply), allowance]); + }); + }); + describe('getBatchBalancesAndAllowances', () => { + it('should query balances and allowances together, from an asset data array', async () => { + const allowance = new BigNumber(1); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, allowance, { + from: tokenOwnerAddress, + }); + const assetData = assetDataUtils.encodeERC20AssetData(erc20Token.address); + expect( + await libAssetData.getBatchBalancesAndAssetProxyAllowances.callAsync(tokenOwnerAddress, [assetData]), + ).to.deep.equal([[new BigNumber(erc20TokenTotalSupply)], [allowance]]); + }); + }); + + describe('getBatchAssetProxyAllowances', () => { + it('should query allowances for a batch of asset data strings', async () => { + const allowance = new BigNumber(1); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, allowance, { + from: tokenOwnerAddress, + }); + await erc721Token.approve.awaitTransactionSuccessAsync(erc721Proxy.address, firstERC721TokenId, { + from: tokenOwnerAddress, + }); + const erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20Token.address); + const erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, firstERC721TokenId); + expect( + await libAssetData.getBatchAssetProxyAllowances.callAsync(tokenOwnerAddress, [ + erc20AssetData, + erc721AssetData, + ]), + ).to.deep.equal([new BigNumber(1), new BigNumber(1)]); + }); + }); +}); diff --git a/contracts/dev-utils/test/lib_transaction_decoder.ts b/contracts/dev-utils/test/lib_transaction_decoder.ts new file mode 100644 index 0000000000..26f5cf42b5 --- /dev/null +++ b/contracts/dev-utils/test/lib_transaction_decoder.ts @@ -0,0 +1,144 @@ +import { artifacts as exchangeArtifacts, IExchangeContract } from '@0x/contracts-exchange'; +import { chaiSetup, constants, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; + +import { artifacts, LibTransactionDecoderContract } from '../src'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +const order = { + makerAddress: '0xe36ea790bc9d7ab70c55260c66d52b1eca985f84', + takerAddress: '0x0000000000000000000000000000000000000000', + feeRecipientAddress: '0x78dc5d2d739606d31509c31d654056a45185ecb6', + senderAddress: '0x6ecbe1db9ef729cbe972c83fb886247691fb6beb', + makerAssetAmount: new BigNumber('100000000000000000000'), + takerAssetAmount: new BigNumber('200000000000000000000'), + makerFee: new BigNumber('1000000000000000000'), + takerFee: new BigNumber('1000000000000000000'), + expirationTimeSeconds: new BigNumber('1552396423'), + salt: new BigNumber('66097384406870180066678463045003379626790660770396923976862707230261946348951'), + makerAssetData: '0xf47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064', + takerAssetData: '0xf47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e3', +}; +const takerAssetFillAmount = new BigNumber('100000000000000000000'); +const signature = + '0x1ce8e3c600d933423172b5021158a6be2e818613ff8e762d70ef490c752fd98a626a215f09f169668990414de75a53da221c294a3002f796d004827258b641876e03'; + +describe('LibTransactionDecoder', () => { + let libTxDecoder: LibTransactionDecoderContract; + const exchangeInterface = new IExchangeContract( + exchangeArtifacts.Exchange.compilerOutput.abi, + constants.NULL_ADDRESS, + provider, + txDefaults, + ); + before(async () => { + await blockchainLifecycle.startAsync(); + libTxDecoder = await LibTransactionDecoderContract.deployFrom0xArtifactAsync( + artifacts.LibTransactionDecoder, + provider, + txDefaults, + ); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + + it('should decode an Exchange.batchCancelOrders() transaction', async () => { + const input = exchangeInterface.batchCancelOrders.getABIEncodedTransactionData([order, order]); + expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([ + 'batchCancelOrders', + [order, order], + [], + [], + ]); + }); + + for (const func of ['batchFillOrders', 'batchFillOrdersNoThrow', 'batchFillOrKillOrders']) { + const input = (exchangeInterface as any)[func].getABIEncodedTransactionData( + [order, order], + [takerAssetFillAmount, takerAssetFillAmount], + [signature, signature], + ); + it(`should decode an Exchange.${func}() transaction`, async () => { + expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([ + func, + [order, order], + [takerAssetFillAmount, takerAssetFillAmount], + [signature, signature], + ]); + }); + } + + it('should decode an Exchange.cancelOrder() transaction', async () => { + const input = exchangeInterface.cancelOrder.getABIEncodedTransactionData(order); + expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([ + 'cancelOrder', + [order], + [], + [], + ]); + }); + + for (const func of ['fillOrder', 'fillOrderNoThrow', 'fillOrKillOrder']) { + const input = (exchangeInterface as any)[func].getABIEncodedTransactionData( + order, + takerAssetFillAmount, + signature, + ); + it(`should decode an Exchange.${func}() transaction`, async () => { + expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([ + func, + [order], + [takerAssetFillAmount], + [signature], + ]); + }); + } + + for (const func of ['marketBuyOrders', 'marketBuyOrdersNoThrow', 'marketSellOrders', 'marketSellOrdersNoThrow']) { + const input = (exchangeInterface as any)[func].getABIEncodedTransactionData( + [order, order], + takerAssetFillAmount, + [signature, signature], + ); + it(`should decode an Exchange.${func}() transaction`, async () => { + expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([ + func, + [order, order], + [takerAssetFillAmount], + [signature, signature], + ]); + }); + } + + it('should decode an Exchange.matchOrders() transaction', async () => { + const complementaryOrder = { + ...order, + makerAddress: order.takerAddress, + takerAddress: order.makerAddress, + makerAssetData: order.takerAssetData, + takerAssetData: order.makerAssetData, + makerAssetAmount: order.takerAssetAmount, + takerAssetAmount: order.makerAssetAmount, + makerFee: order.takerFee, + takerFee: order.makerFee, + }; + const input = exchangeInterface.matchOrders.getABIEncodedTransactionData( + order, + complementaryOrder, + signature, + signature, + ); + expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(input)).to.deep.equal([ + 'matchOrders', + [order, complementaryOrder], + [order.takerAssetAmount, complementaryOrder.takerAssetAmount], + [signature, signature], + ]); + }); +}); diff --git a/contracts/dev-utils/test/order_validation_utils.ts b/contracts/dev-utils/test/order_validation_utils.ts new file mode 100644 index 0000000000..d03cefc18e --- /dev/null +++ b/contracts/dev-utils/test/order_validation_utils.ts @@ -0,0 +1,442 @@ +import { + artifacts as proxyArtifacts, + ERC20ProxyContract, + ERC20Wrapper, + ERC721ProxyContract, + ERC721Wrapper, + MultiAssetProxyContract, +} from '@0x/contracts-asset-proxy'; +import { DummyERC20TokenContract } from '@0x/contracts-erc20'; +import { DummyERC721TokenContract } from '@0x/contracts-erc721'; +import { artifacts as exchangeArtifacts, ExchangeContract, ExchangeWrapper } from '@0x/contracts-exchange'; +import { + chaiSetup, + constants, + OrderFactory, + OrderStatus, + provider, + txDefaults, + web3Wrapper, +} from '@0x/contracts-test-utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; +import { SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import * as chai from 'chai'; + +import { artifacts, DevUtilsContract } from '../src'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('OrderValidationUtils', () => { + let makerAddress: string; + let takerAddress: string; + let owner: string; + let erc20AssetData: string; + let erc20AssetData2: string; + let erc721AssetData: string; + let zrxAssetData: string; + + let erc20Token: DummyERC20TokenContract; + let erc20Token2: DummyERC20TokenContract; + let zrxToken: DummyERC20TokenContract; + let erc721Token: DummyERC721TokenContract; + let exchange: ExchangeContract; + let devUtils: DevUtilsContract; + let erc20Proxy: ERC20ProxyContract; + let erc721Proxy: ERC721ProxyContract; + let multiAssetProxy: MultiAssetProxyContract; + + let signedOrder: SignedOrder; + let orderFactory: OrderFactory; + + const tokenId = new BigNumber(123456789); + + before(async () => { + await blockchainLifecycle.startAsync(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + + before(async () => { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const usedAddresses = ([owner, makerAddress, takerAddress] = accounts.slice(0, 3)); + + const erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); + + const numDummyErc20ToDeploy = 3; + [erc20Token, zrxToken, erc20Token2] = await erc20Wrapper.deployDummyTokensAsync( + numDummyErc20ToDeploy, + constants.DUMMY_TOKEN_DECIMALS, + ); + erc20Proxy = await erc20Wrapper.deployProxyAsync(); + + [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); + erc721Proxy = await erc721Wrapper.deployProxyAsync(); + + zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + exchange = await ExchangeContract.deployFrom0xArtifactAsync( + exchangeArtifacts.Exchange, + provider, + txDefaults, + zrxAssetData, + ); + + multiAssetProxy = await MultiAssetProxyContract.deployFrom0xArtifactAsync( + proxyArtifacts.MultiAssetProxy, + provider, + txDefaults, + ); + const exchangeWrapper = new ExchangeWrapper(exchange, provider); + await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); + await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); + await exchangeWrapper.registerAssetProxyAsync(multiAssetProxy.address, owner); + await erc20Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(exchange.address, { from: owner }); + await erc721Proxy.addAuthorizedAddress.awaitTransactionSuccessAsync(exchange.address, { from: owner }); + + devUtils = await DevUtilsContract.deployFrom0xArtifactAsync( + artifacts.DevUtils, + provider, + txDefaults, + exchange.address, + zrxAssetData, + ); + + erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20Token.address); + erc20AssetData2 = assetDataUtils.encodeERC20AssetData(erc20Token2.address); + erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId); + const defaultOrderParams = { + ...constants.STATIC_ORDER_PARAMS, + exchangeAddress: exchange.address, + makerAddress, + feeRecipientAddress: constants.NULL_ADDRESS, + makerAssetData: erc20AssetData, + takerAssetData: erc20AssetData2, + }; + const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; + orderFactory = new OrderFactory(privateKey, defaultOrderParams); + }); + + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + + describe('getTransferableAssetAmount', () => { + it('should return the balance when balance < allowance', async () => { + const balance = new BigNumber(123); + const allowance = new BigNumber(456); + await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, balance); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, allowance, { + from: makerAddress, + }); + const transferableAmount = await devUtils.getTransferableAssetAmount.callAsync( + makerAddress, + erc20AssetData, + ); + expect(transferableAmount).to.bignumber.equal(balance); + }); + it('should return the allowance when allowance < balance', async () => { + const balance = new BigNumber(456); + const allowance = new BigNumber(123); + await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, balance); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, allowance, { + from: makerAddress, + }); + const transferableAmount = await devUtils.getTransferableAssetAmount.callAsync( + makerAddress, + erc20AssetData, + ); + expect(transferableAmount).to.bignumber.equal(allowance); + }); + it('should return the correct transferable amount for multiAssetData', async () => { + const multiAssetData = assetDataUtils.encodeMultiAssetData( + [new BigNumber(1), new BigNumber(1)], + [erc20AssetData, erc20AssetData2], + ); + const transferableAmount1 = new BigNumber(10); + const transferableAmount2 = new BigNumber(5); + await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, transferableAmount1); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, transferableAmount1, { + from: makerAddress, + }); + await erc20Token2.setBalance.awaitTransactionSuccessAsync(makerAddress, transferableAmount2); + await erc20Token2.approve.awaitTransactionSuccessAsync(erc20Proxy.address, transferableAmount2, { + from: makerAddress, + }); + const transferableAmount = await devUtils.getTransferableAssetAmount.callAsync( + makerAddress, + multiAssetData, + ); + expect(transferableAmount).to.bignumber.equal(transferableAmount2); + }); + }); + describe('getOrderRelevantState', () => { + beforeEach(async () => { + signedOrder = await orderFactory.newSignedOrderAsync(); + }); + it('should return the correct orderInfo when the order is valid', async () => { + const [orderInfo] = await devUtils.getOrderRelevantState.callAsync(signedOrder, signedOrder.signature); + expect(orderInfo.orderHash).to.equal(orderHashUtils.getOrderHashHex(signedOrder)); + expect(orderInfo.orderStatus).to.equal(OrderStatus.Fillable); + expect(orderInfo.orderTakerAssetFilledAmount).to.bignumber.equal(constants.ZERO_AMOUNT); + }); + it('should return isValidSignature=true when the signature is valid', async () => { + const [, , isValidSignature] = await devUtils.getOrderRelevantState.callAsync( + signedOrder, + signedOrder.signature, + ); + expect(isValidSignature).to.equal(true); + }); + it('should return isValidSignature=false when the signature is invalid', async () => { + const invalidSignature = '0x01'; + const [, , isValidSignature] = await devUtils.getOrderRelevantState.callAsync( + signedOrder, + invalidSignature, + ); + expect(isValidSignature).to.equal(false); + }); + it('should return a fillableTakerAssetAmount of 0 when balances or allowances are insufficient', async () => { + const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync( + signedOrder, + signedOrder.signature, + ); + expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT); + }); + it('should return a fillableTakerAssetAmount of 0 when fee balances/allowances are insufficient', async () => { + await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, { + from: makerAddress, + }); + const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync( + signedOrder, + signedOrder.signature, + ); + expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT); + }); + it('should return a fillableTakerAssetAmount of 0 when balances/allowances of one asset within a multiAssetData are insufficient', async () => { + const multiAssetData = assetDataUtils.encodeMultiAssetData( + [new BigNumber(1), new BigNumber(1)], + [erc20AssetData, erc20AssetData2], + ); + signedOrder = await orderFactory.newSignedOrderAsync({ makerAssetData: multiAssetData }); + await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, { + from: makerAddress, + }); + const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync( + signedOrder, + signedOrder.signature, + ); + expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT); + }); + it('should return the correct fillableTakerAssetAmount when fee balances/allowances are partially sufficient', async () => { + await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, { + from: makerAddress, + }); + const divisor = 4; + await zrxToken.setBalance.awaitTransactionSuccessAsync( + makerAddress, + signedOrder.makerFee.dividedToIntegerBy(divisor), + ); + await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerFee, { + from: makerAddress, + }); + const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync( + signedOrder, + signedOrder.signature, + ); + expect(fillableTakerAssetAmount).to.bignumber.equal( + signedOrder.takerAssetAmount.dividedToIntegerBy(divisor), + ); + }); + it('should return the correct fillableTakerAssetAmount when non-fee balances/allowances are partially sufficient', async () => { + const divisor = 4; + await erc20Token.setBalance.awaitTransactionSuccessAsync( + makerAddress, + signedOrder.makerAssetAmount.dividedToIntegerBy(divisor), + ); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, { + from: makerAddress, + }); + await zrxToken.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerFee); + await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerFee, { + from: makerAddress, + }); + const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync( + signedOrder, + signedOrder.signature, + ); + expect(fillableTakerAssetAmount).to.bignumber.equal( + signedOrder.takerAssetAmount.dividedToIntegerBy(divisor), + ); + }); + it('should return the correct fillableTakerAssetAmount when balances/allowances of one asset within a multiAssetData are partially sufficient', async () => { + const multiAssetData = assetDataUtils.encodeMultiAssetData( + [new BigNumber(1), new BigNumber(1)], + [erc20AssetData, erc20AssetData2], + ); + signedOrder = await orderFactory.newSignedOrderAsync({ makerAssetData: multiAssetData }); + await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, { + from: makerAddress, + }); + await zrxToken.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerFee); + await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerFee, { + from: makerAddress, + }); + const divisor = 4; + await erc20Token2.setBalance.awaitTransactionSuccessAsync( + makerAddress, + signedOrder.makerAssetAmount.dividedToIntegerBy(divisor), + ); + await erc20Token2.approve.awaitTransactionSuccessAsync( + erc20Proxy.address, + signedOrder.makerAssetAmount.dividedToIntegerBy(divisor), + { + from: makerAddress, + }, + ); + const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync( + signedOrder, + signedOrder.signature, + ); + expect(fillableTakerAssetAmount).to.bignumber.equal( + signedOrder.takerAssetAmount.dividedToIntegerBy(divisor), + ); + }); + it('should return a fillableTakerAssetAmount of 0 when non-fee balances/allowances are insufficient', async () => { + await zrxToken.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerFee); + await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerFee, { + from: makerAddress, + }); + const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync( + signedOrder, + signedOrder.signature, + ); + expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT); + }); + it('should return a fillableTakerAssetAmount equal to the takerAssetAmount when the order is unfilled and balances/allowances are sufficient', async () => { + await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, { + from: makerAddress, + }); + await zrxToken.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerFee); + await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerFee, { + from: makerAddress, + }); + const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync( + signedOrder, + signedOrder.signature, + ); + expect(fillableTakerAssetAmount).to.bignumber.equal(signedOrder.takerAssetAmount); + }); + it('should return the correct fillableTakerAssetAmount when balances/allowances are partially sufficient and makerAsset=makerFeeAsset', async () => { + signedOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: zrxAssetData, + makerAssetAmount: new BigNumber(10), + takerAssetAmount: new BigNumber(20), + makerFee: new BigNumber(40), + }); + const transferableMakerAssetAmount = new BigNumber(10); + await zrxToken.setBalance.awaitTransactionSuccessAsync(makerAddress, transferableMakerAssetAmount); + await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, transferableMakerAssetAmount, { + from: makerAddress, + }); + const expectedFillableTakerAssetAmount = transferableMakerAssetAmount + .times(signedOrder.takerAssetAmount) + .dividedToIntegerBy(signedOrder.makerAssetAmount.plus(signedOrder.makerFee)); + const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync( + signedOrder, + signedOrder.signature, + ); + expect(fillableTakerAssetAmount).to.bignumber.equal(expectedFillableTakerAssetAmount); + }); + it('should return the correct fillabeTakerassetAmount when makerAsset balances/allowances are sufficient and there are no maker fees', async () => { + signedOrder = await orderFactory.newSignedOrderAsync({ makerFee: constants.ZERO_AMOUNT }); + await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, { + from: makerAddress, + }); + const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync( + signedOrder, + signedOrder.signature, + ); + expect(fillableTakerAssetAmount).to.bignumber.equal(signedOrder.takerAssetAmount); + }); + it('should return a fillableTakerAssetAmount when the remaining takerAssetAmount is less than the transferable amount', async () => { + await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, { + from: makerAddress, + }); + await zrxToken.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerFee); + await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerFee, { + from: makerAddress, + }); + await erc20Token2.setBalance.awaitTransactionSuccessAsync(takerAddress, signedOrder.takerAssetAmount); + await erc20Token2.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.takerAssetAmount, { + from: takerAddress, + }); + await zrxToken.setBalance.awaitTransactionSuccessAsync(takerAddress, signedOrder.takerFee); + + await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.takerFee, { + from: takerAddress, + }); + const takerAssetFillAmount = signedOrder.takerAssetAmount.dividedToIntegerBy(4); + await exchange.fillOrder.awaitTransactionSuccessAsync( + signedOrder, + takerAssetFillAmount, + signedOrder.signature, + { from: takerAddress }, + ); + const [, fillableTakerAssetAmount] = await devUtils.getOrderRelevantState.callAsync( + signedOrder, + signedOrder.signature, + ); + expect(fillableTakerAssetAmount).to.bignumber.equal( + signedOrder.takerAssetAmount.minus(takerAssetFillAmount), + ); + }); + }); + describe('getOrderRelevantStates', async () => { + it('should return the correct information for multiple orders', async () => { + await erc20Token.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerAssetAmount); + await erc20Token.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerAssetAmount, { + from: makerAddress, + }); + await zrxToken.setBalance.awaitTransactionSuccessAsync(makerAddress, signedOrder.makerFee); + await zrxToken.approve.awaitTransactionSuccessAsync(erc20Proxy.address, signedOrder.makerFee, { + from: makerAddress, + }); + const signedOrder2 = await orderFactory.newSignedOrderAsync({ makerAssetData: erc721AssetData }); + const invalidSignature = '0x01'; + await exchange.cancelOrder.awaitTransactionSuccessAsync(signedOrder2, { from: makerAddress }); + const [ + ordersInfo, + fillableTakerAssetAmounts, + isValidSignature, + ] = await devUtils.getOrderRelevantStates.callAsync( + [signedOrder, signedOrder2], + [signedOrder.signature, invalidSignature], + ); + expect(ordersInfo[0].orderHash).to.equal(orderHashUtils.getOrderHashHex(signedOrder)); + expect(ordersInfo[1].orderHash).to.equal(orderHashUtils.getOrderHashHex(signedOrder2)); + expect(ordersInfo[0].orderStatus).to.equal(OrderStatus.Fillable); + expect(ordersInfo[1].orderStatus).to.equal(OrderStatus.Cancelled); + expect(ordersInfo[0].orderTakerAssetFilledAmount).to.bignumber.equal(constants.ZERO_AMOUNT); + expect(ordersInfo[1].orderTakerAssetFilledAmount).to.bignumber.equal(constants.ZERO_AMOUNT); + expect(fillableTakerAssetAmounts[0]).to.bignumber.equal(signedOrder.takerAssetAmount); + expect(fillableTakerAssetAmounts[1]).to.bignumber.equal(constants.ZERO_AMOUNT); + expect(isValidSignature[0]).to.equal(true); + expect(isValidSignature[1]).to.equal(false); + }); + }); +}); +// tslint:disable:max-file-line-count diff --git a/contracts/dev-utils/tsconfig.json b/contracts/dev-utils/tsconfig.json new file mode 100644 index 0000000000..7a4c9021ed --- /dev/null +++ b/contracts/dev-utils/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true }, + "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], + "files": [ + "generated-artifacts/DevUtils.json", + "generated-artifacts/LibAssetData.json", + "generated-artifacts/LibTransactionDecoder.json" + ], + "exclude": ["./deploy/solc/solc_bin"] +} diff --git a/contracts/dev-utils/tslint.json b/contracts/dev-utils/tslint.json new file mode 100644 index 0000000000..1bb3ac2a22 --- /dev/null +++ b/contracts/dev-utils/tslint.json @@ -0,0 +1,6 @@ +{ + "extends": ["@0x/tslint-config"], + "rules": { + "custom-no-magic-numbers": false + } +} diff --git a/contracts/exchange-libs/CHANGELOG.json b/contracts/exchange-libs/CHANGELOG.json index 98dd96f4b3..015ab8be7a 100644 --- a/contracts/exchange-libs/CHANGELOG.json +++ b/contracts/exchange-libs/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "3.0.0", + "changes": [ + { + "note": "Move `LibTransactionDecoder` to contracts/dev-utils package", + "pr": 1848 + } + ] + }, { "timestamp": 1558712885, "version": "2.1.6", diff --git a/contracts/exchange-libs/compiler.json b/contracts/exchange-libs/compiler.json index a7ba8cc2fc..7a985d3c12 100644 --- a/contracts/exchange-libs/compiler.json +++ b/contracts/exchange-libs/compiler.json @@ -30,7 +30,6 @@ "src/LibFillResults.sol", "src/LibMath.sol", "src/LibOrder.sol", - "src/LibTransactionDecoder.sol", "test/TestLibs.sol" ] } diff --git a/contracts/exchange-libs/package.json b/contracts/exchange-libs/package.json index b03cb2489e..ee7ed5332c 100644 --- a/contracts/exchange-libs/package.json +++ b/contracts/exchange-libs/package.json @@ -34,7 +34,7 @@ "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis": "./generated-artifacts/@(LibAbiEncoder|LibAssetProxyErrors|LibConstants|LibEIP712|LibFillResults|LibMath|LibOrder|LibTransactionDecoder|TestLibs).json", + "abis": "./generated-artifacts/@(LibAbiEncoder|LibAssetProxyErrors|LibConstants|LibEIP712|LibFillResults|LibMath|LibOrder|TestLibs).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/exchange-libs/src/artifacts.ts b/contracts/exchange-libs/src/artifacts.ts index 735802ab65..861d6f3d2d 100644 --- a/contracts/exchange-libs/src/artifacts.ts +++ b/contracts/exchange-libs/src/artifacts.ts @@ -12,7 +12,6 @@ import * as LibEIP712 from '../generated-artifacts/LibEIP712.json'; import * as LibFillResults from '../generated-artifacts/LibFillResults.json'; import * as LibMath from '../generated-artifacts/LibMath.json'; import * as LibOrder from '../generated-artifacts/LibOrder.json'; -import * as LibTransactionDecoder from '../generated-artifacts/LibTransactionDecoder.json'; import * as TestLibs from '../generated-artifacts/TestLibs.json'; export const artifacts = { LibAbiEncoder: LibAbiEncoder as ContractArtifact, @@ -22,6 +21,5 @@ export const artifacts = { LibFillResults: LibFillResults as ContractArtifact, LibMath: LibMath as ContractArtifact, LibOrder: LibOrder as ContractArtifact, - LibTransactionDecoder: LibTransactionDecoder as ContractArtifact, TestLibs: TestLibs as ContractArtifact, }; diff --git a/contracts/exchange-libs/src/wrappers.ts b/contracts/exchange-libs/src/wrappers.ts index 0cede57f8c..05244d87b7 100644 --- a/contracts/exchange-libs/src/wrappers.ts +++ b/contracts/exchange-libs/src/wrappers.ts @@ -10,5 +10,4 @@ export * from '../generated-wrappers/lib_e_i_p712'; export * from '../generated-wrappers/lib_fill_results'; export * from '../generated-wrappers/lib_math'; export * from '../generated-wrappers/lib_order'; -export * from '../generated-wrappers/lib_transaction_decoder'; export * from '../generated-wrappers/test_libs'; diff --git a/contracts/exchange-libs/test/lib_transaction_decoder.ts b/contracts/exchange-libs/test/lib_transaction_decoder.ts deleted file mode 100644 index 36e6db3070..0000000000 --- a/contracts/exchange-libs/test/lib_transaction_decoder.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { chaiSetup, provider, txDefaults, web3Wrapper } from '@0x/contracts-test-utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { Order } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; - -import { artifacts, LibTransactionDecoderContract } from '../src'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -type OrderWithoutExchange = Pick>; - -const INPUTS: { order: OrderWithoutExchange; takerAssetFillAmount: BigNumber; signature: string } = { - order: { - makerAddress: '0xe36ea790bc9d7ab70c55260c66d52b1eca985f84', - takerAddress: '0x0000000000000000000000000000000000000000', - feeRecipientAddress: '0x78dc5d2d739606d31509c31d654056a45185ecb6', - senderAddress: '0x6ecbe1db9ef729cbe972c83fb886247691fb6beb', - makerAssetAmount: new BigNumber('100000000000000000000'), - takerAssetAmount: new BigNumber('200000000000000000000'), - makerFee: new BigNumber('1000000000000000000'), - takerFee: new BigNumber('1000000000000000000'), - expirationTimeSeconds: new BigNumber('1552396423'), - salt: new BigNumber('66097384406870180066678463045003379626790660770396923976862707230261946348951'), - makerAssetData: '0xf47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064', - takerAssetData: '0xf47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e3', - }, - takerAssetFillAmount: new BigNumber('100000000000000000000'), - signature: - '0x1ce8e3c600d933423172b5021158a6be2e818613ff8e762d70ef490c752fd98a626a215f09f169668990414de75a53da221c294a3002f796d004827258b641876e03', -}; - -const ENCODED_INPUTS: { [functionName: string]: string } = { - // It would be best to encode inputs as part of the test run, but that's - // not really possible in this case, because doing so would introduce a - // dependency on @0x/contracts-exchange, but of course that package already - // depends on @0x/contracts-exchange-libs (this package), so it would - // introduce a circular dependency. - // - // Values used in this object are declared along with the (commented out) - // code that generated them. Running that code depends on the following: - // - // import { artifacts as exchangeArtifacts, ExchangeContract } from '@0x/contracts-exchange'; - // import { getContractAddressesForNetworkOrThrow, NetworkId } from '@0x/contract-addresses'; - // const exchangeContract = new ExchangeContract( - // exchangeArtifacts.Exchange.compilerOutput.abi, - // getContractAddressesForNetworkOrThrow(NetworkId.Ganache).exchange, - // provider, - // ); - // - batchCancelOrders: - // exchangeContract.batchCancelOrders.getABIEncodedTransactionData([ - // INPUTS.order, - // INPUTS.order, - // ]), - '0x4ac147820000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000e36ea790bc9d7ab70c55260c66d52b1eca985f84000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078dc5d2d739606d31509c31d654056a45185ecb60000000000000000000000006ecbe1db9ef729cbe972c83fb886247691fb6beb0000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000ad78ebc5ac62000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000005c87b0879221cb37dcf690e02b0f9aecf44fcaa5ed9ce99697e86743795fa132596ff597000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e300000000000000000000000000000000000000000000000000000000', - batchFillOrders: - // exchangeContract.batchFillOrders.getABIEncodedTransactionData( - // [INPUTS.order, INPUTS.order], - // [INPUTS.takerAssetFillAmount, INPUTS.takerAssetFillAmount], - // [INPUTS.signature, INPUTS.signature], - // ), - '0x297bb70b000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000e36ea790bc9d7ab70c55260c66d52b1eca985f84000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078dc5d2d739606d31509c31d654056a45185ecb60000000000000000000000006ecbe1db9ef729cbe972c83fb886247691fb6beb0000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000ad78ebc5ac62000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000005c87b0879221cb37dcf690e02b0f9aecf44fcaa5ed9ce99697e86743795fa132596ff597000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000421ce8e3c600d933423172b5021158a6be2e818613ff8e762d70ef490c752fd98a626a215f09f169668990414de75a53da221c294a3002f796d004827258b641876e03000000000000000000000000000000000000000000000000000000000000', - batchFillOrdersNoThrow: - // exchangeContract.batchFillOrdersNoThrow.getABIEncodedTransactionData( - // [INPUTS.order, INPUTS.order], - // [INPUTS.takerAssetFillAmount, INPUTS.takerAssetFillAmount], - // [INPUTS.signature, INPUTS.signature], - // ), - '0x50dde190000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000e36ea790bc9d7ab70c55260c66d52b1eca985f84000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078dc5d2d739606d31509c31d654056a45185ecb60000000000000000000000006ecbe1db9ef729cbe972c83fb886247691fb6beb0000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000ad78ebc5ac62000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000005c87b0879221cb37dcf690e02b0f9aecf44fcaa5ed9ce99697e86743795fa132596ff597000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000421ce8e3c600d933423172b5021158a6be2e818613ff8e762d70ef490c752fd98a626a215f09f169668990414de75a53da221c294a3002f796d004827258b641876e03000000000000000000000000000000000000000000000000000000000000', - batchFillOrKillOrders: - // exchangeContract.batchFillOrKillOrders.getABIEncodedTransactionData( - // [INPUTS.order, INPUTS.order], - // [INPUTS.takerAssetFillAmount, INPUTS.takerAssetFillAmount], - // [INPUTS.signature, INPUTS.signature], - // ), - '0x4d0ae546000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000e36ea790bc9d7ab70c55260c66d52b1eca985f84000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078dc5d2d739606d31509c31d654056a45185ecb60000000000000000000000006ecbe1db9ef729cbe972c83fb886247691fb6beb0000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000ad78ebc5ac62000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000005c87b0879221cb37dcf690e02b0f9aecf44fcaa5ed9ce99697e86743795fa132596ff597000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000421ce8e3c600d933423172b5021158a6be2e818613ff8e762d70ef490c752fd98a626a215f09f169668990414de75a53da221c294a3002f796d004827258b641876e03000000000000000000000000000000000000000000000000000000000000', - cancelOrder: - // exchangeContract.cancelOrder.getABIEncodedTransactionData(INPUTS.order), - '0xd46b02c30000000000000000000000000000000000000000000000000000000000000020000000000000000000000000e36ea790bc9d7ab70c55260c66d52b1eca985f84000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078dc5d2d739606d31509c31d654056a45185ecb60000000000000000000000006ecbe1db9ef729cbe972c83fb886247691fb6beb0000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000ad78ebc5ac62000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000005c87b0879221cb37dcf690e02b0f9aecf44fcaa5ed9ce99697e86743795fa132596ff597000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e300000000000000000000000000000000000000000000000000000000', - fillOrder: - // exchangeContract.fillOrder.getABIEncodedTransactionData( - // INPUTS.order, - // INPUTS.takerAssetFillAmount, - // INPUTS.signature, - // ), - '0xb4be83d500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000e36ea790bc9d7ab70c55260c66d52b1eca985f84000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078dc5d2d739606d31509c31d654056a45185ecb60000000000000000000000006ecbe1db9ef729cbe972c83fb886247691fb6beb0000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000ad78ebc5ac62000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000005c87b0879221cb37dcf690e02b0f9aecf44fcaa5ed9ce99697e86743795fa132596ff597000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421ce8e3c600d933423172b5021158a6be2e818613ff8e762d70ef490c752fd98a626a215f09f169668990414de75a53da221c294a3002f796d004827258b641876e03000000000000000000000000000000000000000000000000000000000000', - fillOrderNoThrow: - // exchangeContract.fillOrderNoThrow.getABIEncodedTransactionData( - // INPUTS.order, - // INPUTS.takerAssetFillAmount, - // INPUTS.signature, - // ), - '0x3e228bae00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000e36ea790bc9d7ab70c55260c66d52b1eca985f84000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078dc5d2d739606d31509c31d654056a45185ecb60000000000000000000000006ecbe1db9ef729cbe972c83fb886247691fb6beb0000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000ad78ebc5ac62000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000005c87b0879221cb37dcf690e02b0f9aecf44fcaa5ed9ce99697e86743795fa132596ff597000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421ce8e3c600d933423172b5021158a6be2e818613ff8e762d70ef490c752fd98a626a215f09f169668990414de75a53da221c294a3002f796d004827258b641876e03000000000000000000000000000000000000000000000000000000000000', - fillOrKillOrder: - // exchangeContract.fillOrKillOrder.getABIEncodedTransactionData( - // INPUTS.order, - // INPUTS.takerAssetFillAmount, - // INPUTS.signature, - // ), - '0x64a3bc1500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000e36ea790bc9d7ab70c55260c66d52b1eca985f84000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078dc5d2d739606d31509c31d654056a45185ecb60000000000000000000000006ecbe1db9ef729cbe972c83fb886247691fb6beb0000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000ad78ebc5ac62000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000005c87b0879221cb37dcf690e02b0f9aecf44fcaa5ed9ce99697e86743795fa132596ff597000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421ce8e3c600d933423172b5021158a6be2e818613ff8e762d70ef490c752fd98a626a215f09f169668990414de75a53da221c294a3002f796d004827258b641876e03000000000000000000000000000000000000000000000000000000000000', - marketBuyOrders: - // exchangeContract.marketBuyOrders.getABIEncodedTransactionData( - // [INPUTS.order, INPUTS.order], - // INPUTS.takerAssetFillAmount, - // [INPUTS.signature, INPUTS.signature], - // ), - '0xe5fa431b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000e36ea790bc9d7ab70c55260c66d52b1eca985f84000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078dc5d2d739606d31509c31d654056a45185ecb60000000000000000000000006ecbe1db9ef729cbe972c83fb886247691fb6beb0000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000ad78ebc5ac62000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000005c87b0879221cb37dcf690e02b0f9aecf44fcaa5ed9ce99697e86743795fa132596ff597000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000421ce8e3c600d933423172b5021158a6be2e818613ff8e762d70ef490c752fd98a626a215f09f169668990414de75a53da221c294a3002f796d004827258b641876e03000000000000000000000000000000000000000000000000000000000000', - marketBuyOrdersNoThrow: - // exchangeContract.marketBuyOrdersNoThrow.getABIEncodedTransactionData( - // [INPUTS.order, INPUTS.order], - // INPUTS.takerAssetFillAmount, - // [INPUTS.signature, INPUTS.signature], - // ), - '0xa3e2038000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000e36ea790bc9d7ab70c55260c66d52b1eca985f84000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078dc5d2d739606d31509c31d654056a45185ecb60000000000000000000000006ecbe1db9ef729cbe972c83fb886247691fb6beb0000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000ad78ebc5ac62000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000005c87b0879221cb37dcf690e02b0f9aecf44fcaa5ed9ce99697e86743795fa132596ff597000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000421ce8e3c600d933423172b5021158a6be2e818613ff8e762d70ef490c752fd98a626a215f09f169668990414de75a53da221c294a3002f796d004827258b641876e03000000000000000000000000000000000000000000000000000000000000', - marketSellOrders: - // exchangeContract.marketSellOrders.getABIEncodedTransactionData( - // [INPUTS.order, INPUTS.order], - // INPUTS.takerAssetFillAmount, - // [INPUTS.signature, INPUTS.signature], - // ), - '0x7e1d980800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000e36ea790bc9d7ab70c55260c66d52b1eca985f84000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078dc5d2d739606d31509c31d654056a45185ecb60000000000000000000000006ecbe1db9ef729cbe972c83fb886247691fb6beb0000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000ad78ebc5ac62000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000005c87b0879221cb37dcf690e02b0f9aecf44fcaa5ed9ce99697e86743795fa132596ff597000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000421ce8e3c600d933423172b5021158a6be2e818613ff8e762d70ef490c752fd98a626a215f09f169668990414de75a53da221c294a3002f796d004827258b641876e03000000000000000000000000000000000000000000000000000000000000', - marketSellOrdersNoThrow: - // exchangeContract.marketSellOrdersNoThrow.getABIEncodedTransactionData( - // [INPUTS.order, INPUTS.order], - // INPUTS.takerAssetFillAmount, - // [INPUTS.signature, INPUTS.signature], - // ), - '0xdd1c7d1800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000e36ea790bc9d7ab70c55260c66d52b1eca985f84000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078dc5d2d739606d31509c31d654056a45185ecb60000000000000000000000006ecbe1db9ef729cbe972c83fb886247691fb6beb0000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000ad78ebc5ac62000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000005c87b0879221cb37dcf690e02b0f9aecf44fcaa5ed9ce99697e86743795fa132596ff597000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000034d402f14d58e001d8efbe6585051bf9706aa064000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000421ce8e3c600d933423172b5021158a6be2e818613ff8e762d70ef490c752fd98a626a215f09f169668990414de75a53da221c294a3002f796d004827258b641876e03000000000000000000000000000000000000000000000000000000000000', - matchOrders: - // exchangeContract.matchOrders.getABIEncodedTransactionData( - // INPUTS.order, - // makeComplementaryOrder(INPUTS.order), - // INPUTS.signature, - // INPUTS.signature, - // ), - '0x3c28d86100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000078dc5d2d739606d31509c31d654056a45185ecb60000000000000000000000006ecbe1db9ef729cbe972c83fb886247691fb6beb00000000000000000000000000000000000000000000000ad78ebc5ac620000000000000000000000000000000000000000000000000000ad78ebc5ac62000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000005c87b0879221cb37dcf690e02b0f9aecf44fcaa5ed9ce99697e86743795fa132596ff597000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000000024f47261b000000000000000000000000025b8fe1de9daf8ba351890744ff28cf7dfa8f5e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000421ce8e3c600d933423172b5021158a6be2e818613ff8e762d70ef490c752fd98a626a215f09f169668990414de75a53da221c294a3002f796d004827258b641876e03000000000000000000000000000000000000000000000000000000000000', -}; - -describe('LibTransactionDecoder', () => { - let libTxDecoder: LibTransactionDecoderContract; - - before(async () => { - await blockchainLifecycle.startAsync(); - libTxDecoder = await LibTransactionDecoderContract.deployFrom0xArtifactAsync( - artifacts.LibTransactionDecoder, - provider, - txDefaults, - ); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - - it('should decode an Exchange.batchCancelOrders() transaction', async () => { - expect( - await libTxDecoder.decodeZeroExTransactionData.callAsync(ENCODED_INPUTS.batchCancelOrders), - ).to.deep.equal(['batchCancelOrders', [INPUTS.order, INPUTS.order], [], []]); - }); - - for (const func of ['batchFillOrders', 'batchFillOrdersNoThrow', 'batchFillOrKillOrders']) { - it(`should decode an Exchange.${func}() transaction`, async () => { - expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(ENCODED_INPUTS[func])).to.deep.equal([ - func, - [INPUTS.order, INPUTS.order], - [INPUTS.takerAssetFillAmount, INPUTS.takerAssetFillAmount], - [INPUTS.signature, INPUTS.signature], - ]); - }); - } - - it('should decode an Exchange.cancelOrder() transaction', async () => { - expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(ENCODED_INPUTS.cancelOrder)).to.deep.equal([ - 'cancelOrder', - [INPUTS.order], - [], - [], - ]); - }); - - for (const func of ['fillOrder', 'fillOrderNoThrow', 'fillOrKillOrder']) { - it(`should decode an Exchange.${func}() transaction`, async () => { - expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(ENCODED_INPUTS[func])).to.deep.equal([ - func, - [INPUTS.order], - [INPUTS.takerAssetFillAmount], - [INPUTS.signature], - ]); - }); - } - - for (const func of ['marketBuyOrders', 'marketBuyOrdersNoThrow', 'marketSellOrders', 'marketSellOrdersNoThrow']) { - it(`should decode an Exchange.${func}() transaction`, async () => { - expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(ENCODED_INPUTS[func])).to.deep.equal([ - func, - [INPUTS.order, INPUTS.order], - [INPUTS.takerAssetFillAmount], - [INPUTS.signature, INPUTS.signature], - ]); - }); - } - - it('should decode an Exchange.matchOrders() transaction', async () => { - function makeComplementaryOrder(order: OrderWithoutExchange): OrderWithoutExchange { - const complementaryOrder = order; - - complementaryOrder.makerAddress = order.takerAddress; - complementaryOrder.takerAddress = order.makerAddress; - - complementaryOrder.makerAssetData = order.takerAssetData; - complementaryOrder.takerAssetData = order.makerAssetData; - - complementaryOrder.makerAssetAmount = order.takerAssetAmount; - complementaryOrder.takerAssetAmount = order.makerAssetAmount; - - complementaryOrder.makerFee = order.takerFee; - complementaryOrder.takerFee = order.makerFee; - - return complementaryOrder; - } - - expect(await libTxDecoder.decodeZeroExTransactionData.callAsync(ENCODED_INPUTS.matchOrders)).to.deep.equal([ - 'matchOrders', - [INPUTS.order, makeComplementaryOrder(INPUTS.order)], - [INPUTS.order.takerAssetAmount, makeComplementaryOrder(INPUTS.order).takerAssetAmount], - [INPUTS.signature, INPUTS.signature], - ]); - }); -}); diff --git a/contracts/exchange-libs/tsconfig.json b/contracts/exchange-libs/tsconfig.json index cf9bf35633..1b5d541758 100644 --- a/contracts/exchange-libs/tsconfig.json +++ b/contracts/exchange-libs/tsconfig.json @@ -10,7 +10,6 @@ "generated-artifacts/LibFillResults.json", "generated-artifacts/LibMath.json", "generated-artifacts/LibOrder.json", - "generated-artifacts/LibTransactionDecoder.json", "generated-artifacts/TestLibs.json" ], "exclude": ["./deploy/solc/solc_bin"] diff --git a/contracts/extensions/CHANGELOG.json b/contracts/extensions/CHANGELOG.json index 26103fe705..160807d275 100644 --- a/contracts/extensions/CHANGELOG.json +++ b/contracts/extensions/CHANGELOG.json @@ -3,12 +3,8 @@ "version": "4.0.0", "changes": [ { - "note": "Rename `OrderValidator` to `OrderValidationUtils`", - "pr": 1835 - }, - { - "note": "Create `DevUtils` contract", - "pr": 1835 + "note": "Move `OrderValidator` to contracts/dev-utils package as `OrderValidationUtils`", + "pr": 1848 } ] }, diff --git a/contracts/extensions/compiler.json b/contracts/extensions/compiler.json index 7d513dc38e..a58e45e4fe 100644 --- a/contracts/extensions/compiler.json +++ b/contracts/extensions/compiler.json @@ -27,7 +27,6 @@ "@0x/contracts-exchange/contracts/examples/ExchangeWrapper.sol", "@0x/contracts-exchange/contracts/src/Exchange.sol", "src/BalanceThresholdFilter/BalanceThresholdFilter.sol", - "src/DevUtils/DevUtils.sol", "src/DutchAuction/DutchAuction.sol", "src/OrderMatcher/OrderMatcher.sol" ] diff --git a/contracts/extensions/contracts/src/DevUtils/OrderValidationUtils.sol b/contracts/extensions/contracts/src/DevUtils/OrderValidationUtils.sol deleted file mode 100644 index 4678a266c3..0000000000 --- a/contracts/extensions/contracts/src/DevUtils/OrderValidationUtils.sol +++ /dev/null @@ -1,173 +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; -pragma experimental ABIEncoderV2; - -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"; -import "@0x/contracts-asset-proxy/contracts/src/libs/LibAssetData.sol"; - - -contract OrderValidationUtils is - LibAssetData -{ - using LibBytes for bytes; - - struct TraderInfo { - uint256 makerBalance; // Maker's balance of makerAsset - uint256 makerAllowance; // Maker's allowance to corresponding AssetProxy - uint256 takerBalance; // Taker's balance of takerAsset - uint256 takerAllowance; // Taker's allowance to corresponding AssetProxy - uint256 makerZrxBalance; // Maker's balance of ZRX - uint256 makerZrxAllowance; // Maker's allowance of ZRX to ERC20Proxy - uint256 takerZrxBalance; // Taker's balance of ZRX - uint256 takerZrxAllowance; // Taker's allowance of ZRX to ERC20Proxy - } - - // solhint-disable var-name-mixedcase - IExchange internal EXCHANGE; - bytes internal ZRX_ASSET_DATA; - address internal ERC20_PROXY_ADDRESS; - // solhint-enable var-name-mixedcase - - constructor (address _exchange, bytes memory _zrxAssetData) - public - { - EXCHANGE = IExchange(_exchange); - ZRX_ASSET_DATA = _zrxAssetData; - ERC20_PROXY_ADDRESS = EXCHANGE.getAssetProxy(ERC20_PROXY_ID); - } - - /// @dev Fetches information for order and maker/taker of order. - /// @param order The order structure. - /// @param signature Proof that order has been created by maker. - /// @param takerAddress Address that will be filling the order. - /// @return OrderInfo, TraderInfo, and validity of signature for given order. - function getOrderAndTraderInfo( - LibOrder.Order memory order, - bytes memory signature, - address takerAddress - ) - public - view - returns ( - LibOrder.OrderInfo memory orderInfo, - TraderInfo memory traderInfo, - bool isValidSignature - ) - { - orderInfo = EXCHANGE.getOrderInfo(order); - isValidSignature = EXCHANGE.isValidSignature( - orderInfo.orderHash, - order.makerAddress, - signature - ); - traderInfo = getTraderInfo(order, takerAddress); - return (orderInfo, traderInfo, isValidSignature); - } - - /// @dev Fetches information for all passed in orders and the makers/takers of each order. - /// @param orders Array of order specifications. - /// @param signatures Proofs that orders have been created by makers. - /// @param takerAddresses Array of taker addresses corresponding to each order. - /// @return Arrays of OrderInfo, TraderInfo, and validity of signatures that correspond to each order. - function getOrdersAndTradersInfo( - LibOrder.Order[] memory orders, - bytes[] memory signatures, - address[] memory takerAddresses - ) - public - view - returns ( - LibOrder.OrderInfo[] memory ordersInfo, - TraderInfo[] memory tradersInfo, - bool[] memory isValidSignature - ) - { - ordersInfo = EXCHANGE.getOrdersInfo(orders); - tradersInfo = getTradersInfo(orders, takerAddresses); - - uint256 length = orders.length; - isValidSignature = new bool[](length); - for (uint256 i = 0; i != length; i++) { - isValidSignature[i] = EXCHANGE.isValidSignature( - ordersInfo[i].orderHash, - orders[i].makerAddress, - signatures[i] - ); - } - - return (ordersInfo, tradersInfo, isValidSignature); - } - - /// @dev Fetches balance and allowances for maker and taker of order. - /// @param order The order structure. - /// @param takerAddress Address that will be filling the order. - /// @return Balances and allowances of maker and taker of order. - function getTraderInfo(LibOrder.Order memory order, address takerAddress) - public - view - returns (TraderInfo memory traderInfo) - { - bytes4 makerAssetProxyId = order.makerAssetData.readBytes4(0); - bytes4 takerAssetProxyId = order.takerAssetData.readBytes4(0); - - (traderInfo.makerBalance, traderInfo.makerAllowance) = getBalanceAndAllowance( - order.makerAddress, - EXCHANGE.getAssetProxy(makerAssetProxyId), - order.makerAssetData - ); - (traderInfo.takerBalance, traderInfo.takerAllowance) = getBalanceAndAllowance( - takerAddress, - EXCHANGE.getAssetProxy(takerAssetProxyId), - order.takerAssetData - ); - bytes memory zrxAssetData = ZRX_ASSET_DATA; - address erc20ProxyAddress = ERC20_PROXY_ADDRESS; - (traderInfo.makerZrxBalance, traderInfo.makerZrxAllowance) = getBalanceAndAllowance( - order.makerAddress, - erc20ProxyAddress, - zrxAssetData - ); - (traderInfo.takerZrxBalance, traderInfo.takerZrxAllowance) = getBalanceAndAllowance( - takerAddress, - erc20ProxyAddress, - zrxAssetData - ); - return traderInfo; - } - - /// @dev Fetches balances and allowances of maker and taker for each provided order. - /// @param orders Array of order specifications. - /// @param takerAddresses Array of taker addresses corresponding to each order. - /// @return Array of balances and allowances for maker and taker of each order. - function getTradersInfo(LibOrder.Order[] memory orders, address[] memory takerAddresses) - public - view - returns (TraderInfo[] memory) - { - uint256 ordersLength = orders.length; - TraderInfo[] memory tradersInfo = new TraderInfo[](ordersLength); - for (uint256 i = 0; i != ordersLength; i++) { - tradersInfo[i] = getTraderInfo(orders[i], takerAddresses[i]); - } - return tradersInfo; - } -} diff --git a/contracts/extensions/package.json b/contracts/extensions/package.json index eb89070979..6aa7f2a705 100644 --- a/contracts/extensions/package.json +++ b/contracts/extensions/package.json @@ -34,7 +34,7 @@ "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol" }, "config": { - "abis": "./generated-artifacts/@(BalanceThresholdFilter|DevUtils|DutchAuction|Exchange|ExchangeWrapper|OrderMatcher|WETH9).json", + "abis": "./generated-artifacts/@(BalanceThresholdFilter|DutchAuction|Exchange|ExchangeWrapper|OrderMatcher|WETH9).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/extensions/src/artifacts.ts b/contracts/extensions/src/artifacts.ts index 49a56d9e48..2a286d5e1d 100644 --- a/contracts/extensions/src/artifacts.ts +++ b/contracts/extensions/src/artifacts.ts @@ -6,7 +6,6 @@ import { ContractArtifact } from 'ethereum-types'; import * as BalanceThresholdFilter from '../generated-artifacts/BalanceThresholdFilter.json'; -import * as DevUtils from '../generated-artifacts/DevUtils.json'; import * as DutchAuction from '../generated-artifacts/DutchAuction.json'; import * as Exchange from '../generated-artifacts/Exchange.json'; import * as ExchangeWrapper from '../generated-artifacts/ExchangeWrapper.json'; @@ -19,5 +18,4 @@ export const artifacts = { BalanceThresholdFilter: BalanceThresholdFilter as ContractArtifact, DutchAuction: DutchAuction as ContractArtifact, OrderMatcher: OrderMatcher as ContractArtifact, - DevUtils: DevUtils as ContractArtifact, }; diff --git a/contracts/extensions/src/wrappers.ts b/contracts/extensions/src/wrappers.ts index 9a227280ee..26ae645ceb 100644 --- a/contracts/extensions/src/wrappers.ts +++ b/contracts/extensions/src/wrappers.ts @@ -4,7 +4,6 @@ * ----------------------------------------------------------------------------- */ export * from '../generated-wrappers/balance_threshold_filter'; -export * from '../generated-wrappers/dev_utils'; export * from '../generated-wrappers/dutch_auction'; export * from '../generated-wrappers/exchange'; export * from '../generated-wrappers/exchange_wrapper'; diff --git a/contracts/extensions/test/dev_utils.ts b/contracts/extensions/test/dev_utils.ts deleted file mode 100644 index 3d2624030f..0000000000 --- a/contracts/extensions/test/dev_utils.ts +++ /dev/null @@ -1,653 +0,0 @@ -import { ERC20ProxyContract, ERC20Wrapper, ERC721ProxyContract, ERC721Wrapper } from '@0x/contracts-asset-proxy'; -import { DummyERC20TokenContract } from '@0x/contracts-erc20'; -import { DummyERC721TokenContract } from '@0x/contracts-erc721'; -import { ExchangeContract, ExchangeWrapper } from '@0x/contracts-exchange'; -import { - chaiSetup, - constants, - OrderFactory, - OrderStatus, - provider, - txDefaults, - web3Wrapper, -} from '@0x/contracts-test-utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; -import { SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import * as chai from 'chai'; -import * as _ from 'lodash'; - -import { artifacts, DevUtilsContract } from '../src'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -describe('DevUtils', () => { - let makerAddress: string; - let owner: string; - let takerAddress: string; - let erc20AssetData: string; - let erc721AssetData: string; - - let erc20Token: DummyERC20TokenContract; - let zrxToken: DummyERC20TokenContract; - let erc721Token: DummyERC721TokenContract; - let exchange: ExchangeContract; - let devUtils: DevUtilsContract; - let erc20Proxy: ERC20ProxyContract; - let erc721Proxy: ERC721ProxyContract; - - let signedOrder: SignedOrder; - let signedOrder2: SignedOrder; - let orderFactory: OrderFactory; - - const tokenId = new BigNumber(123456789); - const tokenId2 = new BigNumber(987654321); - const ERC721_BALANCE = new BigNumber(1); - - before(async () => { - await blockchainLifecycle.startAsync(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - }); - - before(async () => { - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, makerAddress, takerAddress] = _.slice(accounts, 0, 3)); - - const erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - - const numDummyErc20ToDeploy = 2; - [erc20Token, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - erc20Proxy = await erc20Wrapper.deployProxyAsync(); - - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - erc721Proxy = await erc721Wrapper.deployProxyAsync(); - - const zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - exchange = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - zrxAssetData, - ); - const exchangeWrapper = new ExchangeWrapper(exchange, provider); - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); - - devUtils = await DevUtilsContract.deployFrom0xArtifactAsync( - artifacts.DevUtils, - provider, - txDefaults, - exchange.address, - zrxAssetData, - ); - - erc20AssetData = assetDataUtils.encodeERC20AssetData(erc20Token.address); - erc721AssetData = assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId); - const defaultOrderParams = { - ...constants.STATIC_ORDER_PARAMS, - exchangeAddress: exchange.address, - makerAddress, - feeRecipientAddress: constants.NULL_ADDRESS, - makerAssetData: erc20AssetData, - takerAssetData: erc721AssetData, - }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); - }); - - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - - describe('getBalanceAndAllowance', () => { - describe('getERC721TokenOwner', async () => { - it('should return the null address when tokenId is not owned', async () => { - const tokenOwner = await devUtils.getERC721TokenOwner.callAsync(makerAddress, tokenId); - expect(tokenOwner).to.be.equal(constants.NULL_ADDRESS); - }); - it('should return the owner address when tokenId is owned', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const tokenOwner = await devUtils.getERC721TokenOwner.callAsync(erc721Token.address, tokenId); - expect(tokenOwner).to.be.equal(makerAddress); - }); - }); - describe('ERC20 assetData', () => { - it('should return the correct balances and allowances when both values are 0', async () => { - const [newBalance, newAllowance] = await devUtils.getBalanceAndAllowance.callAsync( - makerAddress, - erc20Proxy.address, - erc20AssetData, - ); - expect(constants.ZERO_AMOUNT).to.be.bignumber.equal(newBalance); - expect(constants.ZERO_AMOUNT).to.be.bignumber.equal(newAllowance); - }); - it('should return the correct balance and allowance when both values are non-zero', async () => { - const balance = new BigNumber(123); - const allowance = new BigNumber(456); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.setBalance.sendTransactionAsync(makerAddress, balance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, allowance, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const [newBalance, newAllowance] = await devUtils.getBalanceAndAllowance.callAsync( - makerAddress, - erc20Proxy.address, - erc20AssetData, - ); - expect(balance).to.be.bignumber.equal(newBalance); - expect(allowance).to.be.bignumber.equal(newAllowance); - }); - }); - describe('ERC721 assetData', () => { - it('should return a balance of 0 when the tokenId is not owned by target', async () => { - const [newBalance] = await devUtils.getBalanceAndAllowance.callAsync( - makerAddress, - erc721Proxy.address, - erc721AssetData, - ); - expect(newBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should return an allowance of 0 when no approval is set', async () => { - const [, newAllowance] = await devUtils.getBalanceAndAllowance.callAsync( - makerAddress, - erc721Proxy.address, - erc721AssetData, - ); - expect(newAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should return a balance of 1 when the tokenId is owned by target', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const [newBalance] = await devUtils.getBalanceAndAllowance.callAsync( - makerAddress, - erc721Proxy.address, - erc721AssetData, - ); - expect(newBalance).to.be.bignumber.equal(ERC721_BALANCE); - }); - it('should return an allowance of MAX_UINT256 when ERC721Proxy is approved for all', async () => { - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const [, newAllowance] = await devUtils.getBalanceAndAllowance.callAsync( - makerAddress, - erc721Proxy.address, - erc721AssetData, - ); - expect(newAllowance).to.be.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - it('should return an allowance of 1 when ERC721Proxy is approved for specific tokenId', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.approve.sendTransactionAsync(erc721Proxy.address, tokenId, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const [, newAllowance] = await devUtils.getBalanceAndAllowance.callAsync( - makerAddress, - erc721Proxy.address, - erc721AssetData, - ); - expect(newAllowance).to.be.bignumber.equal(new BigNumber(1)); - }); - }); - }); - describe('getBatchBalancesAndAllowances', () => { - it('should return the correct balances and allowances when all values are 0', async () => { - const [ - [erc20Balance, erc721Balance], - [erc20Allowance, erc721Allowance], - ] = await devUtils.getBatchBalancesAndAllowances.callAsync( - makerAddress, - [erc20Proxy.address, erc721Proxy.address], - [erc20AssetData, erc721AssetData], - ); - expect(erc20Balance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(erc721Balance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(erc20Allowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(erc721Allowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should return the correct balances and allowances when balances and allowances are non-zero', async () => { - const balance = new BigNumber(123); - const allowance = new BigNumber(456); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.setBalance.sendTransactionAsync(makerAddress, balance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, allowance, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.mint.sendTransactionAsync(makerAddress, tokenId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const [ - [erc20Balance, erc721Balance], - [erc20Allowance, erc721Allowance], - ] = await devUtils.getBatchBalancesAndAllowances.callAsync( - makerAddress, - [erc20Proxy.address, erc721Proxy.address], - [erc20AssetData, erc721AssetData], - ); - expect(erc20Balance).to.be.bignumber.equal(balance); - expect(erc721Balance).to.be.bignumber.equal(ERC721_BALANCE); - expect(erc20Allowance).to.be.bignumber.equal(allowance); - expect(erc721Allowance).to.be.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - }); - }); - describe('getTraderInfo', () => { - beforeEach(async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should return the correct info when no balances or allowances are set', async () => { - const traderInfo = await devUtils.getTraderInfo.callAsync(signedOrder, takerAddress); - expect(traderInfo.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should return the correct info when balances and allowances are set', async () => { - const makerBalance = new BigNumber(123); - const makerAllowance = new BigNumber(456); - const makerZrxBalance = new BigNumber(789); - const takerZrxAllowance = new BigNumber(987); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.setBalance.sendTransactionAsync(makerAddress, makerBalance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, makerAllowance, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.setBalance.sendTransactionAsync(makerAddress, makerZrxBalance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, takerZrxAllowance, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const traderInfo = await devUtils.getTraderInfo.callAsync(signedOrder, takerAddress); - expect(traderInfo.makerBalance).to.be.bignumber.equal(makerBalance); - expect(traderInfo.makerAllowance).to.be.bignumber.equal(makerAllowance); - expect(traderInfo.takerBalance).to.be.bignumber.equal(ERC721_BALANCE); - expect(traderInfo.takerAllowance).to.be.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance); - expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance); - }); - }); - describe('getTradersInfo', () => { - beforeEach(async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - signedOrder2 = await orderFactory.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId2), - }); - }); - it('should return the correct info when no balances or allowances have been set', async () => { - const orders = [signedOrder, signedOrder2]; - const takers = [takerAddress, takerAddress]; - const [traderInfo1, traderInfo2] = await devUtils.getTradersInfo.callAsync(orders, takers); - expect(traderInfo1.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - }); - it('should return the correct info when balances and allowances are set', async () => { - const makerBalance = new BigNumber(123); - const makerAllowance = new BigNumber(456); - const makerZrxBalance = new BigNumber(789); - const takerZrxAllowance = new BigNumber(987); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.setBalance.sendTransactionAsync(makerAddress, makerBalance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20Token.approve.sendTransactionAsync(erc20Proxy.address, makerAllowance, { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.setBalance.sendTransactionAsync(makerAddress, makerZrxBalance), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.approve.sendTransactionAsync(erc20Proxy.address, takerZrxAllowance, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const isApproved = true; - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, isApproved, { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await web3Wrapper.awaitTransactionSuccessAsync( - await erc721Token.mint.sendTransactionAsync(takerAddress, tokenId2), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const orders = [signedOrder, signedOrder2]; - const takers = [takerAddress, takerAddress]; - const [traderInfo1, traderInfo2] = await devUtils.getTradersInfo.callAsync(orders, takers); - - expect(traderInfo1.makerBalance).to.be.bignumber.equal(makerBalance); - expect(traderInfo1.makerAllowance).to.be.bignumber.equal(makerAllowance); - expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerAllowance).to.be.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance); - expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance); - expect(traderInfo2.makerBalance).to.be.bignumber.equal(makerBalance); - expect(traderInfo2.makerAllowance).to.be.bignumber.equal(makerAllowance); - expect(traderInfo2.takerBalance).to.be.bignumber.equal(ERC721_BALANCE); - expect(traderInfo2.takerAllowance).to.be.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance); - expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance); - }); - }); - describe('getOrderAndTraderInfo', () => { - beforeEach(async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should return the correct info when no balances/allowances are set and the signature is invalid', async () => { - const invalidSignature = '0x01'; - const [orderInfo, traderInfo, isValidSignature] = await devUtils.getOrderAndTraderInfo.callAsync( - signedOrder, - invalidSignature, - takerAddress, - ); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - expect(orderInfo.orderStatus).to.be.equal(OrderStatus.Fillable); - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(isValidSignature).to.equal(false); - }); - it('should return the correct info when balances/allowances are set and the signature is valid', async () => { - const makerBalance = new BigNumber(123); - const makerAllowance = new BigNumber(456); - const makerZrxBalance = new BigNumber(789); - const takerZrxAllowance = new BigNumber(987); - const txData = undefined; - await erc20Token.setBalance.awaitTransactionSuccessAsync( - makerAddress, - makerBalance, - txData, - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await erc20Token.approve.awaitTransactionSuccessAsync( - erc20Proxy.address, - makerAllowance, - { - from: makerAddress, - }, - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await zrxToken.setBalance.awaitTransactionSuccessAsync( - makerAddress, - makerZrxBalance, - txData, - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await zrxToken.approve.awaitTransactionSuccessAsync( - erc20Proxy.address, - takerZrxAllowance, - { - from: takerAddress, - }, - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await erc721Token.mint.awaitTransactionSuccessAsync( - takerAddress, - tokenId, - { - from: takerAddress, - }, - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const isApproved = true; - await erc721Token.setApprovalForAll.awaitTransactionSuccessAsync( - erc721Proxy.address, - isApproved, - { - from: takerAddress, - }, - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const [orderInfo, traderInfo, isValidSignature] = await devUtils.getOrderAndTraderInfo.callAsync( - signedOrder, - signedOrder.signature, - takerAddress, - ); - const expectedOrderHash = orderHashUtils.getOrderHashHex(signedOrder); - expect(orderInfo.orderStatus).to.be.equal(OrderStatus.Fillable); - expect(orderInfo.orderHash).to.be.equal(expectedOrderHash); - expect(orderInfo.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.makerBalance).to.be.bignumber.equal(makerBalance); - expect(traderInfo.makerAllowance).to.be.bignumber.equal(makerAllowance); - expect(traderInfo.takerBalance).to.be.bignumber.equal(ERC721_BALANCE); - expect(traderInfo.takerAllowance).to.be.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - expect(traderInfo.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance); - expect(traderInfo.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance); - expect(isValidSignature).to.equal(true); - }); - }); - describe('getOrdersAndTradersInfo', () => { - beforeEach(async () => { - signedOrder = await orderFactory.newSignedOrderAsync(); - signedOrder2 = await orderFactory.newSignedOrderAsync({ - takerAssetData: assetDataUtils.encodeERC721AssetData(erc721Token.address, tokenId2), - }); - }); - it('should return the correct info when no balances or allowances have been set and the signatures are invalid', async () => { - const invalidSignature = '0x01'; - const orders = [signedOrder, signedOrder2]; - const signatures = [invalidSignature, invalidSignature]; - const takers = [takerAddress, takerAddress]; - const [ - [orderInfo1, orderInfo2], - [traderInfo1, traderInfo2], - [isValidSignature1, isValidSignature2], - ] = await devUtils.getOrdersAndTradersInfo.callAsync(orders, signatures, takers); - const expectedOrderHash1 = orderHashUtils.getOrderHashHex(signedOrder); - const expectedOrderHash2 = orderHashUtils.getOrderHashHex(signedOrder2); - expect(orderInfo1.orderStatus).to.be.equal(OrderStatus.Fillable); - expect(orderInfo1.orderHash).to.be.equal(expectedOrderHash1); - expect(orderInfo1.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(orderInfo2.orderStatus).to.be.equal(OrderStatus.Fillable); - expect(orderInfo2.orderHash).to.be.equal(expectedOrderHash2); - expect(orderInfo2.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(isValidSignature1).to.equal(false); - expect(isValidSignature2).to.equal(false); - }); - it('should return the correct info when balances and allowances are set and the signatures are valid', async () => { - const makerBalance = new BigNumber(123); - const makerAllowance = new BigNumber(456); - const makerZrxBalance = new BigNumber(789); - const takerZrxAllowance = new BigNumber(987); - const txData = undefined; - await erc20Token.setBalance.awaitTransactionSuccessAsync( - makerAddress, - makerBalance, - txData, - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await erc20Token.approve.awaitTransactionSuccessAsync( - erc20Proxy.address, - makerAllowance, - { - from: makerAddress, - }, - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await zrxToken.setBalance.awaitTransactionSuccessAsync( - makerAddress, - makerZrxBalance, - txData, - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await zrxToken.approve.awaitTransactionSuccessAsync( - erc20Proxy.address, - takerZrxAllowance, - { - from: takerAddress, - }, - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const isApproved = true; - await erc721Token.setApprovalForAll.awaitTransactionSuccessAsync( - erc721Proxy.address, - isApproved, - { - from: takerAddress, - }, - constants.AWAIT_TRANSACTION_MINED_MS, - ); - await erc721Token.mint.awaitTransactionSuccessAsync( - takerAddress, - tokenId2, - { - from: takerAddress, - }, - constants.AWAIT_TRANSACTION_MINED_MS, - ); - const orders = [signedOrder, signedOrder2]; - const takers = [takerAddress, takerAddress]; - const [ - [orderInfo1, orderInfo2], - [traderInfo1, traderInfo2], - [isValidSignature1, isValidSignature2], - ] = await devUtils.getOrdersAndTradersInfo.callAsync(orders, orders.map(order => order.signature), takers); - const expectedOrderHash1 = orderHashUtils.getOrderHashHex(signedOrder); - const expectedOrderHash2 = orderHashUtils.getOrderHashHex(signedOrder2); - expect(orderInfo1.orderStatus).to.be.equal(OrderStatus.Fillable); - expect(orderInfo1.orderHash).to.be.equal(expectedOrderHash1); - expect(orderInfo1.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(orderInfo2.orderStatus).to.be.equal(OrderStatus.Fillable); - expect(orderInfo2.orderHash).to.be.equal(expectedOrderHash2); - expect(orderInfo2.orderTakerAssetFilledAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.makerBalance).to.be.bignumber.equal(makerBalance); - expect(traderInfo1.makerAllowance).to.be.bignumber.equal(makerAllowance); - expect(traderInfo1.takerBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerAllowance).to.be.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - expect(traderInfo1.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance); - expect(traderInfo1.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo1.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance); - expect(traderInfo2.makerBalance).to.be.bignumber.equal(makerBalance); - expect(traderInfo2.makerAllowance).to.be.bignumber.equal(makerAllowance); - expect(traderInfo2.takerBalance).to.be.bignumber.equal(ERC721_BALANCE); - expect(traderInfo2.takerAllowance).to.be.bignumber.equal(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); - expect(traderInfo2.makerZrxBalance).to.be.bignumber.equal(makerZrxBalance); - expect(traderInfo2.makerZrxAllowance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxBalance).to.be.bignumber.equal(constants.ZERO_AMOUNT); - expect(traderInfo2.takerZrxAllowance).to.be.bignumber.equal(takerZrxAllowance); - expect(isValidSignature1).to.equal(true); - expect(isValidSignature2).to.equal(true); - }); - }); -}); -// tslint:disable:max-file-line-count diff --git a/contracts/extensions/tsconfig.json b/contracts/extensions/tsconfig.json index 1246d38e1d..73784c22b3 100644 --- a/contracts/extensions/tsconfig.json +++ b/contracts/extensions/tsconfig.json @@ -4,7 +4,6 @@ "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], "files": [ "generated-artifacts/BalanceThresholdFilter.json", - "generated-artifacts/DevUtils.json", "generated-artifacts/DutchAuction.json", "generated-artifacts/Exchange.json", "generated-artifacts/ExchangeWrapper.json", diff --git a/package.json b/package.json index a14f286adf..edf1b474fb 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "lint": "wsrun lint $PKG --fast-exit --parallel --exclude-missing" }, "config": { - "contractsPackages": "@0x/contracts-asset-proxy @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-exchange @0x/contracts-exchange-forwarder @0x/contracts-exchange-libs @0x/contracts-extensions @0x/contracts-multisig @0x/contracts-test-utils @0x/contracts-utils @0x/contracts-coordinator", + "contractsPackages": "@0x/contracts-asset-proxy @0x/contracts-erc20 @0x/contracts-erc721 @0x/contracts-erc1155 @0x/contracts-exchange @0x/contracts-exchange-forwarder @0x/contracts-exchange-libs @0x/contracts-extensions @0x/contracts-multisig @0x/contracts-test-utils @0x/contracts-utils @0x/contracts-coordinator @0x/contracts-dev-utils", "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/abi-gen-wrappers/src/generated-wrappers/coordinator.ts b/packages/abi-gen-wrappers/src/generated-wrappers/coordinator.ts index c45701b4a9..eba1e902d9 100644 --- a/packages/abi-gen-wrappers/src/generated-wrappers/coordinator.ts +++ b/packages/abi-gen-wrappers/src/generated-wrappers/coordinator.ts @@ -23,7 +23,6 @@ import { assert } from '@0x/assert'; import * as ethers from 'ethers'; // tslint:enable:no-unused-variable - /* istanbul ignore next */ // tslint:disable:no-parameter-reassignment // tslint:disable-next-line:class-name @@ -34,8 +33,7 @@ export class CoordinatorContract extends BaseContract { signature: string, callData: Partial = {}, defaultBlock?: BlockParam, - ): Promise { + ): Promise { assert.isString('hash', hash); assert.isString('signature', signature); assert.doesConformToSchema('callData', callData, schemas.callDataSchema, [ @@ -46,10 +44,8 @@ export class CoordinatorContract extends BaseContract { if (defaultBlock !== undefined) { assert.isBlockParam('defaultBlock', defaultBlock); } - const self = this as any as CoordinatorContract; - const encodedData = self._strictEncodeArguments('getSignerAddress(bytes32,bytes)', [hash, - signature - ]); + const self = (this as any) as CoordinatorContract; + const encodedData = self._strictEncodeArguments('getSignerAddress(bytes32,bytes)', [hash, signature]); const callDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( { to: self.address, @@ -62,20 +58,17 @@ export class CoordinatorContract extends BaseContract { BaseContract._throwIfRevertWithReasonCallResult(rawCallResult); const abiEncoder = self._lookupAbiEncoder('getSignerAddress(bytes32,bytes)'); // tslint:disable boolean-naming - const result = abiEncoder.strictDecodeReturnValue(rawCallResult); + const result = abiEncoder.strictDecodeReturnValue(rawCallResult); // tslint:enable boolean-naming return result; }, }; public getTransactionHash = { async callAsync( - transaction: {salt: BigNumber;signerAddress: string;data: string}, + transaction: { salt: BigNumber; signerAddress: string; data: string }, callData: Partial = {}, defaultBlock?: BlockParam, - ): Promise { - + ): Promise { assert.doesConformToSchema('callData', callData, schemas.callDataSchema, [ schemas.addressSchema, schemas.numberSchema, @@ -84,9 +77,10 @@ export class CoordinatorContract extends BaseContract { if (defaultBlock !== undefined) { assert.isBlockParam('defaultBlock', defaultBlock); } - const self = this as any as CoordinatorContract; - const encodedData = self._strictEncodeArguments('getTransactionHash((uint256,address,bytes))', [transaction - ]); + const self = (this as any) as CoordinatorContract; + const encodedData = self._strictEncodeArguments('getTransactionHash((uint256,address,bytes))', [ + transaction, + ]); const callDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( { to: self.address, @@ -99,20 +93,22 @@ export class CoordinatorContract extends BaseContract { BaseContract._throwIfRevertWithReasonCallResult(rawCallResult); const abiEncoder = self._lookupAbiEncoder('getTransactionHash((uint256,address,bytes))'); // tslint:disable boolean-naming - const result = abiEncoder.strictDecodeReturnValue(rawCallResult); + const result = abiEncoder.strictDecodeReturnValue(rawCallResult); // tslint:enable boolean-naming return result; }, }; public getCoordinatorApprovalHash = { async callAsync( - approval: {txOrigin: string;transactionHash: string;transactionSignature: string;approvalExpirationTimeSeconds: BigNumber}, + approval: { + txOrigin: string; + transactionHash: string; + transactionSignature: string; + approvalExpirationTimeSeconds: BigNumber; + }, callData: Partial = {}, defaultBlock?: BlockParam, - ): Promise { - + ): Promise { assert.doesConformToSchema('callData', callData, schemas.callDataSchema, [ schemas.addressSchema, schemas.numberSchema, @@ -121,9 +117,11 @@ export class CoordinatorContract extends BaseContract { if (defaultBlock !== undefined) { assert.isBlockParam('defaultBlock', defaultBlock); } - const self = this as any as CoordinatorContract; - const encodedData = self._strictEncodeArguments('getCoordinatorApprovalHash((address,bytes32,bytes,uint256))', [approval - ]); + const self = (this as any) as CoordinatorContract; + const encodedData = self._strictEncodeArguments( + 'getCoordinatorApprovalHash((address,bytes32,bytes,uint256))', + [approval], + ); const callDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( { to: self.address, @@ -136,54 +134,50 @@ export class CoordinatorContract extends BaseContract { BaseContract._throwIfRevertWithReasonCallResult(rawCallResult); const abiEncoder = self._lookupAbiEncoder('getCoordinatorApprovalHash((address,bytes32,bytes,uint256))'); // tslint:disable boolean-naming - const result = abiEncoder.strictDecodeReturnValue(rawCallResult); + const result = abiEncoder.strictDecodeReturnValue(rawCallResult); // tslint:enable boolean-naming return result; }, }; public executeTransaction = { async sendTransactionAsync( - transaction: {salt: BigNumber;signerAddress: string;data: string}, + transaction: { salt: BigNumber; signerAddress: string; data: string }, txOrigin: string, transactionSignature: string, approvalExpirationTimeSeconds: BigNumber[], approvalSignatures: string[], - txData?: Partial | undefined, + txData?: Partial | undefined, ): Promise { - - assert.isString('txOrigin', txOrigin); - assert.isString('transactionSignature', transactionSignature); - assert.isArray('approvalExpirationTimeSeconds', approvalExpirationTimeSeconds); - assert.isArray('approvalSignatures', approvalSignatures); - const self = this as any as CoordinatorContract; - const encodedData = self._strictEncodeArguments('executeTransaction((uint256,address,bytes),address,bytes,uint256[],bytes[])', [transaction, - txOrigin, - transactionSignature, - approvalExpirationTimeSeconds, - approvalSignatures - ]); - const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( - { - to: self.address, - ...txData, - data: encodedData, - }, - self._web3Wrapper.getContractDefaults(), - self.executeTransaction.estimateGasAsync.bind( - self, - transaction, - txOrigin, - transactionSignature, - approvalExpirationTimeSeconds, - approvalSignatures - ), - ); - const txHash = await self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); - return txHash; + assert.isString('txOrigin', txOrigin); + assert.isString('transactionSignature', transactionSignature); + assert.isArray('approvalExpirationTimeSeconds', approvalExpirationTimeSeconds); + assert.isArray('approvalSignatures', approvalSignatures); + const self = (this as any) as CoordinatorContract; + const encodedData = self._strictEncodeArguments( + 'executeTransaction((uint256,address,bytes),address,bytes,uint256[],bytes[])', + [transaction, txOrigin, transactionSignature, approvalExpirationTimeSeconds, approvalSignatures], + ); + const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...txData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + self.executeTransaction.estimateGasAsync.bind( + self, + transaction, + txOrigin, + transactionSignature, + approvalExpirationTimeSeconds, + approvalSignatures, + ), + ); + const txHash = await self._web3Wrapper.sendTransactionAsync(txDataWithDefaults); + return txHash; }, awaitTransactionSuccessAsync( - transaction: {salt: BigNumber;signerAddress: string;data: string}, + transaction: { salt: BigNumber; signerAddress: string; data: string }, txOrigin: string, transactionSignature: string, approvalExpirationTimeSeconds: BigNumber[], @@ -192,93 +186,86 @@ export class CoordinatorContract extends BaseContract { pollingIntervalMs?: number, timeoutMs?: number, ): PromiseWithTransactionHash { - - assert.isString('txOrigin', txOrigin); - assert.isString('transactionSignature', transactionSignature); - assert.isArray('approvalExpirationTimeSeconds', approvalExpirationTimeSeconds); - assert.isArray('approvalSignatures', approvalSignatures); - const self = this as any as CoordinatorContract; - const txHashPromise = self.executeTransaction.sendTransactionAsync(transaction, - txOrigin, - transactionSignature, - approvalExpirationTimeSeconds, - approvalSignatures - , txData); - return new PromiseWithTransactionHash( - txHashPromise, - (async (): Promise => { - // When the transaction hash resolves, wait for it to be mined. - return self._web3Wrapper.awaitTransactionSuccessAsync( - await txHashPromise, - pollingIntervalMs, - timeoutMs, - ); - })(), - ); + assert.isString('txOrigin', txOrigin); + assert.isString('transactionSignature', transactionSignature); + assert.isArray('approvalExpirationTimeSeconds', approvalExpirationTimeSeconds); + assert.isArray('approvalSignatures', approvalSignatures); + const self = (this as any) as CoordinatorContract; + const txHashPromise = self.executeTransaction.sendTransactionAsync( + transaction, + txOrigin, + transactionSignature, + approvalExpirationTimeSeconds, + approvalSignatures, + txData, + ); + return new PromiseWithTransactionHash( + txHashPromise, + (async (): Promise => { + // When the transaction hash resolves, wait for it to be mined. + return self._web3Wrapper.awaitTransactionSuccessAsync( + await txHashPromise, + pollingIntervalMs, + timeoutMs, + ); + })(), + ); }, async estimateGasAsync( - transaction: {salt: BigNumber;signerAddress: string;data: string}, + transaction: { salt: BigNumber; signerAddress: string; data: string }, txOrigin: string, transactionSignature: string, approvalExpirationTimeSeconds: BigNumber[], approvalSignatures: string[], txData?: Partial | undefined, ): Promise { - - assert.isString('txOrigin', txOrigin); - assert.isString('transactionSignature', transactionSignature); - assert.isArray('approvalExpirationTimeSeconds', approvalExpirationTimeSeconds); - assert.isArray('approvalSignatures', approvalSignatures); - const self = this as any as CoordinatorContract; - const encodedData = self._strictEncodeArguments('executeTransaction((uint256,address,bytes),address,bytes,uint256[],bytes[])', [transaction, - txOrigin, - transactionSignature, - approvalExpirationTimeSeconds, - approvalSignatures - ]); - const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( - { - to: self.address, - ...txData, - data: encodedData, - }, - self._web3Wrapper.getContractDefaults(), - ); - const gas = await self._web3Wrapper.estimateGasAsync(txDataWithDefaults); - return gas; + assert.isString('txOrigin', txOrigin); + assert.isString('transactionSignature', transactionSignature); + assert.isArray('approvalExpirationTimeSeconds', approvalExpirationTimeSeconds); + assert.isArray('approvalSignatures', approvalSignatures); + const self = (this as any) as CoordinatorContract; + const encodedData = self._strictEncodeArguments( + 'executeTransaction((uint256,address,bytes),address,bytes,uint256[],bytes[])', + [transaction, txOrigin, transactionSignature, approvalExpirationTimeSeconds, approvalSignatures], + ); + const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( + { + to: self.address, + ...txData, + data: encodedData, + }, + self._web3Wrapper.getContractDefaults(), + ); + const gas = await self._web3Wrapper.estimateGasAsync(txDataWithDefaults); + return gas; }, getABIEncodedTransactionData( - transaction: {salt: BigNumber;signerAddress: string;data: string}, + transaction: { salt: BigNumber; signerAddress: string; data: string }, txOrigin: string, transactionSignature: string, approvalExpirationTimeSeconds: BigNumber[], approvalSignatures: string[], ): string { - - assert.isString('txOrigin', txOrigin); - assert.isString('transactionSignature', transactionSignature); - assert.isArray('approvalExpirationTimeSeconds', approvalExpirationTimeSeconds); - assert.isArray('approvalSignatures', approvalSignatures); - const self = this as any as CoordinatorContract; - const abiEncodedTransactionData = self._strictEncodeArguments('executeTransaction((uint256,address,bytes),address,bytes,uint256[],bytes[])', [transaction, - txOrigin, - transactionSignature, - approvalExpirationTimeSeconds, - approvalSignatures - ]); - return abiEncodedTransactionData; + assert.isString('txOrigin', txOrigin); + assert.isString('transactionSignature', transactionSignature); + assert.isArray('approvalExpirationTimeSeconds', approvalExpirationTimeSeconds); + assert.isArray('approvalSignatures', approvalSignatures); + const self = (this as any) as CoordinatorContract; + const abiEncodedTransactionData = self._strictEncodeArguments( + 'executeTransaction((uint256,address,bytes),address,bytes,uint256[],bytes[])', + [transaction, txOrigin, transactionSignature, approvalExpirationTimeSeconds, approvalSignatures], + ); + return abiEncodedTransactionData; }, async callAsync( - transaction: {salt: BigNumber;signerAddress: string;data: string}, + transaction: { salt: BigNumber; signerAddress: string; data: string }, txOrigin: string, transactionSignature: string, approvalExpirationTimeSeconds: BigNumber[], approvalSignatures: string[], callData: Partial = {}, defaultBlock?: BlockParam, - ): Promise { - + ): Promise { assert.isString('txOrigin', txOrigin); assert.isString('transactionSignature', transactionSignature); assert.isArray('approvalExpirationTimeSeconds', approvalExpirationTimeSeconds); @@ -291,13 +278,11 @@ export class CoordinatorContract extends BaseContract { if (defaultBlock !== undefined) { assert.isBlockParam('defaultBlock', defaultBlock); } - const self = this as any as CoordinatorContract; - const encodedData = self._strictEncodeArguments('executeTransaction((uint256,address,bytes),address,bytes,uint256[],bytes[])', [transaction, - txOrigin, - transactionSignature, - approvalExpirationTimeSeconds, - approvalSignatures - ]); + const self = (this as any) as CoordinatorContract; + const encodedData = self._strictEncodeArguments( + 'executeTransaction((uint256,address,bytes),address,bytes,uint256[],bytes[])', + [transaction, txOrigin, transactionSignature, approvalExpirationTimeSeconds, approvalSignatures], + ); const callDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( { to: self.address, @@ -308,20 +293,17 @@ export class CoordinatorContract extends BaseContract { ); const rawCallResult = await self._web3Wrapper.callAsync(callDataWithDefaults, defaultBlock); BaseContract._throwIfRevertWithReasonCallResult(rawCallResult); - const abiEncoder = self._lookupAbiEncoder('executeTransaction((uint256,address,bytes),address,bytes,uint256[],bytes[])'); + const abiEncoder = self._lookupAbiEncoder( + 'executeTransaction((uint256,address,bytes),address,bytes,uint256[],bytes[])', + ); // tslint:disable boolean-naming - const result = abiEncoder.strictDecodeReturnValue(rawCallResult); + const result = abiEncoder.strictDecodeReturnValue(rawCallResult); // tslint:enable boolean-naming return result; }, }; public EIP712_EXCHANGE_DOMAIN_HASH = { - async callAsync( - callData: Partial = {}, - defaultBlock?: BlockParam, - ): Promise { + async callAsync(callData: Partial = {}, defaultBlock?: BlockParam): Promise { assert.doesConformToSchema('callData', callData, schemas.callDataSchema, [ schemas.addressSchema, schemas.numberSchema, @@ -330,7 +312,7 @@ export class CoordinatorContract extends BaseContract { if (defaultBlock !== undefined) { assert.isBlockParam('defaultBlock', defaultBlock); } - const self = this as any as CoordinatorContract; + const self = (this as any) as CoordinatorContract; const encodedData = self._strictEncodeArguments('EIP712_EXCHANGE_DOMAIN_HASH()', []); const callDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( { @@ -344,24 +326,21 @@ export class CoordinatorContract extends BaseContract { BaseContract._throwIfRevertWithReasonCallResult(rawCallResult); const abiEncoder = self._lookupAbiEncoder('EIP712_EXCHANGE_DOMAIN_HASH()'); // tslint:disable boolean-naming - const result = abiEncoder.strictDecodeReturnValue(rawCallResult); + const result = abiEncoder.strictDecodeReturnValue(rawCallResult); // tslint:enable boolean-naming return result; }, }; public assertValidCoordinatorApprovals = { async callAsync( - transaction: {salt: BigNumber;signerAddress: string;data: string}, + transaction: { salt: BigNumber; signerAddress: string; data: string }, txOrigin: string, transactionSignature: string, approvalExpirationTimeSeconds: BigNumber[], approvalSignatures: string[], callData: Partial = {}, defaultBlock?: BlockParam, - ): Promise { - + ): Promise { assert.isString('txOrigin', txOrigin); assert.isString('transactionSignature', transactionSignature); assert.isArray('approvalExpirationTimeSeconds', approvalExpirationTimeSeconds); @@ -374,13 +353,11 @@ export class CoordinatorContract extends BaseContract { if (defaultBlock !== undefined) { assert.isBlockParam('defaultBlock', defaultBlock); } - const self = this as any as CoordinatorContract; - const encodedData = self._strictEncodeArguments('assertValidCoordinatorApprovals((uint256,address,bytes),address,bytes,uint256[],bytes[])', [transaction, - txOrigin, - transactionSignature, - approvalExpirationTimeSeconds, - approvalSignatures - ]); + const self = (this as any) as CoordinatorContract; + const encodedData = self._strictEncodeArguments( + 'assertValidCoordinatorApprovals((uint256,address,bytes),address,bytes,uint256[],bytes[])', + [transaction, txOrigin, transactionSignature, approvalExpirationTimeSeconds, approvalSignatures], + ); const callDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( { to: self.address, @@ -391,10 +368,11 @@ export class CoordinatorContract extends BaseContract { ); const rawCallResult = await self._web3Wrapper.callAsync(callDataWithDefaults, defaultBlock); BaseContract._throwIfRevertWithReasonCallResult(rawCallResult); - const abiEncoder = self._lookupAbiEncoder('assertValidCoordinatorApprovals((uint256,address,bytes),address,bytes,uint256[],bytes[])'); + const abiEncoder = self._lookupAbiEncoder( + 'assertValidCoordinatorApprovals((uint256,address,bytes),address,bytes,uint256[],bytes[])', + ); // tslint:disable boolean-naming - const result = abiEncoder.strictDecodeReturnValue(rawCallResult); + const result = abiEncoder.strictDecodeReturnValue(rawCallResult); // tslint:enable boolean-naming return result; }, @@ -404,7 +382,21 @@ export class CoordinatorContract extends BaseContract { data: string, callData: Partial = {}, defaultBlock?: BlockParam, - ): Promise + ): Promise< + Array<{ + makerAddress: string; + takerAddress: string; + feeRecipientAddress: string; + senderAddress: string; + makerAssetAmount: BigNumber; + takerAssetAmount: BigNumber; + makerFee: BigNumber; + takerFee: BigNumber; + expirationTimeSeconds: BigNumber; + salt: BigNumber; + makerAssetData: string; + takerAssetData: string; + }> > { assert.isString('data', data); assert.doesConformToSchema('callData', callData, schemas.callDataSchema, [ @@ -415,9 +407,8 @@ export class CoordinatorContract extends BaseContract { if (defaultBlock !== undefined) { assert.isBlockParam('defaultBlock', defaultBlock); } - const self = this as any as CoordinatorContract; - const encodedData = self._strictEncodeArguments('decodeOrdersFromFillData(bytes)', [data - ]); + const self = (this as any) as CoordinatorContract; + const encodedData = self._strictEncodeArguments('decodeOrdersFromFillData(bytes)', [data]); const callDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( { to: self.address, @@ -430,18 +421,28 @@ export class CoordinatorContract extends BaseContract { BaseContract._throwIfRevertWithReasonCallResult(rawCallResult); const abiEncoder = self._lookupAbiEncoder('decodeOrdersFromFillData(bytes)'); // tslint:disable boolean-naming - const result = abiEncoder.strictDecodeReturnValue - >(rawCallResult); + const result = abiEncoder.strictDecodeReturnValue< + Array<{ + makerAddress: string; + takerAddress: string; + feeRecipientAddress: string; + senderAddress: string; + makerAssetAmount: BigNumber; + takerAssetAmount: BigNumber; + makerFee: BigNumber; + takerFee: BigNumber; + expirationTimeSeconds: BigNumber; + salt: BigNumber; + makerAssetData: string; + takerAssetData: string; + }> + >(rawCallResult); // tslint:enable boolean-naming return result; }, }; public EIP712_COORDINATOR_DOMAIN_HASH = { - async callAsync( - callData: Partial = {}, - defaultBlock?: BlockParam, - ): Promise { + async callAsync(callData: Partial = {}, defaultBlock?: BlockParam): Promise { assert.doesConformToSchema('callData', callData, schemas.callDataSchema, [ schemas.addressSchema, schemas.numberSchema, @@ -450,7 +451,7 @@ export class CoordinatorContract extends BaseContract { if (defaultBlock !== undefined) { assert.isBlockParam('defaultBlock', defaultBlock); } - const self = this as any as CoordinatorContract; + const self = (this as any) as CoordinatorContract; const encodedData = self._strictEncodeArguments('EIP712_COORDINATOR_DOMAIN_HASH()', []); const callDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( { @@ -464,8 +465,7 @@ export class CoordinatorContract extends BaseContract { BaseContract._throwIfRevertWithReasonCallResult(rawCallResult); const abiEncoder = self._lookupAbiEncoder('EIP712_COORDINATOR_DOMAIN_HASH()'); // tslint:disable boolean-naming - const result = abiEncoder.strictDecodeReturnValue(rawCallResult); + const result = abiEncoder.strictDecodeReturnValue(rawCallResult); // tslint:enable boolean-naming return result; }, @@ -474,7 +474,7 @@ export class CoordinatorContract extends BaseContract { artifact: ContractArtifact | SimpleContractArtifact, supportedProvider: SupportedProvider, txDefaults: Partial, - _exchange: string, + _exchange: string, ): Promise { assert.doesConformToSchema('txDefaults', txDefaults, schemas.txDataSchema, [ schemas.addressSchema, @@ -487,15 +487,14 @@ export class CoordinatorContract extends BaseContract { const provider = providerUtils.standardizeOrThrow(supportedProvider); const bytecode = artifact.compilerOutput.evm.bytecode.object; const abi = artifact.compilerOutput.abi; - return CoordinatorContract.deployAsync(bytecode, abi, provider, txDefaults, _exchange -); + return CoordinatorContract.deployAsync(bytecode, abi, provider, txDefaults, _exchange); } public static async deployAsync( bytecode: string, abi: ContractAbi, supportedProvider: SupportedProvider, txDefaults: Partial, - _exchange: string, + _exchange: string, ): Promise { assert.isHexString('bytecode', bytecode); assert.doesConformToSchema('txDefaults', txDefaults, schemas.txDataSchema, [ @@ -505,20 +504,17 @@ export class CoordinatorContract extends BaseContract { ]); const provider = providerUtils.standardizeOrThrow(supportedProvider); const constructorAbi = BaseContract._lookupConstructorAbi(abi); - [_exchange -] = BaseContract._formatABIDataItemList( + [_exchange] = BaseContract._formatABIDataItemList( constructorAbi.inputs, - [_exchange -], + [_exchange], BaseContract._bigNumberToString, ); const iface = new ethers.utils.Interface(abi); const deployInfo = iface.deployFunction; - const txData = deployInfo.encode(bytecode, [_exchange -]); + const txData = deployInfo.encode(bytecode, [_exchange]); const web3Wrapper = new Web3Wrapper(provider); const txDataWithDefaults = await BaseContract._applyDefaultsToTxDataAsync( - {data: txData}, + { data: txData }, txDefaults, web3Wrapper.estimateGasAsync.bind(web3Wrapper), ); @@ -526,9 +522,13 @@ export class CoordinatorContract extends BaseContract { logUtils.log(`transactionHash: ${txHash}`); const txReceipt = await web3Wrapper.awaitTransactionSuccessAsync(txHash); logUtils.log(`Coordinator successfully deployed at ${txReceipt.contractAddress}`); - const contractInstance = new CoordinatorContract(abi, txReceipt.contractAddress as string, provider, txDefaults); - contractInstance.constructorArgs = [_exchange -]; + const contractInstance = new CoordinatorContract( + abi, + txReceipt.contractAddress as string, + provider, + txDefaults, + ); + contractInstance.constructorArgs = [_exchange]; return contractInstance; } constructor(abi: ContractAbi, address: string, supportedProvider: SupportedProvider, txDefaults?: Partial) { diff --git a/tsconfig.json b/tsconfig.json index bfa83503e8..3382161ab8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,6 +30,7 @@ { "path": "./contracts/multisig" }, { "path": "./contracts/test-utils" }, { "path": "./contracts/utils" }, + { "path": "./contracts/dev-utils" }, { "path": "./packages/0x.js" }, { "path": "./packages/abi-gen-wrappers" }, { "path": "./packages/abi-gen" }, diff --git a/yarn.lock b/yarn.lock index 0bd6bd4f39..4550e56184 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5658,6 +5658,11 @@ deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" +deep-extend@~0.4.0: + version "0.4.2" + resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" + integrity sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8= + deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -16341,7 +16346,7 @@ tape@~2.3.2: resumer "~0.0.0" through "~2.3.4" -tar-fs@~1.16.3: +tar-fs@^1.13.0, tar-fs@~1.16.3: version "1.16.3" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509" dependencies: