diff options
author | Jacob Evans <dekz@dekz.net> | 2018-06-18 19:50:35 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-06-18 19:50:35 +0800 |
commit | 190eafc30e2e444ed15b76217a6162ec04b33f73 (patch) | |
tree | b20cbad73ff7a069dc0f0ef43ebc0373c714ad02 /packages/contracts | |
parent | d4ee0e862297c16f8ee62efccd31f1193052c64e (diff) | |
parent | 0c238448fda99c4d7997901d0fe4d72cb06b79cc (diff) | |
download | dexon-0x-contracts-190eafc30e2e444ed15b76217a6162ec04b33f73.tar.gz dexon-0x-contracts-190eafc30e2e444ed15b76217a6162ec04b33f73.tar.zst dexon-0x-contracts-190eafc30e2e444ed15b76217a6162ec04b33f73.zip |
Merge branch 'v2-prototype' into bug/contracts/eip712-191-prefix
Diffstat (limited to 'packages/contracts')
70 files changed, 2461 insertions, 929 deletions
diff --git a/packages/contracts/README.md b/packages/contracts/README.md index 23551b69d..9c829c753 100644 --- a/packages/contracts/README.md +++ b/packages/contracts/README.md @@ -63,3 +63,12 @@ yarn lint ```bash yarn test ``` + +### Run Tests Against Geth + +Follow the instructions in the README for the devnet package to start the +devnet. + +```bash +TEST_PROVIDER=geth yarn test +``` diff --git a/packages/contracts/compiler.json b/packages/contracts/compiler.json index 48ba4ffcd..6ef8e6a95 100644 --- a/packages/contracts/compiler.json +++ b/packages/contracts/compiler.json @@ -21,6 +21,7 @@ "contracts": [ "AssetProxyOwner", "DummyERC20Token", + "DummyERC721Receiver", "DummyERC721Token", "ERC20Proxy", "ERC721Proxy", @@ -28,8 +29,10 @@ "MixinAuthorizable", "MultiSigWallet", "MultiSigWalletWithTimeLock", + "TestAssetDataDecoders", "TestAssetProxyDispatcher", "TestLibBytes", + "TestLibMem", "TestLibs", "TestSignatureValidator", "TokenRegistry", diff --git a/packages/contracts/coverage/.gitkeep b/packages/contracts/coverage/.gitkeep deleted file mode 100644 index e69de29bb..000000000 --- a/packages/contracts/coverage/.gitkeep +++ /dev/null diff --git a/packages/contracts/package.json b/packages/contracts/package.json index eee2b04d3..2495795dc 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -11,25 +11,29 @@ "test": "test" }, "scripts": { - "watch": "tsc -w", - "prebuild": "run-s clean compile copy_artifacts generate_contract_wrappers", + "watch_without_deps": "yarn pre_build && tsc -w", + "build": "yarn pre_build && tsc", + "pre_build": "run-s compile copy_artifacts generate_contract_wrappers", "copy_artifacts": "copyfiles -u 4 '../migrations/artifacts/2.0.0/**/*' ./lib/src/artifacts;", - "build": "tsc", - "test": "run-s build run_mocha", + "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", - "run_mocha": "mocha 'lib/test/**/*.js' --timeout 100000 --bail --exit", + "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 'lib/test/**/*.js' --timeout 100000 --bail --exit", "compile": "sol-compiler", - "clean": "shx rm -rf lib src/contract_wrappers/generated", + "clean": "shx rm -rf lib src/generated_contract_wrappers", "generate_contract_wrappers": - "abi-gen --abis ${npm_package_config_abis} --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers && prettier --write 'src/contract_wrappers/generated/**.ts'", - "lint": "tslint --project .", + "abi-gen --abis ${npm_package_config_abis} --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/generated_contract_wrappers --backend ethers", + "lint": "tslint --project . --exclude **/src/generated_contract_wrappers/**/* --exclude **/lib/**/*", "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" }, "config": { - "abis": "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetProxyDispatcher|TestLibBytes|TestLibs|TestSignatureValidator|TokenRegistry|Whitelist|WETH9|ZRXToken).json" + "abis": "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetDataDecoders|TestAssetProxyDispatcher|TestLibBytes|TestLibMem|TestLibs|TestSignatureValidator|TokenRegistry|Whitelist|WETH9|ZRXToken).json" }, "repository": { "type": "git", @@ -60,7 +64,6 @@ "make-promises-safe": "^1.1.0", "mocha": "^4.0.1", "npm-run-all": "^4.1.2", - "prettier": "^1.11.1", "shx": "^0.2.2", "solc": "^0.4.24", "tslint": "5.8.0", diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol index 2c321e134..eeddb9707 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol @@ -34,36 +34,51 @@ contract ERC20Proxy is uint8 constant PROXY_ID = 1; /// @dev Internal version of `transferFrom`. - /// @param assetMetadata Encoded byte array. + /// @param assetData Encoded byte array. /// @param from Address to transfer asset from. /// @param to Address to transfer asset to. /// @param amount Amount of asset to transfer. function transferFromInternal( - bytes memory assetMetadata, + bytes memory assetData, address from, address to, uint256 amount ) internal { - // Data must be intended for this proxy. - uint256 length = assetMetadata.length; + // Decode asset data. + address token = readAddress(assetData, 0); + // Transfer tokens. + // We do a raw call so we can check the success separate + // from the return data. + bool success = token.call(abi.encodeWithSelector( + IERC20Token(token).transferFrom.selector, + from, + to, + amount + )); require( - length == 21, - LENGTH_21_REQUIRED - ); - // TODO: Is this too inflexible in the future? - require( - uint8(assetMetadata[length - 1]) == PROXY_ID, - ASSET_PROXY_ID_MISMATCH + success, + TRANSFER_FAILED ); - - // Decode metadata. - address token = readAddress(assetMetadata, 0); - - // Transfer tokens. - bool success = IERC20Token(token).transferFrom(from, to, amount); + + // Check return data. + // If there is no return data, we assume the token incorrectly + // does not return a bool. In this case we expect it to revert + // on failure, which was handled above. + // If the token does return data, we require that it is a single + // value that evaluates to true. + assembly { + if returndatasize { + success := 0 + if eq(returndatasize, 32) { + // First 64 bytes of memory are reserved scratch space + returndatacopy(0, 0, 32) + success := mload(0) + } + } + } require( success, TRANSFER_FAILED diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol index 07e01c774..861fac2c1 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol @@ -34,47 +34,38 @@ contract ERC721Proxy is uint8 constant PROXY_ID = 2; /// @dev Internal version of `transferFrom`. - /// @param assetMetadata Encoded byte array. + /// @param assetData Encoded byte array. /// @param from Address to transfer asset from. /// @param to Address to transfer asset to. /// @param amount Amount of asset to transfer. function transferFromInternal( - bytes memory assetMetadata, + bytes memory assetData, address from, address to, uint256 amount ) internal { - // Data must be intended for this proxy. - uint256 length = assetMetadata.length; - - require( - length == 53, - LENGTH_53_REQUIRED - ); - - // TODO: Is this too inflexible in the future? - require( - uint8(assetMetadata[length - 1]) == PROXY_ID, - ASSET_PROXY_ID_MISMATCH - ); - // There exists only 1 of each token. require( amount == 1, INVALID_AMOUNT ); - - // Decode metadata - address token = readAddress(assetMetadata, 0); - uint256 tokenId = readUint256(assetMetadata, 20); - - // Transfer token. - // Either succeeds or throws. - // @TODO: Call safeTransferFrom if there is additional - // data stored in `assetMetadata`. - ERC721Token(token).transferFrom(from, to, tokenId); + + // Decode asset data. + ( + address token, + uint256 tokenId, + bytes memory receiverData + ) = decodeERC721AssetData(assetData); + + // Transfer token. Saves gas by calling safeTransferFrom only + // when there is receiverData present. Either succeeds or throws. + if (receiverData.length > 0) { + ERC721Token(token).safeTransferFrom(from, to, tokenId, receiverData); + } else { + ERC721Token(token).transferFrom(from, to, tokenId); + } } /// @dev Gets the proxy id associated with the proxy address. @@ -86,4 +77,34 @@ contract ERC721Proxy is { return PROXY_ID; } + + /// @dev Decodes ERC721 Asset data. + /// @param assetData Encoded byte array. + /// @return proxyId Intended ERC721 proxy id. + /// @return token ERC721 token address. + /// @return tokenId ERC721 token id. + /// @return receiverData Additional data with no specific format, which + /// is passed to the receiving contract's onERC721Received. + function decodeERC721AssetData(bytes memory assetData) + internal + pure + returns ( + address token, + uint256 tokenId, + bytes memory receiverData + ) + { + // Decode asset data. + token = readAddress(assetData, 0); + tokenId = readUint256(assetData, 20); + if (assetData.length > 52) { + receiverData = readBytes(assetData, 52); + } + + return ( + token, + tokenId, + receiverData + ); + } } diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol index 5fa33cbef..9032658e7 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAssetProxy.sol @@ -28,12 +28,12 @@ contract MixinAssetProxy is { /// @dev Transfers assets. Either succeeds or throws. - /// @param assetMetadata Encoded byte array. + /// @param assetData Encoded byte array. /// @param from Address to transfer asset from. /// @param to Address to transfer asset to. /// @param amount Amount of asset to transfer. function transferFrom( - bytes assetMetadata, + bytes assetData, address from, address to, uint256 amount @@ -42,7 +42,7 @@ contract MixinAssetProxy is onlyAuthorized { transferFromInternal( - assetMetadata, + assetData, from, to, amount @@ -50,12 +50,12 @@ contract MixinAssetProxy is } /// @dev Makes multiple transfers of assets. Either succeeds or throws. - /// @param assetMetadata Array of byte arrays encoded for the respective asset proxy. + /// @param assetData Array of byte arrays encoded for the respective asset proxy. /// @param from Array of addresses to transfer assets from. /// @param to Array of addresses to transfer assets to. /// @param amounts Array of amounts of assets to transfer. function batchTransferFrom( - bytes[] memory assetMetadata, + bytes[] memory assetData, address[] memory from, address[] memory to, uint256[] memory amounts @@ -63,9 +63,9 @@ contract MixinAssetProxy is public onlyAuthorized { - for (uint256 i = 0; i < assetMetadata.length; i++) { + for (uint256 i = 0; i < assetData.length; i++) { transferFromInternal( - assetMetadata[i], + assetData[i], from[i], to[i], amounts[i] diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol index 7e1848889..22f43b12f 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol @@ -26,12 +26,12 @@ contract IAssetProxy is { /// @dev Transfers assets. Either succeeds or throws. - /// @param assetMetadata Byte array encoded for the respective asset proxy. + /// @param assetData Byte array encoded for the respective asset proxy. /// @param from Address to transfer asset from. /// @param to Address to transfer asset to. /// @param amount Amount of asset to transfer. function transferFrom( - bytes assetMetadata, + bytes assetData, address from, address to, uint256 amount @@ -39,12 +39,12 @@ contract IAssetProxy is external; /// @dev Makes multiple transfers of assets. Either succeeds or throws. - /// @param assetMetadata Array of byte arrays encoded for the respective asset proxy. + /// @param assetData Array of byte arrays encoded for the respective asset proxy. /// @param from Array of addresses to transfer assets from. /// @param to Array of addresses to transfer assets to. /// @param amounts Array of amounts of assets to transfer. function batchTransferFrom( - bytes[] memory assetMetadata, + bytes[] memory assetData, address[] memory from, address[] memory to, uint256[] memory amounts diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibAssetProxyErrors.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibAssetProxyErrors.sol index e0c7fc796..65bdacdb7 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibAssetProxyErrors.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibAssetProxyErrors.sol @@ -27,11 +27,6 @@ contract LibAssetProxyErrors { string constant AUTHORIZED_ADDRESS_MISMATCH = "AUTHORIZED_ADDRESS_MISMATCH"; // Address at index does not match given target address. /// AssetProxy errors /// - string constant ASSET_PROXY_ID_MISMATCH = "ASSET_PROXY_ID_MISMATCH"; // Proxy id in metadata does not match this proxy id. string constant INVALID_AMOUNT = "INVALID_AMOUNT"; // Transfer amount must equal 1. - string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Transfer failed. - - /// Length validation errors /// - string constant LENGTH_21_REQUIRED = "LENGTH_21_REQUIRED"; // Byte array must have a length of 21. - string constant LENGTH_53_REQUIRED = "LENGTH_53_REQUIRED"; // Byte array must have a length of 53. + string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Transfer failed. } diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol index de9d65a53..a52cb56f9 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/mixins/MAssetProxy.sol @@ -26,12 +26,12 @@ contract MAssetProxy is { /// @dev Internal version of `transferFrom`. - /// @param assetMetadata Encoded byte array. + /// @param assetData Encoded byte array. /// @param from Address to transfer asset from. /// @param to Address to transfer asset to. /// @param amount Amount of asset to transfer. function transferFromInternal( - bytes memory assetMetadata, + bytes memory assetData, address from, address to, uint256 amount diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol index b7b308069..51f99bafa 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/Exchange.sol @@ -40,11 +40,11 @@ contract Exchange is string constant public VERSION = "2.0.1-alpha"; // Mixins are instantiated in the order they are inherited - constructor (bytes memory _zrxProxyData) + constructor (bytes memory _zrxAssetData) public MixinExchangeCore() MixinMatchOrders() - MixinSettlement(_zrxProxyData) + MixinSettlement(_zrxAssetData) MixinSignatureValidator() MixinTransactions() MixinAssetProxyDispatcher() diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol index 8f9342739..9e0246303 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol @@ -19,12 +19,14 @@ pragma solidity ^0.4.24; import "../../utils/Ownable/Ownable.sol"; +import "../../utils/LibBytes/LibBytes.sol"; import "./libs/LibExchangeErrors.sol"; import "./mixins/MAssetProxyDispatcher.sol"; import "../AssetProxy/interfaces/IAssetProxy.sol"; contract MixinAssetProxyDispatcher is Ownable, + LibBytes, LibExchangeErrors, MAssetProxyDispatcher { @@ -80,12 +82,14 @@ contract MixinAssetProxyDispatcher is } /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws. - /// @param assetMetadata Byte array encoded for the respective asset proxy. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param assetProxyId Id of assetProxy to dispach to. /// @param from Address to transfer token from. /// @param to Address to transfer token to. /// @param amount Amount of token to transfer. function dispatchTransferFrom( - bytes memory assetMetadata, + bytes memory assetData, + uint8 assetProxyId, address from, address to, uint256 amount @@ -94,18 +98,10 @@ contract MixinAssetProxyDispatcher is { // Do nothing if no amount should be transferred. if (amount > 0) { - - // Lookup asset proxy - uint256 length = assetMetadata.length; - require( - length > 0, - LENGTH_GREATER_THAN_0_REQUIRED - ); - uint8 assetProxyId = uint8(assetMetadata[length - 1]); + // Lookup assetProxy IAssetProxy assetProxy = assetProxies[assetProxyId]; - // transferFrom will either succeed or throw. - assetProxy.transferFrom(assetMetadata, from, to, amount); + assetProxy.transferFrom(assetData, from, to, amount); } } } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 42128c92a..0a0f0209a 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -50,7 +50,7 @@ contract MixinExchangeCore is ////// Core exchange functions ////// - /// @dev Cancels all orders reated by sender with a salt less than or equal to the specified salt value. + /// @dev Cancels all orders created by sender with a salt less than or equal to the specified salt value. /// @param salt Orders created with a salt less or equal to this value will be cancelled. function cancelOrdersUpTo(uint256 salt) external @@ -108,9 +108,6 @@ contract MixinExchangeCore is // Compute proportional fill amounts fillResults = calculateFillResults(order, takerAssetFilledAmount); - // Settle order - settleOrder(order, takerAddress, fillResults); - // Update exchange internal state updateFilledState( order, @@ -119,6 +116,10 @@ contract MixinExchangeCore is orderInfo.orderTakerAssetFilledAmount, fillResults ); + + // Settle order + settleOrder(order, takerAddress, fillResults); + return fillResults; } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index ed76287e0..517b743fe 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -14,7 +14,6 @@ pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; -import "../../utils/LibBytes/LibBytes.sol"; import "./libs/LibMath.sol"; import "./libs/LibOrder.sol"; import "./libs/LibFillResults.sol"; @@ -25,7 +24,6 @@ import "./mixins/MSettlement.sol"; import "./mixins/MTransactions.sol"; contract MixinMatchOrders is - LibBytes, LibMath, LibExchangeErrors, MExchangeCore, @@ -94,14 +92,6 @@ contract MixinMatchOrders is rightSignature ); - // Settle matched orders. Succeeds or throws. - settleMatchedOrders( - leftOrder, - rightOrder, - takerAddress, - matchedFillResults - ); - // Update exchange state updateFilledState( leftOrder, @@ -117,6 +107,14 @@ contract MixinMatchOrders is rightOrderInfo.orderTakerAssetFilledAmount, matchedFillResults.right ); + + // Settle matched orders. Succeeds or throws. + settleMatchedOrders( + leftOrder, + rightOrder, + takerAddress, + matchedFillResults + ); return matchedFillResults; } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol index 646d3ed58..29a9c87bd 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSettlement.sol @@ -19,6 +19,7 @@ pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; +import "../../utils/LibBytes/LibBytes.sol"; import "./libs/LibMath.sol"; import "./libs/LibFillResults.sol"; import "./libs/LibOrder.sol"; @@ -28,33 +29,26 @@ import "./mixins/MSettlement.sol"; import "./mixins/MAssetProxyDispatcher.sol"; contract MixinSettlement is + LibBytes, LibMath, LibExchangeErrors, MMatchOrders, MSettlement, MAssetProxyDispatcher { - // ZRX metadata used for fee transfers. + // ZRX address encoded as a byte array. // This will be constant throughout the life of the Exchange contract, // since ZRX will always be transferred via the ERC20 AssetProxy. - bytes internal ZRX_PROXY_DATA; + bytes internal ZRX_ASSET_DATA; + uint8 constant ZRX_PROXY_ID = 1; - /// @dev Gets the ZRX metadata used for fee transfers. - function zrxProxyData() - external - view - returns (bytes memory) - { - return ZRX_PROXY_DATA; - } - - /// TODO: _zrxProxyData should be a constant in production. + /// TODO: _zrxAssetData should be a constant in production. /// @dev Constructor sets the metadata that will be used for paying ZRX fees. - /// @param _zrxProxyData Byte array containing ERC20 proxy id concatenated with address of ZRX. - constructor (bytes memory _zrxProxyData) + /// @param _zrxAssetData Byte array containing ERC20 proxy id concatenated with address of ZRX. + constructor (bytes memory _zrxAssetData) public { - ZRX_PROXY_DATA = _zrxProxyData; + ZRX_ASSET_DATA = _zrxAssetData; } /// @dev Settles an order by transferring assets between counterparties. @@ -68,26 +62,33 @@ contract MixinSettlement is ) internal { + uint8 makerAssetProxyId = uint8(popLastByte(order.makerAssetData)); + uint8 takerAssetProxyId = uint8(popLastByte(order.takerAssetData)); + bytes memory zrxAssetData = ZRX_ASSET_DATA; dispatchTransferFrom( order.makerAssetData, + makerAssetProxyId, order.makerAddress, takerAddress, fillResults.makerAssetFilledAmount ); dispatchTransferFrom( order.takerAssetData, + takerAssetProxyId, takerAddress, order.makerAddress, fillResults.takerAssetFilledAmount ); dispatchTransferFrom( - ZRX_PROXY_DATA, + zrxAssetData, + ZRX_PROXY_ID, order.makerAddress, order.feeRecipientAddress, fillResults.makerFeePaid ); dispatchTransferFrom( - ZRX_PROXY_DATA, + zrxAssetData, + ZRX_PROXY_ID, takerAddress, order.feeRecipientAddress, fillResults.takerFeePaid @@ -107,21 +108,27 @@ contract MixinSettlement is ) internal { + uint8 leftMakerAssetProxyId = uint8(popLastByte(leftOrder.makerAssetData)); + uint8 rightMakerAssetProxyId = uint8(popLastByte(rightOrder.makerAssetData)); + bytes memory zrxAssetData = ZRX_ASSET_DATA; // Order makers and taker dispatchTransferFrom( leftOrder.makerAssetData, + leftMakerAssetProxyId, leftOrder.makerAddress, rightOrder.makerAddress, matchedFillResults.right.takerAssetFilledAmount ); dispatchTransferFrom( rightOrder.makerAssetData, + rightMakerAssetProxyId, rightOrder.makerAddress, leftOrder.makerAddress, matchedFillResults.left.takerAssetFilledAmount ); dispatchTransferFrom( leftOrder.makerAssetData, + leftMakerAssetProxyId, leftOrder.makerAddress, takerAddress, matchedFillResults.leftMakerAssetSpreadAmount @@ -129,13 +136,15 @@ contract MixinSettlement is // Maker fees dispatchTransferFrom( - ZRX_PROXY_DATA, + zrxAssetData, + ZRX_PROXY_ID, leftOrder.makerAddress, leftOrder.feeRecipientAddress, matchedFillResults.left.makerFeePaid ); dispatchTransferFrom( - ZRX_PROXY_DATA, + zrxAssetData, + ZRX_PROXY_ID, rightOrder.makerAddress, rightOrder.feeRecipientAddress, matchedFillResults.right.makerFeePaid @@ -144,7 +153,8 @@ contract MixinSettlement is // Taker fees if (leftOrder.feeRecipientAddress == rightOrder.feeRecipientAddress) { dispatchTransferFrom( - ZRX_PROXY_DATA, + zrxAssetData, + ZRX_PROXY_ID, takerAddress, leftOrder.feeRecipientAddress, safeAdd( @@ -154,13 +164,15 @@ contract MixinSettlement is ); } else { dispatchTransferFrom( - ZRX_PROXY_DATA, + zrxAssetData, + ZRX_PROXY_ID, takerAddress, leftOrder.feeRecipientAddress, matchedFillResults.left.takerFeePaid ); dispatchTransferFrom( - ZRX_PROXY_DATA, + zrxAssetData, + ZRX_PROXY_ID, takerAddress, rightOrder.feeRecipientAddress, matchedFillResults.right.takerFeePaid diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol index 48a0c5552..1a556dfe2 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol @@ -93,7 +93,7 @@ contract MixinSignatureValidator is ); // Pop last byte off of signature byte array. - SignatureType signatureType = SignatureType(uint8(popByte(signature))); + SignatureType signatureType = SignatureType(uint8(popLastByte(signature))); // Variables are not scoped in Solidity. uint8 v; @@ -183,7 +183,7 @@ contract MixinSignatureValidator is // | 0x14 + x | 1 | Signature type is always "\x06" | } else if (signatureType == SignatureType.Validator) { // Pop last 20 bytes off of signature byte array. - address validator = popAddress(signature); + address validator = popLast20Bytes(signature); // Ensure signer has approved validator. if (!allowedValidators[signer][validator]) { return false; diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol index 0ad0710ce..a7849f4cb 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol @@ -19,7 +19,6 @@ pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; -import "../../utils/LibBytes/LibBytes.sol"; import "./libs/LibMath.sol"; import "./libs/LibOrder.sol"; import "./libs/LibFillResults.sol"; @@ -27,7 +26,6 @@ import "./libs/LibExchangeErrors.sol"; import "./mixins/MExchangeCore.sol"; contract MixinWrapperFunctions is - LibBytes, LibMath, LibFillResults, LibExchangeErrors, @@ -40,7 +38,8 @@ contract MixinWrapperFunctions is function fillOrKillOrder( LibOrder.Order memory order, uint256 takerAssetFillAmount, - bytes memory signature) + bytes memory signature + ) public returns (FillResults memory fillResults) { @@ -65,7 +64,8 @@ contract MixinWrapperFunctions is function fillOrderNoThrow( LibOrder.Order memory order, uint256 takerAssetFillAmount, - bytes memory signature) + bytes memory signature + ) public returns (FillResults memory fillResults) { @@ -91,12 +91,12 @@ contract MixinWrapperFunctions is // | | 0x0E0 | | 8. takerFeeAmount | // | | 0x100 | | 9. expirationTimeSeconds | // | | 0x120 | | 10. salt | - // | | 0x140 | | 11. Offset to makerAssetProxyMetadata (*) | - // | | 0x160 | | 12. Offset to takerAssetProxyMetadata (*) | - // | | 0x180 | 32 | makerAssetProxyMetadata Length | - // | | 0x1A0 | ** | makerAssetProxyMetadata Contents | - // | | 0x1C0 | 32 | takerAssetProxyMetadata Length | - // | | 0x1E0 | ** | takerAssetProxyMetadata Contents | + // | | 0x140 | | 11. Offset to makerAssetData (*) | + // | | 0x160 | | 12. Offset to takerAssetData (*) | + // | | 0x180 | 32 | makerAssetData Length | + // | | 0x1A0 | ** | makerAssetData Contents | + // | | 0x1C0 | 32 | takerAssetData Length | + // | | 0x1E0 | ** | takerAssetData Contents | // | | 0x200 | 32 | signature Length | // | | 0x220 | ** | signature Contents | @@ -163,43 +163,45 @@ contract MixinWrapperFunctions is mstore(add(dataAreaEnd, 0xE0), mload(add(sourceOffset, 0xE0))) // takerFeeAmount mstore(add(dataAreaEnd, 0x100), mload(add(sourceOffset, 0x100))) // expirationTimeSeconds mstore(add(dataAreaEnd, 0x120), mload(add(sourceOffset, 0x120))) // salt - mstore(add(dataAreaEnd, 0x140), mload(add(sourceOffset, 0x140))) // Offset to makerAssetProxyMetadata - mstore(add(dataAreaEnd, 0x160), mload(add(sourceOffset, 0x160))) // Offset to takerAssetProxyMetadata + mstore(add(dataAreaEnd, 0x140), mload(add(sourceOffset, 0x140))) // Offset to makerAssetData + mstore(add(dataAreaEnd, 0x160), mload(add(sourceOffset, 0x160))) // Offset to takerAssetData dataAreaEnd := add(dataAreaEnd, 0x180) sourceOffset := add(sourceOffset, 0x180) - // Write offset to <order.makerAssetProxyMetadata> + // Write offset to <order.makerAssetData> mstore(add(dataAreaStart, mul(10, 0x20)), sub(dataAreaEnd, dataAreaStart)) - // Calculate length of <order.makerAssetProxyMetadata> + // Calculate length of <order.makerAssetData> + sourceOffset := mload(add(order, 0x140)) // makerAssetData arrayLenBytes := mload(sourceOffset) sourceOffset := add(sourceOffset, 0x20) arrayLenWords := div(add(arrayLenBytes, 0x1F), 0x20) - // Write length of <order.makerAssetProxyMetadata> + // Write length of <order.makerAssetData> mstore(dataAreaEnd, arrayLenBytes) dataAreaEnd := add(dataAreaEnd, 0x20) - // Write contents of <order.makerAssetProxyMetadata> + // Write contents of <order.makerAssetData> for {let i := 0} lt(i, arrayLenWords) {i := add(i, 1)} { mstore(dataAreaEnd, mload(sourceOffset)) dataAreaEnd := add(dataAreaEnd, 0x20) sourceOffset := add(sourceOffset, 0x20) } - // Write offset to <order.takerAssetProxyMetadata> + // Write offset to <order.takerAssetData> mstore(add(dataAreaStart, mul(11, 0x20)), sub(dataAreaEnd, dataAreaStart)) - // Calculate length of <order.takerAssetProxyMetadata> + // Calculate length of <order.takerAssetData> + sourceOffset := mload(add(order, 0x160)) // takerAssetData arrayLenBytes := mload(sourceOffset) sourceOffset := add(sourceOffset, 0x20) arrayLenWords := div(add(arrayLenBytes, 0x1F), 0x20) - // Write length of <order.takerAssetProxyMetadata> + // Write length of <order.takerAssetData> mstore(dataAreaEnd, arrayLenBytes) dataAreaEnd := add(dataAreaEnd, 0x20) - // Write contents of <order.takerAssetProxyMetadata> + // Write contents of <order.takerAssetData> for {let i := 0} lt(i, arrayLenWords) {i := add(i, 1)} { mstore(dataAreaEnd, mload(sourceOffset)) dataAreaEnd := add(dataAreaEnd, 0x20) @@ -264,7 +266,8 @@ contract MixinWrapperFunctions is function batchFillOrders( LibOrder.Order[] memory orders, uint256[] memory takerAssetFillAmounts, - bytes[] memory signatures) + bytes[] memory signatures + ) public { for (uint256 i = 0; i < orders.length; i++) { @@ -283,7 +286,8 @@ contract MixinWrapperFunctions is function batchFillOrKillOrders( LibOrder.Order[] memory orders, uint256[] memory takerAssetFillAmounts, - bytes[] memory signatures) + bytes[] memory signatures + ) public { for (uint256 i = 0; i < orders.length; i++) { @@ -303,7 +307,8 @@ contract MixinWrapperFunctions is function batchFillOrdersNoThrow( LibOrder.Order[] memory orders, uint256[] memory takerAssetFillAmounts, - bytes[] memory signatures) + bytes[] memory signatures + ) public { for (uint256 i = 0; i < orders.length; i++) { @@ -323,18 +328,18 @@ contract MixinWrapperFunctions is function marketSellOrders( LibOrder.Order[] memory orders, uint256 takerAssetFillAmount, - bytes[] memory signatures) + bytes[] memory signatures + ) public returns (FillResults memory totalFillResults) { + bytes memory takerAssetData = orders[0].takerAssetData; + for (uint256 i = 0; i < orders.length; i++) { - // Token being sold by taker must be the same for each order - // TODO: optimize by only using takerAssetData for first order. - require( - areBytesEqual(orders[i].takerAssetData, orders[0].takerAssetData), - ASSET_DATA_MISMATCH - ); + // We assume that asset being sold by taker is the same for each order. + // Rather than passing this in as calldata, we use the takerAssetData from the first order in all later orders. + orders[i].takerAssetData = takerAssetData; // Calculate the remaining amount of takerAsset to sell uint256 remainingTakerAssetFillAmount = safeSub(takerAssetFillAmount, totalFillResults.takerAssetFilledAmount); @@ -346,6 +351,14 @@ contract MixinWrapperFunctions is signatures[i] ); + // HACK: the proxyId is "popped" from the byte array before a fill is settled + // by subtracting from the length of the array. Since the popped byte is + // still in memory, we can "unpop" it by incrementing the length of the byte array. + assembly { + let len := mload(takerAssetData) + mstore(takerAssetData, add(len, 1)) + } + // Update amounts filled and fees paid by maker and taker addFillResults(totalFillResults, singleFillResults); @@ -366,18 +379,18 @@ contract MixinWrapperFunctions is function marketSellOrdersNoThrow( LibOrder.Order[] memory orders, uint256 takerAssetFillAmount, - bytes[] memory signatures) + bytes[] memory signatures + ) public returns (FillResults memory totalFillResults) { + bytes memory takerAssetData = orders[0].takerAssetData; + for (uint256 i = 0; i < orders.length; i++) { - // Token being sold by taker must be the same for each order - // TODO: optimize by only using takerAssetData for first order. - require( - areBytesEqual(orders[i].takerAssetData, orders[0].takerAssetData), - ASSET_DATA_MISMATCH - ); + // We assume that asset being sold by taker is the same for each order. + // Rather than passing this in as calldata, we use the takerAssetData from the first order in all later orders. + orders[i].takerAssetData = takerAssetData; // Calculate the remaining amount of takerAsset to sell uint256 remainingTakerAssetFillAmount = safeSub(takerAssetFillAmount, totalFillResults.takerAssetFilledAmount); @@ -408,18 +421,18 @@ contract MixinWrapperFunctions is function marketBuyOrders( LibOrder.Order[] memory orders, uint256 makerAssetFillAmount, - bytes[] memory signatures) + bytes[] memory signatures + ) public returns (FillResults memory totalFillResults) { + bytes memory makerAssetData = orders[0].makerAssetData; + for (uint256 i = 0; i < orders.length; i++) { - // Token being bought by taker must be the same for each order - // TODO: optimize by only using makerAssetData for first order. - require( - areBytesEqual(orders[i].makerAssetData, orders[0].makerAssetData), - ASSET_DATA_MISMATCH - ); + // We assume that asset being bought by taker is the same for each order. + // Rather than passing this in as calldata, we copy the makerAssetData from the first order onto all later orders. + orders[i].makerAssetData = makerAssetData; // Calculate the remaining amount of makerAsset to buy uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount); @@ -439,6 +452,14 @@ contract MixinWrapperFunctions is signatures[i] ); + // HACK: the proxyId is "popped" from the byte array before a fill is settled + // by subtracting from the length of the array. Since the popped byte is + // still in memory, we can "unpop" it by incrementing the length of the byte array. + assembly { + let len := mload(makerAssetData) + mstore(makerAssetData, add(len, 1)) + } + // Update amounts filled and fees paid by maker and taker addFillResults(totalFillResults, singleFillResults); @@ -459,18 +480,18 @@ contract MixinWrapperFunctions is function marketBuyOrdersNoThrow( LibOrder.Order[] memory orders, uint256 makerAssetFillAmount, - bytes[] memory signatures) + bytes[] memory signatures + ) public returns (FillResults memory totalFillResults) { + bytes memory makerAssetData = orders[0].makerAssetData; + for (uint256 i = 0; i < orders.length; i++) { - // Token being bought by taker must be the same for each order - // TODO: optimize by only using makerAssetData for first order. - require( - areBytesEqual(orders[i].makerAssetData, orders[0].makerAssetData), - ASSET_DATA_MISMATCH - ); + // We assume that asset being bought by taker is the same for each order. + // Rather than passing this in as calldata, we copy the makerAssetData from the first order onto all later orders. + orders[i].makerAssetData = makerAssetData; // Calculate the remaining amount of makerAsset to buy uint256 remainingMakerAssetFillAmount = safeSub(makerAssetFillAmount, totalFillResults.makerAssetFilledAmount); diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol index 3ce5ef157..2c331dc34 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol @@ -28,7 +28,8 @@ contract IAssetProxyDispatcher { function registerAssetProxy( uint8 assetProxyId, address newAssetProxy, - address oldAssetProxy) + address oldAssetProxy + ) external; /// @dev Gets an asset proxy. diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol index d973bf001..2f9a5bc7c 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/ITransactions.sol @@ -28,6 +28,7 @@ contract ITransactions { uint256 salt, address signer, bytes data, - bytes signature) + bytes signature + ) external; } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IWrapperFunctions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IWrapperFunctions.sol index 8682b394a..acd4f359c 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IWrapperFunctions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IWrapperFunctions.sol @@ -33,7 +33,8 @@ contract IWrapperFunctions is function fillOrKillOrder( LibOrder.Order memory order, uint256 takerAssetFillAmount, - bytes memory signature) + bytes memory signature + ) public returns (LibFillResults.FillResults memory fillResults); @@ -46,7 +47,8 @@ contract IWrapperFunctions is function fillOrderNoThrow( LibOrder.Order memory order, uint256 takerAssetFillAmount, - bytes memory signature) + bytes memory signature + ) public returns (LibFillResults.FillResults memory fillResults); @@ -57,7 +59,8 @@ contract IWrapperFunctions is function batchFillOrders( LibOrder.Order[] memory orders, uint256[] memory takerAssetFillAmounts, - bytes[] memory signatures) + bytes[] memory signatures + ) public; /// @dev Synchronously executes multiple calls of fillOrKill. @@ -67,7 +70,8 @@ contract IWrapperFunctions is function batchFillOrKillOrders( LibOrder.Order[] memory orders, uint256[] memory takerAssetFillAmounts, - bytes[] memory signatures) + bytes[] memory signatures + ) public; /// @dev Fills an order with specified parameters and ECDSA signature. @@ -78,7 +82,8 @@ contract IWrapperFunctions is function batchFillOrdersNoThrow( LibOrder.Order[] memory orders, uint256[] memory takerAssetFillAmounts, - bytes[] memory signatures) + bytes[] memory signatures + ) public; /// @dev Synchronously executes multiple calls of fillOrder until total amount of takerAsset is sold by taker. @@ -89,7 +94,8 @@ contract IWrapperFunctions is function marketSellOrders( LibOrder.Order[] memory orders, uint256 takerAssetFillAmount, - bytes[] memory signatures) + bytes[] memory signatures + ) public returns (LibFillResults.FillResults memory totalFillResults); @@ -102,7 +108,8 @@ contract IWrapperFunctions is function marketSellOrdersNoThrow( LibOrder.Order[] memory orders, uint256 takerAssetFillAmount, - bytes[] memory signatures) + bytes[] memory signatures + ) public returns (LibFillResults.FillResults memory totalFillResults); @@ -114,7 +121,8 @@ contract IWrapperFunctions is function marketBuyOrders( LibOrder.Order[] memory orders, uint256 makerAssetFillAmount, - bytes[] memory signatures) + bytes[] memory signatures + ) public returns (LibFillResults.FillResults memory totalFillResults); @@ -127,7 +135,8 @@ contract IWrapperFunctions is function marketBuyOrdersNoThrow( LibOrder.Order[] memory orders, uint256 makerAssetFillAmount, - bytes[] memory signatures) + bytes[] memory signatures + ) public returns (LibFillResults.FillResults memory totalFillResults); diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol index 2b4bbeec4..48dd0f8be 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol @@ -25,7 +25,6 @@ contract LibExchangeErrors { string constant INVALID_TAKER = "INVALID_TAKER"; // Invalid takerAddress. string constant INVALID_SENDER = "INVALID_SENDER"; // Invalid `msg.sender`. string constant INVALID_ORDER_SIGNATURE = "INVALID_ORDER_SIGNATURE"; // Signature validation failed. - string constant ASSET_DATA_MISMATCH = "ASSET_DATA_MISMATCH"; // Asset data must be the same for each order. /// fillOrder validation errors /// string constant INVALID_TAKER_AMOUNT = "INVALID_TAKER_AMOUNT"; // takerAssetFillAmount cannot equal 0. diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol index 87c5f6361..d16a830f4 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol @@ -33,12 +33,14 @@ contract MAssetProxyDispatcher is ); /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws. - /// @param assetMetadata Byte array encoded for the respective asset proxy. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param assetProxyId Id of assetProxy to dispach to. /// @param from Address to transfer token from. /// @param to Address to transfer token to. /// @param amount Amount of token to transfer. function dispatchTransferFrom( - bytes memory assetMetadata, + bytes memory assetData, + uint8 assetProxyId, address from, address to, uint256 amount diff --git a/packages/contracts/src/contracts/current/test/DummyERC20Token/DummyERC20Token.sol b/packages/contracts/src/contracts/current/test/DummyERC20Token/DummyERC20Token.sol index 0c7b18c0c..b2fe2df06 100644 --- a/packages/contracts/src/contracts/current/test/DummyERC20Token/DummyERC20Token.sol +++ b/packages/contracts/src/contracts/current/test/DummyERC20Token/DummyERC20Token.sol @@ -31,7 +31,8 @@ contract DummyERC20Token is Mintable, Ownable { string _name, string _symbol, uint256 _decimals, - uint256 _totalSupply) + uint256 _totalSupply + ) public { name = _name; diff --git a/packages/contracts/src/contracts/current/test/DummyERC721Receiver/DummyERC721Receiver.sol b/packages/contracts/src/contracts/current/test/DummyERC721Receiver/DummyERC721Receiver.sol new file mode 100644 index 000000000..c584d0b54 --- /dev/null +++ b/packages/contracts/src/contracts/current/test/DummyERC721Receiver/DummyERC721Receiver.sol @@ -0,0 +1,63 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 Smart Contract Solutions, Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +pragma solidity ^0.4.24; + +import "../../tokens/ERC721Token/IERC721Receiver.sol"; + +contract DummyERC721Receiver is + IERC721Receiver +{ + + event TokenReceived( + address from, + uint256 tokenId, + bytes data + ); + + /** + * @notice Handle the receipt of an NFT + * @dev The ERC721 smart contract calls this function on the recipient + * after a `safetransfer`. This function MAY throw to revert and reject the + * transfer. This function MUST use 50,000 gas or less. Return of other + * than the magic value MUST result in the transaction being reverted. + * Note: the contract address is always the message sender. + * @param _from The sending address + * @param _tokenId The NFT identifier which is being transfered + * @param _data Additional data with no specified format + * @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))` + */ + function onERC721Received( + address _from, + uint256 _tokenId, + bytes _data + ) + public + returns (bytes4) + { + emit TokenReceived(_from, _tokenId, _data); + return ERC721_RECEIVED; + } +} diff --git a/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol b/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol index 369a2950d..5503eb2f2 100644 --- a/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol +++ b/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol @@ -34,7 +34,8 @@ contract DummyERC721Token is */ constructor ( string name, - string symbol) + string symbol + ) public ERC721Token(name, symbol) {} diff --git a/packages/contracts/src/contracts/current/test/TestAssetDataDecoders/TestAssetDataDecoders.sol b/packages/contracts/src/contracts/current/test/TestAssetDataDecoders/TestAssetDataDecoders.sol new file mode 100644 index 000000000..5987291d2 --- /dev/null +++ b/packages/contracts/src/contracts/current/test/TestAssetDataDecoders/TestAssetDataDecoders.sol @@ -0,0 +1,56 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; +pragma experimental ABIEncoderV2; + +import "../../protocol/AssetProxy/ERC20Proxy.sol"; +import "../../protocol/AssetProxy/ERC721Proxy.sol"; + +contract TestAssetDataDecoders is + ERC721Proxy +{ + /// @dev Decodes ERC721 Asset data. + /// @param assetData Encoded byte array. + /// @return proxyId Intended ERC721 proxy id. + /// @return token ERC721 token address. + /// @return tokenId ERC721 token id. + /// @return receiverData Additional data with no specific format, which + /// is passed to the receiving contract's onERC721Received. + function publicDecodeERC721Data(bytes memory assetData) + public + pure + returns ( + address token, + uint256 tokenId, + bytes memory receiverData + ) + { + ( + token, + tokenId, + receiverData + ) = decodeERC721AssetData(assetData); + + return ( + token, + tokenId, + receiverData + ); + } +} diff --git a/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol index 11ca0617d..d469a07f0 100644 --- a/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol @@ -23,12 +23,13 @@ import "../../protocol/Exchange/MixinAssetProxyDispatcher.sol"; contract TestAssetProxyDispatcher is MixinAssetProxyDispatcher { function publicDispatchTransferFrom( - bytes memory assetMetadata, + bytes memory assetData, + uint8 assetProxyId, address from, address to, uint256 amount) public { - dispatchTransferFrom(assetMetadata, from, to, amount); + dispatchTransferFrom(assetData, assetProxyId, from, to, amount); } } diff --git a/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol b/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol index 69554605d..6f1898acd 100644 --- a/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol +++ b/packages/contracts/src/contracts/current/test/TestLibBytes/TestLibBytes.sol @@ -28,24 +28,24 @@ contract TestLibBytes is /// @dev Pops the last byte off of a byte array by modifying its length. /// @param b Byte array that will be modified. /// @return The byte that was popped off. - function publicPopByte(bytes memory b) + function publicPopLastByte(bytes memory b) public pure returns (bytes memory, bytes1 result) { - result = popByte(b); + result = popLastByte(b); return (b, result); } /// @dev Pops the last 20 bytes off of a byte array by modifying its length. /// @param b Byte array that will be modified. /// @return The 20 byte address that was popped off. - function publicPopAddress(bytes memory b) + function publicPopLast20Bytes(bytes memory b) public pure returns (bytes memory, address result) { - result = popAddress(b); + result = popLast20Bytes(b); return (b, result); } @@ -62,13 +62,29 @@ contract TestLibBytes is return equal; } + /// @dev Performs a deep copy of a byte array onto another byte array of greater than or equal length. + /// @param dest Byte array that will be overwritten with source bytes. + /// @param source Byte array to copy onto dest bytes. + function publicDeepCopyBytes( + bytes memory dest, + bytes memory source + ) + public + pure + returns (bytes memory) + { + deepCopyBytes(dest, source); + return dest; + } + /// @dev Reads an address from a position in a byte array. /// @param b Byte array containing an address. /// @param index Index in byte array of address. /// @return address from byte array. function publicReadAddress( bytes memory b, - uint256 index) + uint256 index + ) public pure returns (address result) @@ -84,7 +100,8 @@ contract TestLibBytes is function publicWriteAddress( bytes memory b, uint256 index, - address input) + address input + ) public pure returns (bytes memory) @@ -99,7 +116,8 @@ contract TestLibBytes is /// @return bytes32 value from byte array. function publicReadBytes32( bytes memory b, - uint256 index) + uint256 index + ) public pure returns (bytes32 result) @@ -115,7 +133,8 @@ contract TestLibBytes is function publicWriteBytes32( bytes memory b, uint256 index, - bytes32 input) + bytes32 input + ) public pure returns (bytes memory) @@ -130,7 +149,8 @@ contract TestLibBytes is /// @return uint256 value from byte array. function publicReadUint256( bytes memory b, - uint256 index) + uint256 index + ) public pure returns (uint256 result) @@ -146,7 +166,8 @@ contract TestLibBytes is function publicWriteUint256( bytes memory b, uint256 index, - uint256 input) + uint256 input + ) public pure returns (bytes memory) @@ -166,4 +187,38 @@ contract TestLibBytes is result = readFirst4(b); return result; } + + /// @dev Reads nested bytes from a specific position. + /// @param b Byte array containing nested bytes. + /// @param index Index of nested bytes. + /// @return result Nested bytes. + function publicReadBytes( + bytes memory b, + uint256 index + ) + public + pure + returns (bytes memory result) + { + result = readBytes(b, index); + return result; + } + + /// @dev Inserts bytes at a specific position in a byte array. + /// @param b Byte array to insert <input> into. + /// @param index Index in byte array of <input>. + /// @param input bytes to insert. + /// @return b Updated input byte array + function publicWriteBytes( + bytes memory b, + uint256 index, + bytes memory input + ) + public + pure + returns (bytes memory) + { + writeBytes(b, index, input); + return b; + } } diff --git a/packages/contracts/src/contracts/current/test/TestLibMem/TestLibMem.sol b/packages/contracts/src/contracts/current/test/TestLibMem/TestLibMem.sol new file mode 100644 index 000000000..b7e2e06b8 --- /dev/null +++ b/packages/contracts/src/contracts/current/test/TestLibMem/TestLibMem.sol @@ -0,0 +1,56 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; + +import "../../utils/LibMem/LibMem.sol"; + +contract TestLibMem is + LibMem +{ + + /// @dev Copies a block of memory from one location to another. + /// @param mem Memory contents we want to apply memCopy to + /// @param dest Destination offset into <mem>. + /// @param source Source offset into <mem>. + /// @param length Length of bytes to copy from <source> to <dest> + /// @return mem Memory contents after calling memCopy. + function testMemcpy( + bytes mem, + uint256 dest, + uint256 source, + uint256 length + ) + public // not external, we need input in memory + pure + returns (bytes) + { + // Sanity check. Overflows are not checked. + require(source + length <= mem.length); + require(dest + length <= mem.length); + + // Get pointer to memory contents + uint256 offset = getMemAddress(mem) + 32; + + // Execute memCopy adjusted for memory array location + memCopy(offset + dest, offset + source, length); + + // Return modified memory contents + return mem; + } +} diff --git a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol index df2221c93..10d7ce41a 100644 --- a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol +++ b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol @@ -18,31 +18,36 @@ pragma solidity ^0.4.24; -contract LibBytes { +import "../LibMem/LibMem.sol"; + +contract LibBytes is + LibMem +{ // Revert reasons - string constant GT_ZERO_LENGTH_REQUIRED = "Length must be greater than 0."; - string constant GTE_4_LENGTH_REQUIRED = "Length must be greater than or equal to 4."; - string constant GTE_20_LENGTH_REQUIRED = "Length must be greater than or equal to 20."; - string constant GTE_32_LENGTH_REQUIRED = "Length must be greater than or equal to 32."; - string constant INDEX_OUT_OF_BOUNDS = "Specified array index is out of bounds."; + string constant GREATER_THAN_ZERO_LENGTH_REQUIRED = "GREATER_THAN_ZERO_LENGTH_REQUIRED"; + string constant GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED"; + string constant GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED"; + string constant GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED"; + string constant GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED"; + string constant GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED = "GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED"; /// @dev Pops the last byte off of a byte array by modifying its length. /// @param b Byte array that will be modified. /// @return The byte that was popped off. - function popByte(bytes memory b) + function popLastByte(bytes memory b) internal pure returns (bytes1 result) { require( b.length > 0, - GT_ZERO_LENGTH_REQUIRED + GREATER_THAN_ZERO_LENGTH_REQUIRED ); // Store last byte. result = b[b.length - 1]; - + assembly { // Decrement length of byte array. let newLen := sub(mload(b), 1) @@ -54,14 +59,14 @@ contract LibBytes { /// @dev Pops the last 20 bytes off of a byte array by modifying its length. /// @param b Byte array that will be modified. /// @return The 20 byte address that was popped off. - function popAddress(bytes memory b) + function popLast20Bytes(bytes memory b) internal pure returns (address result) { require( b.length >= 20, - GTE_20_LENGTH_REQUIRED + GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED ); // Store last 20 bytes. @@ -75,41 +80,6 @@ contract LibBytes { return result; } - /// @dev Tests equality of two byte arrays. - /// @param lhs First byte array to compare. - /// @param rhs Second byte array to compare. - /// @return True if arrays are the same. False otherwise. - function areBytesEqual( - bytes memory lhs, - bytes memory rhs - ) - internal - pure - returns (bool equal) - { - assembly { - // Get the number of words occupied by <lhs> - let lenFullWords := div(add(mload(lhs), 0x1F), 0x20) - - // Add 1 to the number of words, to account for the length field - lenFullWords := add(lenFullWords, 0x1) - - // Test equality word-by-word. - // Terminates early if there is a mismatch. - for {let i := 0} lt(i, lenFullWords) {i := add(i, 1)} { - let lhsWord := mload(add(lhs, mul(i, 0x20))) - let rhsWord := mload(add(rhs, mul(i, 0x20))) - equal := eq(lhsWord, rhsWord) - if eq(equal, 0) { - // Break - i := lenFullWords - } - } - } - - return equal; - } - /// @dev Reads an address from a position in a byte array. /// @param b Byte array containing an address. /// @param index Index in byte array of address. @@ -124,8 +94,8 @@ contract LibBytes { { require( b.length >= index + 20, // 20 is length of address - GTE_20_LENGTH_REQUIRED - ); + GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED + ); // Add offset to index: // 1. Arrays are prefixed by 32-byte length parameter (add 32 to index) @@ -156,8 +126,8 @@ contract LibBytes { { require( b.length >= index + 20, // 20 is length of address - GTE_20_LENGTH_REQUIRED - ); + GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED + ); // Add offset to index: // 1. Arrays are prefixed by 32-byte length parameter (add 32 to index) @@ -195,7 +165,7 @@ contract LibBytes { { require( b.length >= index + 32, - GTE_32_LENGTH_REQUIRED + GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED ); // Arrays are prefixed by a 256 bit length parameter @@ -222,7 +192,7 @@ contract LibBytes { { require( b.length >= index + 32, - GTE_32_LENGTH_REQUIRED + GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED ); // Arrays are prefixed by a 256 bit length parameter @@ -274,11 +244,130 @@ contract LibBytes { { require( b.length >= 4, - GTE_4_LENGTH_REQUIRED + GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED ); assembly { result := mload(add(b, 32)) } return result; } + + /// @dev Reads nested bytes from a specific position. + /// @param b Byte array containing nested bytes. + /// @param index Index of nested bytes. + /// @return result Nested bytes. + function readBytes( + bytes memory b, + uint256 index + ) + internal + pure + returns (bytes memory result) + { + // Read length of nested bytes + uint256 nestedBytesLength = readUint256(b, index); + index += 32; + + // Assert length of <b> is valid, given + // length of nested bytes + require( + b.length >= index + nestedBytesLength, + GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED + ); + + // Allocate memory and copy value to result + result = new bytes(nestedBytesLength); + memCopy( + getMemAddress(result) + 32, // +32 skips array length + getMemAddress(b) + index + 32, + nestedBytesLength + ); + + return result; + } + + /// @dev Inserts bytes at a specific position in a byte array. + /// @param b Byte array to insert <input> into. + /// @param index Index in byte array of <input>. + /// @param input bytes to insert. + function writeBytes( + bytes memory b, + uint256 index, + bytes memory input + ) + internal + pure + { + // Assert length of <b> is valid, given + // length of input + require( + b.length >= index + 32 /* 32 bytes to store length */ + input.length, + GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED + ); + + // Copy <input> into <b> + memCopy( + getMemAddress(b) + 32 + index, // +32 to skip length of <b> + getMemAddress(input), // includes length of <input> + input.length + 32 // +32 bytes to store <input> length + ); + } + + /// @dev Tests equality of two byte arrays. + /// @param lhs First byte array to compare. + /// @param rhs Second byte array to compare. + /// @return True if arrays are the same. False otherwise. + function areBytesEqual( + bytes memory lhs, + bytes memory rhs + ) + internal + pure + returns (bool equal) + { + assembly { + // Get the number of words occupied by <lhs> + let lenFullWords := div(add(mload(lhs), 0x1F), 0x20) + + // Add 1 to the number of words, to account for the length field + lenFullWords := add(lenFullWords, 0x1) + + // Test equality word-by-word. + // Terminates early if there is a mismatch. + for {let i := 0} lt(i, lenFullWords) {i := add(i, 1)} { + let lhsWord := mload(add(lhs, mul(i, 0x20))) + let rhsWord := mload(add(rhs, mul(i, 0x20))) + equal := eq(lhsWord, rhsWord) + if eq(equal, 0) { + // Break + i := lenFullWords + } + } + } + + return equal; + } + + /// @dev Performs a deep copy of a byte array onto another byte array of greater than or equal length. + /// @param dest Byte array that will be overwritten with source bytes. + /// @param source Byte array to copy onto dest bytes. + function deepCopyBytes( + bytes memory dest, + bytes memory source + ) + internal + pure + { + uint256 sourceLen = source.length; + // Dest length must be >= source length, or some bytes would not be copied. + require( + dest.length >= sourceLen, + GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED + ); + memCopy( + getMemAddress(dest) + 32, // +32 to skip length of <dest> + getMemAddress(source) + 32, // +32 to skip length of <source> + sourceLen + ); + } } diff --git a/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol b/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol new file mode 100644 index 000000000..6afb9973a --- /dev/null +++ b/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol @@ -0,0 +1,140 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.4.24; + +contract LibMem +{ + + /// @dev Gets the memory address for a byte array. + /// @param input Byte array to lookup. + /// @return memoryAddress Memory address of byte array. + function getMemAddress(bytes memory input) + internal + pure + returns (uint256 memoryAddress) + { + assembly { + memoryAddress := input + } + return memoryAddress; + } + + /// @dev Copies `length` bytes from memory location `source` to `dest`. + /// @param dest memory address to copy bytes to. + /// @param source memory address to copy bytes from. + /// @param length number of bytes to copy. + function memCopy( + uint256 dest, + uint256 source, + uint256 length + ) + internal + pure + { + if (length < 32) { + // Handle a partial word by reading destination and masking + // off the bits we are interested in. + // This correctly handles overlap, zero lengths and source == dest + assembly { + let mask := sub(exp(256, sub(32, length)), 1) + let s := and(mload(source), not(mask)) + let d := and(mload(dest), mask) + mstore(dest, or(s, d)) + } + } else { + // Skip the O(length) loop when source == dest. + if (source == dest) { + return; + } + + // For large copies we copy whole words at a time. The final + // word is aligned to the end of the range (instead of after the + // previous) to handle partial words. So a copy will look like this: + // + // #### + // #### + // #### + // #### + // + // We handle overlap in the source and destination range by + // changing the copying direction. This prevents us from + // overwriting parts of source that we still need to copy. + // + // This correctly handles source == dest + // + if (source > dest) { + assembly { + // Record the total number of full words to copy + let nWords := div(length, 32) + + // We subtract 32 from `sEnd` and `dEnd` because it + // is easier to compare with in the loop, and these + // are also the addresses we need for copying the + // last bytes. + length := sub(length, 32) + let sEnd := add(source, length) + let dEnd := add(dest, length) + + // Remember the last 32 bytes of source + // This needs to be done here and not after the loop + // because we may have overwritten the last bytes in + // source already due to overlap. + let last := mload(sEnd) + + // Copy whole words front to back + for {let i := 0} lt(i, nWords) {i := add(i, 1)} { + mstore(dest, mload(source)) + source := add(source, 32) + dest := add(dest, 32) + } + + // Write the last 32 bytes + mstore(dEnd, last) + } + } else { + assembly { + // Record the total number of full words to copy + let nWords := div(length, 32) + + // We subtract 32 from `sEnd` and `dEnd` because those + // are the starting points when copying a word at the end. + length := sub(length, 32) + let sEnd := add(source, length) + let dEnd := add(dest, length) + + // Remember the first 32 bytes of source + // This needs to be done here and not after the loop + // because we may have overwritten the first bytes in + // source already due to overlap. + let first := mload(source) + + // Copy whole words back to front + for {let i := 0} lt(i, nWords) {i := add(i, 1)} { + mstore(dEnd, mload(sEnd)) + sEnd := sub(sEnd, 32) + dEnd := sub(dEnd, 32) + } + + // Write the first 32 bytes + mstore(dest, first) + } + } + } + } +} diff --git a/packages/contracts/src/utils/artifacts.ts b/packages/contracts/src/utils/artifacts.ts index 357c66a0a..bf7221d6d 100644 --- a/packages/contracts/src/utils/artifacts.ts +++ b/packages/contracts/src/utils/artifacts.ts @@ -2,6 +2,7 @@ import { ContractArtifact } from '@0xproject/sol-compiler'; import * as AssetProxyOwner from '../artifacts/AssetProxyOwner.json'; import * as DummyERC20Token from '../artifacts/DummyERC20Token.json'; +import * as DummyERC721Receiver from '../artifacts/DummyERC721Receiver.json'; import * as DummyERC721Token from '../artifacts/DummyERC721Token.json'; import * as ERC20Proxy from '../artifacts/ERC20Proxy.json'; import * as ERC721Proxy from '../artifacts/ERC721Proxy.json'; @@ -9,8 +10,10 @@ import * as Exchange from '../artifacts/Exchange.json'; import * as MixinAuthorizable from '../artifacts/MixinAuthorizable.json'; import * as MultiSigWallet from '../artifacts/MultiSigWallet.json'; import * as MultiSigWalletWithTimeLock from '../artifacts/MultiSigWalletWithTimeLock.json'; +import * as TestAssetDataDecoders from '../artifacts/TestAssetDataDecoders.json'; import * as TestAssetProxyDispatcher from '../artifacts/TestAssetProxyDispatcher.json'; import * as TestLibBytes from '../artifacts/TestLibBytes.json'; +import * as TestLibMem from '../artifacts/TestLibMem.json'; import * as TestLibs from '../artifacts/TestLibs.json'; import * as TestSignatureValidator from '../artifacts/TestSignatureValidator.json'; import * as TokenRegistry from '../artifacts/TokenRegistry.json'; @@ -21,6 +24,7 @@ import * as ZRX from '../artifacts/ZRXToken.json'; export const artifacts = { AssetProxyOwner: (AssetProxyOwner as any) as ContractArtifact, DummyERC20Token: (DummyERC20Token as any) as ContractArtifact, + DummyERC721Receiver: (DummyERC721Receiver as any) as ContractArtifact, DummyERC721Token: (DummyERC721Token as any) as ContractArtifact, ERC20Proxy: (ERC20Proxy as any) as ContractArtifact, ERC721Proxy: (ERC721Proxy as any) as ContractArtifact, @@ -30,7 +34,9 @@ export const artifacts = { MultiSigWallet: (MultiSigWallet as any) as ContractArtifact, MultiSigWalletWithTimeLock: (MultiSigWalletWithTimeLock as any) as ContractArtifact, TestAssetProxyDispatcher: (TestAssetProxyDispatcher as any) as ContractArtifact, + TestAssetDataDecoders: (TestAssetDataDecoders as any) as ContractArtifact, TestLibBytes: (TestLibBytes as any) as ContractArtifact, + TestLibMem: (TestLibMem as any) as ContractArtifact, TestLibs: (TestLibs as any) as ContractArtifact, TestSignatureValidator: (TestSignatureValidator as any) as ContractArtifact, TokenRegistry: (TokenRegistry as any) as ContractArtifact, diff --git a/packages/contracts/src/utils/assertions.ts b/packages/contracts/src/utils/assertions.ts new file mode 100644 index 000000000..615e648f3 --- /dev/null +++ b/packages/contracts/src/utils/assertions.ts @@ -0,0 +1,63 @@ +import * as chai from 'chai'; +import * as _ from 'lodash'; + +import { constants } from './constants'; + +const expect = chai.expect; + +function _expectEitherErrorAsync<T>(p: Promise<T>, error1: string, error2: string): PromiseLike<void> { + return expect(p) + .to.be.rejected() + .then(e => { + expect(e).to.satisfy( + (err: Error) => _.includes(err.message, error1) || _.includes(err.message, error2), + `expected promise to reject with error message that includes "${error1}" or "${error2}", but got: ` + + `"${e.message}"\n`, + ); + }); +} + +/** + * Rejects if the given Promise does not reject with an error indicating + * insufficient funds. + * @param p the Promise which is expected to reject + * @returns a new Promise which will reject if the conditions are not met and + * otherwise resolve with no value. + */ +export function expectInsufficientFundsAsync<T>(p: Promise<T>): PromiseLike<void> { + return _expectEitherErrorAsync(p, 'insufficient funds', "sender doesn't have enough funds"); +} + +/** + * Rejects if the given Promise does not reject with a "revert" error or the + * given otherError. + * @param p the Promise which is expected to reject + * @param otherError the other error which is accepted as a valid reject error. + * @returns a new Promise which will reject if the conditions are not met and + * otherwise resolve with no value. + */ +export function expectRevertOrOtherErrorAsync<T>(p: Promise<T>, otherError: string): PromiseLike<void> { + return _expectEitherErrorAsync(p, constants.REVERT, otherError); +} + +/** + * Rejects if the given Promise does not reject with a "revert" or "always + * failing transaction" error. + * @param p the Promise which is expected to reject + * @returns a new Promise which will reject if the conditions are not met and + * otherwise resolve with no value. + */ +export function expectRevertOrAlwaysFailingTransactionAsync<T>(p: Promise<T>): PromiseLike<void> { + return expectRevertOrOtherErrorAsync(p, 'always failing transaction'); +} + +/** + * Rejects if the given Promise does not reject with a "revert" or "Contract + * call failed" error. + * @param p the Promise which is expected to reject + * @returns a new Promise which will reject if the conditions are not met and + * otherwise resolve with no value. + */ +export function expectRevertOrContractCallFailedAsync<T>(p: Promise<T>): PromiseLike<void> { + return expectRevertOrOtherErrorAsync<T>(p, 'Contract call failed'); +} diff --git a/packages/contracts/src/utils/constants.ts b/packages/contracts/src/utils/constants.ts index 9b0b92545..ec3c8fd36 100644 --- a/packages/contracts/src/utils/constants.ts +++ b/packages/contracts/src/utils/constants.ts @@ -19,8 +19,19 @@ const TESTRPC_PRIVATE_KEYS_STRINGS = [ export const constants = { INVALID_OPCODE: 'invalid opcode', REVERT: 'revert', + LIB_BYTES_GREATER_THAN_ZERO_LENGTH_REQUIRED: 'GREATER_THAN_ZERO_LENGTH_REQUIRED', + LIB_BYTES_GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED: 'GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED', + LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED: 'GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED', + LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED: 'GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED', + LIB_BYTES_GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED: 'GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED', + LIB_BYTES_GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED: 'GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED', + ERC20_INSUFFICIENT_BALANCE: 'Insufficient balance to complete transfer.', + ERC20_INSUFFICIENT_ALLOWANCE: 'Insufficient allowance to complete transfer.', TESTRPC_NETWORK_ID: 50, - AWAIT_TRANSACTION_MINED_MS: 100, + // Note(albrow): In practice V8 and most other engines limit the minimum + // interval for setInterval to 10ms. We still set it to 0 here in order to + // ensure we always use the minimum interval. + AWAIT_TRANSACTION_MINED_MS: 0, MAX_ETHERTOKEN_WITHDRAW_GAS: 43000, MAX_TOKEN_TRANSFERFROM_GAS: 80000, MAX_TOKEN_APPROVE_GAS: 60000, diff --git a/packages/contracts/src/utils/coverage.ts b/packages/contracts/src/utils/coverage.ts index a37939464..de29a3ecc 100644 --- a/packages/contracts/src/utils/coverage.ts +++ b/packages/contracts/src/utils/coverage.ts @@ -1,6 +1,5 @@ import { devConstants } from '@0xproject/dev-utils'; import { CoverageSubprovider, SolCompilerArtifactAdapter } from '@0xproject/sol-cov'; -import * as fs from 'fs'; import * as _ from 'lodash'; let coverageSubprovider: CoverageSubprovider; @@ -15,7 +14,8 @@ export const coverage = { _getCoverageSubprovider(): CoverageSubprovider { const defaultFromAddress = devConstants.TESTRPC_FIRST_ADDRESS; const solCompilerArtifactAdapter = new SolCompilerArtifactAdapter(); - const subprovider = new CoverageSubprovider(solCompilerArtifactAdapter, defaultFromAddress); + const isVerbose = true; + const subprovider = new CoverageSubprovider(solCompilerArtifactAdapter, defaultFromAddress, isVerbose); return subprovider; }, }; diff --git a/packages/contracts/src/utils/erc20_wrapper.ts b/packages/contracts/src/utils/erc20_wrapper.ts index dceeceeea..91c9d50b7 100644 --- a/packages/contracts/src/utils/erc20_wrapper.ts +++ b/packages/contracts/src/utils/erc20_wrapper.ts @@ -3,8 +3,8 @@ import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Provider } from 'ethereum-types'; import * as _ from 'lodash'; -import { DummyERC20TokenContract } from '../contract_wrappers/generated/dummy_e_r_c20_token'; -import { ERC20ProxyContract } from '../contract_wrappers/generated/e_r_c20_proxy'; +import { DummyERC20TokenContract } from '../generated_contract_wrappers/dummy_e_r_c20_token'; +import { ERC20ProxyContract } from '../generated_contract_wrappers/e_r_c20_proxy'; import { artifacts } from './artifacts'; import { constants } from './constants'; diff --git a/packages/contracts/src/utils/erc721_wrapper.ts b/packages/contracts/src/utils/erc721_wrapper.ts index 13fdf630e..b20d9acd2 100644 --- a/packages/contracts/src/utils/erc721_wrapper.ts +++ b/packages/contracts/src/utils/erc721_wrapper.ts @@ -4,8 +4,8 @@ import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Provider } from 'ethereum-types'; import * as _ from 'lodash'; -import { DummyERC721TokenContract } from '../contract_wrappers/generated/dummy_e_r_c721_token'; -import { ERC721ProxyContract } from '../contract_wrappers/generated/e_r_c721_proxy'; +import { DummyERC721TokenContract } from '../generated_contract_wrappers/dummy_e_r_c721_token'; +import { ERC721ProxyContract } from '../generated_contract_wrappers/e_r_c721_proxy'; import { artifacts } from './artifacts'; import { constants } from './constants'; diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts index dd278e77c..4cc8f0b89 100644 --- a/packages/contracts/src/utils/exchange_wrapper.ts +++ b/packages/contracts/src/utils/exchange_wrapper.ts @@ -1,10 +1,10 @@ import { AssetProxyId, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import { LogEntry, Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; +import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import * as _ from 'lodash'; -import { ExchangeContract } from '../contract_wrappers/generated/exchange'; +import { ExchangeContract } from '../generated_contract_wrappers/exchange'; import { constants } from './constants'; import { formatters } from './formatters'; @@ -60,14 +60,14 @@ export class ExchangeWrapper { public async fillOrderNoThrowAsync( signedOrder: SignedOrder, from: string, - opts: { takerAssetFillAmount?: BigNumber } = {}, + opts: { takerAssetFillAmount?: BigNumber; gas?: number } = {}, ): Promise<TransactionReceiptWithDecodedLogs> { const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); const txHash = await this._exchange.fillOrderNoThrow.sendTransactionAsync( params.order, params.takerAssetFillAmount, params.signature, - { from }, + { from, gas: opts.gas }, ); const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); return tx; @@ -105,14 +105,14 @@ export class ExchangeWrapper { public async batchFillOrdersNoThrowAsync( orders: SignedOrder[], from: string, - opts: { takerAssetFillAmounts?: BigNumber[] } = {}, + opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number } = {}, ): Promise<TransactionReceiptWithDecodedLogs> { const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); const txHash = await this._exchange.batchFillOrdersNoThrow.sendTransactionAsync( params.orders, params.takerAssetFillAmounts, params.signatures, - { from }, + { from, gas: opts.gas }, ); const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); return tx; @@ -135,14 +135,14 @@ export class ExchangeWrapper { public async marketSellOrdersNoThrowAsync( orders: SignedOrder[], from: string, - opts: { takerAssetFillAmount: BigNumber }, + opts: { takerAssetFillAmount: BigNumber; gas?: number }, ): Promise<TransactionReceiptWithDecodedLogs> { const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); const txHash = await this._exchange.marketSellOrdersNoThrow.sendTransactionAsync( params.orders, params.takerAssetFillAmount, params.signatures, - { from }, + { from, gas: opts.gas }, ); const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); return tx; @@ -165,14 +165,14 @@ export class ExchangeWrapper { public async marketBuyOrdersNoThrowAsync( orders: SignedOrder[], from: string, - opts: { makerAssetFillAmount: BigNumber }, + opts: { makerAssetFillAmount: BigNumber; gas?: number }, ): Promise<TransactionReceiptWithDecodedLogs> { const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); const txHash = await this._exchange.marketBuyOrdersNoThrow.sendTransactionAsync( params.orders, params.makerAssetFillAmount, params.signatures, - { from }, + { from, gas: opts.gas }, ); const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); return tx; diff --git a/packages/contracts/src/utils/formatters.ts b/packages/contracts/src/utils/formatters.ts index 1035f2d7c..32e4787d6 100644 --- a/packages/contracts/src/utils/formatters.ts +++ b/packages/contracts/src/utils/formatters.ts @@ -2,6 +2,7 @@ import { SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; +import { constants } from './constants'; import { orderUtils } from './order_utils'; import { BatchCancelOrders, BatchFillOrders, MarketBuyOrders, MarketSellOrders } from './types'; @@ -28,8 +29,11 @@ export const formatters = { signatures: [], takerAssetFillAmount, }; - _.forEach(signedOrders, signedOrder => { + _.forEach(signedOrders, (signedOrder, i) => { const orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); + if (i !== 0) { + orderWithoutExchangeAddress.takerAssetData = constants.NULL_BYTES; + } marketSellOrders.orders.push(orderWithoutExchangeAddress); marketSellOrders.signatures.push(signedOrder.signature); }); @@ -41,8 +45,11 @@ export const formatters = { signatures: [], makerAssetFillAmount, }; - _.forEach(signedOrders, signedOrder => { + _.forEach(signedOrders, (signedOrder, i) => { const orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); + if (i !== 0) { + orderWithoutExchangeAddress.makerAssetData = constants.NULL_BYTES; + } marketBuyOrders.orders.push(orderWithoutExchangeAddress); marketBuyOrders.signatures.push(signedOrder.signature); }); diff --git a/packages/contracts/src/utils/increase_time.ts b/packages/contracts/src/utils/increase_time.ts new file mode 100644 index 000000000..4565d8dbc --- /dev/null +++ b/packages/contracts/src/utils/increase_time.ts @@ -0,0 +1,31 @@ +import * as _ from 'lodash'; + +import { constants } from './constants'; +import { web3Wrapper } from './web3_wrapper'; + +let firstAccount: string | undefined; + +/** + * Increases time by the given number of seconds and then mines a block so that + * the current block timestamp has the offset applied. + * @param seconds the number of seconds by which to incrase the time offset. + * @returns a new Promise which will resolve with the new total time offset or + * reject if the time could not be increased. + */ +export async function increaseTimeAndMineBlockAsync(seconds: number): Promise<number> { + if (_.isUndefined(firstAccount)) { + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + firstAccount = accounts[0]; + } + + const offset = await web3Wrapper.increaseTimeAsync(seconds); + // Note: we need to send a transaction after increasing time so + // that a block is actually mined. The contract looks at the + // last mined block for the timestamp. + await web3Wrapper.awaitTransactionSuccessAsync( + await web3Wrapper.sendTransactionAsync({ from: firstAccount, to: firstAccount, value: 0 }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + return offset; +} diff --git a/packages/contracts/src/utils/log_decoder.ts b/packages/contracts/src/utils/log_decoder.ts index 07d10e69d..07127ba79 100644 --- a/packages/contracts/src/utils/log_decoder.ts +++ b/packages/contracts/src/utils/log_decoder.ts @@ -39,6 +39,7 @@ export class LogDecoder { } public decodeLogOrThrow<ArgsType extends DecodedLogArgs>(log: LogEntry): LogWithDecodedArgs<ArgsType> | RawLog { const logWithDecodedArgsOrLog = this._abiDecoder.tryToDecodeLogOrNoop(log); + // tslint:disable-next-line:no-unnecessary-type-assertion if (_.isUndefined((logWithDecodedArgsOrLog as LogWithDecodedArgs<ArgsType>).args)) { throw new Error(`Unable to decode log: ${JSON.stringify(log)}`); } diff --git a/packages/contracts/src/utils/match_order_tester.ts b/packages/contracts/src/utils/match_order_tester.ts index 6170188bc..fbb1b99db 100644 --- a/packages/contracts/src/utils/match_order_tester.ts +++ b/packages/contracts/src/utils/match_order_tester.ts @@ -1,38 +1,21 @@ -import { BlockchainLifecycle } from '@0xproject/dev-utils'; -import { assetProxyUtils, crypto, orderHashUtils } from '@0xproject/order-utils'; +import { assetProxyUtils, orderHashUtils } from '@0xproject/order-utils'; import { AssetProxyId, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; -import { LogWithDecodedArgs } from 'ethereum-types'; -import ethUtil = require('ethereumjs-util'); import * as _ from 'lodash'; -import { DummyERC20TokenContract } from '../contract_wrappers/generated/dummy_e_r_c20_token'; -import { DummyERC721TokenContract } from '../contract_wrappers/generated/dummy_e_r_c721_token'; -import { ERC20ProxyContract } from '../contract_wrappers/generated/e_r_c20_proxy'; -import { ERC721ProxyContract } from '../contract_wrappers/generated/e_r_c721_proxy'; -import { - CancelContractEventArgs, - ExchangeContract, - FillContractEventArgs, -} from '../contract_wrappers/generated/exchange'; import { chaiSetup } from '../utils/chai_setup'; -import { constants } from '../utils/constants'; import { ERC20Wrapper } from '../utils/erc20_wrapper'; import { ERC721Wrapper } from '../utils/erc721_wrapper'; import { ExchangeWrapper } from '../utils/exchange_wrapper'; -import { OrderFactory } from '../utils/order_factory'; import { - ContractName, ERC20BalancesByOwner, ERC721TokenIdsByOwner, TransferAmountsByMatchOrders as TransferAmounts, } from '../utils/types'; -import { provider, web3Wrapper } from '../utils/web3_wrapper'; chaiSetup.configure(); const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); export class MatchOrderTester { private _exchangeWrapper: ExchangeWrapper; @@ -112,11 +95,6 @@ export class MatchOrderTester { initialTakerAssetFilledAmountLeft?: BigNumber, initialTakerAssetFilledAmountRight?: BigNumber, ): Promise<[ERC20BalancesByOwner, ERC721TokenIdsByOwner]> { - // Test setup & verify preconditions - const makerAddressLeft = signedOrderLeft.makerAddress; - const makerAddressRight = signedOrderRight.makerAddress; - const feeRecipientAddressLeft = signedOrderLeft.feeRecipientAddress; - const feeRecipientAddressRight = signedOrderRight.feeRecipientAddress; // Verify Left order preconditions const orderTakerAssetFilledAmountLeft = await this._exchangeWrapper.getTakerAssetFilledAmountAsync( orderHashUtils.getOrderHashHex(signedOrderLeft), @@ -259,11 +237,11 @@ export class MatchOrderTester { const expectedNewERC20BalancesByOwner = _.cloneDeep(erc20BalancesByOwner); const expectedNewERC721TokenIdsByOwner = _.cloneDeep(erc721TokenIdsByOwner); // Left Maker Asset (Right Taker Asset) - const makerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.makerAssetData); + const makerAssetProxyIdLeft = assetProxyUtils.decodeAssetDataId(signedOrderLeft.makerAssetData); if (makerAssetProxyIdLeft === AssetProxyId.ERC20) { // Decode asset data - const erc20ProxyData = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.makerAssetData); - const makerAssetAddressLeft = erc20ProxyData.tokenAddress; + const erc20AssetData = assetProxyUtils.decodeERC20AssetData(signedOrderLeft.makerAssetData); + const makerAssetAddressLeft = erc20AssetData.tokenAddress; const takerAssetAddressRight = makerAssetAddressLeft; // Left Maker expectedNewERC20BalancesByOwner[makerAddressLeft][makerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ @@ -281,9 +259,9 @@ export class MatchOrderTester { ][makerAssetAddressLeft].add(expectedTransferAmounts.amountReceivedByTaker); } else if (makerAssetProxyIdLeft === AssetProxyId.ERC721) { // Decode asset data - const erc721ProxyData = assetProxyUtils.decodeERC721ProxyData(signedOrderLeft.makerAssetData); - const makerAssetAddressLeft = erc721ProxyData.tokenAddress; - const makerAssetIdLeft = erc721ProxyData.tokenId; + const erc721AssetData = assetProxyUtils.decodeERC721AssetData(signedOrderLeft.makerAssetData); + const makerAssetAddressLeft = erc721AssetData.tokenAddress; + const makerAssetIdLeft = erc721AssetData.tokenId; const takerAssetAddressRight = makerAssetAddressLeft; const takerAssetIdRight = makerAssetIdLeft; // Left Maker @@ -294,11 +272,11 @@ export class MatchOrderTester { } // Left Taker Asset (Right Maker Asset) // Note: This exchange is only between the order makers: the Taker does not receive any of the left taker asset. - const takerAssetProxyIdLeft = assetProxyUtils.decodeProxyDataId(signedOrderLeft.takerAssetData); + const takerAssetProxyIdLeft = assetProxyUtils.decodeAssetDataId(signedOrderLeft.takerAssetData); if (takerAssetProxyIdLeft === AssetProxyId.ERC20) { // Decode asset data - const erc20ProxyData = assetProxyUtils.decodeERC20ProxyData(signedOrderLeft.takerAssetData); - const takerAssetAddressLeft = erc20ProxyData.tokenAddress; + const erc20AssetData = assetProxyUtils.decodeERC20AssetData(signedOrderLeft.takerAssetData); + const takerAssetAddressLeft = erc20AssetData.tokenAddress; const makerAssetAddressRight = takerAssetAddressLeft; // Left Maker expectedNewERC20BalancesByOwner[makerAddressLeft][takerAssetAddressLeft] = expectedNewERC20BalancesByOwner[ @@ -312,9 +290,9 @@ export class MatchOrderTester { ); } else if (takerAssetProxyIdLeft === AssetProxyId.ERC721) { // Decode asset data - const erc721ProxyData = assetProxyUtils.decodeERC721ProxyData(signedOrderRight.makerAssetData); - const makerAssetAddressRight = erc721ProxyData.tokenAddress; - const makerAssetIdRight = erc721ProxyData.tokenId; + const erc721AssetData = assetProxyUtils.decodeERC721AssetData(signedOrderRight.makerAssetData); + const makerAssetAddressRight = erc721AssetData.tokenAddress; + const makerAssetIdRight = erc721AssetData.tokenId; const takerAssetAddressLeft = makerAssetAddressRight; const takerAssetIdLeft = makerAssetIdRight; // Right Maker diff --git a/packages/contracts/src/utils/multi_sig_wrapper.ts b/packages/contracts/src/utils/multi_sig_wrapper.ts index 9971e8f6e..f0098bd5e 100644 --- a/packages/contracts/src/utils/multi_sig_wrapper.ts +++ b/packages/contracts/src/utils/multi_sig_wrapper.ts @@ -3,10 +3,9 @@ import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import * as _ from 'lodash'; -import { AssetProxyOwnerContract } from '../contract_wrappers/generated/asset_proxy_owner'; -import { MultiSigWalletContract } from '../contract_wrappers/generated/multi_sig_wallet'; +import { AssetProxyOwnerContract } from '../generated_contract_wrappers/asset_proxy_owner'; +import { MultiSigWalletContract } from '../generated_contract_wrappers/multi_sig_wallet'; -import { constants } from './constants'; import { LogDecoder } from './log_decoder'; export class MultiSigWrapper { @@ -45,6 +44,7 @@ export class MultiSigWrapper { txId: BigNumber, from: string, ): Promise<TransactionReceiptWithDecodedLogs> { + // tslint:disable-next-line:no-unnecessary-type-assertion const txHash = await (this ._multiSig as AssetProxyOwnerContract).executeRemoveAuthorizedAddress.sendTransactionAsync(txId, { from }); const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); diff --git a/packages/contracts/src/utils/order_factory.ts b/packages/contracts/src/utils/order_factory.ts index dd02e1f5c..009dbc396 100644 --- a/packages/contracts/src/utils/order_factory.ts +++ b/packages/contracts/src/utils/order_factory.ts @@ -1,7 +1,6 @@ import { generatePseudoRandomSalt, orderHashUtils } from '@0xproject/order-utils'; import { Order, SignatureType, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; -import * as _ from 'lodash'; import { constants } from './constants'; import { signingUtils } from './signing_utils'; diff --git a/packages/contracts/src/utils/order_utils.ts b/packages/contracts/src/utils/order_utils.ts index 180761f2c..a9f994d80 100644 --- a/packages/contracts/src/utils/order_utils.ts +++ b/packages/contracts/src/utils/order_utils.ts @@ -1,7 +1,7 @@ import { OrderWithoutExchangeAddress, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; -import ethUtil = require('ethereumjs-util'); +import { constants } from './constants'; import { CancelOrder, MatchOrder } from './types'; export const orderUtils = { @@ -44,6 +44,8 @@ export const orderUtils = { leftSignature: signedOrderLeft.signature, rightSignature: signedOrderRight.signature, }; + fill.right.makerAssetData = constants.NULL_BYTES; + fill.right.takerAssetData = constants.NULL_BYTES; return fill; }, }; diff --git a/packages/contracts/src/utils/profiler.ts b/packages/contracts/src/utils/profiler.ts new file mode 100644 index 000000000..85ee24f22 --- /dev/null +++ b/packages/contracts/src/utils/profiler.ts @@ -0,0 +1,27 @@ +import { devConstants } from '@0xproject/dev-utils'; +import { ProfilerSubprovider, SolCompilerArtifactAdapter } from '@0xproject/sol-cov'; +import * as _ from 'lodash'; + +let profilerSubprovider: ProfilerSubprovider; + +export const profiler = { + start(): void { + profiler.getProfilerSubproviderSingleton().start(); + }, + stop(): void { + profiler.getProfilerSubproviderSingleton().stop(); + }, + getProfilerSubproviderSingleton(): ProfilerSubprovider { + if (_.isUndefined(profilerSubprovider)) { + profilerSubprovider = profiler._getProfilerSubprovider(); + } + return profilerSubprovider; + }, + _getProfilerSubprovider(): ProfilerSubprovider { + const defaultFromAddress = devConstants.TESTRPC_FIRST_ADDRESS; + const solCompilerArtifactAdapter = new SolCompilerArtifactAdapter(); + const isVerbose = true; + const subprovider = new ProfilerSubprovider(solCompilerArtifactAdapter, defaultFromAddress, isVerbose); + return subprovider; + }, +}; diff --git a/packages/contracts/src/utils/revert_trace.ts b/packages/contracts/src/utils/revert_trace.ts new file mode 100644 index 000000000..0bf8384bc --- /dev/null +++ b/packages/contracts/src/utils/revert_trace.ts @@ -0,0 +1,21 @@ +import { devConstants } from '@0xproject/dev-utils'; +import { RevertTraceSubprovider, SolCompilerArtifactAdapter } from '@0xproject/sol-cov'; +import * as _ from 'lodash'; + +let revertTraceSubprovider: RevertTraceSubprovider; + +export const revertTrace = { + getRevertTraceSubproviderSingleton(): RevertTraceSubprovider { + if (_.isUndefined(revertTraceSubprovider)) { + revertTraceSubprovider = revertTrace._getRevertTraceSubprovider(); + } + return revertTraceSubprovider; + }, + _getRevertTraceSubprovider(): RevertTraceSubprovider { + const defaultFromAddress = devConstants.TESTRPC_FIRST_ADDRESS; + const solCompilerArtifactAdapter = new SolCompilerArtifactAdapter(); + const isVerbose = true; + const subprovider = new RevertTraceSubprovider(solCompilerArtifactAdapter, defaultFromAddress, isVerbose); + return subprovider; + }, +}; diff --git a/packages/contracts/src/utils/token_registry_wrapper.ts b/packages/contracts/src/utils/token_registry_wrapper.ts index 240c06fdc..91895aa59 100644 --- a/packages/contracts/src/utils/token_registry_wrapper.ts +++ b/packages/contracts/src/utils/token_registry_wrapper.ts @@ -1,7 +1,7 @@ import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Provider } from 'ethereum-types'; -import { TokenRegistryContract } from '../contract_wrappers/generated/token_registry'; +import { TokenRegistryContract } from '../generated_contract_wrappers/token_registry'; import { Token } from './types'; diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts index 360e1fdbc..bb8c12088 100644 --- a/packages/contracts/src/utils/types.ts +++ b/packages/contracts/src/utils/types.ts @@ -1,6 +1,6 @@ -import { Order, OrderWithoutExchangeAddress } from '@0xproject/types'; +import { OrderWithoutExchangeAddress } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; -import { AbiDefinition, ContractAbi } from 'ethereum-types'; +import { AbiDefinition } from 'ethereum-types'; export interface ERC20BalancesByOwner { [ownerAddress: string]: { @@ -90,11 +90,14 @@ export enum ContractName { AccountLevels = 'AccountLevels', EtherDelta = 'EtherDelta', Arbitrage = 'Arbitrage', + TestAssetDataDecoders = 'TestAssetDataDecoders', TestAssetProxyDispatcher = 'TestAssetProxyDispatcher', + TestLibMem = 'TestLibMem', TestLibs = 'TestLibs', TestSignatureValidator = 'TestSignatureValidator', ERC20Proxy = 'ERC20Proxy', ERC721Proxy = 'ERC721Proxy', + DummyERC721Receiver = 'DummyERC721Receiver', DummyERC721Token = 'DummyERC721Token', TestLibBytes = 'TestLibBytes', Authorizable = 'Authorizable', diff --git a/packages/contracts/src/utils/web3_wrapper.ts b/packages/contracts/src/utils/web3_wrapper.ts index 1049ab967..c9d83a02d 100644 --- a/packages/contracts/src/utils/web3_wrapper.ts +++ b/packages/contracts/src/utils/web3_wrapper.ts @@ -1,19 +1,82 @@ import { devConstants, env, EnvVars, web3Factory } from '@0xproject/dev-utils'; import { prependSubprovider } from '@0xproject/subproviders'; +import { logUtils } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; -import { Provider } from 'ethereum-types'; +import * as _ from 'lodash'; import { coverage } from './coverage'; +import { profiler } from './profiler'; +import { revertTrace } from './revert_trace'; -export const txDefaults = { +enum ProviderType { + Ganache = 'ganache', + Geth = 'geth', +} + +let testProvider: ProviderType; +switch (process.env.TEST_PROVIDER) { + case undefined: + testProvider = ProviderType.Ganache; + break; + case 'ganache': + testProvider = ProviderType.Ganache; + break; + case 'geth': + testProvider = ProviderType.Geth; + break; + default: + throw new Error(`Unknown TEST_PROVIDER: ${process.env.TEST_PROVIDER}`); +} + +const ganacheTxDefaults = { from: devConstants.TESTRPC_FIRST_ADDRESS, gas: devConstants.GAS_LIMIT, }; -const providerConfigs = { shouldUseInProcessGanache: true }; +const gethTxDefaults = { + from: devConstants.TESTRPC_FIRST_ADDRESS, +}; +export const txDefaults = testProvider === ProviderType.Ganache ? ganacheTxDefaults : gethTxDefaults; + +const gethConfigs = { + shouldUseInProcessGanache: false, + rpcUrl: 'http://localhost:8501', + shouldUseFakeGasEstimate: false, +}; +const ganacheConfigs = { + shouldUseInProcessGanache: true, +}; +const providerConfigs = testProvider === ProviderType.Ganache ? ganacheConfigs : gethConfigs; + export const provider = web3Factory.getRpcProvider(providerConfigs); const isCoverageEnabled = env.parseBoolean(EnvVars.SolidityCoverage); +const isProfilerEnabled = env.parseBoolean(EnvVars.SolidityProfiler); +const isRevertTraceEnabled = env.parseBoolean(EnvVars.SolidityRevertTrace); +const enabledSubproviderCount = _.filter([isCoverageEnabled, isProfilerEnabled, isRevertTraceEnabled], _.identity) + .length; +if (enabledSubproviderCount > 1) { + throw new Error(`Only one of coverage, profiler, or revert trace subproviders can be enabled at a time`); +} if (isCoverageEnabled) { const coverageSubprovider = coverage.getCoverageSubproviderSingleton(); prependSubprovider(provider, coverageSubprovider); } +if (isProfilerEnabled) { + if (testProvider === ProviderType.Ganache) { + logUtils.warn( + "Gas costs in Ganache traces are incorrect and we don't recommend using it for profiling. Please switch to Geth", + ); + process.exit(1); + } + const profilerSubprovider = profiler.getProfilerSubproviderSingleton(); + logUtils.log( + "By default profilerSubprovider is stopped so that you don't get noise from setup code. Don't forget to start it before the code you want to profile and stop it afterwards", + ); + profilerSubprovider.stop(); + prependSubprovider(provider, profilerSubprovider); +} +if (isRevertTraceEnabled) { + const revertTraceSubprovider = revertTrace.getRevertTraceSubproviderSingleton(); + prependSubprovider(provider, revertTraceSubprovider); +} + export const web3Wrapper = new Web3Wrapper(provider); diff --git a/packages/contracts/test/asset_proxy/authorizable.ts b/packages/contracts/test/asset_proxy/authorizable.ts index e8274acb1..347d060d6 100644 --- a/packages/contracts/test/asset_proxy/authorizable.ts +++ b/packages/contracts/test/asset_proxy/authorizable.ts @@ -1,11 +1,10 @@ -import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import { BlockchainLifecycle } from '@0xproject/dev-utils'; import * as chai from 'chai'; import 'make-promises-safe'; -import * as Web3 from 'web3'; -import { MixinAuthorizableContract } from '../../src/contract_wrappers/generated/mixin_authorizable'; +import { MixinAuthorizableContract } from '../../src/generated_contract_wrappers/mixin_authorizable'; import { artifacts } from '../../src/utils/artifacts'; +import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; @@ -44,9 +43,9 @@ describe('Authorizable', () => { }); describe('addAuthorizedAddress', () => { it('should throw if not called by owner', async () => { - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( authorizable.addAuthorizedAddress.sendTransactionAsync(notOwner, { from: notOwner }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should allow owner to add an authorized address', async () => { await web3Wrapper.awaitTransactionSuccessAsync( @@ -61,9 +60,9 @@ describe('Authorizable', () => { await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), constants.AWAIT_TRANSACTION_MINED_MS, ); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), - ).to.be.rejectedWith(constants.REVERT); + ); }); }); @@ -73,11 +72,11 @@ describe('Authorizable', () => { await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), constants.AWAIT_TRANSACTION_MINED_MS, ); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( authorizable.removeAuthorizedAddress.sendTransactionAsync(address, { from: notOwner, }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should allow owner to remove an authorized address', async () => { @@ -96,11 +95,11 @@ describe('Authorizable', () => { }); it('should throw if owner attempts to remove an address that is not authorized', async () => { - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( authorizable.removeAuthorizedAddress.sendTransactionAsync(address, { from: owner, }), - ).to.be.rejectedWith(constants.REVERT); + ); }); }); diff --git a/packages/contracts/test/asset_proxy/decoder.ts b/packages/contracts/test/asset_proxy/decoder.ts new file mode 100644 index 000000000..875d55daa --- /dev/null +++ b/packages/contracts/test/asset_proxy/decoder.ts @@ -0,0 +1,86 @@ +import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils'; +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; +import ethUtil = require('ethereumjs-util'); + +import { TestAssetDataDecodersContract } from '../../src/generated_contract_wrappers/test_asset_data_decoders'; +import { artifacts } from '../../src/utils/artifacts'; +import { chaiSetup } from '../../src/utils/chai_setup'; +import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('TestAssetDataDecoders', () => { + let testAssetProxyDecoder: TestAssetDataDecodersContract; + let testAddress: string; + + before(async () => { + await blockchainLifecycle.startAsync(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + before(async () => { + // Setup accounts & addresses + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + testAddress = accounts[0]; + // Deploy TestLibMem + testAssetProxyDecoder = await TestAssetDataDecodersContract.deployFrom0xArtifactAsync( + artifacts.TestAssetDataDecoders, + provider, + txDefaults, + ); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + + describe('Asset Data Decoders', () => { + it('should correctly decode ERC721 asset data', async () => { + const tokenId = generatePseudoRandomSalt(); + const encodedAssetData = assetProxyUtils.encodeERC721AssetData(testAddress, tokenId); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); + const expectedDecodedAssetData = assetProxyUtils.decodeERC721AssetData(encodedAssetData); + let decodedTokenAddress: string; + let decodedTokenId: BigNumber; + let decodedData: string; + [ + decodedTokenAddress, + decodedTokenId, + decodedData, + ] = await testAssetProxyDecoder.publicDecodeERC721Data.callAsync(encodedAssetDataWithoutProxyId); + expect(decodedTokenAddress).to.be.equal(expectedDecodedAssetData.tokenAddress); + expect(decodedTokenId).to.be.bignumber.equal(expectedDecodedAssetData.tokenId); + expect(decodedData).to.be.equal(expectedDecodedAssetData.receiverData); + }); + + it('should correctly decode ERC721 asset data with receiver data', async () => { + const tokenId = generatePseudoRandomSalt(); + const receiverDataFirst32Bytes = ethUtil.bufferToHex( + assetProxyUtils.encodeUint256(generatePseudoRandomSalt()), + ); + const receiverDataExtraBytes = 'FFFF'; + // We add extra bytes to generate a value that doesn't fit perfectly into one word + const receiverData = receiverDataFirst32Bytes + receiverDataExtraBytes; + const encodedAssetData = assetProxyUtils.encodeERC721AssetData(testAddress, tokenId, receiverData); + const expectedDecodedAssetData = assetProxyUtils.decodeERC721AssetData(encodedAssetData); + let decodedTokenAddress: string; + let decodedTokenId: BigNumber; + let decodedReceiverData: string; + [ + decodedTokenAddress, + decodedTokenId, + decodedReceiverData, + ] = await testAssetProxyDecoder.publicDecodeERC721Data.callAsync(encodedAssetData); + expect(decodedTokenAddress).to.be.equal(expectedDecodedAssetData.tokenAddress); + expect(decodedTokenId).to.be.bignumber.equal(expectedDecodedAssetData.tokenId); + expect(decodedReceiverData).to.be.equal(expectedDecodedAssetData.receiverData); + }); + }); +}); diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts index faab39759..9760d3b9c 100644 --- a/packages/contracts/test/asset_proxy/proxies.ts +++ b/packages/contracts/test/asset_proxy/proxies.ts @@ -1,25 +1,33 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; -import { assetProxyUtils } from '@0xproject/order-utils'; -import { AssetProxyId } from '@0xproject/types'; +import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; +import { LogWithDecodedArgs } from 'ethereum-types'; +import ethUtil = require('ethereumjs-util'); import * as _ from 'lodash'; -import * as Web3 from 'web3'; -import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token'; -import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token'; -import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy'; -import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy'; +import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token'; +import { + DummyERC721ReceiverContract, + TokenReceivedContractEventArgs, +} from '../../src/generated_contract_wrappers/dummy_e_r_c721_receiver'; +import { DummyERC721TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c721_token'; +import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy'; +import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy'; +import { artifacts } from '../../src/utils/artifacts'; +import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; import { ERC721Wrapper } from '../../src/utils/erc721_wrapper'; -import { provider, web3Wrapper } from '../../src/utils/web3_wrapper'; +import { LogDecoder } from '../../src/utils/log_decoder'; +import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +// tslint:disable:no-unnecessary-type-assertion describe('Asset Transfer Proxies', () => { let owner: string; let notAuthorized: string; @@ -29,6 +37,7 @@ describe('Asset Transfer Proxies', () => { let zrxToken: DummyERC20TokenContract; let erc721Token: DummyERC721TokenContract; + let erc721Receiver: DummyERC721ReceiverContract; let erc20Proxy: ERC20ProxyContract; let erc721Proxy: ERC721ProxyContract; @@ -70,6 +79,11 @@ describe('Asset Transfer Proxies', () => { }), constants.AWAIT_TRANSACTION_MINED_MS, ); + erc721Receiver = await DummyERC721ReceiverContract.deployFrom0xArtifactAsync( + artifacts.DummyERC721Receiver, + provider, + txDefaults, + ); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -80,14 +94,15 @@ describe('Asset Transfer Proxies', () => { describe('Transfer Proxy - ERC20', () => { describe('transferFrom', () => { it('should successfully transfer tokens', async () => { - // Construct metadata for ERC20 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); + // Construct ERC20 asset data + const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); // Perform a transfer from makerAddress to takerAddress const erc20Balances = await erc20Wrapper.getBalancesAsync(); const amount = new BigNumber(10); await web3Wrapper.awaitTransactionSuccessAsync( await erc20Proxy.transferFrom.sendTransactionAsync( - encodedProxyMetadata, + encodedAssetDataWithoutProxyId, makerAddress, takerAddress, amount, @@ -106,14 +121,15 @@ describe('Asset Transfer Proxies', () => { }); it('should do nothing if transferring 0 amount of a token', async () => { - // Construct metadata for ERC20 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); + // Construct ERC20 asset data + const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); // Perform a transfer from makerAddress to takerAddress const erc20Balances = await erc20Wrapper.getBalancesAsync(); const amount = new BigNumber(0); await web3Wrapper.awaitTransactionSuccessAsync( await erc20Proxy.transferFrom.sendTransactionAsync( - encodedProxyMetadata, + encodedAssetDataWithoutProxyId, makerAddress, takerAddress, amount, @@ -132,8 +148,8 @@ describe('Asset Transfer Proxies', () => { }); it('should throw if allowances are too low', async () => { - // Construct metadata for ERC20 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); + // Construct ERC20 asset data + const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address); // Create allowance less than transfer amount. Set allowance on proxy. const allowance = new BigNumber(0); const transferAmount = new BigNumber(10); @@ -144,25 +160,26 @@ describe('Asset Transfer Proxies', () => { constants.AWAIT_TRANSACTION_MINED_MS, ); // Perform a transfer; expect this to fail. - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( erc20Proxy.transferFrom.sendTransactionAsync( - encodedProxyMetadata, + encodedAssetData, makerAddress, takerAddress, transferAmount, { from: notAuthorized }, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw if requesting address is not authorized', async () => { - // Construct metadata for ERC20 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); + // Construct ERC20 asset data + const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); // Perform a transfer from makerAddress to takerAddress const amount = new BigNumber(10); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( erc20Proxy.transferFrom.sendTransactionAsync( - encodedProxyMetadata, + encodedAssetDataWithoutProxyId, makerAddress, takerAddress, amount, @@ -170,7 +187,7 @@ describe('Asset Transfer Proxies', () => { from: notAuthorized, }, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); }); @@ -178,16 +195,17 @@ describe('Asset Transfer Proxies', () => { it('should succesfully make multiple token transfers', async () => { const erc20Balances = await erc20Wrapper.getBalancesAsync(); - const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); + const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); const amount = new BigNumber(10); const numTransfers = 2; - const assetMetadata = _.times(numTransfers, () => encodedProxyMetadata); + const assetData = _.times(numTransfers, () => encodedAssetDataWithoutProxyId); const fromAddresses = _.times(numTransfers, () => makerAddress); const toAddresses = _.times(numTransfers, () => takerAddress); const amounts = _.times(numTransfers, () => amount); const txHash = await erc20Proxy.batchTransferFrom.sendTransactionAsync( - assetMetadata, + assetData, fromAddresses, toAddresses, amounts, @@ -209,23 +227,20 @@ describe('Asset Transfer Proxies', () => { }); it('should throw if not called by an authorized address', async () => { - const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); + const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); const amount = new BigNumber(10); const numTransfers = 2; - const assetMetadata = _.times(numTransfers, () => encodedProxyMetadata); + const assetData = _.times(numTransfers, () => encodedAssetDataWithoutProxyId); const fromAddresses = _.times(numTransfers, () => makerAddress); const toAddresses = _.times(numTransfers, () => takerAddress); const amounts = _.times(numTransfers, () => amount); - return expect( - erc20Proxy.batchTransferFrom.sendTransactionAsync( - assetMetadata, - fromAddresses, - toAddresses, - amounts, - { from: notAuthorized }, - ), - ).to.be.rejectedWith(constants.REVERT); + return expectRevertOrAlwaysFailingTransactionAsync( + erc20Proxy.batchTransferFrom.sendTransactionAsync(assetData, fromAddresses, toAddresses, amounts, { + from: notAuthorized, + }), + ); }); }); @@ -238,20 +253,17 @@ describe('Asset Transfer Proxies', () => { describe('Transfer Proxy - ERC721', () => { describe('transferFrom', () => { it('should successfully transfer tokens', async () => { - // Construct metadata for ERC721 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData( - erc721Token.address, - erc721MakerTokenId, - ); + // Construct ERC721 asset data + const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); // Verify pre-condition const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress); // Perform a transfer from makerAddress to takerAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); const amount = new BigNumber(1); await web3Wrapper.awaitTransactionSuccessAsync( await erc721Proxy.transferFrom.sendTransactionAsync( - encodedProxyMetadata, + encodedAssetDataWithoutProxyId, makerAddress, takerAddress, amount, @@ -264,58 +276,138 @@ describe('Asset Transfer Proxies', () => { expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress); }); - it('should throw if transferring 0 amount of a token', async () => { - // Construct metadata for ERC721 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData( + it('should not call onERC721Received when transferring to a smart contract without receiver data', async () => { + // Construct ERC721 asset data + const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); + // Verify pre-condition + const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); + expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress); + // Perform a transfer from makerAddress to takerAddress + const amount = new BigNumber(1); + const txHash = await erc721Proxy.transferFrom.sendTransactionAsync( + encodedAssetDataWithoutProxyId, + makerAddress, + erc721Receiver.address, + amount, + { from: exchangeAddress }, + ); + await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); + // Parse transaction logs + const logDecoder = new LogDecoder(web3Wrapper, erc721Receiver.address); + const tx = await logDecoder.getTxWithDecodedLogsAsync(txHash); + // Verify that no log was emitted by erc721 receiver + expect(tx.logs.length).to.be.equal(0); + // Verify transfer was successful + const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); + expect(newOwnerMakerAsset).to.be.bignumber.equal(erc721Receiver.address); + }); + + it('should call onERC721Received when transferring to a smart contract with receiver data', async () => { + // Construct ERC721 asset data + const receiverData = ethUtil.bufferToHex(assetProxyUtils.encodeUint256(generatePseudoRandomSalt())); + const encodedAssetData = assetProxyUtils.encodeERC721AssetData( erc721Token.address, erc721MakerTokenId, + receiverData, ); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); + // Verify pre-condition + const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); + expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress); + // Perform a transfer from makerAddress to takerAddress + const amount = new BigNumber(1); + const txHash = await erc721Proxy.transferFrom.sendTransactionAsync( + encodedAssetDataWithoutProxyId, + makerAddress, + erc721Receiver.address, + amount, + { from: exchangeAddress }, + ); + await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS); + // Parse transaction logs + const logDecoder = new LogDecoder(web3Wrapper, erc721Receiver.address); + const tx = await logDecoder.getTxWithDecodedLogsAsync(txHash); + // Validate log emitted by erc721 receiver + expect(tx.logs.length).to.be.equal(1); + const tokenReceivedLog = tx.logs[0] as LogWithDecodedArgs<TokenReceivedContractEventArgs>; + expect(tokenReceivedLog.args.from).to.be.equal(makerAddress); + expect(tokenReceivedLog.args.tokenId).to.be.bignumber.equal(erc721MakerTokenId); + expect(tokenReceivedLog.args.data).to.be.equal(receiverData); + // Verify transfer was successful + const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); + expect(newOwnerMakerAsset).to.be.bignumber.equal(erc721Receiver.address); + }); + + it('should throw if there is receiver data but contract does not have onERC721Received', async () => { + // Construct ERC721 asset data + const receiverData = ethUtil.bufferToHex(assetProxyUtils.encodeUint256(generatePseudoRandomSalt())); + const encodedAssetData = assetProxyUtils.encodeERC721AssetData( + erc721Token.address, + erc721MakerTokenId, + receiverData, + ); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); + // Verify pre-condition + const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); + expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress); + // Perform a transfer from makerAddress to takerAddress + const amount = new BigNumber(1); + return expectRevertOrAlwaysFailingTransactionAsync( + erc721Proxy.transferFrom.sendTransactionAsync( + encodedAssetDataWithoutProxyId, + makerAddress, + erc20Proxy.address, // the ERC20 proxy does not have an ERC721 receiver + amount, + { from: exchangeAddress }, + ), + ); + }); + + it('should throw if transferring 0 amount of a token', async () => { + // Construct ERC721 asset data + const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); // Verify pre-condition const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress); // Perform a transfer from makerAddress to takerAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); const amount = new BigNumber(0); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( erc721Proxy.transferFrom.sendTransactionAsync( - encodedProxyMetadata, + encodedAssetDataWithoutProxyId, makerAddress, takerAddress, amount, { from: exchangeAddress }, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw if transferring > 1 amount of a token', async () => { - // Construct metadata for ERC721 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData( - erc721Token.address, - erc721MakerTokenId, - ); + // Construct ERC721 asset data + const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); // Verify pre-condition const ownerMakerAsset = await erc721Token.ownerOf.callAsync(erc721MakerTokenId); expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress); // Perform a transfer from makerAddress to takerAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); const amount = new BigNumber(500); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( erc721Proxy.transferFrom.sendTransactionAsync( - encodedProxyMetadata, + encodedAssetDataWithoutProxyId, makerAddress, takerAddress, amount, { from: exchangeAddress }, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw if allowances are too low', async () => { - // Construct metadata for ERC721 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData( - erc721Token.address, - erc721MakerTokenId, - ); + // Construct ERC721 asset data + const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); // Remove transfer approval for makerAddress. await web3Wrapper.awaitTransactionSuccessAsync( await erc721Token.setApprovalForAll.sendTransactionAsync(erc721Proxy.address, false, { @@ -325,9 +417,9 @@ describe('Asset Transfer Proxies', () => { ); // Perform a transfer; expect this to fail. const amount = new BigNumber(1); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( erc20Proxy.transferFrom.sendTransactionAsync( - encodedProxyMetadata, + encodedAssetDataWithoutProxyId, makerAddress, takerAddress, amount, @@ -335,26 +427,24 @@ describe('Asset Transfer Proxies', () => { from: notAuthorized, }, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw if requesting address is not authorized', async () => { - // Construct metadata for ERC721 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC721ProxyData( - erc721Token.address, - erc721MakerTokenId, - ); + // Construct ERC721 asset data + const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); // Perform a transfer from makerAddress to takerAddress const amount = new BigNumber(1); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( erc721Proxy.transferFrom.sendTransactionAsync( - encodedProxyMetadata, + encodedAssetDataWithoutProxyId, makerAddress, takerAddress, amount, { from: notAuthorized }, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); }); @@ -364,16 +454,16 @@ describe('Asset Transfer Proxies', () => { const [makerTokenIdA, makerTokenIdB] = erc721TokensById[makerAddress][erc721Token.address]; const numTransfers = 2; - const assetMetadata = [ - assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerTokenIdA), - assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerTokenIdB), + const assetData = [ + assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdA).slice(0, -2), + assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdB).slice(0, -2), ]; const fromAddresses = _.times(numTransfers, () => makerAddress); const toAddresses = _.times(numTransfers, () => takerAddress); const amounts = _.times(numTransfers, () => new BigNumber(1)); const txHash = await erc721Proxy.batchTransferFrom.sendTransactionAsync( - assetMetadata, + assetData, fromAddresses, toAddresses, amounts, @@ -396,23 +486,19 @@ describe('Asset Transfer Proxies', () => { const [makerTokenIdA, makerTokenIdB] = erc721TokensById[makerAddress][erc721Token.address]; const numTransfers = 2; - const assetMetadata = [ - assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerTokenIdA), - assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerTokenIdB), + const assetData = [ + assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdA).slice(0, -2), + assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdB).slice(0, -2), ]; const fromAddresses = _.times(numTransfers, () => makerAddress); const toAddresses = _.times(numTransfers, () => takerAddress); const amounts = _.times(numTransfers, () => new BigNumber(1)); - return expect( - erc721Proxy.batchTransferFrom.sendTransactionAsync( - assetMetadata, - fromAddresses, - toAddresses, - amounts, - { from: notAuthorized }, - ), - ).to.be.rejectedWith(constants.REVERT); + return expectRevertOrAlwaysFailingTransactionAsync( + erc721Proxy.batchTransferFrom.sendTransactionAsync(assetData, fromAddresses, toAddresses, amounts, { + from: notAuthorized, + }), + ); }); }); @@ -422,3 +508,5 @@ describe('Asset Transfer Proxies', () => { }); }); }); +// tslint:enable:no-unnecessary-type-assertion +// tslint:disable:max-file-line-count diff --git a/packages/contracts/test/asset_proxy_owner.ts b/packages/contracts/test/asset_proxy_owner.ts index 4c16b5cff..cbe429b12 100644 --- a/packages/contracts/test/asset_proxy_owner.ts +++ b/packages/contracts/test/asset_proxy_owner.ts @@ -2,9 +2,7 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import { LogWithDecodedArgs } from 'ethereum-types'; -import * as _ from 'lodash'; import 'make-promises-safe'; -import * as Web3 from 'web3'; import { AssetProxyOwnerContract, @@ -12,18 +10,23 @@ import { ExecutionContractEventArgs, ExecutionFailureContractEventArgs, SubmissionContractEventArgs, -} from '../src/contract_wrappers/generated/asset_proxy_owner'; -import { MixinAuthorizableContract } from '../src/contract_wrappers/generated/mixin_authorizable'; +} from '../src/generated_contract_wrappers/asset_proxy_owner'; +import { MixinAuthorizableContract } from '../src/generated_contract_wrappers/mixin_authorizable'; import { artifacts } from '../src/utils/artifacts'; +import { + expectRevertOrAlwaysFailingTransactionAsync, + expectRevertOrContractCallFailedAsync, +} from '../src/utils/assertions'; import { chaiSetup } from '../src/utils/chai_setup'; import { constants } from '../src/utils/constants'; +import { increaseTimeAndMineBlockAsync } from '../src/utils/increase_time'; import { MultiSigWrapper } from '../src/utils/multi_sig_wrapper'; import { provider, txDefaults, web3Wrapper } from '../src/utils/web3_wrapper'; chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - +// tslint:disable:no-unnecessary-type-assertion describe('AssetProxyOwner', () => { let owners: string[]; let authorized: string; @@ -101,7 +104,7 @@ describe('AssetProxyOwner', () => { }); it('should throw if a null address is included in assetProxyContracts', async () => { const assetProxyContractAddresses = [erc20Proxy.address, constants.NULL_ADDRESS]; - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( AssetProxyOwnerContract.deployFrom0xArtifactAsync( artifacts.AssetProxyOwner, provider, @@ -111,7 +114,7 @@ describe('AssetProxyOwner', () => { REQUIRED_APPROVALS, SECONDS_TIME_LOCKED, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); }); @@ -120,9 +123,9 @@ describe('AssetProxyOwner', () => { const notRemoveAuthorizedAddressData = erc20Proxy.addAuthorizedAddress.getABIEncodedTransactionData( owners[0], ); - return expect( + return expectRevertOrContractCallFailedAsync( multiSig.isFunctionRemoveAuthorizedAddress.callAsync(notRemoveAuthorizedAddressData), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should return true if data is for removeAuthorizedAddress', async () => { @@ -139,9 +142,9 @@ describe('AssetProxyOwner', () => { describe('registerAssetProxy', () => { it('should throw if not called by multisig', async () => { const isRegistered = true; - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( multiSig.registerAssetProxy.sendTransactionAsync(erc20Proxy.address, isRegistered, { from: owners[0] }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should register an address if called by multisig after timelock', async () => { @@ -156,11 +159,12 @@ describe('AssetProxyOwner', () => { registerAssetProxyData, owners[0], ); + const log = submitTxRes.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>; const txId = log.args.transactionId; await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); - await web3Wrapper.increaseTimeAsync(SECONDS_TIME_LOCKED.toNumber()); + await increaseTimeAndMineBlockAsync(SECONDS_TIME_LOCKED.toNumber()); const executeTxRes = await multiSigWrapper.executeTransactionAsync(txId, owners[0]); const registerLog = executeTxRes.logs[0] as LogWithDecodedArgs<AssetProxyRegistrationContractEventArgs>; @@ -187,7 +191,7 @@ describe('AssetProxyOwner', () => { const txId = log.args.transactionId; await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); - await web3Wrapper.increaseTimeAsync(SECONDS_TIME_LOCKED.toNumber()); + await increaseTimeAndMineBlockAsync(SECONDS_TIME_LOCKED.toNumber()); const executeTxRes = await multiSigWrapper.executeTransactionAsync(txId, owners[0]); const failureLog = executeTxRes.logs[0] as LogWithDecodedArgs<ExecutionFailureContractEventArgs>; @@ -239,7 +243,7 @@ describe('AssetProxyOwner', () => { await multiSigWrapper.confirmTransactionAsync(erc20AddAuthorizedAddressTxId, owners[1]); await multiSigWrapper.confirmTransactionAsync(erc721AddAuthorizedAddressTxId, owners[1]); - await web3Wrapper.increaseTimeAsync(SECONDS_TIME_LOCKED.toNumber()); + await increaseTimeAndMineBlockAsync(SECONDS_TIME_LOCKED.toNumber()); await multiSigWrapper.executeTransactionAsync(registerAssetProxyTxId, owners[0]); await multiSigWrapper.executeTransactionAsync(erc20AddAuthorizedAddressTxId, owners[0]); await multiSigWrapper.executeTransactionAsync(erc721AddAuthorizedAddressTxId, owners[0]); @@ -257,9 +261,9 @@ describe('AssetProxyOwner', () => { const log = res.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>; const txId = log.args.transactionId; - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( multiSig.executeRemoveAuthorizedAddress.sendTransactionAsync(txId, { from: owners[1] }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw if tx destination is not registered', async () => { @@ -276,9 +280,9 @@ describe('AssetProxyOwner', () => { await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( multiSig.executeRemoveAuthorizedAddress.sendTransactionAsync(txId, { from: owners[1] }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw if tx data is not for removeAuthorizedAddress', async () => { @@ -296,9 +300,9 @@ describe('AssetProxyOwner', () => { await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( multiSig.executeRemoveAuthorizedAddress.sendTransactionAsync(txId, { from: owners[1] }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should execute removeAuthorizedAddress for registered address if fully confirmed', async () => { @@ -349,9 +353,10 @@ describe('AssetProxyOwner', () => { const isExecuted = tx[3]; expect(isExecuted).to.equal(true); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( multiSig.executeRemoveAuthorizedAddress.sendTransactionAsync(txId, { from: owners[1] }), - ).to.be.rejectedWith(constants.REVERT); + ); }); }); }); +// tslint:enable:no-unnecessary-type-assertion diff --git a/packages/contracts/test/ether_token.ts b/packages/contracts/test/ether_token.ts index 4e52b658f..e2fc69aee 100644 --- a/packages/contracts/test/ether_token.ts +++ b/packages/contracts/test/ether_token.ts @@ -1,11 +1,12 @@ -import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; -import { BigNumber, promisify } from '@0xproject/utils'; +import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; import 'make-promises-safe'; -import { WETH9Contract } from '../src/contract_wrappers/generated/weth9'; +import { WETH9Contract } from '../src/generated_contract_wrappers/weth9'; import { artifacts } from '../src/utils/artifacts'; +import { expectInsufficientFundsAsync, expectRevertOrAlwaysFailingTransactionAsync } from '../src/utils/assertions'; import { chaiSetup } from '../src/utils/chai_setup'; import { constants } from '../src/utils/constants'; import { provider, txDefaults, web3Wrapper } from '../src/utils/web3_wrapper'; @@ -45,9 +46,7 @@ describe('EtherToken', () => { const initEthBalance = await web3Wrapper.getBalanceInWeiAsync(account); const ethToDeposit = initEthBalance.plus(1); - return expect(etherToken.deposit.sendTransactionAsync({ value: ethToDeposit })).to.be.rejectedWith( - "ender doesn't have enough funds to send tx.", - ); + return expectInsufficientFundsAsync(etherToken.deposit.sendTransactionAsync({ value: ethToDeposit })); }); it('should convert deposited Ether to wrapped Ether tokens', async () => { @@ -76,8 +75,8 @@ describe('EtherToken', () => { const initEthTokenBalance = await etherToken.balanceOf.callAsync(account); const ethTokensToWithdraw = initEthTokenBalance.plus(1); - return expect(etherToken.withdraw.sendTransactionAsync(ethTokensToWithdraw)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + etherToken.withdraw.sendTransactionAsync(ethTokensToWithdraw), ); }); diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts index 6d62c2b86..63c2fa6c0 100644 --- a/packages/contracts/test/exchange/core.ts +++ b/packages/contracts/test/exchange/core.ts @@ -8,16 +8,17 @@ import { LogWithDecodedArgs } from 'ethereum-types'; import ethUtil = require('ethereumjs-util'); import 'make-promises-safe'; -import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token'; -import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token'; -import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy'; -import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy'; +import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token'; +import { DummyERC721TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c721_token'; +import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy'; +import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy'; import { CancelContractEventArgs, ExchangeContract, FillContractEventArgs, -} from '../../src/contract_wrappers/generated/exchange'; +} from '../../src/generated_contract_wrappers/exchange'; import { artifacts } from '../../src/utils/artifacts'; +import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; @@ -30,7 +31,7 @@ import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper' chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - +// tslint:disable:no-unnecessary-type-assertion describe('Exchange core', () => { let makerAddress: string; let owner: string; @@ -86,7 +87,7 @@ describe('Exchange core', () => { artifacts.Exchange, provider, txDefaults, - assetProxyUtils.encodeERC20ProxyData(zrxToken.address), + zrxToken.address, ); exchangeWrapper = new ExchangeWrapper(exchange, provider); await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner); @@ -113,8 +114,8 @@ describe('Exchange core', () => { exchangeAddress: exchange.address, makerAddress, feeRecipientAddress, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultMakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultTakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultMakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultTakerAssetAddress), }; const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); @@ -414,8 +415,8 @@ describe('Exchange core', () => { makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), }); - return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), ); }); @@ -431,8 +432,8 @@ describe('Exchange core', () => { const invalidSigBuff = Buffer.concat([v, invalidR, invalidS, signatureType]); const invalidSigHex = `0x${invalidSigBuff.toString('hex')}`; signedOrder.signature = invalidSigHex; - return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), ); }); @@ -441,8 +442,8 @@ describe('Exchange core', () => { makerAssetAmount: new BigNumber(0), }); - return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), ); }); @@ -451,19 +452,19 @@ describe('Exchange core', () => { takerAssetAmount: new BigNumber(0), }); - return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), ); }); it('should throw if takerAssetFillAmount is 0', async () => { signedOrder = orderFactory.newSignedOrder(); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount: new BigNumber(0), }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw if maker erc20Balances are too low to fill order', async () => { @@ -471,8 +472,8 @@ describe('Exchange core', () => { makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18), }); - return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), ); }); @@ -480,9 +481,8 @@ describe('Exchange core', () => { signedOrder = orderFactory.newSignedOrder({ takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18), }); - - return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), ); }); @@ -493,11 +493,8 @@ describe('Exchange core', () => { }), constants.AWAIT_TRANSACTION_MINED_MS, ); - // HACK: `rejectWith` returns a "promise-like" type, but not an actual "Promise", so TSLint - // complains, even though we do need to `await` it. So we disable the TSLint error below. - // tslint:disable-next-line:await-promise - await expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), ); }); @@ -508,11 +505,8 @@ describe('Exchange core', () => { }), constants.AWAIT_TRANSACTION_MINED_MS, ); - // HACK: `rejectWith` returns a "promise-like" type, but not an actual "Promise", so TSLint - // complains, even though we do need to `await` it. So we disable the TSLint error below. - // tslint:disable-next-line:await-promise - await expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), ); }); @@ -520,16 +514,16 @@ describe('Exchange core', () => { signedOrder = orderFactory.newSignedOrder({ expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)), }); - return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), ); }); it('should throw if no value is filled', async () => { signedOrder = orderFactory.newSignedOrder(); await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - return expect(exchangeWrapper.fillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), ); }); }); @@ -541,8 +535,8 @@ describe('Exchange core', () => { }); it('should throw if not sent by maker', async () => { - return expect(exchangeWrapper.cancelOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.cancelOrderAsync(signedOrder, takerAddress), ); }); @@ -551,8 +545,8 @@ describe('Exchange core', () => { makerAssetAmount: new BigNumber(0), }); - return expect(exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), ); }); @@ -561,22 +555,21 @@ describe('Exchange core', () => { takerAssetAmount: new BigNumber(0), }); - return expect(exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), ); }); it('should be able to cancel a full order', async () => { await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount: signedOrder.takerAssetAmount.div(2), }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should log 1 event with correct arguments', async () => { - const divisor = 2; const res = await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); expect(res.logs).to.have.length(1); @@ -592,8 +585,8 @@ describe('Exchange core', () => { it('should throw if already cancelled', async () => { await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); - return expect(exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), ); }); @@ -601,8 +594,8 @@ describe('Exchange core', () => { signedOrder = orderFactory.newSignedOrder({ expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)), }); - return expect(exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), ); }); @@ -618,11 +611,11 @@ describe('Exchange core', () => { }); const fillTakerAssetAmount2 = new BigNumber(1); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount: fillTakerAssetAmount2, }), - ).to.be.rejectedWith(constants.REVERT); + ); }); }); @@ -631,16 +624,16 @@ describe('Exchange core', () => { const makerEpoch = new BigNumber(1); await exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress); const lesserMakerEpoch = new BigNumber(0); - return expect(exchangeWrapper.cancelOrdersUpToAsync(lesserMakerEpoch, makerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.cancelOrdersUpToAsync(lesserMakerEpoch, makerAddress), ); }); it('should fail to set makerEpoch equal to existing makerEpoch', async () => { const makerEpoch = new BigNumber(1); await exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress); - return expect(exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.cancelOrdersUpToAsync(makerEpoch, makerAddress), ); }); @@ -674,7 +667,12 @@ describe('Exchange core', () => { salt: new BigNumber(3), }), ]; - await exchangeWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress); + await exchangeWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress, { + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 490000, + }); const newBalances = await erc20Wrapper.getBalancesAsync(); const fillMakerAssetAmount = signedOrders[2].makerAssetAmount.add(signedOrders[3].makerAssetAmount); @@ -713,8 +711,8 @@ describe('Exchange core', () => { signedOrder = orderFactory.newSignedOrder({ makerAssetAmount: new BigNumber(1), takerAssetAmount: new BigNumber(1), - makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId), - takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId), + makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), + takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), }); // Verify pre-conditions const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); @@ -723,6 +721,7 @@ describe('Exchange core', () => { expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); // Call Exchange const takerAssetFillAmount = signedOrder.takerAssetAmount; + // tslint:disable-next-line:no-unused-variable const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); // Verify post-conditions const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); @@ -738,8 +737,8 @@ describe('Exchange core', () => { signedOrder = orderFactory.newSignedOrder({ makerAssetAmount: new BigNumber(1), takerAssetAmount: new BigNumber(1), - makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId), - takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId), + makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), + takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), }); // Verify pre-conditions const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); @@ -748,9 +747,9 @@ describe('Exchange core', () => { expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); // Call Exchange const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw when taker does not own the token with id takerAssetId', async () => { @@ -760,8 +759,8 @@ describe('Exchange core', () => { signedOrder = orderFactory.newSignedOrder({ makerAssetAmount: new BigNumber(1), takerAssetAmount: new BigNumber(1), - makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId), - takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId), + makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), + takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), }); // Verify pre-conditions const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); @@ -770,9 +769,9 @@ describe('Exchange core', () => { expect(initialOwnerTakerAsset).to.be.bignumber.not.equal(takerAddress); // Call Exchange const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw when makerAssetAmount is greater than 1', async () => { @@ -782,8 +781,8 @@ describe('Exchange core', () => { signedOrder = orderFactory.newSignedOrder({ makerAssetAmount: new BigNumber(2), takerAssetAmount: new BigNumber(1), - makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId), - takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId), + makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), + takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), }); // Verify pre-conditions const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); @@ -792,9 +791,9 @@ describe('Exchange core', () => { expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); // Call Exchange const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw when takerAssetAmount is greater than 1', async () => { @@ -804,8 +803,8 @@ describe('Exchange core', () => { signedOrder = orderFactory.newSignedOrder({ makerAssetAmount: new BigNumber(1), takerAssetAmount: new BigNumber(500), - makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId), - takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId), + makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), + takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), }); // Verify pre-conditions const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); @@ -814,9 +813,9 @@ describe('Exchange core', () => { expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); // Call Exchange const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw on partial fill', async () => { @@ -826,8 +825,8 @@ describe('Exchange core', () => { signedOrder = orderFactory.newSignedOrder({ makerAssetAmount: new BigNumber(1), takerAssetAmount: new BigNumber(0), - makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId), - takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId), + makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), + takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), }); // Verify pre-conditions const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); @@ -836,9 +835,9 @@ describe('Exchange core', () => { expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); // Call Exchange const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should successfully fill order when makerAsset is ERC721 and takerAsset is ERC20', async () => { @@ -847,8 +846,8 @@ describe('Exchange core', () => { signedOrder = orderFactory.newSignedOrder({ makerAssetAmount: new BigNumber(1), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultTakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultTakerAssetAddress), }); // Verify pre-conditions const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); @@ -887,8 +886,8 @@ describe('Exchange core', () => { signedOrder = orderFactory.newSignedOrder({ takerAssetAmount: new BigNumber(1), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId), - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultMakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultMakerAssetAddress), }); // Verify pre-conditions const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); @@ -921,4 +920,6 @@ describe('Exchange core', () => { ); }); }); -}); // tslint:disable-line:max-file-line-count +}); +// tslint:disable:max-file-line-count +// tslint:enable:no-unnecessary-type-assertion diff --git a/packages/contracts/test/exchange/dispatcher.ts b/packages/contracts/test/exchange/dispatcher.ts index 8bc66e3cf..abbfd7ac7 100644 --- a/packages/contracts/test/exchange/dispatcher.ts +++ b/packages/contracts/test/exchange/dispatcher.ts @@ -3,13 +3,13 @@ import { assetProxyUtils } from '@0xproject/order-utils'; import { AssetProxyId } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; -import * as Web3 from 'web3'; -import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token'; -import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy'; -import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy'; -import { TestAssetProxyDispatcherContract } from '../../src/contract_wrappers/generated/test_asset_proxy_dispatcher'; +import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token'; +import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy'; +import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy'; +import { TestAssetProxyDispatcherContract } from '../../src/generated_contract_wrappers/test_asset_proxy_dispatcher'; import { artifacts } from '../../src/utils/artifacts'; +import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; @@ -23,7 +23,6 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); describe('AssetProxyDispatcher', () => { let owner: string; let notOwner: string; - let notAuthorized: string; let makerAddress: string; let takerAddress: string; @@ -45,7 +44,6 @@ describe('AssetProxyDispatcher', () => { // Setup accounts & addresses const accounts = await web3Wrapper.getAvailableAddressesAsync(); const usedAddresses = ([owner, notOwner, makerAddress, takerAddress] = accounts); - notAuthorized = notOwner; erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); @@ -177,14 +175,14 @@ describe('AssetProxyDispatcher', () => { const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); expect(proxyAddress).to.be.equal(erc20Proxy.address); // The following transaction will throw because the currentAddress is no longer constants.NULL_ADDRESS - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( assetProxyDispatcher.registerAssetProxy.sendTransactionAsync( AssetProxyId.ERC20, erc20Proxy.address, constants.NULL_ADDRESS, { from: owner }, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should be able to reset proxy address to NULL', async () => { @@ -218,26 +216,26 @@ describe('AssetProxyDispatcher', () => { it('should throw if requesting address is not owner', async () => { const prevProxyAddress = constants.NULL_ADDRESS; - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( assetProxyDispatcher.registerAssetProxy.sendTransactionAsync( AssetProxyId.ERC20, erc20Proxy.address, prevProxyAddress, { from: notOwner }, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw if attempting to register a proxy to the incorrect id', async () => { const prevProxyAddress = constants.NULL_ADDRESS; - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( assetProxyDispatcher.registerAssetProxy.sendTransactionAsync( AssetProxyId.ERC721, erc20Proxy.address, prevProxyAddress, { from: owner }, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); }); @@ -277,13 +275,15 @@ describe('AssetProxyDispatcher', () => { constants.AWAIT_TRANSACTION_MINED_MS, ); // Construct metadata for ERC20 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); + const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); // Perform a transfer from makerAddress to takerAddress const erc20Balances = await erc20Wrapper.getBalancesAsync(); const amount = new BigNumber(10); await web3Wrapper.awaitTransactionSuccessAsync( await assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( - encodedProxyMetadata, + encodedAssetDataWithoutProxyId, + AssetProxyId.ERC20, makerAddress, takerAddress, amount, @@ -303,19 +303,20 @@ describe('AssetProxyDispatcher', () => { it('should throw if dispatching to unregistered proxy', async () => { // Construct metadata for ERC20 proxy - const encodedProxyMetadata = assetProxyUtils.encodeERC20ProxyData(zrxToken.address); + const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address); + const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); // Perform a transfer from makerAddress to takerAddress - const erc20Balances = await erc20Wrapper.getBalancesAsync(); const amount = new BigNumber(10); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( - encodedProxyMetadata, + encodedAssetDataWithoutProxyId, + AssetProxyId.ERC20, makerAddress, takerAddress, amount, { from: owner }, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); }); }); diff --git a/packages/contracts/test/exchange/libs.ts b/packages/contracts/test/exchange/libs.ts index ac9020bce..c08001198 100644 --- a/packages/contracts/test/exchange/libs.ts +++ b/packages/contracts/test/exchange/libs.ts @@ -3,9 +3,8 @@ import { assetProxyUtils, EIP712Utils, orderHashUtils } from '@0xproject/order-u import { SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; -import ethUtil = require('ethereumjs-util'); -import { TestLibsContract } from '../../src/contract_wrappers/generated/test_libs'; +import { TestLibsContract } from '../../src/generated_contract_wrappers/test_libs'; import { addressUtils } from '../../src/utils/address_utils'; import { artifacts } from '../../src/utils/artifacts'; import { chaiSetup } from '../../src/utils/chai_setup'; @@ -39,8 +38,8 @@ describe('Exchange libs', () => { exchangeAddress: libs.address, makerAddress, feeRecipientAddress: addressUtils.generatePseudoRandomAddress(), - makerAssetData: assetProxyUtils.encodeERC20ProxyData(addressUtils.generatePseudoRandomAddress()), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(addressUtils.generatePseudoRandomAddress()), + makerAssetData: assetProxyUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), + takerAssetData: assetProxyUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), }; const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); diff --git a/packages/contracts/test/exchange/match_orders.ts b/packages/contracts/test/exchange/match_orders.ts index 24ee794bc..b8dca04fd 100644 --- a/packages/contracts/test/exchange/match_orders.ts +++ b/packages/contracts/test/exchange/match_orders.ts @@ -1,23 +1,18 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; -import { assetProxyUtils, crypto } from '@0xproject/order-utils'; -import { AssetProxyId, SignedOrder } from '@0xproject/types'; +import { assetProxyUtils } from '@0xproject/order-utils'; +import { AssetProxyId } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; -import { LogWithDecodedArgs } from 'ethereum-types'; -import ethUtil = require('ethereumjs-util'); import * as _ from 'lodash'; -import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token'; -import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token'; -import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy'; -import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy'; -import { - CancelContractEventArgs, - ExchangeContract, - FillContractEventArgs, -} from '../../src/contract_wrappers/generated/exchange'; +import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token'; +import { DummyERC721TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c721_token'; +import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy'; +import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy'; +import { ExchangeContract } from '../../src/generated_contract_wrappers/exchange'; import { artifacts } from '../../src/utils/artifacts'; +import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; @@ -25,13 +20,7 @@ import { ERC721Wrapper } from '../../src/utils/erc721_wrapper'; import { ExchangeWrapper } from '../../src/utils/exchange_wrapper'; import { MatchOrderTester } from '../../src/utils/match_order_tester'; import { OrderFactory } from '../../src/utils/order_factory'; -import { - ContractName, - ERC20BalancesByOwner, - ERC721TokenIdsByOwner, - OrderInfo, - OrderStatus, -} from '../../src/utils/types'; +import { ERC20BalancesByOwner, ERC721TokenIdsByOwner, OrderInfo, OrderStatus } from '../../src/utils/types'; import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; chaiSetup.configure(); @@ -64,7 +53,6 @@ describe('matchOrders', () => { let erc721LeftMakerAssetIds: BigNumber[]; let erc721RightMakerAssetIds: BigNumber[]; - let erc721TakerAssetIds: BigNumber[]; let defaultERC20MakerAssetAddress: string; let defaultERC20TakerAssetAddress: string; @@ -103,13 +91,12 @@ describe('matchOrders', () => { const erc721Balances = await erc721Wrapper.getBalancesAsync(); erc721LeftMakerAssetIds = erc721Balances[makerAddressLeft][erc721Token.address]; erc721RightMakerAssetIds = erc721Balances[makerAddressRight][erc721Token.address]; - erc721TakerAssetIds = erc721Balances[takerAddress][erc721Token.address]; // Depoy exchange exchange = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, provider, txDefaults, - assetProxyUtils.encodeERC20ProxyData(zrxToken.address), + zrxToken.address, ); exchangeWrapper = new ExchangeWrapper(exchange, provider); await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner); @@ -135,8 +122,8 @@ describe('matchOrders', () => { const defaultOrderParams = { ...constants.STATIC_ORDER_PARAMS, exchangeAddress: exchange.address, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), }; const privateKeyLeft = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddressLeft)]; orderFactoryLeft = new OrderFactory(privateKeyLeft, defaultOrderParams); @@ -161,16 +148,14 @@ describe('matchOrders', () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), feeRecipientAddress: feeRecipientAddressRight, @@ -195,16 +180,14 @@ describe('matchOrders', () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), feeRecipientAddress: feeRecipientAddressRight, @@ -240,16 +223,14 @@ describe('matchOrders', () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(20), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(4), 18), feeRecipientAddress: feeRecipientAddressRight, @@ -274,16 +255,14 @@ describe('matchOrders', () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), feeRecipientAddress: feeRecipientAddressRight, @@ -308,16 +287,14 @@ describe('matchOrders', () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), feeRecipientAddress: feeRecipientAddressRight, @@ -347,8 +324,8 @@ describe('matchOrders', () => { // branch in the contract twice for this test. const signedOrderRight2 = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), feeRecipientAddress: feeRecipientAddressRight, @@ -377,8 +354,6 @@ describe('matchOrders', () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), feeRecipientAddress: feeRecipientAddressLeft, @@ -386,8 +361,8 @@ describe('matchOrders', () => { const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), feeRecipientAddress: feeRecipientAddressRight, @@ -417,8 +392,6 @@ describe('matchOrders', () => { // branch in the contract twice for this test. const signedOrderLeft2 = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(50), 18), feeRecipientAddress: feeRecipientAddressLeft, @@ -450,16 +423,14 @@ describe('matchOrders', () => { const feeRecipientAddress = feeRecipientAddressLeft; const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), feeRecipientAddress, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), feeRecipientAddress, @@ -478,16 +449,14 @@ describe('matchOrders', () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), feeRecipientAddress: feeRecipientAddressRight, @@ -507,16 +476,14 @@ describe('matchOrders', () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), feeRecipientAddress: feeRecipientAddressRight, @@ -536,16 +503,14 @@ describe('matchOrders', () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), feeRecipientAddress: feeRecipientAddressRight, @@ -565,16 +530,14 @@ describe('matchOrders', () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), feeRecipientAddress: feeRecipientAddressRight, @@ -594,16 +557,14 @@ describe('matchOrders', () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), feeRecipientAddress: makerAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), feeRecipientAddress: makerAddressRight, @@ -622,16 +583,14 @@ describe('matchOrders', () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), feeRecipientAddress: feeRecipientAddressRight, @@ -639,25 +598,23 @@ describe('matchOrders', () => { // Cancel left order await exchangeWrapper.cancelOrderAsync(signedOrderLeft, signedOrderLeft.makerAddress); // Match orders - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('Should throw if right order is not fillable', async () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), feeRecipientAddress: feeRecipientAddressRight, @@ -665,31 +622,29 @@ describe('matchOrders', () => { // Cancel right order await exchangeWrapper.cancelOrderAsync(signedOrderRight, signedOrderRight.makerAddress); // Match orders - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw if there is not a positive spread', async () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), feeRecipientAddress: feeRecipientAddressRight, }); // Match orders - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, @@ -697,29 +652,27 @@ describe('matchOrders', () => { erc20BalancesByOwner, erc721TokenIdsByOwner, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw if the left maker asset is not equal to the right taker asset ', async () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), feeRecipientAddress: feeRecipientAddressRight, }); // Match orders - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, @@ -727,29 +680,29 @@ describe('matchOrders', () => { erc20BalancesByOwner, erc721TokenIdsByOwner, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw if the right maker asset is not equal to the left taker asset', async () => { // Create orders to match const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(5), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(2), 18), feeRecipientAddress: feeRecipientAddressRight, }); // Match orders - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, @@ -757,7 +710,7 @@ describe('matchOrders', () => { erc20BalancesByOwner, erc721TokenIdsByOwner, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should transfer correct amounts when left order maker asset is an ERC721 token', async () => { @@ -765,16 +718,16 @@ describe('matchOrders', () => { const erc721TokenToTransfer = erc721LeftMakerAssetIds[0]; const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), makerAssetAmount: new BigNumber(1), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20TakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20TakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: new BigNumber(1), feeRecipientAddress: feeRecipientAddressRight, @@ -800,16 +753,16 @@ describe('matchOrders', () => { const erc721TokenToTransfer = erc721RightMakerAssetIds[0]; const signedOrderLeft = orderFactoryLeft.newSignedOrder({ makerAddress: makerAddressLeft, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), takerAssetAmount: new BigNumber(1), feeRecipientAddress: feeRecipientAddressLeft, }); const signedOrderRight = orderFactoryRight.newSignedOrder({ makerAddress: makerAddressRight, - makerAssetData: assetProxyUtils.encodeERC721ProxyData(defaultERC721AssetAddress, erc721TokenToTransfer), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultERC20MakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC721AssetData(defaultERC721AssetAddress, erc721TokenToTransfer), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultERC20MakerAssetAddress), makerAssetAmount: new BigNumber(1), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), 18), feeRecipientAddress: feeRecipientAddressRight, diff --git a/packages/contracts/test/exchange/signature_validator.ts b/packages/contracts/test/exchange/signature_validator.ts index 936910ee9..c39fd6ee4 100644 --- a/packages/contracts/test/exchange/signature_validator.ts +++ b/packages/contracts/test/exchange/signature_validator.ts @@ -1,11 +1,10 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { assetProxyUtils, orderHashUtils } from '@0xproject/order-utils'; import { SignedOrder } from '@0xproject/types'; -import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import ethUtil = require('ethereumjs-util'); -import { TestSignatureValidatorContract } from '../../src/contract_wrappers/generated/test_signature_validator'; +import { TestSignatureValidatorContract } from '../../src/generated_contract_wrappers/test_signature_validator'; import { addressUtils } from '../../src/utils/address_utils'; import { artifacts } from '../../src/utils/artifacts'; import { chaiSetup } from '../../src/utils/chai_setup'; @@ -43,8 +42,8 @@ describe('MixinSignatureValidator', () => { exchangeAddress: signatureValidator.address, makerAddress, feeRecipientAddress: addressUtils.generatePseudoRandomAddress(), - makerAssetData: assetProxyUtils.encodeERC20ProxyData(addressUtils.generatePseudoRandomAddress()), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(addressUtils.generatePseudoRandomAddress()), + makerAssetData: assetProxyUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), + takerAssetData: assetProxyUtils.encodeERC20AssetData(addressUtils.generatePseudoRandomAddress()), }; const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); diff --git a/packages/contracts/test/exchange/transactions.ts b/packages/contracts/test/exchange/transactions.ts index f31053ad3..21ff123e5 100644 --- a/packages/contracts/test/exchange/transactions.ts +++ b/packages/contracts/test/exchange/transactions.ts @@ -1,16 +1,15 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils'; -import { AssetProxyId, Order, OrderWithoutExchangeAddress, SignedOrder } from '@0xproject/types'; +import { AssetProxyId, OrderWithoutExchangeAddress, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; -import * as ethUtil from 'ethereumjs-util'; -import * as Web3 from 'web3'; -import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token'; -import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy'; -import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange'; -import { WhitelistContract } from '../../src/contract_wrappers/generated/whitelist'; +import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token'; +import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy'; +import { ExchangeContract } from '../../src/generated_contract_wrappers/exchange'; +import { WhitelistContract } from '../../src/generated_contract_wrappers/whitelist'; import { artifacts } from '../../src/utils/artifacts'; +import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; @@ -18,7 +17,7 @@ import { ExchangeWrapper } from '../../src/utils/exchange_wrapper'; import { OrderFactory } from '../../src/utils/order_factory'; import { orderUtils } from '../../src/utils/order_utils'; import { TransactionFactory } from '../../src/utils/transaction_factory'; -import { ERC20BalancesByOwner, OrderStatus, SignedTransaction } from '../../src/utils/types'; +import { ERC20BalancesByOwner, SignedTransaction } from '../../src/utils/types'; import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; chaiSetup.configure(); @@ -73,7 +72,7 @@ describe('Exchange transactions', () => { artifacts.Exchange, provider, txDefaults, - assetProxyUtils.encodeERC20ProxyData(zrxToken.address), + zrxToken.address, ); exchangeWrapper = new ExchangeWrapper(exchange, provider); await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner); @@ -92,8 +91,8 @@ describe('Exchange transactions', () => { exchangeAddress: exchange.address, makerAddress, feeRecipientAddress, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultMakerTokenAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultTakerTokenAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultMakerTokenAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultTakerTokenAddress), }; makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(takerAddress)]; @@ -126,8 +125,8 @@ describe('Exchange transactions', () => { }); it('should throw if not called by specified sender', async () => { - return expect(exchangeWrapper.executeTransactionAsync(signedTx, takerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.executeTransactionAsync(signedTx, takerAddress), ); }); @@ -168,8 +167,8 @@ describe('Exchange transactions', () => { it('should throw if the a 0x transaction with the same transactionHash has already been executed', async () => { await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - return expect(exchangeWrapper.executeTransactionAsync(signedTx, senderAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.executeTransactionAsync(signedTx, senderAddress), ); }); @@ -187,15 +186,15 @@ describe('Exchange transactions', () => { }); it('should throw if not called by specified sender', async () => { - return expect(exchangeWrapper.executeTransactionAsync(signedTx, makerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.executeTransactionAsync(signedTx, makerAddress), ); }); it('should cancel the order when signed by maker and called by sender', async () => { await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - return expect(exchangeWrapper.fillOrderAsync(signedOrder, senderAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.fillOrderAsync(signedOrder, senderAddress), ); }); }); @@ -217,6 +216,7 @@ describe('Exchange transactions', () => { await exchange.setSignatureValidatorApproval.sendTransactionAsync(whitelist.address, isApproved, { from: takerAddress, }), + constants.AWAIT_TRANSACTION_MINED_MS, ); const defaultOrderParams = { ...constants.STATIC_ORDER_PARAMS, @@ -224,8 +224,8 @@ describe('Exchange transactions', () => { exchangeAddress: exchange.address, makerAddress, feeRecipientAddress, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultMakerTokenAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultTakerTokenAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultMakerTokenAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultTakerTokenAddress), }; whitelistOrderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams); }); @@ -239,12 +239,13 @@ describe('Exchange transactions', () => { const isApproved = true; await web3Wrapper.awaitTransactionSuccessAsync( await whitelist.updateWhitelistStatus.sendTransactionAsync(takerAddress, isApproved, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, ); orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); const takerAssetFillAmount = signedOrder.takerAssetAmount; const salt = generatePseudoRandomSalt(); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( whitelist.fillOrderIfWhitelisted.sendTransactionAsync( orderWithoutExchangeAddress, takerAssetFillAmount, @@ -252,19 +253,20 @@ describe('Exchange transactions', () => { signedOrder.signature, { from: takerAddress }, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should revert if taker has not been whitelisted', async () => { const isApproved = true; await web3Wrapper.awaitTransactionSuccessAsync( await whitelist.updateWhitelistStatus.sendTransactionAsync(makerAddress, isApproved, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, ); orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); const takerAssetFillAmount = signedOrder.takerAssetAmount; const salt = generatePseudoRandomSalt(); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( whitelist.fillOrderIfWhitelisted.sendTransactionAsync( orderWithoutExchangeAddress, takerAssetFillAmount, @@ -272,17 +274,19 @@ describe('Exchange transactions', () => { signedOrder.signature, { from: takerAddress }, ), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should fill the order if maker and taker have been whitelisted', async () => { const isApproved = true; await web3Wrapper.awaitTransactionSuccessAsync( await whitelist.updateWhitelistStatus.sendTransactionAsync(makerAddress, isApproved, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, ); await web3Wrapper.awaitTransactionSuccessAsync( await whitelist.updateWhitelistStatus.sendTransactionAsync(takerAddress, isApproved, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, ); orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); @@ -296,6 +300,7 @@ describe('Exchange transactions', () => { signedOrder.signature, { from: takerAddress }, ), + constants.AWAIT_TRANSACTION_MINED_MS, ); const newBalances = await erc20Wrapper.getBalancesAsync(); diff --git a/packages/contracts/test/exchange/wrapper.ts b/packages/contracts/test/exchange/wrapper.ts index 583ec9f91..abba1ac4f 100644 --- a/packages/contracts/test/exchange/wrapper.ts +++ b/packages/contracts/test/exchange/wrapper.ts @@ -1,4 +1,4 @@ -import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; +import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { assetProxyUtils } from '@0xproject/order-utils'; import { AssetProxyId, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; @@ -6,15 +6,14 @@ import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; import * as _ from 'lodash'; import 'make-promises-safe'; -import * as Web3 from 'web3'; - -import { DummyERC20TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c20_token'; -import { DummyERC721TokenContract } from '../../src/contract_wrappers/generated/dummy_e_r_c721_token'; -import { ERC20ProxyContract } from '../../src/contract_wrappers/generated/e_r_c20_proxy'; -import { ERC721ProxyContract } from '../../src/contract_wrappers/generated/e_r_c721_proxy'; -import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange'; -import { TokenRegistryContract } from '../../src/contract_wrappers/generated/token_registry'; + +import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c20_token'; +import { DummyERC721TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c721_token'; +import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy'; +import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy'; +import { ExchangeContract } from '../../src/generated_contract_wrappers/exchange'; import { artifacts } from '../../src/utils/artifacts'; +import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; @@ -82,7 +81,7 @@ describe('Exchange wrappers', () => { artifacts.Exchange, provider, txDefaults, - assetProxyUtils.encodeERC20ProxyData(zrxToken.address), + zrxToken.address, ); exchangeWrapper = new ExchangeWrapper(exchange, provider); await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner); @@ -109,8 +108,8 @@ describe('Exchange wrappers', () => { exchangeAddress: exchange.address, makerAddress, feeRecipientAddress, - makerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultMakerAssetAddress), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(defaultTakerAssetAddress), + makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultMakerAssetAddress), + takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultTakerAssetAddress), }; const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); @@ -172,8 +171,8 @@ describe('Exchange wrappers', () => { expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)), }); - return expect(exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress), ); }); @@ -184,8 +183,8 @@ describe('Exchange wrappers', () => { takerAssetFillAmount: signedOrder.takerAssetAmount.div(2), }); - return expect(exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress), ); }); }); @@ -197,12 +196,16 @@ describe('Exchange wrappers', () => { takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), }); const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); + await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress, { takerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 250000, }); const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFilledAmount = takerAssetFillAmount .times(signedOrder.makerAssetAmount) .dividedToIntegerBy(signedOrder.takerAssetAmount); @@ -212,6 +215,7 @@ describe('Exchange wrappers', () => { const takerFee = signedOrder.takerFee .times(makerAssetFilledAmount) .dividedToIntegerBy(signedOrder.makerAssetAmount); + expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), ); @@ -300,7 +304,7 @@ describe('Exchange wrappers', () => { const signedOrder = orderFactory.newSignedOrder({ makerAssetAmount: makerZRXBalance, makerFee: new BigNumber(1), - makerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address), + makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address), }); await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); const newBalances = await erc20Wrapper.getBalancesAsync(); @@ -312,7 +316,7 @@ describe('Exchange wrappers', () => { const signedOrder = orderFactory.newSignedOrder({ makerAssetAmount: new BigNumber(makerZRXAllowance), makerFee: new BigNumber(1), - makerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address), + makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address), }); await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); const newBalances = await erc20Wrapper.getBalancesAsync(); @@ -324,7 +328,7 @@ describe('Exchange wrappers', () => { const signedOrder = orderFactory.newSignedOrder({ takerAssetAmount: takerZRXBalance, takerFee: new BigNumber(1), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address), + takerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address), }); await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); const newBalances = await erc20Wrapper.getBalancesAsync(); @@ -336,7 +340,7 @@ describe('Exchange wrappers', () => { const signedOrder = orderFactory.newSignedOrder({ takerAssetAmount: new BigNumber(takerZRXAllowance), takerFee: new BigNumber(1), - takerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address), + takerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address), }); await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress); const newBalances = await erc20Wrapper.getBalancesAsync(); @@ -350,8 +354,8 @@ describe('Exchange wrappers', () => { const signedOrder = orderFactory.newSignedOrder({ makerAssetAmount: new BigNumber(1), takerAssetAmount: new BigNumber(1), - makerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, makerAssetId), - takerAssetData: assetProxyUtils.encodeERC721ProxyData(erc721Token.address, takerAssetId), + makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), + takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), }); // Verify pre-conditions const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); @@ -360,7 +364,13 @@ describe('Exchange wrappers', () => { expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); // Call Exchange const takerAssetFillAmount = signedOrder.takerAssetAmount; - await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress, { takerAssetFillAmount }); + await exchangeWrapper.fillOrderNoThrowAsync(signedOrder, takerAddress, { + takerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 270000, + }); // Verify post-conditions const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress); @@ -485,11 +495,11 @@ describe('Exchange wrappers', () => { await exchangeWrapper.fillOrKillOrderAsync(signedOrders[0], takerAddress); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( exchangeWrapper.batchFillOrKillOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmounts, }), - ).to.be.rejectedWith(constants.REVERT); + ); }); }); @@ -535,6 +545,10 @@ describe('Exchange wrappers', () => { await exchangeWrapper.batchFillOrdersNoThrowAsync(signedOrders, takerAddress, { takerAssetFillAmounts, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, }); const newBalances = await erc20Wrapper.getBalancesAsync(); @@ -591,6 +605,10 @@ describe('Exchange wrappers', () => { const newOrders = [invalidOrder, ...validOrders]; await exchangeWrapper.batchFillOrdersNoThrowAsync(newOrders, takerAddress, { takerAssetFillAmounts, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 450000, }); const newBalances = await erc20Wrapper.getBalancesAsync(); @@ -674,16 +692,16 @@ describe('Exchange wrappers', () => { signedOrders = [ orderFactory.newSignedOrder(), orderFactory.newSignedOrder({ - takerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address), + takerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address), }), orderFactory.newSignedOrder(), ]; - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18), }), - ).to.be.rejectedWith(constants.REVERT); + ); }); }); @@ -694,6 +712,10 @@ describe('Exchange wrappers', () => { ); await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, { takerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 6000000, }); const newBalances = await erc20Wrapper.getBalancesAsync(); @@ -753,26 +775,59 @@ describe('Exchange wrappers', () => { }); await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, { takerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, }); const newBalances = await erc20Wrapper.getBalancesAsync(); expect(newBalances).to.be.deep.equal(erc20Balances); }); - it('should throw when a signedOrder does not use the same takerAssetAddress', async () => { + it('should not fill a signedOrder that does not use the same takerAssetAddress', async () => { signedOrders = [ orderFactory.newSignedOrder(), + orderFactory.newSignedOrder(), orderFactory.newSignedOrder({ - takerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address), + takerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address), }), - orderFactory.newSignedOrder(), ]; + const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); + const filledSignedOrders = signedOrders.slice(0, -1); + _.forEach(filledSignedOrders, signedOrder => { + erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ + defaultMakerAssetAddress + ].minus(signedOrder.makerAssetAmount); + erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ + defaultTakerAssetAddress + ].add(signedOrder.takerAssetAmount); + erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( + signedOrder.makerFee, + ); + erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ + defaultMakerAssetAddress + ].add(signedOrder.makerAssetAmount); + erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ + defaultTakerAssetAddress + ].minus(signedOrder.takerAssetAmount); + erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( + signedOrder.takerFee, + ); + erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ + zrxToken.address + ].add(signedOrder.makerFee.add(signedOrder.takerFee)); + }); + await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, { + takerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, + }); - return expect( - exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, { - takerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18), - }), - ).to.be.rejectedWith(constants.REVERT); + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); }); }); @@ -852,16 +907,16 @@ describe('Exchange wrappers', () => { signedOrders = [ orderFactory.newSignedOrder(), orderFactory.newSignedOrder({ - makerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address), + makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address), }), orderFactory.newSignedOrder(), ]; - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, { makerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18), }), - ).to.be.rejectedWith(constants.REVERT); + ); }); }); @@ -872,6 +927,10 @@ describe('Exchange wrappers', () => { ); await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, { makerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, }); const newBalances = await erc20Wrapper.getBalancesAsync(); @@ -904,8 +963,8 @@ describe('Exchange wrappers', () => { ); }); - it('should fill all signedOrders if cannot fill entire takerAssetFillAmount', async () => { - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); + it('should fill all signedOrders if cannot fill entire makerAssetFillAmount', async () => { + const makerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); _.forEach(signedOrders, signedOrder => { erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ defaultMakerAssetAddress @@ -929,28 +988,62 @@ describe('Exchange wrappers', () => { zrxToken.address ].add(signedOrder.makerFee.add(signedOrder.takerFee)); }); - await exchangeWrapper.marketSellOrdersNoThrowAsync(signedOrders, takerAddress, { - takerAssetFillAmount, + await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, { + makerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, }); const newBalances = await erc20Wrapper.getBalancesAsync(); expect(newBalances).to.be.deep.equal(erc20Balances); }); - it('should throw when a signedOrder does not use the same makerAssetAddress', async () => { + it('should not fill a signedOrder that does not use the same makerAssetAddress', async () => { signedOrders = [ orderFactory.newSignedOrder(), + orderFactory.newSignedOrder(), orderFactory.newSignedOrder({ - makerAssetData: assetProxyUtils.encodeERC20ProxyData(zrxToken.address), + makerAssetData: assetProxyUtils.encodeERC20AssetData(zrxToken.address), }), - orderFactory.newSignedOrder(), ]; - return expect( - exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, { - makerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18), - }), - ).to.be.rejectedWith(constants.REVERT); + const makerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18); + const filledSignedOrders = signedOrders.slice(0, -1); + _.forEach(filledSignedOrders, signedOrder => { + erc20Balances[makerAddress][defaultMakerAssetAddress] = erc20Balances[makerAddress][ + defaultMakerAssetAddress + ].minus(signedOrder.makerAssetAmount); + erc20Balances[makerAddress][defaultTakerAssetAddress] = erc20Balances[makerAddress][ + defaultTakerAssetAddress + ].add(signedOrder.takerAssetAmount); + erc20Balances[makerAddress][zrxToken.address] = erc20Balances[makerAddress][zrxToken.address].minus( + signedOrder.makerFee, + ); + erc20Balances[takerAddress][defaultMakerAssetAddress] = erc20Balances[takerAddress][ + defaultMakerAssetAddress + ].add(signedOrder.makerAssetAmount); + erc20Balances[takerAddress][defaultTakerAssetAddress] = erc20Balances[takerAddress][ + defaultTakerAssetAddress + ].minus(signedOrder.takerAssetAmount); + erc20Balances[takerAddress][zrxToken.address] = erc20Balances[takerAddress][zrxToken.address].minus( + signedOrder.takerFee, + ); + erc20Balances[feeRecipientAddress][zrxToken.address] = erc20Balances[feeRecipientAddress][ + zrxToken.address + ].add(signedOrder.makerFee.add(signedOrder.takerFee)); + }); + await exchangeWrapper.marketBuyOrdersNoThrowAsync(signedOrders, takerAddress, { + makerAssetFillAmount, + // HACK(albrow): We need to hardcode the gas estimate here because + // the Geth gas estimator doesn't work with the way we use + // delegatecall and swallow errors. + gas: 600000, + }); + + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect(newBalances).to.be.deep.equal(erc20Balances); }); }); diff --git a/packages/contracts/test/global_hooks.ts b/packages/contracts/test/global_hooks.ts index 8b48b030d..83263c5b3 100644 --- a/packages/contracts/test/global_hooks.ts +++ b/packages/contracts/test/global_hooks.ts @@ -1,10 +1,15 @@ import { env, EnvVars } from '@0xproject/dev-utils'; import { coverage } from '../src/utils/coverage'; +import { profiler } from '../src/utils/profiler'; 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(); + } }); diff --git a/packages/contracts/test/libraries/lib_bytes.ts b/packages/contracts/test/libraries/lib_bytes.ts index 26cfa8291..a31a4789c 100644 --- a/packages/contracts/test/libraries/lib_bytes.ts +++ b/packages/contracts/test/libraries/lib_bytes.ts @@ -1,15 +1,14 @@ -import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; -import { AssetProxyId } from '@0xproject/types'; +import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils'; import { BigNumber } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; import BN = require('bn.js'); import * as chai from 'chai'; -import { LogWithDecodedArgs, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import ethUtil = require('ethereumjs-util'); -import * as Web3 from 'web3'; +import * as _ from 'lodash'; -import { TestLibBytesContract } from '../../src/contract_wrappers/generated/test_lib_bytes'; +import { TestLibBytesContract } from '../../src/generated_contract_wrappers/test_lib_bytes'; import { artifacts } from '../../src/utils/artifacts'; +import { expectRevertOrOtherErrorAsync } from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; @@ -19,7 +18,6 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); describe('LibBytes', () => { - let owner: string; let libBytes: TestLibBytesContract; const byteArrayShorterThan32Bytes = '0x012345'; const byteArrayShorterThan20Bytes = byteArrayShorterThan32Bytes; @@ -30,8 +28,20 @@ describe('LibBytes', () => { const byteArrayLongerThan32BytesLastBytesSwapped = '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abefcd'; let testAddress: string; + let testAddressB: string; const testBytes32 = '0x102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f01020'; + const testBytes32B = '0x534877abd8443578526845cdfef020047528759477fedef87346527659aced32'; const testUint256 = new BigNumber(testBytes32, 16); + const testUint256B = new BigNumber(testBytes32B, 16); + let shortData: string; + let shortTestBytes: string; + let shortTestBytesAsBuffer: Buffer; + let wordOfData: string; + let wordOfTestBytes: string; + let wordOfTestBytesAsBuffer: Buffer; + let longData: string; + let longTestBytes: string; + let longTestBytesAsBuffer: Buffer; before(async () => { await blockchainLifecycle.startAsync(); @@ -42,8 +52,8 @@ describe('LibBytes', () => { before(async () => { // Setup accounts & addresses const accounts = await web3Wrapper.getAvailableAddressesAsync(); - owner = accounts[0]; testAddress = accounts[1]; + testAddressB = accounts[2]; // Deploy LibBytes libBytes = await TestLibBytesContract.deployFrom0xArtifactAsync(artifacts.TestLibBytes, provider, txDefaults); // Verify lengths of test data @@ -53,6 +63,26 @@ describe('LibBytes', () => { expect(byteArrayLongerThan32BytesLength).to.be.greaterThan(32); const testBytes32Length = ethUtil.toBuffer(testBytes32).byteLength; expect(testBytes32Length).to.be.equal(32); + // Create short test bytes + shortData = '0xffffaa'; + const encodedShortData = ethUtil.toBuffer(shortData); + const shortDataLength = new BigNumber(encodedShortData.byteLength); + const encodedShortDataLength = assetProxyUtils.encodeUint256(shortDataLength); + shortTestBytesAsBuffer = Buffer.concat([encodedShortDataLength, encodedShortData]); + shortTestBytes = ethUtil.bufferToHex(shortTestBytesAsBuffer); + // Create test bytes one word in length + wordOfData = ethUtil.bufferToHex(assetProxyUtils.encodeUint256(generatePseudoRandomSalt())); + const encodedWordOfData = ethUtil.toBuffer(wordOfData); + const wordOfDataLength = new BigNumber(encodedWordOfData.byteLength); + const encodedWordOfDataLength = assetProxyUtils.encodeUint256(wordOfDataLength); + wordOfTestBytesAsBuffer = Buffer.concat([encodedWordOfDataLength, encodedWordOfData]); + wordOfTestBytes = ethUtil.bufferToHex(wordOfTestBytesAsBuffer); + // Create long test bytes (combines short test bytes with word of test bytes) + longData = ethUtil.bufferToHex(Buffer.concat([encodedShortData, encodedWordOfData])); + const longDataLength = new BigNumber(encodedShortData.byteLength + encodedWordOfData.byteLength); + const encodedLongDataLength = assetProxyUtils.encodeUint256(longDataLength); + longTestBytesAsBuffer = Buffer.concat([encodedLongDataLength, encodedShortData, encodedWordOfData]); + longTestBytes = ethUtil.bufferToHex(longTestBytesAsBuffer); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -61,13 +91,15 @@ describe('LibBytes', () => { await blockchainLifecycle.revertAsync(); }); - describe('popByte', () => { + describe('popLastByte', () => { it('should revert if length is 0', async () => { - return expect(libBytes.publicPopByte.callAsync(constants.NULL_BYTES)).to.be.rejectedWith(constants.REVERT); + return expectRevertOrOtherErrorAsync( + libBytes.publicPopLastByte.callAsync(constants.NULL_BYTES), + constants.LIB_BYTES_GREATER_THAN_ZERO_LENGTH_REQUIRED, + ); }); - it('should pop the last byte from the input and return it', async () => { - const [newBytes, poppedByte] = await libBytes.publicPopByte.callAsync(byteArrayLongerThan32Bytes); + const [newBytes, poppedByte] = await libBytes.publicPopLastByte.callAsync(byteArrayLongerThan32Bytes); const expectedNewBytes = byteArrayLongerThan32Bytes.slice(0, -2); const expectedPoppedByte = `0x${byteArrayLongerThan32Bytes.slice(-2)}`; expect(newBytes).to.equal(expectedNewBytes); @@ -75,15 +107,15 @@ describe('LibBytes', () => { }); }); - describe('popAddress', () => { + describe('popLast20Bytes', () => { it('should revert if length is less than 20', async () => { - return expect(libBytes.publicPopAddress.callAsync(byteArrayShorterThan20Bytes)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrOtherErrorAsync( + libBytes.publicPopLast20Bytes.callAsync(byteArrayShorterThan20Bytes), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED, ); }); - it('should pop the last 20 bytes from the input and return it', async () => { - const [newBytes, poppedAddress] = await libBytes.publicPopAddress.callAsync(byteArrayLongerThan32Bytes); + const [newBytes, poppedAddress] = await libBytes.publicPopLast20Bytes.callAsync(byteArrayLongerThan32Bytes); const expectedNewBytes = byteArrayLongerThan32Bytes.slice(0, -40); const expectedPoppedAddress = `0x${byteArrayLongerThan32Bytes.slice(-40)}`; expect(newBytes).to.equal(expectedNewBytes); @@ -99,7 +131,6 @@ describe('LibBytes', () => { ); return expect(areBytesEqual).to.be.true(); }); - it('should return true if byte arrays are equal (both arrays > 32 bytes)', async () => { const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync( byteArrayLongerThan32Bytes, @@ -107,7 +138,6 @@ describe('LibBytes', () => { ); return expect(areBytesEqual).to.be.true(); }); - it('should return false if byte arrays are not equal (first array < 32 bytes, second array > 32 bytes)', async () => { const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync( byteArrayShorterThan32Bytes, @@ -115,7 +145,6 @@ describe('LibBytes', () => { ); return expect(areBytesEqual).to.be.false(); }); - it('should return false if byte arrays are not equal (first array > 32 bytes, second array < 32 bytes)', async () => { const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync( byteArrayLongerThan32Bytes, @@ -123,7 +152,6 @@ describe('LibBytes', () => { ); return expect(areBytesEqual).to.be.false(); }); - it('should return false if byte arrays are not equal (same length, but a byte in first word differs)', async () => { const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync( byteArrayLongerThan32BytesFirstBytesSwapped, @@ -131,7 +159,6 @@ describe('LibBytes', () => { ); return expect(areBytesEqual).to.be.false(); }); - it('should return false if byte arrays are not equal (same length, but a byte in last word differs)', async () => { const areBytesEqual = await libBytes.publicAreBytesEqual.callAsync( byteArrayLongerThan32BytesLastBytesSwapped, @@ -141,15 +168,50 @@ describe('LibBytes', () => { }); }); + describe('deepCopyBytes', () => { + it('should revert if dest is shorter than source', async () => { + return expectRevertOrOtherErrorAsync( + libBytes.publicDeepCopyBytes.callAsync(byteArrayShorterThan32Bytes, byteArrayLongerThan32Bytes), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED, + ); + }); + it('should overwrite dest with source if source and dest have equal length', async () => { + const zeroedByteArrayLongerThan32Bytes = `0x${_.repeat('0', byteArrayLongerThan32Bytes.length - 2)}`; + const zeroedBytesAfterCopy = await libBytes.publicDeepCopyBytes.callAsync( + zeroedByteArrayLongerThan32Bytes, + byteArrayLongerThan32Bytes, + ); + return expect(zeroedBytesAfterCopy).to.be.equal(byteArrayLongerThan32Bytes); + }); + it('should overwrite the leftmost len(source) bytes of dest if dest is larger than source', async () => { + const zeroedByteArrayLongerThan32Bytes = `0x${_.repeat('0', byteArrayLongerThan32Bytes.length * 2)}`; + const zeroedBytesAfterCopy = await libBytes.publicDeepCopyBytes.callAsync( + zeroedByteArrayLongerThan32Bytes, + byteArrayLongerThan32Bytes, + ); + const copiedBytes = zeroedBytesAfterCopy.slice(0, byteArrayLongerThan32Bytes.length); + return expect(copiedBytes).to.be.equal(byteArrayLongerThan32Bytes); + }); + it('should not overwrite the rightmost bytes of dest if dest is larger than source', async () => { + const zeroedByteArrayLongerThan32Bytes = `0x${_.repeat('0', byteArrayLongerThan32Bytes.length * 2)}`; + const zeroedBytesAfterCopy = await libBytes.publicDeepCopyBytes.callAsync( + zeroedByteArrayLongerThan32Bytes, + byteArrayLongerThan32Bytes, + ); + const expectedNotCopiedBytes = zeroedByteArrayLongerThan32Bytes.slice(byteArrayLongerThan32Bytes.length); + const notCopiedBytes = zeroedBytesAfterCopy.slice(byteArrayLongerThan32Bytes.length); + return expect(notCopiedBytes).to.be.equal(expectedNotCopiedBytes); + }); + }); + describe('readAddress', () => { - it('should successfully read address when the address takes up the whole array)', async () => { + it('should successfully read address when the address takes up the whole array', async () => { const byteArray = ethUtil.addHexPrefix(testAddress); const testAddressOffset = new BigNumber(0); const address = await libBytes.publicReadAddress.callAsync(byteArray, testAddressOffset); return expect(address).to.be.equal(testAddress); }); - - it('should successfully read address when it is offset in the array)', async () => { + it('should successfully read address when it is offset in the array', async () => { const addressByteArrayBuffer = ethUtil.toBuffer(testAddress); const prefixByteArrayBuffer = ethUtil.toBuffer('0xabcdef'); const combinedByteArrayBuffer = Buffer.concat([prefixByteArrayBuffer, addressByteArrayBuffer]); @@ -158,80 +220,145 @@ describe('LibBytes', () => { const address = await libBytes.publicReadAddress.callAsync(combinedByteArray, testAddressOffset); return expect(address).to.be.equal(testAddress); }); - - it('should fail if the byte array is too short to hold an address)', async () => { + it('should fail if the byte array is too short to hold an address', async () => { const shortByteArray = '0xabcdef'; const offset = new BigNumber(0); - return expect(libBytes.publicReadAddress.callAsync(shortByteArray, offset)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrOtherErrorAsync( + libBytes.publicReadAddress.callAsync(shortByteArray, offset), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED, ); }); - - it('should fail if the length between the offset and end of the byte array is too short to hold an address)', async () => { - const byteArray = ethUtil.addHexPrefix(testAddress); + it('should fail if the length between the offset and end of the byte array is too short to hold an address', async () => { + const byteArray = testAddress; const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength); - return expect(libBytes.publicReadAddress.callAsync(byteArray, badOffset)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrOtherErrorAsync( + libBytes.publicReadAddress.callAsync(byteArray, badOffset), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED, ); }); }); - /// @TODO Implement test cases for writeAddress. Test template below. - /// Currently, the generated contract wrappers do not support this library's write methods. - /* describe('writeAddress', () => { - it('should successfully write address when the address takes up the whole array)', async () => {}); - it('should successfully write address when it is offset in the array)', async () => {}); - it('should fail if the byte array is too short to hold an address)', async () => {}); - it('should fail if the length between the offset and end of the byte array is too short to hold an address)', async () => {}); + it('should successfully write address when the address takes up the whole array', async () => { + const byteArray = testAddress; + const testAddressOffset = new BigNumber(0); + const newByteArray = await libBytes.publicWriteAddress.callAsync( + byteArray, + testAddressOffset, + testAddressB, + ); + return expect(newByteArray).to.be.equal(testAddressB); + }); + it('should successfully write address when it is offset in the array', async () => { + const addressByteArrayBuffer = ethUtil.toBuffer(testAddress); + const prefixByteArrayBuffer = ethUtil.toBuffer('0xabcdef'); + const combinedByteArrayBuffer = Buffer.concat([prefixByteArrayBuffer, addressByteArrayBuffer]); + const combinedByteArray = ethUtil.bufferToHex(combinedByteArrayBuffer); + const testAddressOffset = new BigNumber(prefixByteArrayBuffer.byteLength); + const newByteArray = await libBytes.publicWriteAddress.callAsync( + combinedByteArray, + testAddressOffset, + testAddressB, + ); + const newByteArrayBuffer = ethUtil.toBuffer(newByteArray); + const addressFromOffsetBuffer = newByteArrayBuffer.slice(prefixByteArrayBuffer.byteLength); + const addressFromOffset = ethUtil.addHexPrefix(ethUtil.bufferToHex(addressFromOffsetBuffer)); + return expect(addressFromOffset).to.be.equal(testAddressB); + }); + it('should fail if the byte array is too short to hold an address', async () => { + const offset = new BigNumber(0); + return expectRevertOrOtherErrorAsync( + libBytes.publicWriteAddress.callAsync(byteArrayShorterThan20Bytes, offset, testAddress), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED, + ); + }); + it('should fail if the length between the offset and end of the byte array is too short to hold an address', async () => { + const byteArray = byteArrayLongerThan32Bytes; + const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength); + return expectRevertOrOtherErrorAsync( + libBytes.publicWriteAddress.callAsync(byteArray, badOffset, testAddress), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED, + ); + }); }); - */ describe('readBytes32', () => { - it('should successfully read bytes32 when the bytes32 takes up the whole array)', async () => { + it('should successfully read bytes32 when the bytes32 takes up the whole array', async () => { const testBytes32Offset = new BigNumber(0); const bytes32 = await libBytes.publicReadBytes32.callAsync(testBytes32, testBytes32Offset); return expect(bytes32).to.be.equal(testBytes32); }); - it('should successfully read bytes32 when it is offset in the array)', async () => { const bytes32ByteArrayBuffer = ethUtil.toBuffer(testBytes32); const prefixByteArrayBuffer = ethUtil.toBuffer('0xabcdef'); const combinedByteArrayBuffer = Buffer.concat([prefixByteArrayBuffer, bytes32ByteArrayBuffer]); const combinedByteArray = ethUtil.bufferToHex(combinedByteArrayBuffer); - const testAddressOffset = new BigNumber(prefixByteArrayBuffer.byteLength); - const bytes32 = await libBytes.publicReadBytes32.callAsync(combinedByteArray, testAddressOffset); + const testBytes32Offset = new BigNumber(prefixByteArrayBuffer.byteLength); + const bytes32 = await libBytes.publicReadBytes32.callAsync(combinedByteArray, testBytes32Offset); return expect(bytes32).to.be.equal(testBytes32); }); - - it('should fail if the byte array is too short to hold a bytes32)', async () => { + it('should fail if the byte array is too short to hold a bytes32', async () => { const offset = new BigNumber(0); - return expect(libBytes.publicReadBytes32.callAsync(byteArrayShorterThan32Bytes, offset)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrOtherErrorAsync( + libBytes.publicReadBytes32.callAsync(byteArrayShorterThan32Bytes, offset), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, ); }); - - it('should fail if the length between the offset and end of the byte array is too short to hold a bytes32)', async () => { + it('should fail if the length between the offset and end of the byte array is too short to hold a bytes32', async () => { const badOffset = new BigNumber(ethUtil.toBuffer(testBytes32).byteLength); - return expect(libBytes.publicReadBytes32.callAsync(testBytes32, badOffset)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrOtherErrorAsync( + libBytes.publicReadBytes32.callAsync(testBytes32, badOffset), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, ); }); }); - /// @TODO Implement test cases for writeBytes32. Test template below. - /// Currently, the generated contract wrappers do not support this library's write methods. - /* describe('writeBytes32', () => { - it('should successfully write bytes32 when the address takes up the whole array)', async () => {}); - it('should successfully write bytes32 when it is offset in the array)', async () => {}); - it('should fail if the byte array is too short to hold a bytes32)', async () => {}); - it('should fail if the length between the offset and end of the byte array is too short to hold a bytes32)', async () => {}); + it('should successfully write bytes32 when the address takes up the whole array', async () => { + const byteArray = testBytes32; + const testBytes32Offset = new BigNumber(0); + const newByteArray = await libBytes.publicWriteBytes32.callAsync( + byteArray, + testBytes32Offset, + testBytes32B, + ); + return expect(newByteArray).to.be.equal(testBytes32B); + }); + it('should successfully write bytes32 when it is offset in the array', async () => { + const bytes32ByteArrayBuffer = ethUtil.toBuffer(testBytes32); + const prefixByteArrayBuffer = ethUtil.toBuffer('0xabcdef'); + const combinedByteArrayBuffer = Buffer.concat([prefixByteArrayBuffer, bytes32ByteArrayBuffer]); + const combinedByteArray = ethUtil.bufferToHex(combinedByteArrayBuffer); + const testBytes32Offset = new BigNumber(prefixByteArrayBuffer.byteLength); + const newByteArray = await libBytes.publicWriteBytes32.callAsync( + combinedByteArray, + testBytes32Offset, + testBytes32B, + ); + const newByteArrayBuffer = ethUtil.toBuffer(newByteArray); + const bytes32FromOffsetBuffer = newByteArrayBuffer.slice(prefixByteArrayBuffer.byteLength); + const bytes32FromOffset = ethUtil.addHexPrefix(ethUtil.bufferToHex(bytes32FromOffsetBuffer)); + return expect(bytes32FromOffset).to.be.equal(testBytes32B); + }); + it('should fail if the byte array is too short to hold a bytes32', async () => { + const offset = new BigNumber(0); + return expectRevertOrOtherErrorAsync( + libBytes.publicWriteBytes32.callAsync(byteArrayShorterThan32Bytes, offset, testBytes32), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + ); + }); + it('should fail if the length between the offset and end of the byte array is too short to hold a bytes32', async () => { + const byteArray = byteArrayLongerThan32Bytes; + const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength); + return expectRevertOrOtherErrorAsync( + libBytes.publicWriteBytes32.callAsync(byteArray, badOffset, testBytes32), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + ); + }); }); - */ describe('readUint256', () => { - it('should successfully read uint256 when the uint256 takes up the whole array)', async () => { + it('should successfully read uint256 when the uint256 takes up the whole array', async () => { const formattedTestUint256 = new BN(testUint256.toString(10)); const testUint256AsBuffer = ethUtil.toBuffer(formattedTestUint256); const byteArray = ethUtil.bufferToHex(testUint256AsBuffer); @@ -239,8 +366,7 @@ describe('LibBytes', () => { const uint256 = await libBytes.publicReadUint256.callAsync(byteArray, testUint256Offset); return expect(uint256).to.bignumber.equal(testUint256); }); - - it('should successfully read uint256 when it is offset in the array)', async () => { + it('should successfully read uint256 when it is offset in the array', async () => { const prefixByteArrayBuffer = ethUtil.toBuffer('0xabcdef'); const formattedTestUint256 = new BN(testUint256.toString(10)); const testUint256AsBuffer = ethUtil.toBuffer(formattedTestUint256); @@ -250,41 +376,80 @@ describe('LibBytes', () => { const uint256 = await libBytes.publicReadUint256.callAsync(combinedByteArray, testUint256Offset); return expect(uint256).to.bignumber.equal(testUint256); }); - - it('should fail if the byte array is too short to hold a uint256)', async () => { + it('should fail if the byte array is too short to hold a uint256', async () => { const offset = new BigNumber(0); - return expect(libBytes.publicReadUint256.callAsync(byteArrayShorterThan32Bytes, offset)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrOtherErrorAsync( + libBytes.publicReadUint256.callAsync(byteArrayShorterThan32Bytes, offset), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, ); }); - - it('should fail if the length between the offset and end of the byte array is too short to hold a uint256)', async () => { + it('should fail if the length between the offset and end of the byte array is too short to hold a uint256', async () => { const formattedTestUint256 = new BN(testUint256.toString(10)); const testUint256AsBuffer = ethUtil.toBuffer(formattedTestUint256); const byteArray = ethUtil.bufferToHex(testUint256AsBuffer); const badOffset = new BigNumber(testUint256AsBuffer.byteLength); - return expect(libBytes.publicReadUint256.callAsync(byteArray, badOffset)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrOtherErrorAsync( + libBytes.publicReadUint256.callAsync(byteArray, badOffset), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, ); }); }); - /// @TODO Implement test cases for writeUint256. Test template below. - /// Currently, the generated contract wrappers do not support this library's write methods. - /* describe('writeUint256', () => { - it('should successfully write uint256 when the address takes up the whole array)', async () => {}); - it('should successfully write uint256 when it is offset in the array)', async () => {}); - it('should fail if the byte array is too short to hold a uint256)', async () => {}); - it('should fail if the length between the offset and end of the byte array is too short to hold a uint256)', async () => {}); + it('should successfully write uint256 when the address takes up the whole array', async () => { + const byteArray = testBytes32; + const testUint256Offset = new BigNumber(0); + const newByteArray = await libBytes.publicWriteUint256.callAsync( + byteArray, + testUint256Offset, + testUint256B, + ); + const newByteArrayAsUint256 = new BigNumber(newByteArray, 16); + return expect(newByteArrayAsUint256).to.be.bignumber.equal(testUint256B); + }); + it('should successfully write uint256 when it is offset in the array', async () => { + const bytes32ByteArrayBuffer = ethUtil.toBuffer(testBytes32); + const prefixByteArrayBuffer = ethUtil.toBuffer('0xabcdef'); + const combinedByteArrayBuffer = Buffer.concat([prefixByteArrayBuffer, bytes32ByteArrayBuffer]); + const combinedByteArray = ethUtil.bufferToHex(combinedByteArrayBuffer); + const testUint256Offset = new BigNumber(prefixByteArrayBuffer.byteLength); + const newByteArray = await libBytes.publicWriteUint256.callAsync( + combinedByteArray, + testUint256Offset, + testUint256B, + ); + const newByteArrayBuffer = ethUtil.toBuffer(newByteArray); + const uint256FromOffsetBuffer = newByteArrayBuffer.slice(prefixByteArrayBuffer.byteLength); + const uint256FromOffset = new BigNumber( + ethUtil.addHexPrefix(ethUtil.bufferToHex(uint256FromOffsetBuffer)), + 16, + ); + return expect(uint256FromOffset).to.be.bignumber.equal(testUint256B); + }); + it('should fail if the byte array is too short to hold a uint256', async () => { + const offset = new BigNumber(0); + return expectRevertOrOtherErrorAsync( + libBytes.publicWriteUint256.callAsync(byteArrayShorterThan32Bytes, offset, testUint256), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + ); + }); + it('should fail if the length between the offset and end of the byte array is too short to hold a uint256', async () => { + const byteArray = byteArrayLongerThan32Bytes; + const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength); + return expectRevertOrOtherErrorAsync( + libBytes.publicWriteUint256.callAsync(byteArray, badOffset, testUint256), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + ); + }); }); - */ describe('readFirst4', () => { + // AssertionError: expected promise to be rejected with an error including 'revert' but it was fulfilled with '0x08c379a0' it('should revert if byte array has a length < 4', async () => { const byteArrayLessThan4Bytes = '0x010101'; - return expect(libBytes.publicReadFirst4.callAsync(byteArrayLessThan4Bytes)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrOtherErrorAsync( + libBytes.publicReadFirst4.callAsync(byteArrayLessThan4Bytes), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED, ); }); it('should return the first 4 bytes of a byte array of arbitrary length', async () => { @@ -293,4 +458,164 @@ describe('LibBytes', () => { expect(first4Bytes).to.equal(expectedFirst4Bytes); }); }); + + describe('readBytes', () => { + it('should successfully read short, nested array of bytes when it takes up the whole array', async () => { + const testBytesOffset = new BigNumber(0); + const bytes = await libBytes.publicReadBytes.callAsync(shortTestBytes, testBytesOffset); + return expect(bytes).to.be.equal(shortData); + }); + it('should successfully read short, nested array of bytes when it is offset in the array', async () => { + const prefixByteArrayBuffer = ethUtil.toBuffer('0xabcdef'); + const combinedByteArrayBuffer = Buffer.concat([prefixByteArrayBuffer, shortTestBytesAsBuffer]); + const combinedByteArray = ethUtil.bufferToHex(combinedByteArrayBuffer); + const testUint256Offset = new BigNumber(prefixByteArrayBuffer.byteLength); + const bytes = await libBytes.publicReadBytes.callAsync(combinedByteArray, testUint256Offset); + return expect(bytes).to.be.equal(shortData); + }); + it('should successfully read a nested array of bytes - one word in length - when it takes up the whole array', async () => { + const testBytesOffset = new BigNumber(0); + const bytes = await libBytes.publicReadBytes.callAsync(wordOfTestBytes, testBytesOffset); + return expect(bytes).to.be.equal(wordOfData); + }); + it('should successfully read a nested array of bytes - one word in length - when it is offset in the array', async () => { + const prefixByteArrayBuffer = ethUtil.toBuffer('0xabcdef'); + const combinedByteArrayBuffer = Buffer.concat([prefixByteArrayBuffer, wordOfTestBytesAsBuffer]); + const combinedByteArray = ethUtil.bufferToHex(combinedByteArrayBuffer); + const testUint256Offset = new BigNumber(prefixByteArrayBuffer.byteLength); + const bytes = await libBytes.publicReadBytes.callAsync(combinedByteArray, testUint256Offset); + return expect(bytes).to.be.equal(wordOfData); + }); + it('should successfully read long, nested array of bytes when it takes up the whole array', async () => { + const testBytesOffset = new BigNumber(0); + const bytes = await libBytes.publicReadBytes.callAsync(longTestBytes, testBytesOffset); + return expect(bytes).to.be.equal(longData); + }); + it('should successfully read long, nested array of bytes when it is offset in the array', async () => { + const prefixByteArrayBuffer = ethUtil.toBuffer('0xabcdef'); + const combinedByteArrayBuffer = Buffer.concat([prefixByteArrayBuffer, longTestBytesAsBuffer]); + const combinedByteArray = ethUtil.bufferToHex(combinedByteArrayBuffer); + const testUint256Offset = new BigNumber(prefixByteArrayBuffer.byteLength); + const bytes = await libBytes.publicReadBytes.callAsync(combinedByteArray, testUint256Offset); + return expect(bytes).to.be.equal(longData); + }); + it('should fail if the byte array is too short to hold the length of a nested byte array', async () => { + // The length of the nested array is 32 bytes. By storing less than 32 bytes, a length cannot be read. + const offset = new BigNumber(0); + return expectRevertOrOtherErrorAsync( + libBytes.publicReadBytes.callAsync(byteArrayShorterThan32Bytes, offset), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + ); + }); + it('should fail if we store a nested byte array length, without a nested byte array', async () => { + const offset = new BigNumber(0); + return expectRevertOrOtherErrorAsync( + libBytes.publicReadBytes.callAsync(testBytes32, offset), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED, + ); + }); + it('should fail if the length between the offset and end of the byte array is too short to hold the length of a nested byte array', async () => { + const badOffset = new BigNumber(ethUtil.toBuffer(byteArrayShorterThan32Bytes).byteLength); + return expectRevertOrOtherErrorAsync( + libBytes.publicReadBytes.callAsync(byteArrayShorterThan32Bytes, badOffset), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + ); + }); + it('should fail if the length between the offset and end of the byte array is too short to hold the nested byte array', async () => { + const badOffset = new BigNumber(ethUtil.toBuffer(testBytes32).byteLength); + return expectRevertOrOtherErrorAsync( + libBytes.publicReadBytes.callAsync(testBytes32, badOffset), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + ); + }); + }); + + describe('writeBytes', () => { + it('should successfully write short, nested array of bytes when it takes up the whole array)', async () => { + const testBytesOffset = new BigNumber(0); + const emptyByteArray = ethUtil.bufferToHex(new Buffer(shortTestBytesAsBuffer.byteLength)); + const bytesWritten = await libBytes.publicWriteBytes.callAsync(emptyByteArray, testBytesOffset, shortData); + const bytesRead = await libBytes.publicReadBytes.callAsync(bytesWritten, testBytesOffset); + return expect(bytesRead).to.be.equal(shortData); + }); + it('should successfully write short, nested array of bytes when it is offset in the array', async () => { + // Write a prefix to the array + const prefixData = '0xabcdef'; + const prefixDataAsBuffer = ethUtil.toBuffer(prefixData); + const prefixOffset = new BigNumber(0); + const emptyByteArray = ethUtil.bufferToHex( + new Buffer(prefixDataAsBuffer.byteLength + shortTestBytesAsBuffer.byteLength), + ); + let bytesWritten = await libBytes.publicWriteBytes.callAsync(emptyByteArray, prefixOffset, prefixData); + // Write data after prefix + const testBytesOffset = new BigNumber(prefixDataAsBuffer.byteLength); + bytesWritten = await libBytes.publicWriteBytes.callAsync(bytesWritten, testBytesOffset, shortData); + // Read data after prefix and validate + const bytes = await libBytes.publicReadBytes.callAsync(bytesWritten, testBytesOffset); + return expect(bytes).to.be.equal(shortData); + }); + it('should successfully write a nested array of bytes - one word in length - when it takes up the whole array', async () => { + const testBytesOffset = new BigNumber(0); + const emptyByteArray = ethUtil.bufferToHex(new Buffer(wordOfTestBytesAsBuffer.byteLength)); + const bytesWritten = await libBytes.publicWriteBytes.callAsync(emptyByteArray, testBytesOffset, wordOfData); + const bytesRead = await libBytes.publicReadBytes.callAsync(bytesWritten, testBytesOffset); + return expect(bytesRead).to.be.equal(wordOfData); + }); + it('should successfully write a nested array of bytes - one word in length - when it is offset in the array', async () => { + // Write a prefix to the array + const prefixData = '0xabcdef'; + const prefixDataAsBuffer = ethUtil.toBuffer(prefixData); + const prefixOffset = new BigNumber(0); + const emptyByteArray = ethUtil.bufferToHex( + new Buffer(prefixDataAsBuffer.byteLength + wordOfTestBytesAsBuffer.byteLength), + ); + let bytesWritten = await libBytes.publicWriteBytes.callAsync(emptyByteArray, prefixOffset, prefixData); + // Write data after prefix + const testBytesOffset = new BigNumber(prefixDataAsBuffer.byteLength); + bytesWritten = await libBytes.publicWriteBytes.callAsync(bytesWritten, testBytesOffset, wordOfData); + // Read data after prefix and validate + const bytes = await libBytes.publicReadBytes.callAsync(bytesWritten, testBytesOffset); + return expect(bytes).to.be.equal(wordOfData); + }); + it('should successfully write a long, nested bytes when it takes up the whole array', async () => { + const testBytesOffset = new BigNumber(0); + const emptyByteArray = ethUtil.bufferToHex(new Buffer(longTestBytesAsBuffer.byteLength)); + const bytesWritten = await libBytes.publicWriteBytes.callAsync(emptyByteArray, testBytesOffset, longData); + const bytesRead = await libBytes.publicReadBytes.callAsync(bytesWritten, testBytesOffset); + return expect(bytesRead).to.be.equal(longData); + }); + it('should successfully write long, nested array of bytes when it is offset in the array', async () => { + // Write a prefix to the array + const prefixData = '0xabcdef'; + const prefixDataAsBuffer = ethUtil.toBuffer(prefixData); + const prefixOffset = new BigNumber(0); + const emptyByteArray = ethUtil.bufferToHex( + new Buffer(prefixDataAsBuffer.byteLength + longTestBytesAsBuffer.byteLength), + ); + let bytesWritten = await libBytes.publicWriteBytes.callAsync(emptyByteArray, prefixOffset, prefixData); + // Write data after prefix + const testBytesOffset = new BigNumber(prefixDataAsBuffer.byteLength); + bytesWritten = await libBytes.publicWriteBytes.callAsync(bytesWritten, testBytesOffset, longData); + // Read data after prefix and validate + const bytes = await libBytes.publicReadBytes.callAsync(bytesWritten, testBytesOffset); + return expect(bytes).to.be.equal(longData); + }); + it('should fail if the byte array is too short to hold the length of a nested byte array', async () => { + const offset = new BigNumber(0); + const emptyByteArray = ethUtil.bufferToHex(new Buffer(1)); + return expectRevertOrOtherErrorAsync( + libBytes.publicWriteBytes.callAsync(emptyByteArray, offset, longData), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED, + ); + }); + it('should fail if the length between the offset and end of the byte array is too short to hold the length of a nested byte array)', async () => { + const emptyByteArray = ethUtil.bufferToHex(new Buffer(shortTestBytesAsBuffer.byteLength)); + const badOffset = new BigNumber(ethUtil.toBuffer(shortTestBytesAsBuffer).byteLength); + return expectRevertOrOtherErrorAsync( + libBytes.publicWriteBytes.callAsync(emptyByteArray, badOffset, shortData), + constants.LIB_BYTES_GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED, + ); + }); + }); }); +// tslint:disable:max-file-line-count diff --git a/packages/contracts/test/libraries/lib_mem.ts b/packages/contracts/test/libraries/lib_mem.ts new file mode 100644 index 000000000..00f7c4d8b --- /dev/null +++ b/packages/contracts/test/libraries/lib_mem.ts @@ -0,0 +1,190 @@ +import { BigNumber } from '@0xproject/utils'; +import * as chai from 'chai'; + +import { TestLibMemContract } from '../../src/generated_contract_wrappers/test_lib_mem'; +import { artifacts } from '../../src/utils/artifacts'; +import { chaiSetup } from '../../src/utils/chai_setup'; +import { provider, txDefaults } from '../../src/utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; + +// BUG: Ideally we would use Buffer.from(memory).toString('hex') +// https://github.com/Microsoft/TypeScript/issues/23155 +const toHex = (buf: Uint8Array): string => buf.reduce((a, v) => a + ('00' + v.toString(16)).slice(-2), '0x'); + +const fromHex = (str: string): Uint8Array => Uint8Array.from(Buffer.from(str.slice(2), 'hex')); + +describe('LibMem', () => { + let testLibMem: TestLibMemContract; + + before(async () => { + // Deploy TestLibMem + testLibMem = await TestLibMemContract.deployFrom0xArtifactAsync(artifacts.TestLibMem, provider, txDefaults); + }); + + describe('memCopy', () => { + // Create memory 0x000102...FF + const memSize = 256; + const memory = new Uint8Array(memSize).map((_, i) => i); + const memHex = toHex(memory); + + // Reference implementation to test against + const refMemcpy = (_mem: Uint8Array, dest: number, source: number, length: number): Uint8Array => + Uint8Array.from(memory).copyWithin(dest, source, source + length); + + // Test vectors: destination, source, length, job description + type Tests = Array<[number, number, number, string]>; + + const test = (tests: Tests) => + tests.forEach(([dest, source, length, job]) => + it(job, async () => { + const expected = refMemcpy(memory, dest, source, length); + const resultStr = await testLibMem.testMemcpy.callAsync( + memHex, + new BigNumber(dest), + new BigNumber(source), + new BigNumber(length), + ); + const result = fromHex(resultStr); + expect(result).to.deep.equal(expected); + }), + ); + + test([[0, 0, 0, 'copies zero bytes with overlap']]); + + describe('copies forward', () => + test([ + [128, 0, 0, 'zero bytes'], + [128, 0, 1, 'one byte'], + [128, 0, 11, 'eleven bytes'], + [128, 0, 31, 'thirty-one bytes'], + [128, 0, 32, 'one word'], + [128, 0, 64, 'two words'], + [128, 0, 96, 'three words'], + [128, 0, 33, 'one word and one byte'], + [128, 0, 72, 'two words and eight bytes'], + [128, 0, 100, 'three words and four bytes'], + ])); + + describe('copies forward within one word', () => + test([ + [16, 0, 0, 'zero bytes'], + [16, 0, 1, 'one byte'], + [16, 0, 11, 'eleven bytes'], + [16, 0, 16, 'sixteen bytes'], + ])); + + describe('copies forward with one byte overlap', () => + test([ + [0, 0, 1, 'one byte'], + [10, 0, 11, 'eleven bytes'], + [30, 0, 31, 'thirty-one bytes'], + [31, 0, 32, 'one word'], + [32, 0, 33, 'one word and one byte'], + [71, 0, 72, 'two words and eight bytes'], + [99, 0, 100, 'three words and four bytes'], + ])); + + describe('copies forward with thirty-one bytes overlap', () => + test([ + [0, 0, 31, 'thirty-one bytes'], + [1, 0, 32, 'one word'], + [2, 0, 33, 'one word and one byte'], + [41, 0, 72, 'two words and eight bytes'], + [69, 0, 100, 'three words and four bytes'], + ])); + + describe('copies forward with one word overlap', () => + test([ + [0, 0, 32, 'one word'], + [1, 0, 33, 'one word and one byte'], + [41, 0, 72, 'two words and eight bytes'], + [69, 0, 100, 'three words and four bytes'], + ])); + + describe('copies forward with one word and one byte overlap', () => + test([ + [0, 0, 33, 'one word and one byte'], + [40, 0, 72, 'two words and eight bytes'], + [68, 0, 100, 'three words and four bytes'], + ])); + + describe('copies forward with two words overlap', () => + test([ + [0, 0, 64, 'two words'], + [8, 0, 72, 'two words and eight bytes'], + [36, 0, 100, 'three words and four bytes'], + ])); + + describe('copies forward within one word and one byte overlap', () => + test([[0, 0, 1, 'one byte'], [10, 0, 11, 'eleven bytes'], [15, 0, 16, 'sixteen bytes']])); + + describe('copies backward', () => + test([ + [0, 128, 0, 'zero bytes'], + [0, 128, 1, 'one byte'], + [0, 128, 11, 'eleven bytes'], + [0, 128, 31, 'thirty-one bytes'], + [0, 128, 32, 'one word'], + [0, 128, 64, 'two words'], + [0, 128, 96, 'three words'], + [0, 128, 33, 'one word and one byte'], + [0, 128, 72, 'two words and eight bytes'], + [0, 128, 100, 'three words and four bytes'], + ])); + + describe('copies backward within one word', () => + test([ + [0, 16, 0, 'zero bytes'], + [0, 16, 1, 'one byte'], + [0, 16, 11, 'eleven bytes'], + [0, 16, 16, 'sixteen bytes'], + ])); + + describe('copies backward with one byte overlap', () => + test([ + [0, 0, 1, 'one byte'], + [0, 10, 11, 'eleven bytes'], + [0, 30, 31, 'thirty-one bytes'], + [0, 31, 32, 'one word'], + [0, 32, 33, 'one word and one byte'], + [0, 71, 72, 'two words and eight bytes'], + [0, 99, 100, 'three words and four bytes'], + ])); + + describe('copies backward with thirty-one bytes overlap', () => + test([ + [0, 0, 31, 'thirty-one bytes'], + [0, 1, 32, 'one word'], + [0, 2, 33, 'one word and one byte'], + [0, 41, 72, 'two words and eight bytes'], + [0, 69, 100, 'three words and four bytes'], + ])); + + describe('copies backward with one word overlap', () => + test([ + [0, 0, 32, 'one word'], + [0, 1, 33, 'one word and one byte'], + [0, 41, 72, 'two words and eight bytes'], + [0, 69, 100, 'three words and four bytes'], + ])); + + describe('copies backward with one word and one byte overlap', () => + test([ + [0, 0, 33, 'one word and one byte'], + [0, 40, 72, 'two words and eight bytes'], + [0, 68, 100, 'three words and four bytes'], + ])); + + describe('copies backward with two words overlap', () => + test([ + [0, 0, 64, 'two words'], + [0, 8, 72, 'two words and eight bytes'], + [0, 36, 100, 'three words and four bytes'], + ])); + + describe('copies forward within one word and one byte overlap', () => + test([[0, 0, 1, 'one byte'], [0, 10, 11, 'eleven bytes'], [0, 15, 16, 'sixteen bytes']])); + }); +}); diff --git a/packages/contracts/test/multi_sig_with_time_lock.ts b/packages/contracts/test/multi_sig_with_time_lock.ts index 1302d0fa0..f630743e1 100644 --- a/packages/contracts/test/multi_sig_with_time_lock.ts +++ b/packages/contracts/test/multi_sig_with_time_lock.ts @@ -1,26 +1,25 @@ -import { BlockchainLifecycle, web3Factory } from '@0xproject/dev-utils'; +import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; import { LogWithDecodedArgs } from 'ethereum-types'; -import * as _ from 'lodash'; import 'make-promises-safe'; -import * as Web3 from 'web3'; import { MultiSigWalletWithTimeLockContract, SubmissionContractEventArgs, -} from '../src/contract_wrappers/generated/multi_sig_wallet_with_time_lock'; +} from '../src/generated_contract_wrappers/multi_sig_wallet_with_time_lock'; import { artifacts } from '../src/utils/artifacts'; +import { expectRevertOrAlwaysFailingTransactionAsync } from '../src/utils/assertions'; import { chaiSetup } from '../src/utils/chai_setup'; import { constants } from '../src/utils/constants'; +import { increaseTimeAndMineBlockAsync } from '../src/utils/increase_time'; import { MultiSigWrapper } from '../src/utils/multi_sig_wrapper'; import { provider, txDefaults, web3Wrapper } from '../src/utils/web3_wrapper'; chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - +// tslint:disable:no-unnecessary-type-assertion describe('MultiSigWalletWithTimeLock', () => { let owners: string[]; const REQUIRED_APPROVALS = new BigNumber(2); @@ -69,9 +68,9 @@ describe('MultiSigWalletWithTimeLock', () => { }); it('should throw when not called by wallet', async () => { - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( multiSig.changeTimeLock.sendTransactionAsync(SECONDS_TIME_LOCKED, { from: owners[0] }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw without enough confirmations', async () => { @@ -80,10 +79,9 @@ describe('MultiSigWalletWithTimeLock', () => { const res = await multiSigWrapper.submitTransactionAsync(destination, changeTimeLockData, owners[0]); const log = res.logs[0] as LogWithDecodedArgs<SubmissionContractEventArgs>; const txId = log.args.transactionId; - - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( multiSig.executeTransaction.sendTransactionAsync(txId, { from: owners[0] }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should set confirmation time with enough confirmations', async () => { @@ -148,14 +146,15 @@ describe('MultiSigWalletWithTimeLock', () => { txId = log.args.transactionId; await multiSigWrapper.confirmTransactionAsync(txId, owners[1]); }); + it('should throw if it has enough confirmations but is not past the time lock', async () => { - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( multiSig.executeTransaction.sendTransactionAsync(txId, { from: owners[0] }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should execute if it has enough confirmations and is past the time lock', async () => { - await web3Wrapper.increaseTimeAsync(SECONDS_TIME_LOCKED.toNumber()); + await increaseTimeAndMineBlockAsync(SECONDS_TIME_LOCKED.toNumber()); await web3Wrapper.awaitTransactionSuccessAsync( await multiSig.executeTransaction.sendTransactionAsync(txId, { from: owners[0] }), constants.AWAIT_TRANSACTION_MINED_MS, @@ -167,3 +166,4 @@ describe('MultiSigWalletWithTimeLock', () => { }); }); }); +// tslint:enable:no-unnecessary-type-assertion diff --git a/packages/contracts/test/token_registry.ts b/packages/contracts/test/token_registry.ts index 64caac387..f368fd73d 100644 --- a/packages/contracts/test/token_registry.ts +++ b/packages/contracts/test/token_registry.ts @@ -1,14 +1,13 @@ -import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; +import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { BigNumber, NULL_BYTES } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; import ethUtil = require('ethereumjs-util'); import * as _ from 'lodash'; import 'make-promises-safe'; -import * as Web3 from 'web3'; -import { TokenRegistryContract } from '../src/contract_wrappers/generated/token_registry'; +import { TokenRegistryContract } from '../src/generated_contract_wrappers/token_registry'; import { artifacts } from '../src/utils/artifacts'; +import { expectRevertOrAlwaysFailingTransactionAsync } from '../src/utils/assertions'; import { chaiSetup } from '../src/utils/chai_setup'; import { constants } from '../src/utils/constants'; import { TokenRegWrapper } from '../src/utils/token_registry_wrapper'; @@ -76,7 +75,7 @@ describe('TokenRegistry', () => { describe('addToken', () => { it('should throw when not called by owner', async () => { - return expect(tokenRegWrapper.addTokenAsync(token1, notOwner)).to.be.rejectedWith(constants.REVERT); + return expectRevertOrAlwaysFailingTransactionAsync(tokenRegWrapper.addTokenAsync(token1, notOwner)); }); it('should add token metadata when called by owner', async () => { @@ -88,19 +87,19 @@ describe('TokenRegistry', () => { it('should throw if token already exists', async () => { await tokenRegWrapper.addTokenAsync(token1, owner); - return expect(tokenRegWrapper.addTokenAsync(token1, owner)).to.be.rejectedWith(constants.REVERT); + return expectRevertOrAlwaysFailingTransactionAsync(tokenRegWrapper.addTokenAsync(token1, owner)); }); it('should throw if token address is null', async () => { - return expect(tokenRegWrapper.addTokenAsync(nullToken, owner)).to.be.rejectedWith(constants.REVERT); + return expectRevertOrAlwaysFailingTransactionAsync(tokenRegWrapper.addTokenAsync(nullToken, owner)); }); it('should throw if name already exists', async () => { await tokenRegWrapper.addTokenAsync(token1, owner); const duplicateNameToken = _.assign({}, token2, { name: token1.name }); - return expect(tokenRegWrapper.addTokenAsync(duplicateNameToken, owner)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + tokenRegWrapper.addTokenAsync(duplicateNameToken, owner), ); }); @@ -110,8 +109,8 @@ describe('TokenRegistry', () => { symbol: token1.symbol, }); - return expect(tokenRegWrapper.addTokenAsync(duplicateSymbolToken, owner)).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrAlwaysFailingTransactionAsync( + tokenRegWrapper.addTokenAsync(duplicateSymbolToken, owner), ); }); }); @@ -137,9 +136,9 @@ describe('TokenRegistry', () => { describe('setTokenName', () => { it('should throw when not called by owner', async () => { - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( tokenReg.setTokenName.sendTransactionAsync(token1.address, token2.name, { from: notOwner }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should change the token name when called by owner', async () => { @@ -163,25 +162,25 @@ describe('TokenRegistry', () => { it('should throw if the name already exists', async () => { await tokenRegWrapper.addTokenAsync(token2, owner); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( tokenReg.setTokenName.sendTransactionAsync(token1.address, token2.name, { from: owner }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw if token does not exist', async () => { - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( tokenReg.setTokenName.sendTransactionAsync(nullToken.address, token2.name, { from: owner }), - ).to.be.rejectedWith(constants.REVERT); + ); }); }); describe('setTokenSymbol', () => { it('should throw when not called by owner', async () => { - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( tokenReg.setTokenSymbol.sendTransactionAsync(token1.address, token2.symbol, { from: notOwner, }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should change the token symbol when called by owner', async () => { @@ -203,28 +202,28 @@ describe('TokenRegistry', () => { it('should throw if the symbol already exists', async () => { await tokenRegWrapper.addTokenAsync(token2, owner); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( tokenReg.setTokenSymbol.sendTransactionAsync(token1.address, token2.symbol, { from: owner, }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw if token does not exist', async () => { - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( tokenReg.setTokenSymbol.sendTransactionAsync(nullToken.address, token2.symbol, { from: owner, }), - ).to.be.rejectedWith(constants.REVERT); + ); }); }); describe('removeToken', () => { it('should throw if not called by owner', async () => { const index = new BigNumber(0); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( tokenReg.removeToken.sendTransactionAsync(token1.address, index, { from: notOwner }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should remove token metadata when called by owner', async () => { @@ -241,17 +240,17 @@ describe('TokenRegistry', () => { it('should throw if token does not exist', async () => { const index = new BigNumber(0); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( tokenReg.removeToken.sendTransactionAsync(nullToken.address, index, { from: owner }), - ).to.be.rejectedWith(constants.REVERT); + ); }); it('should throw if token at given index does not match address', async () => { await tokenRegWrapper.addTokenAsync(token2, owner); const incorrectIndex = new BigNumber(0); - return expect( + return expectRevertOrAlwaysFailingTransactionAsync( tokenReg.removeToken.sendTransactionAsync(token2.address, incorrectIndex, { from: owner }), - ).to.be.rejectedWith(constants.REVERT); + ); }); }); }); diff --git a/packages/contracts/test/tutorials/arbitrage.ts b/packages/contracts/test/tutorials/arbitrage.ts index df01f31bb..32fcedb43 100644 --- a/packages/contracts/test/tutorials/arbitrage.ts +++ b/packages/contracts/test/tutorials/arbitrage.ts @@ -8,12 +8,12 @@ // import ethUtil = require('ethereumjs-util'); // import * as Web3 from 'web3'; -// import { AccountLevelsContract } from '../../src/contract_wrappers/generated/account_levels'; -// import { ArbitrageContract } from '../../src/contract_wrappers/generated/arbitrage'; -// import { DummyTokenContract } from '../../src/contract_wrappers/generated/dummy_token'; -// import { EtherDeltaContract } from '../../src/contract_wrappers/generated/ether_delta'; -// import { ExchangeContract } from '../../src/contract_wrappers/generated/exchange'; -// import { TokenTransferProxyContract } from '../../src/contract_wrappers/generated/token_transfer_proxy'; +// import { AccountLevelsContract } from '../../src/generated_contract_wrappers/account_levels'; +// import { ArbitrageContract } from '../../src/generated_contract_wrappers/arbitrage'; +// import { DummyTokenContract } from '../../src/generated_contract_wrappers/dummy_token'; +// import { EtherDeltaContract } from '../../src/generated_contract_wrappers/ether_delta'; +// import { ExchangeContract } from '../../src/generated_contract_wrappers/exchange'; +// import { TokenTransferProxyContract } from '../../src/generated_contract_wrappers/token_transfer_proxy'; // import { artifacts } from '../../util/artifacts'; // import { Balances } from '../../util/balances'; // import { constants } from '../../util/constants'; diff --git a/packages/contracts/test/unlimited_allowance_token.ts b/packages/contracts/test/unlimited_allowance_token.ts index b2acdebaa..0c3f5094b 100644 --- a/packages/contracts/test/unlimited_allowance_token.ts +++ b/packages/contracts/test/unlimited_allowance_token.ts @@ -1,12 +1,11 @@ -import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; +import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; -import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; import 'make-promises-safe'; -import * as Web3 from 'web3'; -import { DummyERC20TokenContract } from '../src/contract_wrappers/generated/dummy_e_r_c20_token'; +import { DummyERC20TokenContract } from '../src/generated_contract_wrappers/dummy_e_r_c20_token'; import { artifacts } from '../src/utils/artifacts'; +import { expectRevertOrOtherErrorAsync } from '../src/utils/assertions'; import { chaiSetup } from '../src/utils/chai_setup'; import { constants } from '../src/utils/constants'; import { provider, txDefaults, web3Wrapper } from '../src/utils/web3_wrapper'; @@ -55,8 +54,9 @@ describe('UnlimitedAllowanceToken', () => { it('should throw if owner has insufficient balance', async () => { const ownerBalance = await token.balanceOf.callAsync(owner); const amountToTransfer = ownerBalance.plus(1); - return expect(token.transfer.callAsync(spender, amountToTransfer, { from: owner })).to.be.rejectedWith( - constants.REVERT, + return expectRevertOrOtherErrorAsync( + token.transfer.callAsync(spender, amountToTransfer, { from: owner }), + constants.ERC20_INSUFFICIENT_BALANCE, ); }); @@ -93,11 +93,12 @@ describe('UnlimitedAllowanceToken', () => { await token.approve.sendTransactionAsync(spender, amountToTransfer, { from: owner }), constants.AWAIT_TRANSACTION_MINED_MS, ); - return expect( + return expectRevertOrOtherErrorAsync( token.transferFrom.callAsync(owner, spender, amountToTransfer, { from: spender, }), - ).to.be.rejectedWith(constants.REVERT); + constants.ERC20_INSUFFICIENT_BALANCE, + ); }); it('should throw if spender has insufficient allowance', async () => { @@ -108,11 +109,12 @@ describe('UnlimitedAllowanceToken', () => { const isSpenderAllowanceInsufficient = spenderAllowance.cmp(amountToTransfer) < 0; expect(isSpenderAllowanceInsufficient).to.be.true(); - return expect( + return expectRevertOrOtherErrorAsync( token.transferFrom.callAsync(owner, spender, amountToTransfer, { from: spender, }), - ).to.be.rejectedWith(constants.REVERT); + constants.ERC20_INSUFFICIENT_ALLOWANCE, + ); }); it('should return true on a 0 value transfer', async () => { diff --git a/packages/contracts/test/zrx_token.ts b/packages/contracts/test/zrx_token.ts index fe37e802b..0629c7a88 100644 --- a/packages/contracts/test/zrx_token.ts +++ b/packages/contracts/test/zrx_token.ts @@ -1,11 +1,10 @@ -import { BlockchainLifecycle, devConstants, web3Factory } from '@0xproject/dev-utils'; +import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; import 'make-promises-safe'; -import * as Web3 from 'web3'; -import { ZRXTokenContract } from '../src/contract_wrappers/generated/zrx_token'; +import { ZRXTokenContract } from '../src/generated_contract_wrappers/zrx_token'; import { artifacts } from '../src/utils/artifacts'; import { chaiSetup } from '../src/utils/chai_setup'; import { constants } from '../src/utils/constants'; @@ -79,7 +78,10 @@ describe('ZRXToken', () => { const receiver = spender; const initOwnerBalance = await zrxToken.balanceOf.callAsync(owner); const amountToTransfer = new BigNumber(1); - await zrxToken.transfer.sendTransactionAsync(receiver, amountToTransfer, { from: owner }); + await web3Wrapper.awaitTransactionSuccessAsync( + await zrxToken.transfer.sendTransactionAsync(receiver, amountToTransfer, { from: owner }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); const finalOwnerBalance = await zrxToken.balanceOf.callAsync(owner); const finalReceiverBalance = await zrxToken.balanceOf.callAsync(receiver); @@ -101,10 +103,13 @@ describe('ZRXToken', () => { it('should return false if owner has insufficient balance', async () => { const ownerBalance = await zrxToken.balanceOf.callAsync(owner); const amountToTransfer = ownerBalance.plus(1); - await zrxToken.approve.sendTransactionAsync(spender, amountToTransfer, { - from: owner, - gas: constants.MAX_TOKEN_APPROVE_GAS, - }); + await web3Wrapper.awaitTransactionSuccessAsync( + await zrxToken.approve.sendTransactionAsync(spender, amountToTransfer, { + from: owner, + gas: constants.MAX_TOKEN_APPROVE_GAS, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); const didReturnTrue = await zrxToken.transferFrom.callAsync(owner, spender, amountToTransfer, { from: spender, }); @@ -137,14 +142,20 @@ describe('ZRXToken', () => { const initOwnerBalance = await zrxToken.balanceOf.callAsync(owner); const amountToTransfer = initOwnerBalance; const initSpenderAllowance = MAX_UINT; - await zrxToken.approve.sendTransactionAsync(spender, initSpenderAllowance, { - from: owner, - gas: constants.MAX_TOKEN_APPROVE_GAS, - }); - await zrxToken.transferFrom.sendTransactionAsync(owner, spender, amountToTransfer, { - from: spender, - gas: constants.MAX_TOKEN_TRANSFERFROM_GAS, - }); + await web3Wrapper.awaitTransactionSuccessAsync( + await zrxToken.approve.sendTransactionAsync(spender, initSpenderAllowance, { + from: owner, + gas: constants.MAX_TOKEN_APPROVE_GAS, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await zrxToken.transferFrom.sendTransactionAsync(owner, spender, amountToTransfer, { + from: spender, + gas: constants.MAX_TOKEN_TRANSFERFROM_GAS, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); const newSpenderAllowance = await zrxToken.allowance.callAsync(owner, spender); expect(initSpenderAllowance).to.be.bignumber.equal(newSpenderAllowance); @@ -155,11 +166,17 @@ describe('ZRXToken', () => { const initSpenderBalance = await zrxToken.balanceOf.callAsync(spender); const amountToTransfer = initOwnerBalance; const initSpenderAllowance = initOwnerBalance; - await zrxToken.approve.sendTransactionAsync(spender, initSpenderAllowance); - await zrxToken.transferFrom.sendTransactionAsync(owner, spender, amountToTransfer, { - from: spender, - gas: constants.MAX_TOKEN_TRANSFERFROM_GAS, - }); + await web3Wrapper.awaitTransactionSuccessAsync( + await zrxToken.approve.sendTransactionAsync(spender, initSpenderAllowance), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await zrxToken.transferFrom.sendTransactionAsync(owner, spender, amountToTransfer, { + from: spender, + gas: constants.MAX_TOKEN_TRANSFERFROM_GAS, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); const newOwnerBalance = await zrxToken.balanceOf.callAsync(owner); const newSpenderBalance = await zrxToken.balanceOf.callAsync(spender); @@ -171,11 +188,17 @@ describe('ZRXToken', () => { it('should modify allowance if spender has sufficient allowance less than 2^256 - 1', async () => { const initOwnerBalance = await zrxToken.balanceOf.callAsync(owner); const amountToTransfer = initOwnerBalance; - await zrxToken.approve.sendTransactionAsync(spender, amountToTransfer); - await zrxToken.transferFrom.sendTransactionAsync(owner, spender, amountToTransfer, { - from: spender, - gas: constants.MAX_TOKEN_TRANSFERFROM_GAS, - }); + await web3Wrapper.awaitTransactionSuccessAsync( + await zrxToken.approve.sendTransactionAsync(spender, amountToTransfer), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await zrxToken.transferFrom.sendTransactionAsync(owner, spender, amountToTransfer, { + from: spender, + gas: constants.MAX_TOKEN_TRANSFERFROM_GAS, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); const newSpenderAllowance = await zrxToken.allowance.callAsync(owner, spender); expect(newSpenderAllowance).to.be.bignumber.equal(0); |