From ec9d1ca57ed2caf47a05546705bb59eeda2322f2 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 25 Jun 2018 15:18:02 +0200 Subject: Document all the contract test options in README --- packages/contracts/README.md | 46 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/packages/contracts/README.md b/packages/contracts/README.md index 9c829c753..2e6376f39 100644 --- a/packages/contracts/README.md +++ b/packages/contracts/README.md @@ -64,11 +64,51 @@ yarn lint yarn test ``` -### Run Tests Against Geth +#### Testing options -Follow the instructions in the README for the devnet package to start the -devnet. +###### Revert stack traces + +If you want to see helpful stack traces (incl. line number, code snippet) for smart contract reverts, run the tests with: + +``` +yarn test:trace +``` + +**Note:** This currently slows down the test runs and is therefore not enabled by default. + +###### Backing Ethereum node + +By default, our tests run against an in-process [Ganache](https://github.com/trufflesuite/ganache-core) instance. In order to run the tests against [Geth](https://github.com/ethereum/go-ethereum), first follow the instructions in the README for the devnet package to start the devnet Geth node. Then run: ```bash TEST_PROVIDER=geth yarn test ``` + +###### Code coverage + +In order to see the Solidity code coverage output generated by `@0xproject/sol-cov`, run: + +``` +yarn test:coverage +``` + +###### Gas profiler + +In order to profile the gas costs for a specific smart contract call/transaction, you can run the tests in `profiler` mode. + +**Note:** Traces emitted by ganache have incorrect gas costs so we recommend using Geth for profiling. + +``` +TEST_PROVIDER=geth yarn test:profiler +``` + +You'll see a warning that you need to explicitly enable and disable the profiler before and after the block of code you want to profile. + +```typescript +import { profiler } from './utils/profiler'; +profiler.start(); +// Some call to a smart contract +profiler.stop(); +``` + +Without explicitly starting and stopping the profiler, the profiler output will be too busy, and therefore unusable. -- cgit v1.2.3 From f7337c1a05ff49eac6dbade08a73ee32b9c28f36 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 21 Jun 2018 18:46:59 +0200 Subject: Remove proxyId argument from dispatchTransferFrom --- .../protocol/Exchange/MixinAssetProxyDispatcher.sol | 18 ++++++++++++++---- .../current/protocol/Exchange/MixinExchangeCore.sol | 9 --------- .../current/protocol/Exchange/MixinMatchOrders.sol | 13 ------------- .../current/protocol/Exchange/libs/LibConstants.sol | 3 --- .../protocol/Exchange/mixins/MAssetProxyDispatcher.sol | 4 +--- .../TestAssetProxyDispatcher.sol | 3 +-- packages/contracts/test/exchange/core.ts | 2 +- packages/contracts/test/exchange/dispatcher.ts | 9 +++------ 8 files changed, 20 insertions(+), 41 deletions(-) diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol index b8d6c0722..1d07587e9 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol @@ -19,6 +19,7 @@ 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"; @@ -28,6 +29,8 @@ contract MixinAssetProxyDispatcher is LibExchangeErrors, MAssetProxyDispatcher { + using LibBytes for bytes; + // Mapping from Asset Proxy Id's to their respective Asset Proxy mapping (uint8 => IAssetProxy) public assetProxies; @@ -83,14 +86,12 @@ contract MixinAssetProxyDispatcher is } /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws. - /// @param assetData Byte array encoded for the respective asset proxy. - /// @param assetProxyId Id of assetProxy to dispach to. + /// @param assetData Byte array encoded for the asset. /// @param from Address to transfer token from. /// @param to Address to transfer token to. /// @param amount Amount of token to transfer. function dispatchTransferFrom( bytes memory assetData, - uint8 assetProxyId, address from, address to, uint256 amount @@ -100,6 +101,15 @@ contract MixinAssetProxyDispatcher is // Do nothing if no amount should be transferred. if (amount > 0) { // Lookup assetProxy + uint8 assetProxyId = uint8(assetData[assetData.length - 1]); + + bytes memory assetDataP = new bytes(assetData.length - 1); + LibBytes.memCopy( + assetDataP.contentAddress(), + assetData.contentAddress(), + assetDataP.length + ); + IAssetProxy assetProxy = assetProxies[assetProxyId]; // Ensure that assetProxy exists require( @@ -108,7 +118,7 @@ contract MixinAssetProxyDispatcher is ); // transferFrom will either succeed or throw. assetProxy.transferFrom( - assetData, + assetDataP, 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 b207b3e57..38a73d885 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -20,7 +20,6 @@ pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "./libs/LibConstants.sol"; -import "../../utils/LibBytes/LibBytes.sol"; import "./libs/LibFillResults.sol"; import "./libs/LibOrder.sol"; import "./libs/LibMath.sol"; @@ -41,8 +40,6 @@ contract MixinExchangeCore is MSignatureValidator, MTransactions { - using LibBytes for bytes; - // Mapping of orderHash => amount of takerAsset already bought by maker mapping (bytes32 => uint256) public filled; @@ -412,33 +409,27 @@ contract MixinExchangeCore is ) private { - uint8 makerAssetProxyId = uint8(order.makerAssetData.popLastByte()); - uint8 takerAssetProxyId = uint8(order.takerAssetData.popLastByte()); 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( zrxAssetData, - ZRX_PROXY_ID, order.makerAddress, order.feeRecipientAddress, fillResults.makerFeePaid ); dispatchTransferFrom( zrxAssetData, - ZRX_PROXY_ID, takerAddress, order.feeRecipientAddress, fillResults.takerFeePaid diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index e36fcc2c5..a3e8d8dde 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -15,7 +15,6 @@ pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; import "./libs/LibConstants.sol"; -import "../../utils/LibBytes/LibBytes.sol"; import "./libs/LibMath.sol"; import "./libs/LibOrder.sol"; import "./libs/LibFillResults.sol"; @@ -34,8 +33,6 @@ contract MixinMatchOrders is MMatchOrders, MTransactions { - using LibBytes for bytes; - /// @dev Match two complementary orders that have a profitable spread. /// Each order is filled at their respective price point. However, the calculations are /// carried out as though the orders are both being filled at the right order's price point. @@ -242,27 +239,22 @@ contract MixinMatchOrders is ) private { - uint8 leftMakerAssetProxyId = uint8(leftOrder.makerAssetData.popLastByte()); - uint8 rightMakerAssetProxyId = uint8(rightOrder.makerAssetData.popLastByte()); 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 @@ -271,14 +263,12 @@ contract MixinMatchOrders is // Maker fees dispatchTransferFrom( zrxAssetData, - ZRX_PROXY_ID, leftOrder.makerAddress, leftOrder.feeRecipientAddress, matchedFillResults.left.makerFeePaid ); dispatchTransferFrom( zrxAssetData, - ZRX_PROXY_ID, rightOrder.makerAddress, rightOrder.feeRecipientAddress, matchedFillResults.right.makerFeePaid @@ -288,7 +278,6 @@ contract MixinMatchOrders is if (leftOrder.feeRecipientAddress == rightOrder.feeRecipientAddress) { dispatchTransferFrom( zrxAssetData, - ZRX_PROXY_ID, takerAddress, leftOrder.feeRecipientAddress, safeAdd( @@ -299,14 +288,12 @@ contract MixinMatchOrders is } else { dispatchTransferFrom( zrxAssetData, - ZRX_PROXY_ID, takerAddress, leftOrder.feeRecipientAddress, matchedFillResults.left.takerFeePaid ); dispatchTransferFrom( zrxAssetData, - ZRX_PROXY_ID, takerAddress, rightOrder.feeRecipientAddress, matchedFillResults.right.takerFeePaid diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibConstants.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibConstants.sol index 4a9452448..488ca956c 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibConstants.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibConstants.sol @@ -25,9 +25,6 @@ contract LibConstants { // not constant to make testing easier. bytes public ZRX_ASSET_DATA; - // Proxy Id for ZRX token. - uint8 constant ZRX_PROXY_ID = 1; - // @TODO: Remove when we deploy. constructor (bytes memory zrxAssetData) public 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 788f42c60..970082a61 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol @@ -33,14 +33,12 @@ contract MAssetProxyDispatcher is ); /// @dev Forwards arguments to assetProxy and calls `transferFrom`. Either succeeds or throws. - /// @param assetData Byte array encoded for the respective asset proxy. - /// @param assetProxyId Id of assetProxy to dispach to. + /// @param assetData Byte array encoded for the asset. /// @param from Address to transfer token from. /// @param to Address to transfer token to. /// @param amount Amount of token to transfer. function dispatchTransferFrom( bytes memory assetData, - uint8 assetProxyId, address from, address to, uint256 amount diff --git a/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol index d469a07f0..2ae69e0ef 100644 --- a/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/test/TestAssetProxyDispatcher/TestAssetProxyDispatcher.sol @@ -24,12 +24,11 @@ import "../../protocol/Exchange/MixinAssetProxyDispatcher.sol"; contract TestAssetProxyDispatcher is MixinAssetProxyDispatcher { function publicDispatchTransferFrom( bytes memory assetData, - uint8 assetProxyId, address from, address to, uint256 amount) public { - dispatchTransferFrom(assetData, assetProxyId, from, to, amount); + dispatchTransferFrom(assetData, from, to, amount); } } diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts index ff652d3aa..5b2d4590f 100644 --- a/packages/contracts/test/exchange/core.ts +++ b/packages/contracts/test/exchange/core.ts @@ -86,7 +86,7 @@ describe('Exchange core', () => { artifacts.Exchange, provider, txDefaults, - zrxToken.address, + assetProxyUtils.encodeERC20AssetData(zrxToken.address), ); exchangeWrapper = new ExchangeWrapper(exchange, provider); await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner); diff --git a/packages/contracts/test/exchange/dispatcher.ts b/packages/contracts/test/exchange/dispatcher.ts index abbfd7ac7..f96f15f16 100644 --- a/packages/contracts/test/exchange/dispatcher.ts +++ b/packages/contracts/test/exchange/dispatcher.ts @@ -276,14 +276,13 @@ describe('AssetProxyDispatcher', () => { ); // Construct metadata for ERC20 proxy 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( - encodedAssetDataWithoutProxyId, - AssetProxyId.ERC20, + encodedAssetData, makerAddress, takerAddress, amount, @@ -304,13 +303,11 @@ describe('AssetProxyDispatcher', () => { it('should throw if dispatching to unregistered proxy', async () => { // Construct metadata for ERC20 proxy 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 expectRevertOrAlwaysFailingTransactionAsync( assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( - encodedAssetDataWithoutProxyId, - AssetProxyId.ERC20, + encodedAssetData, makerAddress, takerAddress, amount, -- cgit v1.2.3 From a98ecc05af5b264f981087005bb3fa041d5c8756 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Thu, 21 Jun 2018 20:00:48 +0200 Subject: ABI encoded asset data --- .../current/protocol/AssetProxy/ERC20Proxy.sol | 4 +-- .../current/protocol/AssetProxy/ERC721Proxy.sol | 4 +-- .../protocol/AssetProxy/MixinERC20Transfer.sol | 2 +- .../protocol/AssetProxy/MixinERC721Transfer.sol | 8 ++---- .../protocol/AssetProxy/interfaces/IAssetData.sol | 33 ++++++++++++++++++++++ .../protocol/AssetProxy/interfaces/IAssetProxy.sol | 3 +- .../Exchange/MixinAssetProxyDispatcher.sol | 20 ++++--------- .../Exchange/interfaces/IAssetProxyDispatcher.sol | 4 +-- .../Exchange/mixins/MAssetProxyDispatcher.sol | 2 +- 9 files changed, 51 insertions(+), 29 deletions(-) create mode 100644 packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetData.sol diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol index 7ca823d1f..69e2dc913 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol @@ -30,14 +30,14 @@ contract ERC20Proxy is MixinERC20Transfer { // Id of this proxy. - uint8 constant PROXY_ID = 1; + bytes4 constant PROXY_ID = 1; /// @dev Gets the proxy id associated with the proxy address. /// @return Proxy id. function getProxyId() external view - returns (uint8) + returns (bytes4) { return PROXY_ID; } diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol index 7ff25aea3..7a1b13d80 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol @@ -30,14 +30,14 @@ contract ERC721Proxy is MixinERC721Transfer { // Id of this proxy. - uint8 constant PROXY_ID = 2; + bytes4 constant PROXY_ID = 2; /// @dev Gets the proxy id associated with the proxy address. /// @return Proxy id. function getProxyId() external view - returns (uint8) + returns (bytes4) { return PROXY_ID; } diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol index 0e7f3fc89..deac59f4d 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol @@ -42,7 +42,7 @@ contract MixinERC20Transfer is internal { // Decode asset data. - address token = assetData.readAddress(0); + address token = assetData.readAddress(16); // Transfer tokens. // We do a raw call so we can check the success separate diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol index 944068bbb..ebc879644 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol @@ -79,11 +79,9 @@ contract MixinERC721Transfer is ) { // Decode asset data. - token = assetData.readAddress(0); - tokenId = assetData.readUint256(20); - if (assetData.length > 52) { - receiverData = assetData.readBytesWithLength(52); - } + token = assetData.readAddress(16); + tokenId = assetData.readUint256(36); + receiverData = assetData.readBytesWithLength(100); return ( token, diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetData.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetData.sol new file mode 100644 index 000000000..6f49a8ba9 --- /dev/null +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetData.sol @@ -0,0 +1,33 @@ +/* + + 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.23; + +interface IAssetData { + + function ERC20Token( + address tokenContract) + external pure; + + function ERC721Token( + address tokenContract, + uint256 tokenId, + bytes receiverData) + external pure; + +} 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 22f43b12f..d12d23610 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetProxy.sol @@ -56,6 +56,5 @@ contract IAssetProxy is function getProxyId() external view - returns (uint8); + returns (bytes4); } - diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol index 1d07587e9..da0beefde 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol @@ -32,7 +32,7 @@ contract MixinAssetProxyDispatcher is using LibBytes for bytes; // Mapping from Asset Proxy Id's to their respective Asset Proxy - mapping (uint8 => IAssetProxy) public assetProxies; + mapping (bytes4 => IAssetProxy) public assetProxies; /// @dev Registers an asset proxy to an asset proxy id. /// An id can only be assigned to a single proxy at a given time. @@ -40,7 +40,7 @@ contract MixinAssetProxyDispatcher is /// @param newAssetProxy Address of new asset proxy to register, or 0x0 to unset assetProxyId. /// @param oldAssetProxy Existing asset proxy to overwrite, or 0x0 if assetProxyId is currently unused. function registerAssetProxy( - uint8 assetProxyId, + bytes4 assetProxyId, address newAssetProxy, address oldAssetProxy ) @@ -58,7 +58,7 @@ contract MixinAssetProxyDispatcher is // Ensure that the id of newAssetProxy matches the passed in assetProxyId, unless it is being reset to 0. if (newAssetProxy != address(0)) { - uint8 newAssetProxyId = assetProxy.getProxyId(); + bytes4 newAssetProxyId = assetProxy.getProxyId(); require( newAssetProxyId == assetProxyId, ASSET_PROXY_ID_MISMATCH @@ -77,7 +77,7 @@ contract MixinAssetProxyDispatcher is /// @dev Gets an asset proxy. /// @param assetProxyId Id of the asset proxy. /// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered. - function getAssetProxy(uint8 assetProxyId) + function getAssetProxy(bytes4 assetProxyId) external view returns (address) @@ -101,15 +101,7 @@ contract MixinAssetProxyDispatcher is // Do nothing if no amount should be transferred. if (amount > 0) { // Lookup assetProxy - uint8 assetProxyId = uint8(assetData[assetData.length - 1]); - - bytes memory assetDataP = new bytes(assetData.length - 1); - LibBytes.memCopy( - assetDataP.contentAddress(), - assetData.contentAddress(), - assetDataP.length - ); - + bytes4 assetProxyId = assetData.readBytes4(0); IAssetProxy assetProxy = assetProxies[assetProxyId]; // Ensure that assetProxy exists require( @@ -118,7 +110,7 @@ contract MixinAssetProxyDispatcher is ); // transferFrom will either succeed or throw. assetProxy.transferFrom( - assetDataP, + assetData, from, to, amount 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 2c331dc34..fa55dff00 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/interfaces/IAssetProxyDispatcher.sol @@ -26,7 +26,7 @@ contract IAssetProxyDispatcher { /// @param newAssetProxy Address of new asset proxy to register, or 0x0 to unset assetProxyId. /// @param oldAssetProxy Existing asset proxy to overwrite, or 0x0 if assetProxyId is currently unused. function registerAssetProxy( - uint8 assetProxyId, + bytes4 assetProxyId, address newAssetProxy, address oldAssetProxy ) @@ -35,7 +35,7 @@ contract IAssetProxyDispatcher { /// @dev Gets an asset proxy. /// @param assetProxyId Id of the asset proxy. /// @return The asset proxy registered to assetProxyId. Returns 0x0 if no proxy is registered. - function getAssetProxy(uint8 assetProxyId) + function getAssetProxy(bytes4 assetProxyId) external view returns (address); 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 970082a61..c2b506dcf 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/mixins/MAssetProxyDispatcher.sol @@ -27,7 +27,7 @@ contract MAssetProxyDispatcher is // Logs registration of new asset proxy event AssetProxySet( - uint8 id, // Id of new registered AssetProxy. + bytes4 id, // Id of new registered AssetProxy. address newAssetProxy, // Address of new registered AssetProxy. address oldAssetProxy // Address of AssetProxy that was overwritten at given id (or null address). ); -- cgit v1.2.3 From 7b0c13c1219fbe2d47b540476eaebb1f6358213d Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 22 Jun 2018 07:03:42 +0200 Subject: WIP --- .../current/protocol/AssetProxy/ERC20Proxy.sol | 2 +- .../current/protocol/AssetProxy/ERC721Proxy.sol | 2 +- packages/contracts/test/asset_proxy/decoder.ts | 1 + packages/order-utils/src/asset_proxy_utils.ts | 44 ++++++++++++++-------- packages/types/src/index.ts | 10 ++--- 5 files changed, 36 insertions(+), 23 deletions(-) diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol index 69e2dc913..002e8b662 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol @@ -30,7 +30,7 @@ contract ERC20Proxy is MixinERC20Transfer { // Id of this proxy. - bytes4 constant PROXY_ID = 1; + bytes4 constant PROXY_ID = 0xf47261b0; /// @dev Gets the proxy id associated with the proxy address. /// @return Proxy id. diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol index 7a1b13d80..f6bdf2a5f 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol @@ -30,7 +30,7 @@ contract ERC721Proxy is MixinERC721Transfer { // Id of this proxy. - bytes4 constant PROXY_ID = 2; + bytes4 constant PROXY_ID = 0xf47261b0; /// @dev Gets the proxy id associated with the proxy address. /// @return Proxy id. diff --git a/packages/contracts/test/asset_proxy/decoder.ts b/packages/contracts/test/asset_proxy/decoder.ts index 98d18aa38..f671f495e 100644 --- a/packages/contracts/test/asset_proxy/decoder.ts +++ b/packages/contracts/test/asset_proxy/decoder.ts @@ -44,6 +44,7 @@ describe('TestAssetDataDecoders', () => { it('should correctly decode ERC721 asset data', async () => { const tokenId = generatePseudoRandomSalt(); const encodedAssetData = assetProxyUtils.encodeERC721AssetData(testAddress, tokenId); + console.log(encodedAssetData); const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); const expectedDecodedAssetData = assetProxyUtils.decodeERC721AssetData(encodedAssetData); let decodedTokenAddress: string; diff --git a/packages/order-utils/src/asset_proxy_utils.ts b/packages/order-utils/src/asset_proxy_utils.ts index 915ee5032..fe91a3525 100644 --- a/packages/order-utils/src/asset_proxy_utils.ts +++ b/packages/order-utils/src/asset_proxy_utils.ts @@ -4,7 +4,7 @@ import BN = require('bn.js'); import ethUtil = require('ethereumjs-util'); import * as _ from 'lodash'; -const ERC20_ASSET_DATA_BYTE_LENGTH = 21; +const ERC20_ASSET_DATA_BYTE_LENGTH = 36; const ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH = 53; const ASSET_DATA_ADDRESS_OFFSET = 0; const ERC721_ASSET_DATA_TOKEN_ID_OFFSET = 20; @@ -16,17 +16,26 @@ export const assetProxyUtils = { return ethUtil.toBuffer(assetProxyId); }, decodeAssetProxyId(encodedAssetProxyId: Buffer): AssetProxyId { - return ethUtil.bufferToInt(encodedAssetProxyId); + const string = ethUtil.bufferToHex(encodedAssetProxyId); + if (string === AssetProxyId.ERC20) { + return AssetProxyId.ERC20; + } + if (string === AssetProxyId.ERC721) { + return AssetProxyId.ERC721; + } + throw new Error(`Invalid ProxyId: ${string}`); }, encodeAddress(address: string): Buffer { if (!ethUtil.isValidAddress(address)) { throw new Error(`Invalid Address: ${address}`); } const encodedAddress = ethUtil.toBuffer(address); - return encodedAddress; + const padded = ethUtil.setLengthLeft(encodedAddress, 32); + return padded; }, decodeAddress(encodedAddress: Buffer): string { - const address = ethUtil.bufferToHex(encodedAddress); + const unpadded = ethUtil.setLengthLeft(encodedAddress, 20); + const address = ethUtil.bufferToHex(unpadded); if (!ethUtil.isValidAddress(address)) { throw new Error(`Invalid Address: ${address}`); } @@ -48,7 +57,7 @@ export const assetProxyUtils = { encodeERC20AssetData(tokenAddress: string): string { const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC20); const encodedAddress = assetProxyUtils.encodeAddress(tokenAddress); - const encodedAssetData = Buffer.concat([encodedAddress, encodedAssetProxyId]); + const encodedAssetData = Buffer.concat([encodedAssetProxyId, encodedAddress]); const encodedAssetDataHex = ethUtil.bufferToHex(encodedAssetData); return encodedAssetDataHex; }, @@ -56,13 +65,12 @@ export const assetProxyUtils = { const encodedAssetData = ethUtil.toBuffer(proxyData); if (encodedAssetData.byteLength !== ERC20_ASSET_DATA_BYTE_LENGTH) { throw new Error( - `Could not decode ERC20 Proxy Data. Expected length of encoded data to be 21. Got ${ + `Could not decode ERC20 Proxy Data. Expected length of encoded data to be ${ERC20_ASSET_DATA_BYTE_LENGTH}. Got ${ encodedAssetData.byteLength }`, ); } - const assetProxyIdOffset = encodedAssetData.byteLength - 1; - const encodedAssetProxyId = encodedAssetData.slice(assetProxyIdOffset); + const encodedAssetProxyId = encodedAssetData.slice(0, 4); const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId); if (assetProxyId !== AssetProxyId.ERC20) { throw new Error( @@ -71,7 +79,7 @@ export const assetProxyUtils = { }), but got ${assetProxyId}`, ); } - const encodedTokenAddress = encodedAssetData.slice(ASSET_DATA_ADDRESS_OFFSET, assetProxyIdOffset); + const encodedTokenAddress = encodedAssetData.slice(16, 20); const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress); const erc20AssetData = { assetProxyId, @@ -90,26 +98,30 @@ export const assetProxyUtils = { const encodedReceiverDataLength = assetProxyUtils.encodeUint256(receiverDataLength); encodedAssetData = Buffer.concat([encodedAssetData, encodedReceiverDataLength, encodedReceiverData]); } - encodedAssetData = Buffer.concat([encodedAssetData, encodedAssetProxyId]); + encodedAssetData = Buffer.concat([encodedAssetProxyId, encodedAssetData]); const encodedAssetDataHex = ethUtil.bufferToHex(encodedAssetData); return encodedAssetDataHex; }, decodeERC721AssetData(assetData: string): ERC721AssetData { + const encodedAssetData = ethUtil.toBuffer(assetData); if (encodedAssetData.byteLength < ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH) { throw new Error( - `Could not decode ERC20 Proxy Data. Expected length of encoded data to be at least 53. Got ${ + `Could not decode ERC20 Proxy Data. Expected length of encoded data to be at least ${ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH}. Got ${ encodedAssetData.byteLength }`, ); } + console.log(rawDecode); + //const result = ethAbi.rawDecode(['address', 'uint256', 'bytes'], encodedAssetData.slice(4)); + //console.log(result); + const encodedTokenAddress = encodedAssetData.slice( ASSET_DATA_ADDRESS_OFFSET, ERC721_ASSET_DATA_TOKEN_ID_OFFSET, ); - const proxyIdOffset = encodedAssetData.byteLength - 1; - const encodedAssetProxyId = encodedAssetData.slice(proxyIdOffset); + const encodedAssetProxyId = encodedAssetData.slice(0, 4); const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId); if (assetProxyId !== AssetProxyId.ERC721) { throw new Error( @@ -155,14 +167,14 @@ export const assetProxyUtils = { }, decodeAssetDataId(assetData: string): AssetProxyId { const encodedAssetData = ethUtil.toBuffer(assetData); - if (encodedAssetData.byteLength < 1) { + if (encodedAssetData.byteLength < 4) { throw new Error( - `Could not decode Proxy Data. Expected length of encoded data to be at least 1. Got ${ + `Could not decode Proxy Data. Expected length of encoded data to be at least 4. Got ${ encodedAssetData.byteLength }`, ); } - const encodedAssetProxyId = encodedAssetData.slice(-1); + const encodedAssetProxyId = encodedAssetData.slice(0, 4); const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId); return assetProxyId; }, diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index e5a0f41a9..88abdc1e7 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -152,18 +152,18 @@ export interface ECSignature { } export enum AssetProxyId { - INVALID, - ERC20, - ERC721, + INVALID = '0x00000000', + ERC20 = '0xf47261b0', + ERC721 = '0x08e937fa', } export interface ERC20AssetData { - assetProxyId: AssetProxyId; + assetProxyId: string; tokenAddress: string; } export interface ERC721AssetData { - assetProxyId: AssetProxyId; + assetProxyId: string; tokenAddress: string; tokenId: BigNumber; receiverData: string; -- cgit v1.2.3 From b7b0185baf647f835aa14b38f43c1dd5b58c965a Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 22 Jun 2018 09:50:24 +0200 Subject: Add IAssetData --- packages/contracts/compiler.json | 1 + packages/contracts/package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/contracts/compiler.json b/packages/contracts/compiler.json index 6b7b69caa..e1ea401b3 100644 --- a/packages/contracts/compiler.json +++ b/packages/contracts/compiler.json @@ -27,6 +27,7 @@ "ERC721Proxy", "Exchange", "ExchangeWrapper", + "IAssetData", "MixinAuthorizable", "MultiSigWallet", "MultiSigWalletWithTimeLock", diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 8249b9e0c..95f2dbb4e 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -34,7 +34,7 @@ }, "config": { "abis": - "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|ExchangeWrapper|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetProxyOwner|TestAssetDataDecoders|TestAssetProxyDispatcher|TestLibBytes|TestLibs|TestSignatureValidator|TestValidator|TestWallet|TokenRegistry|Whitelist|WETH9|ZRXToken).json" + "../migrations/artifacts/2.0.0/@(AssetProxyOwner|DummyERC20Token|DummyERC721Receiver|DummyERC721Token|ERC20Proxy|ERC721Proxy|Exchange|ExchangeWrapper|IAssetData|MixinAuthorizable|MultiSigWallet|MultiSigWalletWithTimeLock|TestAssetProxyOwner|TestAssetDataDecoders|TestAssetProxyDispatcher|TestLibBytes|TestLibs|TestSignatureValidator|TestValidator|TestWallet|TokenRegistry|Whitelist|WETH9|ZRXToken).json" }, "repository": { "type": "git", -- cgit v1.2.3 From 08d22d2f3c358b61699459f54ac191ee91827da3 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 22 Jun 2018 09:51:59 +0200 Subject: Update AssetProxyUtils --- packages/order-utils/src/asset_proxy_utils.ts | 131 ++++++++------------------ 1 file changed, 41 insertions(+), 90 deletions(-) diff --git a/packages/order-utils/src/asset_proxy_utils.ts b/packages/order-utils/src/asset_proxy_utils.ts index fe91a3525..aad14d687 100644 --- a/packages/order-utils/src/asset_proxy_utils.ts +++ b/packages/order-utils/src/asset_proxy_utils.ts @@ -1,8 +1,7 @@ import { AssetProxyId, ERC20AssetData, ERC721AssetData } from '@0xproject/types'; -import { BigNumber, NULL_BYTES } from '@0xproject/utils'; +import { BigNumber } from '@0xproject/utils'; import BN = require('bn.js'); import ethUtil = require('ethereumjs-util'); -import * as _ from 'lodash'; const ERC20_ASSET_DATA_BYTE_LENGTH = 36; const ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH = 53; @@ -11,6 +10,13 @@ const ERC721_ASSET_DATA_TOKEN_ID_OFFSET = 20; const ERC721_ASSET_DATA_RECEIVER_DATA_LENGTH_OFFSET = 52; const ERC721_ASSET_DATA_RECEIVER_DATA_OFFSET = 84; +// TODO: Push upstream to DefinitelyTyped +interface EthAbi { + simpleEncode(signature: string, ...args: any[]): Buffer; + rawDecode(signature: string[], data: Buffer): any[]; +} +const ethAbi = require('ethereumjs-abi') as EthAbi; + export const assetProxyUtils = { encodeAssetProxyId(assetProxyId: AssetProxyId): Buffer { return ethUtil.toBuffer(assetProxyId); @@ -55,123 +61,68 @@ export const assetProxyUtils = { return value; }, encodeERC20AssetData(tokenAddress: string): string { - const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC20); - const encodedAddress = assetProxyUtils.encodeAddress(tokenAddress); - const encodedAssetData = Buffer.concat([encodedAssetProxyId, encodedAddress]); - const encodedAssetDataHex = ethUtil.bufferToHex(encodedAssetData); - return encodedAssetDataHex; + return ethUtil.bufferToHex(ethAbi.simpleEncode( + 'ERC20Token(address)', + tokenAddress, + )); }, - decodeERC20AssetData(proxyData: string): ERC20AssetData { - const encodedAssetData = ethUtil.toBuffer(proxyData); - if (encodedAssetData.byteLength !== ERC20_ASSET_DATA_BYTE_LENGTH) { + decodeERC20AssetData(assetData: string): ERC20AssetData { + const data = ethUtil.toBuffer(assetData); + if (data.byteLength < ERC20_ASSET_DATA_BYTE_LENGTH) { throw new Error( - `Could not decode ERC20 Proxy Data. Expected length of encoded data to be ${ERC20_ASSET_DATA_BYTE_LENGTH}. Got ${ - encodedAssetData.byteLength - }`, + `Could not decode ERC20 Proxy Data. Expected length of encoded data to be at least ${ERC20_ASSET_DATA_BYTE_LENGTH}. Got ${data.byteLength}`, ); } - const encodedAssetProxyId = encodedAssetData.slice(0, 4); - const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId); + const assetProxyId = ethUtil.bufferToHex(data.slice(0, 4)); if (assetProxyId !== AssetProxyId.ERC20) { throw new Error( - `Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be ERC20 (${ - AssetProxyId.ERC20 - }), but got ${assetProxyId}`, + `Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be ERC20 (${AssetProxyId.ERC20}), but got ${assetProxyId}`, ); } - const encodedTokenAddress = encodedAssetData.slice(16, 20); - const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress); - const erc20AssetData = { + const [tokenAddress] = ethAbi.rawDecode(['address'], data.slice(4)); + return { assetProxyId, - tokenAddress, + tokenAddress: ethUtil.addHexPrefix(tokenAddress), }; - return erc20AssetData; }, encodeERC721AssetData(tokenAddress: string, tokenId: BigNumber, receiverData?: string): string { - const encodedAssetProxyId = assetProxyUtils.encodeAssetProxyId(AssetProxyId.ERC721); - const encodedAddress = assetProxyUtils.encodeAddress(tokenAddress); - const encodedTokenId = assetProxyUtils.encodeUint256(tokenId); - let encodedAssetData = Buffer.concat([encodedAddress, encodedTokenId]); - if (!_.isUndefined(receiverData)) { - const encodedReceiverData = ethUtil.toBuffer(receiverData); - const receiverDataLength = new BigNumber(encodedReceiverData.byteLength); - const encodedReceiverDataLength = assetProxyUtils.encodeUint256(receiverDataLength); - encodedAssetData = Buffer.concat([encodedAssetData, encodedReceiverDataLength, encodedReceiverData]); - } - encodedAssetData = Buffer.concat([encodedAssetProxyId, encodedAssetData]); - const encodedAssetDataHex = ethUtil.bufferToHex(encodedAssetData); - return encodedAssetDataHex; + // TODO: Pass `tokendId` as a BigNumber. + return ethUtil.bufferToHex(ethAbi.simpleEncode( + 'ERC721Token(address,uint256,bytes)', + tokenAddress, + tokenId.toString(16), + ethUtil.toBuffer(receiverData || '0x'), + )); }, decodeERC721AssetData(assetData: string): ERC721AssetData { - - const encodedAssetData = ethUtil.toBuffer(assetData); - if (encodedAssetData.byteLength < ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH) { + const data = ethUtil.toBuffer(assetData); + if (data.byteLength < ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH) { throw new Error( - `Could not decode ERC20 Proxy Data. Expected length of encoded data to be at least ${ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH}. Got ${ - encodedAssetData.byteLength - }`, + `Could not decode ERC721 Asset Data. Expected length of encoded data to be at least ${ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH}. Got ${data.byteLength}`, ); } - - console.log(rawDecode); - //const result = ethAbi.rawDecode(['address', 'uint256', 'bytes'], encodedAssetData.slice(4)); - //console.log(result); - - const encodedTokenAddress = encodedAssetData.slice( - ASSET_DATA_ADDRESS_OFFSET, - ERC721_ASSET_DATA_TOKEN_ID_OFFSET, - ); - const encodedAssetProxyId = encodedAssetData.slice(0, 4); - const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId); + const assetProxyId = ethUtil.bufferToHex(data.slice(0, 4)); if (assetProxyId !== AssetProxyId.ERC721) { throw new Error( - `Could not decode ERC721 Proxy Data. Expected Asset Proxy Id to be ERC721 (${ - AssetProxyId.ERC721 - }), but got ${assetProxyId}`, + `Could not decode ERC721 Asset Data. Expected Asset Proxy Id to be ERC721 (${AssetProxyId.ERC721}), but got ${assetProxyId}`, ); } - const tokenAddress = assetProxyUtils.decodeAddress(encodedTokenAddress); - const encodedTokenId = encodedAssetData.slice( - ERC721_ASSET_DATA_TOKEN_ID_OFFSET, - ERC721_ASSET_DATA_RECEIVER_DATA_LENGTH_OFFSET, + const [tokenAddress, tokenId, receiverData] = ethAbi.rawDecode( + ['address', 'uint256', 'bytes'], + data.slice(4), ); - const tokenId = assetProxyUtils.decodeUint256(encodedTokenId); - let receiverData = NULL_BYTES; - const lengthUpToReceiverDataLength = ERC721_ASSET_DATA_RECEIVER_DATA_LENGTH_OFFSET + 1; - if (encodedAssetData.byteLength > lengthUpToReceiverDataLength) { - const encodedReceiverDataLength = encodedAssetData.slice( - ERC721_ASSET_DATA_RECEIVER_DATA_LENGTH_OFFSET, - ERC721_ASSET_DATA_RECEIVER_DATA_OFFSET, - ); - const receiverDataLength = assetProxyUtils.decodeUint256(encodedReceiverDataLength); - const lengthUpToReceiverData = ERC721_ASSET_DATA_RECEIVER_DATA_OFFSET + 1; - const expectedReceiverDataLength = new BigNumber(encodedAssetData.byteLength - lengthUpToReceiverData); - if (!receiverDataLength.equals(expectedReceiverDataLength)) { - throw new Error( - `Data length (${receiverDataLength}) does not match actual length of data (${expectedReceiverDataLength})`, - ); - } - const encodedReceiverData = encodedAssetData.slice( - ERC721_ASSET_DATA_RECEIVER_DATA_OFFSET, - receiverDataLength.add(ERC721_ASSET_DATA_RECEIVER_DATA_OFFSET).toNumber(), - ); - receiverData = ethUtil.bufferToHex(encodedReceiverData); - } - const erc721AssetData: ERC721AssetData = { + return { assetProxyId, - tokenAddress, - tokenId, - receiverData, + tokenAddress: ethUtil.addHexPrefix(tokenAddress), + tokenId: new BigNumber(tokenId.toString()), + receiverData: ethUtil.bufferToHex(receiverData), }; - return erc721AssetData; }, decodeAssetDataId(assetData: string): AssetProxyId { const encodedAssetData = ethUtil.toBuffer(assetData); if (encodedAssetData.byteLength < 4) { throw new Error( - `Could not decode Proxy Data. Expected length of encoded data to be at least 4. Got ${ - encodedAssetData.byteLength - }`, + `Could not decode Proxy Data. Expected length of encoded data to be at least 4. Got ${encodedAssetData.byteLength}`, ); } const encodedAssetProxyId = encodedAssetData.slice(0, 4); -- cgit v1.2.3 From 32d499421953851002492b6eef68fd7cc7b43639 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 22 Jun 2018 16:32:19 +0200 Subject: Fix ERC721Proxy TokenId --- .../contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol index f6bdf2a5f..ce2da76c2 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol @@ -30,7 +30,7 @@ contract ERC721Proxy is MixinERC721Transfer { // Id of this proxy. - bytes4 constant PROXY_ID = 0xf47261b0; + bytes4 constant PROXY_ID = 0x08e937fa; /// @dev Gets the proxy id associated with the proxy address. /// @return Proxy id. -- cgit v1.2.3 From 07734a5184e6e53230381953aaf8546abf5195a7 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 22 Jun 2018 16:36:56 +0200 Subject: Update tests --- packages/contracts/test/asset_proxy/decoder.ts | 5 ++- packages/contracts/test/asset_proxy/proxies.ts | 48 ++++++++++---------------- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/packages/contracts/test/asset_proxy/decoder.ts b/packages/contracts/test/asset_proxy/decoder.ts index f671f495e..b5a457933 100644 --- a/packages/contracts/test/asset_proxy/decoder.ts +++ b/packages/contracts/test/asset_proxy/decoder.ts @@ -45,7 +45,6 @@ describe('TestAssetDataDecoders', () => { const tokenId = generatePseudoRandomSalt(); const encodedAssetData = assetProxyUtils.encodeERC721AssetData(testAddress, tokenId); console.log(encodedAssetData); - const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); const expectedDecodedAssetData = assetProxyUtils.decodeERC721AssetData(encodedAssetData); let decodedTokenAddress: string; let decodedTokenId: BigNumber; @@ -54,8 +53,8 @@ describe('TestAssetDataDecoders', () => { decodedTokenAddress, decodedTokenId, decodedData, - ] = await testAssetProxyDecoder.publicDecodeERC721Data.callAsync(encodedAssetDataWithoutProxyId); - expect(decodedTokenAddress).to.be.equal(expectedDecodedAssetData.tokenAddress); + ] = await testAssetProxyDecoder.publicDecodeERC721Data.callAsync(encodedAssetData); + expect(decodedTokenAddress).to.be.equal( expectedDecodedAssetData.tokenAddress); expect(decodedTokenId).to.be.bignumber.equal(expectedDecodedAssetData.tokenId); expect(decodedData).to.be.equal(expectedDecodedAssetData.receiverData); }); diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts index 2c27f7382..608d599a1 100644 --- a/packages/contracts/test/asset_proxy/proxies.ts +++ b/packages/contracts/test/asset_proxy/proxies.ts @@ -96,13 +96,12 @@ describe('Asset Transfer Proxies', () => { it('should successfully transfer tokens', async () => { // 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( - encodedAssetDataWithoutProxyId, + encodedAssetData, makerAddress, takerAddress, amount, @@ -123,13 +122,12 @@ describe('Asset Transfer Proxies', () => { it('should do nothing if transferring 0 amount of a token', async () => { // 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( - encodedAssetDataWithoutProxyId, + encodedAssetData, makerAddress, takerAddress, amount, @@ -174,12 +172,12 @@ describe('Asset Transfer Proxies', () => { it('should throw if requesting address is not authorized', async () => { // 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 expectRevertOrAlwaysFailingTransactionAsync( erc20Proxy.transferFrom.sendTransactionAsync( - encodedAssetDataWithoutProxyId, + encodedAssetData, makerAddress, takerAddress, amount, @@ -196,10 +194,9 @@ describe('Asset Transfer Proxies', () => { const erc20Balances = await erc20Wrapper.getBalancesAsync(); const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address); - const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); const amount = new BigNumber(10); const numTransfers = 2; - const assetData = _.times(numTransfers, () => encodedAssetDataWithoutProxyId); + const assetData = _.times(numTransfers, () => encodedAssetData); const fromAddresses = _.times(numTransfers, () => makerAddress); const toAddresses = _.times(numTransfers, () => takerAddress); const amounts = _.times(numTransfers, () => amount); @@ -228,10 +225,9 @@ describe('Asset Transfer Proxies', () => { it('should throw if not called by an authorized address', async () => { const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address); - const encodedAssetDataWithoutProxyId = encodedAssetData.slice(0, -2); const amount = new BigNumber(10); const numTransfers = 2; - const assetData = _.times(numTransfers, () => encodedAssetDataWithoutProxyId); + const assetData = _.times(numTransfers, () => encodedAssetData); const fromAddresses = _.times(numTransfers, () => makerAddress); const toAddresses = _.times(numTransfers, () => takerAddress); const amounts = _.times(numTransfers, () => amount); @@ -244,9 +240,9 @@ describe('Asset Transfer Proxies', () => { }); }); - it('should have an id of 1', async () => { + it('should have an id of 0xf47261b0', async () => { const proxyId = await erc20Proxy.getProxyId.callAsync(); - expect(proxyId).to.equal(1); + expect(proxyId).to.equal('0xf47261b0'); }); }); @@ -255,7 +251,6 @@ describe('Asset Transfer Proxies', () => { it('should successfully transfer tokens', 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); @@ -263,7 +258,7 @@ describe('Asset Transfer Proxies', () => { const amount = new BigNumber(1); await web3Wrapper.awaitTransactionSuccessAsync( await erc721Proxy.transferFrom.sendTransactionAsync( - encodedAssetDataWithoutProxyId, + encodedAssetData, makerAddress, takerAddress, amount, @@ -279,14 +274,13 @@ describe('Asset Transfer Proxies', () => { it('should 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, + encodedAssetData, makerAddress, erc721Receiver.address, amount, @@ -315,14 +309,13 @@ describe('Asset Transfer Proxies', () => { 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, + encodedAssetData, makerAddress, erc721Receiver.address, amount, @@ -351,7 +344,6 @@ describe('Asset Transfer Proxies', () => { 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); @@ -359,7 +351,7 @@ describe('Asset Transfer Proxies', () => { const amount = new BigNumber(1); return expectRevertOrAlwaysFailingTransactionAsync( erc721Proxy.transferFrom.sendTransactionAsync( - encodedAssetDataWithoutProxyId, + encodedAssetData, makerAddress, erc20Proxy.address, // the ERC20 proxy does not have an ERC721 receiver amount, @@ -371,7 +363,6 @@ describe('Asset Transfer Proxies', () => { 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); @@ -379,7 +370,7 @@ describe('Asset Transfer Proxies', () => { const amount = new BigNumber(0); return expectRevertOrAlwaysFailingTransactionAsync( erc721Proxy.transferFrom.sendTransactionAsync( - encodedAssetDataWithoutProxyId, + encodedAssetData, makerAddress, takerAddress, amount, @@ -391,7 +382,6 @@ describe('Asset Transfer Proxies', () => { it('should throw if transferring > 1 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); @@ -399,7 +389,7 @@ describe('Asset Transfer Proxies', () => { const amount = new BigNumber(500); return expectRevertOrAlwaysFailingTransactionAsync( erc721Proxy.transferFrom.sendTransactionAsync( - encodedAssetDataWithoutProxyId, + encodedAssetData, makerAddress, takerAddress, amount, @@ -411,7 +401,6 @@ describe('Asset Transfer Proxies', () => { it('should throw if allowances are too low', async () => { // 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, { @@ -423,7 +412,7 @@ describe('Asset Transfer Proxies', () => { const amount = new BigNumber(1); return expectRevertOrAlwaysFailingTransactionAsync( erc20Proxy.transferFrom.sendTransactionAsync( - encodedAssetDataWithoutProxyId, + encodedAssetData, makerAddress, takerAddress, amount, @@ -437,12 +426,11 @@ describe('Asset Transfer Proxies', () => { it('should throw if requesting address is not authorized', async () => { // 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 expectRevertOrAlwaysFailingTransactionAsync( erc721Proxy.transferFrom.sendTransactionAsync( - encodedAssetDataWithoutProxyId, + encodedAssetData, makerAddress, takerAddress, amount, @@ -506,9 +494,9 @@ describe('Asset Transfer Proxies', () => { }); }); - it('should have an id of 2', async () => { + it('should have an id of 0x08e937fa', async () => { const proxyId = await erc721Proxy.getProxyId.callAsync(); - expect(proxyId).to.equal(2); + expect(proxyId).to.equal('0x08e937fa'); }); }); }); -- cgit v1.2.3 From 64a6fb2344ede6e81017e2336311688d9fa27c06 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 22 Jun 2018 16:37:10 +0200 Subject: Fix AssetProxyUtils --- packages/order-utils/src/asset_proxy_utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/order-utils/src/asset_proxy_utils.ts b/packages/order-utils/src/asset_proxy_utils.ts index aad14d687..43e23a7fd 100644 --- a/packages/order-utils/src/asset_proxy_utils.ts +++ b/packages/order-utils/src/asset_proxy_utils.ts @@ -90,7 +90,7 @@ export const assetProxyUtils = { return ethUtil.bufferToHex(ethAbi.simpleEncode( 'ERC721Token(address,uint256,bytes)', tokenAddress, - tokenId.toString(16), + '0x' + tokenId.toString(16), ethUtil.toBuffer(receiverData || '0x'), )); }, -- cgit v1.2.3 From 3c6d0dc3e01c844f0765650dcb19e8d331a6b230 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Fri, 22 Jun 2018 17:36:30 +0200 Subject: Update tests --- packages/contracts/test/asset_proxy/proxies.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts index 608d599a1..4823cc7b0 100644 --- a/packages/contracts/test/asset_proxy/proxies.ts +++ b/packages/contracts/test/asset_proxy/proxies.ts @@ -447,8 +447,8 @@ describe('Asset Transfer Proxies', () => { const numTransfers = 2; const assetData = [ - assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdA).slice(0, -2), - assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdB).slice(0, -2), + assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdA), + assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerTokenIdB), ]; const fromAddresses = _.times(numTransfers, () => makerAddress); const toAddresses = _.times(numTransfers, () => takerAddress); -- cgit v1.2.3 From 7ce0f9682f5af7510d6bf8e5d731fd28ddbc4d3a Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Fri, 22 Jun 2018 16:27:41 -0700 Subject: Fix tests --- .../current/protocol/Exchange/MixinWrapperFunctions.sol | 16 ---------------- packages/contracts/test/asset_proxy/decoder.ts | 3 +-- packages/contracts/test/exchange/match_orders.ts | 2 +- packages/contracts/test/exchange/transactions.ts | 2 +- packages/contracts/test/exchange/wrapper.ts | 2 +- 5 files changed, 4 insertions(+), 21 deletions(-) diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol index 724f95518..9cb2cacd4 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol @@ -366,14 +366,6 @@ 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); @@ -467,14 +459,6 @@ 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); diff --git a/packages/contracts/test/asset_proxy/decoder.ts b/packages/contracts/test/asset_proxy/decoder.ts index b5a457933..902255caf 100644 --- a/packages/contracts/test/asset_proxy/decoder.ts +++ b/packages/contracts/test/asset_proxy/decoder.ts @@ -44,7 +44,6 @@ describe('TestAssetDataDecoders', () => { it('should correctly decode ERC721 asset data', async () => { const tokenId = generatePseudoRandomSalt(); const encodedAssetData = assetProxyUtils.encodeERC721AssetData(testAddress, tokenId); - console.log(encodedAssetData); const expectedDecodedAssetData = assetProxyUtils.decodeERC721AssetData(encodedAssetData); let decodedTokenAddress: string; let decodedTokenId: BigNumber; @@ -54,7 +53,7 @@ describe('TestAssetDataDecoders', () => { decodedTokenId, decodedData, ] = await testAssetProxyDecoder.publicDecodeERC721Data.callAsync(encodedAssetData); - expect(decodedTokenAddress).to.be.equal( expectedDecodedAssetData.tokenAddress); + expect(decodedTokenAddress).to.be.equal(expectedDecodedAssetData.tokenAddress); expect(decodedTokenId).to.be.bignumber.equal(expectedDecodedAssetData.tokenId); expect(decodedData).to.be.equal(expectedDecodedAssetData.receiverData); }); diff --git a/packages/contracts/test/exchange/match_orders.ts b/packages/contracts/test/exchange/match_orders.ts index b8dca04fd..18a46187f 100644 --- a/packages/contracts/test/exchange/match_orders.ts +++ b/packages/contracts/test/exchange/match_orders.ts @@ -96,7 +96,7 @@ describe('matchOrders', () => { artifacts.Exchange, provider, txDefaults, - zrxToken.address, + assetProxyUtils.encodeERC20AssetData(zrxToken.address), ); exchangeWrapper = new ExchangeWrapper(exchange, provider); await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner); diff --git a/packages/contracts/test/exchange/transactions.ts b/packages/contracts/test/exchange/transactions.ts index 9a625880c..43b9f46b2 100644 --- a/packages/contracts/test/exchange/transactions.ts +++ b/packages/contracts/test/exchange/transactions.ts @@ -73,7 +73,7 @@ describe('Exchange transactions', () => { artifacts.Exchange, provider, txDefaults, - zrxToken.address, + assetProxyUtils.encodeERC20AssetData(zrxToken.address), ); exchangeWrapper = new ExchangeWrapper(exchange, provider); await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner); diff --git a/packages/contracts/test/exchange/wrapper.ts b/packages/contracts/test/exchange/wrapper.ts index 703f644b8..a39a81057 100644 --- a/packages/contracts/test/exchange/wrapper.ts +++ b/packages/contracts/test/exchange/wrapper.ts @@ -80,7 +80,7 @@ describe('Exchange wrappers', () => { artifacts.Exchange, provider, txDefaults, - zrxToken.address, + assetProxyUtils.encodeERC20AssetData(zrxToken.address), ); exchangeWrapper = new ExchangeWrapper(exchange, provider); await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, owner); -- cgit v1.2.3 From a2ff63daa5dce79cd5109a01f645a7075335565d Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Fri, 22 Jun 2018 17:41:38 -0700 Subject: Run prettier and fix linting errors --- packages/contracts/test/asset_proxy/proxies.ts | 24 ++------ packages/order-utils/src/asset_proxy_utils.ts | 79 ++++++++++++++------------ packages/order-utils/src/constants.ts | 6 ++ 3 files changed, 54 insertions(+), 55 deletions(-) diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts index 4823cc7b0..43aea9a79 100644 --- a/packages/contracts/test/asset_proxy/proxies.ts +++ b/packages/contracts/test/asset_proxy/proxies.ts @@ -176,15 +176,9 @@ describe('Asset Transfer Proxies', () => { // Perform a transfer from makerAddress to takerAddress const amount = new BigNumber(10); return expectRevertOrAlwaysFailingTransactionAsync( - erc20Proxy.transferFrom.sendTransactionAsync( - encodedAssetData, - makerAddress, - takerAddress, - amount, - { - from: notAuthorized, - }, - ), + erc20Proxy.transferFrom.sendTransactionAsync(encodedAssetData, makerAddress, takerAddress, amount, { + from: notAuthorized, + }), ); }); }); @@ -411,15 +405,9 @@ describe('Asset Transfer Proxies', () => { // Perform a transfer; expect this to fail. const amount = new BigNumber(1); return expectRevertOrAlwaysFailingTransactionAsync( - erc20Proxy.transferFrom.sendTransactionAsync( - encodedAssetData, - makerAddress, - takerAddress, - amount, - { - from: notAuthorized, - }, - ), + erc20Proxy.transferFrom.sendTransactionAsync(encodedAssetData, makerAddress, takerAddress, amount, { + from: notAuthorized, + }), ); }); diff --git a/packages/order-utils/src/asset_proxy_utils.ts b/packages/order-utils/src/asset_proxy_utils.ts index 43e23a7fd..8140ad89d 100644 --- a/packages/order-utils/src/asset_proxy_utils.ts +++ b/packages/order-utils/src/asset_proxy_utils.ts @@ -3,18 +3,14 @@ import { BigNumber } from '@0xproject/utils'; import BN = require('bn.js'); import ethUtil = require('ethereumjs-util'); -const ERC20_ASSET_DATA_BYTE_LENGTH = 36; -const ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH = 53; -const ASSET_DATA_ADDRESS_OFFSET = 0; -const ERC721_ASSET_DATA_TOKEN_ID_OFFSET = 20; -const ERC721_ASSET_DATA_RECEIVER_DATA_LENGTH_OFFSET = 52; -const ERC721_ASSET_DATA_RECEIVER_DATA_OFFSET = 84; +import { constants } from './constants'; // TODO: Push upstream to DefinitelyTyped interface EthAbi { simpleEncode(signature: string, ...args: any[]): Buffer; rawDecode(signature: string[], data: Buffer): any[]; } +// tslint:disable:no-var-requires const ethAbi = require('ethereumjs-abi') as EthAbi; export const assetProxyUtils = { @@ -22,25 +18,25 @@ export const assetProxyUtils = { return ethUtil.toBuffer(assetProxyId); }, decodeAssetProxyId(encodedAssetProxyId: Buffer): AssetProxyId { - const string = ethUtil.bufferToHex(encodedAssetProxyId); - if (string === AssetProxyId.ERC20) { + const hexString = ethUtil.bufferToHex(encodedAssetProxyId); + if (hexString === AssetProxyId.ERC20) { return AssetProxyId.ERC20; } - if (string === AssetProxyId.ERC721) { + if (hexString === AssetProxyId.ERC721) { return AssetProxyId.ERC721; } - throw new Error(`Invalid ProxyId: ${string}`); + throw new Error(`Invalid ProxyId: ${hexString}`); }, encodeAddress(address: string): Buffer { if (!ethUtil.isValidAddress(address)) { throw new Error(`Invalid Address: ${address}`); } const encodedAddress = ethUtil.toBuffer(address); - const padded = ethUtil.setLengthLeft(encodedAddress, 32); + const padded = ethUtil.setLengthLeft(encodedAddress, constants.WORD_LENGTH); return padded; }, decodeAddress(encodedAddress: Buffer): string { - const unpadded = ethUtil.setLengthLeft(encodedAddress, 20); + const unpadded = ethUtil.setLengthLeft(encodedAddress, constants.ADDRESS_LENGTH); const address = ethUtil.bufferToHex(unpadded); if (!ethUtil.isValidAddress(address)) { throw new Error(`Invalid Address: ${address}`); @@ -52,34 +48,35 @@ export const assetProxyUtils = { const formattedValue = new BN(value.toString(base)); const encodedValue = ethUtil.toBuffer(formattedValue); // tslint:disable-next-line:custom-no-magic-numbers - const paddedValue = ethUtil.setLengthLeft(encodedValue, 32); + const paddedValue = ethUtil.setLengthLeft(encodedValue, constants.WORD_LENGTH); return paddedValue; }, decodeUint256(encodedValue: Buffer): BigNumber { const formattedValue = ethUtil.bufferToHex(encodedValue); - const value = new BigNumber(formattedValue, 16); + const value = new BigNumber(formattedValue, constants.BASE_16); return value; }, encodeERC20AssetData(tokenAddress: string): string { - return ethUtil.bufferToHex(ethAbi.simpleEncode( - 'ERC20Token(address)', - tokenAddress, - )); + return ethUtil.bufferToHex(ethAbi.simpleEncode('ERC20Token(address)', tokenAddress)); }, decodeERC20AssetData(assetData: string): ERC20AssetData { const data = ethUtil.toBuffer(assetData); - if (data.byteLength < ERC20_ASSET_DATA_BYTE_LENGTH) { + if (data.byteLength < constants.ERC20_ASSET_DATA_BYTE_LENGTH) { throw new Error( - `Could not decode ERC20 Proxy Data. Expected length of encoded data to be at least ${ERC20_ASSET_DATA_BYTE_LENGTH}. Got ${data.byteLength}`, + `Could not decode ERC20 Proxy Data. Expected length of encoded data to be at least ${ + constants.ERC20_ASSET_DATA_BYTE_LENGTH + }. Got ${data.byteLength}`, ); } - const assetProxyId = ethUtil.bufferToHex(data.slice(0, 4)); + const assetProxyId = ethUtil.bufferToHex(data.slice(0, constants.SELECTOR_LENGTH)); if (assetProxyId !== AssetProxyId.ERC20) { throw new Error( - `Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be ERC20 (${AssetProxyId.ERC20}), but got ${assetProxyId}`, + `Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be ERC20 (${ + AssetProxyId.ERC20 + }), but got ${assetProxyId}`, ); } - const [tokenAddress] = ethAbi.rawDecode(['address'], data.slice(4)); + const [tokenAddress] = ethAbi.rawDecode(['address'], data.slice(constants.SELECTOR_LENGTH)); return { assetProxyId, tokenAddress: ethUtil.addHexPrefix(tokenAddress), @@ -87,29 +84,35 @@ export const assetProxyUtils = { }, encodeERC721AssetData(tokenAddress: string, tokenId: BigNumber, receiverData?: string): string { // TODO: Pass `tokendId` as a BigNumber. - return ethUtil.bufferToHex(ethAbi.simpleEncode( - 'ERC721Token(address,uint256,bytes)', - tokenAddress, - '0x' + tokenId.toString(16), - ethUtil.toBuffer(receiverData || '0x'), - )); + return ethUtil.bufferToHex( + ethAbi.simpleEncode( + 'ERC721Token(address,uint256,bytes)', + tokenAddress, + `0x${tokenId.toString(constants.BASE_16)}`, + ethUtil.toBuffer(receiverData || '0x'), + ), + ); }, decodeERC721AssetData(assetData: string): ERC721AssetData { const data = ethUtil.toBuffer(assetData); - if (data.byteLength < ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH) { + if (data.byteLength < constants.ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH) { throw new Error( - `Could not decode ERC721 Asset Data. Expected length of encoded data to be at least ${ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH}. Got ${data.byteLength}`, + `Could not decode ERC721 Asset Data. Expected length of encoded data to be at least ${ + constants.ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH + }. Got ${data.byteLength}`, ); } - const assetProxyId = ethUtil.bufferToHex(data.slice(0, 4)); + const assetProxyId = ethUtil.bufferToHex(data.slice(0, constants.SELECTOR_LENGTH)); if (assetProxyId !== AssetProxyId.ERC721) { throw new Error( - `Could not decode ERC721 Asset Data. Expected Asset Proxy Id to be ERC721 (${AssetProxyId.ERC721}), but got ${assetProxyId}`, + `Could not decode ERC721 Asset Data. Expected Asset Proxy Id to be ERC721 (${ + AssetProxyId.ERC721 + }), but got ${assetProxyId}`, ); } const [tokenAddress, tokenId, receiverData] = ethAbi.rawDecode( ['address', 'uint256', 'bytes'], - data.slice(4), + data.slice(constants.SELECTOR_LENGTH), ); return { assetProxyId, @@ -120,12 +123,14 @@ export const assetProxyUtils = { }, decodeAssetDataId(assetData: string): AssetProxyId { const encodedAssetData = ethUtil.toBuffer(assetData); - if (encodedAssetData.byteLength < 4) { + if (encodedAssetData.byteLength < constants.SELECTOR_LENGTH) { throw new Error( - `Could not decode Proxy Data. Expected length of encoded data to be at least 4. Got ${encodedAssetData.byteLength}`, + `Could not decode Proxy Data. Expected length of encoded data to be at least 4. Got ${ + encodedAssetData.byteLength + }`, ); } - const encodedAssetProxyId = encodedAssetData.slice(0, 4); + const encodedAssetProxyId = encodedAssetData.slice(0, constants.SELECTOR_LENGTH); const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId); return assetProxyId; }, diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts index ed5bd8101..383a657b8 100644 --- a/packages/order-utils/src/constants.ts +++ b/packages/order-utils/src/constants.ts @@ -5,4 +5,10 @@ export const constants = { // tslint:disable-next-line:custom-no-magic-numbers UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1), TESTRPC_NETWORK_ID: 50, + ADDRESS_LENGTH: 20, + WORD_LENGTH: 32, + ERC20_ASSET_DATA_BYTE_LENGTH: 36, + ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH: 53, + SELECTOR_LENGTH: 4, + BASE_16: 16, }; -- cgit v1.2.3 From 4df66a4802fc75cd9d3e0c01a92e1cc1ae3dfd58 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Sat, 23 Jun 2018 18:03:49 +0200 Subject: Optimize like in PR #767 --- .../protocol/AssetProxy/MixinERC20Transfer.sol | 64 ++++++++++-------- .../protocol/AssetProxy/MixinERC721Transfer.sol | 77 ++++++++++++++++++++-- .../Exchange/MixinAssetProxyDispatcher.sol | 50 +++++++++++++- 3 files changed, 157 insertions(+), 34 deletions(-) diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol index deac59f4d..b5f1f9480 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol @@ -43,35 +43,47 @@ contract MixinERC20Transfer is { // Decode asset data. address token = assetData.readAddress(16); - - // 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( - success, - TRANSFER_FAILED - ); - // 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. + bytes4 transferFromSelector = IERC20Token(token).transferFrom.selector; + bool success; 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) + /////// Setup State /////// + // `cdStart` is the start of the calldata for `token.transferFrom` (equal to free memory ptr). + let cdStart := mload(64) + // `cdEnd` is the end of the calldata for `token.transferFrom`. + let cdEnd := add(cdStart, 100) + + /////// Setup Header Area /////// + // This area holds the 4-byte `transferFromSelector`. + mstore(cdStart, transferFromSelector) + + /////// Setup Params Area /////// + // Each parameter is padded to 32-bytes. The entire Params Area is 96 bytes. + // A 20-byte mask is applied to addresses to zero-out the unused bytes. + mstore(add(cdStart, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(cdStart, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(cdStart, 68), amount) + + /////// Call `token.transferFrom` using the constructed calldata /////// + success := call( + gas, + token, + 0, + cdStart, + sub(cdEnd, cdStart), + cdStart, + 32 + ) + if success { + 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( diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol index ebc879644..af986adfa 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol @@ -27,7 +27,7 @@ contract MixinERC721Transfer is LibTransferErrors { using LibBytes for bytes; - + bytes4 constant SAFE_TRANSFER_FROM_SELECTOR = bytes4(keccak256("safeTransferFrom(address,address,uint256,bytes)")); /// @dev Internal version of `transferFrom`. /// @param assetData Encoded byte array. /// @param from Address to transfer asset from. @@ -54,11 +54,76 @@ contract MixinERC721Transfer is bytes memory receiverData ) = decodeERC721AssetData(assetData); - ERC721Token(token).safeTransferFrom( - from, - to, - tokenId, - receiverData + // We construct calldata for the `token.safeTransferFrom` ABI. + // The layout of this calldata is in the table below. + // + // | Area | Offset | Length | Contents | + // | -------- |--------|---------|-------------------------------------------- | + // | Header | 0 | 4 | function selector | + // | Params | | 4 * 32 | function parameters: | + // | | 4 | | 1. from | + // | | 36 | | 2. to | + // | | 68 | | 3. tokenId | + // | | 100 | | 4. offset to receiverData (*) | + // | Data | | | receiverData: | + // | | 132 | 32 | receiverData Length | + // | | 164 | ** | receiverData Contents | + + bytes4 safeTransferFromSelector = SAFE_TRANSFER_FROM_SELECTOR; + bool success; + assembly { + /////// Setup State /////// + // `cdStart` is the start of the calldata for `token.safeTransferFrom` (equal to free memory ptr). + let cdStart := mload(64) + // `dataAreaLength` is the total number of words needed to store `receiverData` + // As-per the ABI spec, this value is padded up to the nearest multiple of 32, + // and includes 32-bytes for length. + // It's calculated as folows: + // - Unpadded length in bytes = `mload(receiverData) + 32` + // - Add 31 to this value then divide by 32 to get the length in words. + // - Multiply this value by 32 to get the padded length in bytes. + let dataAreaLength := mul(div(add(mload(receiverData), 63), 32), 32) + // `cdEnd` is the end of the calldata for `token.safeTransferFrom`. + let cdEnd := add(cdStart, add(132, dataAreaLength)) + + /////// Setup Header Area /////// + // This area holds the 4-byte `transferFromSelector`. + mstore(cdStart, safeTransferFromSelector) + + /////// Setup Params Area /////// + // Each parameter is padded to 32-bytes. The entire Params Area is 128 bytes. + // Notes: + // 1. A 20-byte mask is applied to addresses to zero-out the unused bytes. + // 2. The offset to `receiverData` is the length of the Params Area (128 bytes). + mstore(add(cdStart, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(cdStart, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(cdStart, 68), tokenId) + mstore(add(cdStart, 100), 128) + + /////// Setup Data Area /////// + // This area holds `receiverData`. + let dataArea := add(cdStart, 132) + for {} lt(dataArea, cdEnd) {} { + mstore(dataArea, mload(receiverData)) + dataArea := add(dataArea, 32) + receiverData := add(receiverData, 32) + } + + /////// Call `token.safeTransferFrom` using the constructed calldata /////// + success := call( + gas, + token, + 0, + cdStart, + sub(cdEnd, cdStart), + cdStart, + 0 + ) + } + + require( + success, + TRANSFER_FAILED ); } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol index da0beefde..96763970c 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol @@ -100,21 +100,67 @@ contract MixinAssetProxyDispatcher is { // Do nothing if no amount should be transferred. if (amount > 0) { + require(assetData.length >= 4, "ASSET_DATA_LENGTH"); + // Lookup assetProxy - bytes4 assetProxyId = assetData.readBytes4(0); + bytes4 assetProxyId; + assembly { + assetProxyId := and(mload(add(assetData, 32)), +0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 + ) + } IAssetProxy assetProxy = assetProxies[assetProxyId]; // Ensure that assetProxy exists require( assetProxy != address(0), ASSET_PROXY_DOES_NOT_EXIST ); - // transferFrom will either succeed or throw. + + /* assetProxy.transferFrom( assetData, from, to, amount ); + return; + */ + + bytes4 transferFromSelector = IAssetProxy(assetProxy).transferFrom.selector; + bool success; + assembly { + + let cdStart := mload(0x40) + let dataAreaLength := mul(div(add(mload(assetData), 63), 32), 32) + let cdEnd := add(cdStart, add(132, dataAreaLength)) + + mstore(cdStart, transferFromSelector) + mstore(add(cdStart, 4), 128) + mstore(add(cdStart, 36), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(cdStart, 68), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) + mstore(add(cdStart, 100), amount) + + let dataArea := add(cdStart, 132) + for {} lt(dataArea, cdEnd) {} { + mstore(dataArea, mload(assetData)) + dataArea := add(dataArea, 32) + assetData := add(assetData, 32) + } + + success := call( + gas, + assetProxy, + 0, + cdStart, + sub(cdEnd, cdStart), + cdStart, + 0 + ) + } + require( + success, + "TRANSFER_FAILED" + ); } } } -- cgit v1.2.3 From b8051c8fed0f42778692de135df922626da3fb4f Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Sat, 23 Jun 2018 18:03:49 +0200 Subject: Gas golf the transfer success logic --- .../protocol/AssetProxy/MixinERC20Transfer.sol | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol index b5f1f9480..54fe7d726 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol @@ -74,17 +74,16 @@ contract MixinERC20Transfer is cdStart, 32 ) - if success { - if returndatasize { - success := 0 - if eq(returndatasize, 32) { - // First 64 bytes of memory are reserved scratch space - returndatacopy(0, 0, 32) - success := mload(0) - } - } - - } + + // The transfer succeeded if the call succeeded and either + // returned nothing, or returned a non-zero 32 byte value. + success := and(success, or( + iszero(returndatasize), + and( + eq(returndatasize, 32), + gt(mload(cdStart), 0) + ) + )) } require( success, -- cgit v1.2.3 From 562fec01d89cae43b813e0f22ffe924953d5fadc Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Sat, 23 Jun 2018 18:03:49 +0200 Subject: Add Greg's documentation to MixinErc20Transfer --- .../protocol/AssetProxy/MixinERC20Transfer.sol | 48 ++++++++++++++++------ 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol index 54fe7d726..32d5f394e 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol @@ -44,38 +44,60 @@ contract MixinERC20Transfer is // Decode asset data. address token = assetData.readAddress(16); + // Transfer tokens. + // We do a raw call so we can check the success separate + // from the return data. + // We construct calldata for the `token.transferFrom` ABI. + // The layout of this calldata is in the table below. + // + // | Area | Offset | Length | Contents | + // |----------|--------|---------|-------------------------------------| + // | Header | 0 | 4 | function selector | + // | Params | | 3 * 32 | function parameters: | + // | | 4 | | 1. from | + // | | 36 | | 2. to | + // | | 68 | | 3. amount | + bytes4 transferFromSelector = IERC20Token(token).transferFrom.selector; bool success; assembly { /////// Setup State /////// // `cdStart` is the start of the calldata for `token.transferFrom` (equal to free memory ptr). let cdStart := mload(64) - // `cdEnd` is the end of the calldata for `token.transferFrom`. - let cdEnd := add(cdStart, 100) /////// Setup Header Area /////// // This area holds the 4-byte `transferFromSelector`. + // Any trailing data in transferFromSelector will be + // overwritten in the next `mstore` call. mstore(cdStart, transferFromSelector) /////// Setup Params Area /////// - // Each parameter is padded to 32-bytes. The entire Params Area is 96 bytes. - // A 20-byte mask is applied to addresses to zero-out the unused bytes. + // Each parameter is padded to 32-bytes. + // The entire Params Area is 96 bytes. + // A 20-byte mask is applied to addresses to + // zero-out the unused bytes. mstore(add(cdStart, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) mstore(add(cdStart, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) mstore(add(cdStart, 68), amount) - /////// Call `token.transferFrom` using the constructed calldata /////// + /////// Call `token.transferFrom` using the calldata /////// success := call( - gas, - token, - 0, - cdStart, - sub(cdEnd, cdStart), - cdStart, - 32 + gas, // forward all gas + token, // call address of token contract + 0, // don't send any ETH + cdStart, // pointer to start of input + 100, // length of input + cdStart, // write output over input + 32 // output size should be 32 bytes ) - // The transfer succeeded if the call succeeded and either + /////// Check return data. /////// + // If there is no return data, we assume the token incorrectly + // does not return a bool. In this case we expect it to revert + // on failure, which was handled above. + // If the token does return data, we require that it is a single + // nonzero 32 bytes value. + // So the transfer succeeded if the call succeeded and either // returned nothing, or returned a non-zero 32 byte value. success := and(success, or( iszero(returndatasize), -- cgit v1.2.3 From 9175b43542a8797b0eb3c24e8188ebac67242733 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Sat, 23 Jun 2018 18:03:49 +0200 Subject: Add Greg's documentation to MixinErc721Transfer --- .../protocol/AssetProxy/MixinERC721Transfer.sol | 77 ++++++++++++---------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol index af986adfa..c170917ea 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC721Transfer.sol @@ -27,7 +27,9 @@ contract MixinERC721Transfer is LibTransferErrors { using LibBytes for bytes; - bytes4 constant SAFE_TRANSFER_FROM_SELECTOR = bytes4(keccak256("safeTransferFrom(address,address,uint256,bytes)")); + + bytes4 constant SAFE_TRANSFER_FROM_SELECTOR = bytes4(keccak256("safeTransferFrom(address,address,uint256,bytes)")); + /// @dev Internal version of `transferFrom`. /// @param assetData Encoded byte array. /// @param from Address to transfer asset from. @@ -57,44 +59,54 @@ contract MixinERC721Transfer is // We construct calldata for the `token.safeTransferFrom` ABI. // The layout of this calldata is in the table below. // - // | Area | Offset | Length | Contents | - // | -------- |--------|---------|-------------------------------------------- | - // | Header | 0 | 4 | function selector | - // | Params | | 4 * 32 | function parameters: | - // | | 4 | | 1. from | - // | | 36 | | 2. to | - // | | 68 | | 3. tokenId | - // | | 100 | | 4. offset to receiverData (*) | - // | Data | | | receiverData: | - // | | 132 | 32 | receiverData Length | - // | | 164 | ** | receiverData Contents | + // | Area | Offset | Length | Contents | + // |----------|--------|---------|-------------------------------------| + // | Header | 0 | 4 | function selector | + // | Params | | 4 * 32 | function parameters: | + // | | 4 | | 1. from | + // | | 36 | | 2. to | + // | | 68 | | 3. tokenId | + // | | 100 | | 4. offset to receiverData (*) | + // | Data | | | receiverData: | + // | | 132 | 32 | receiverData Length | + // | | 164 | ** | receiverData Contents | bytes4 safeTransferFromSelector = SAFE_TRANSFER_FROM_SELECTOR; bool success; assembly { /////// Setup State /////// - // `cdStart` is the start of the calldata for `token.safeTransferFrom` (equal to free memory ptr). + // `cdStart` is the start of the calldata for + // `token.safeTransferFrom` (equal to free memory ptr). let cdStart := mload(64) - // `dataAreaLength` is the total number of words needed to store `receiverData` - // As-per the ABI spec, this value is padded up to the nearest multiple of 32, - // and includes 32-bytes for length. - // It's calculated as folows: + // `dataAreaLength` is the total number of words + // needed to store `receiverData` + // As-per the ABI spec, this value is padded up to + // the nearest multiple of 32, + // and includes 32-bytes for length. + // It's calculated as folows: // - Unpadded length in bytes = `mload(receiverData) + 32` - // - Add 31 to this value then divide by 32 to get the length in words. - // - Multiply this value by 32 to get the padded length in bytes. - let dataAreaLength := mul(div(add(mload(receiverData), 63), 32), 32) + // - Add 31 to convert rounding down to rounding up. + // Combined with the previous and this is `63`. + // - Round down to nearest multiple of 32 by clearing + // bits 0x1F. This is done with `and` and a mask. + let dataAreaLength := and(add(mload(receiverData), 63), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0) // `cdEnd` is the end of the calldata for `token.safeTransferFrom`. let cdEnd := add(cdStart, add(132, dataAreaLength)) /////// Setup Header Area /////// // This area holds the 4-byte `transferFromSelector`. + // Any trailing data in transferFromSelector will be + // overwritten in the next `mstore` call. mstore(cdStart, safeTransferFromSelector) /////// Setup Params Area /////// - // Each parameter is padded to 32-bytes. The entire Params Area is 128 bytes. + // Each parameter is padded to 32-bytes. + // The entire Params Area is 128 bytes. // Notes: - // 1. A 20-byte mask is applied to addresses to zero-out the unused bytes. - // 2. The offset to `receiverData` is the length of the Params Area (128 bytes). + // 1. A 20-byte mask is applied to addresses + // to zero-out the unused bytes. + // 2. The offset to `receiverData` is the length + // of the Params Area (128 bytes). mstore(add(cdStart, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) mstore(add(cdStart, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) mstore(add(cdStart, 68), tokenId) @@ -108,19 +120,18 @@ contract MixinERC721Transfer is dataArea := add(dataArea, 32) receiverData := add(receiverData, 32) } - - /////// Call `token.safeTransferFrom` using the constructed calldata /////// + + /////// Call `token.safeTransferFrom` using the calldata /////// success := call( - gas, - token, - 0, - cdStart, - sub(cdEnd, cdStart), - cdStart, - 0 + gas, // forward all gas + token, // call address of token contract + 0, // don't send any ETH + cdStart, // pointer to start of input + sub(cdEnd, cdStart), // length of input + cdStart, // write output over input + 0 // output size is 0 bytes ) } - require( success, TRANSFER_FAILED -- cgit v1.2.3 From 5127cbb22c8be536996137416f826afd231b0c14 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Sat, 23 Jun 2018 18:03:49 +0200 Subject: Return inplace bytes in LibBytes.readBytesWithLength --- .../src/contracts/current/utils/LibBytes/LibBytes.sol | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol index e4cbf318b..289d086d6 100644 --- a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol +++ b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol @@ -459,6 +459,8 @@ library LibBytes { } /// @dev Reads nested bytes from a specific position. + /// @dev NOTE: the returned value overlaps with the input value. + /// Both should be treated as immutable. /// @param b Byte array containing nested bytes. /// @param index Index of nested bytes. /// @return result Nested bytes. @@ -480,15 +482,11 @@ library LibBytes { b.length >= index + nestedBytesLength, GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED ); - - // Allocate memory and copy value to result - result = new bytes(nestedBytesLength); - memCopy( - result.contentAddress(), - b.contentAddress() + index, - nestedBytesLength - ); - + + // Return a pointer to the byte array as it exists inside `b` + assembly { + result := add(b, index) + } return result; } -- cgit v1.2.3 From 1076959006be7ad42f933e476aa75cbca17520c8 Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Mon, 25 Jun 2018 18:08:49 +0200 Subject: Compute bytes4 proxyid constants --- .../contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol | 2 +- .../contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol index 002e8b662..b74d2c231 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol @@ -30,7 +30,7 @@ contract ERC20Proxy is MixinERC20Transfer { // Id of this proxy. - bytes4 constant PROXY_ID = 0xf47261b0; + bytes4 constant PROXY_ID = bytes4(keccak256("ERC20Token(address)")); /// @dev Gets the proxy id associated with the proxy address. /// @return Proxy id. diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol index ce2da76c2..f6293f97d 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol @@ -30,7 +30,7 @@ contract ERC721Proxy is MixinERC721Transfer { // Id of this proxy. - bytes4 constant PROXY_ID = 0x08e937fa; + bytes4 constant PROXY_ID = bytes4(keccak256("ERC721Token(address,uint256,bytes)")); /// @dev Gets the proxy id associated with the proxy address. /// @return Proxy id. -- cgit v1.2.3 From db3ce08cab7e8c273121996867f491abed1179af Mon Sep 17 00:00:00 2001 From: Remco Bloemen Date: Mon, 25 Jun 2018 18:08:59 +0200 Subject: Document IAssetData --- .../contracts/current/protocol/AssetProxy/interfaces/IAssetData.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetData.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetData.sol index 6f49a8ba9..8c78ee8c4 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetData.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/interfaces/IAssetData.sol @@ -18,6 +18,9 @@ pragma solidity ^0.4.23; +// @dev Interface of the asset proxy's assetData. +// The asset proxies take an ABI encoded `bytes assetData` as argument. +// This argument is ABI encoded as one of the methods of this interface. interface IAssetData { function ERC20Token( -- cgit v1.2.3 From 0163984ea4f9d7623613d2d7cd8e2e946acecdf5 Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Mon, 25 Jun 2018 14:13:31 -0700 Subject: Add comments to dispatchTransferFrom --- .../Exchange/MixinAssetProxyDispatcher.sol | 86 ++++++++++++++-------- 1 file changed, 56 insertions(+), 30 deletions(-) diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol index 96763970c..3203322aa 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinAssetProxyDispatcher.sol @@ -20,13 +20,11 @@ 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, - LibExchangeErrors, MAssetProxyDispatcher { using LibBytes for bytes; @@ -51,7 +49,7 @@ contract MixinAssetProxyDispatcher is address currentAssetProxy = assetProxies[assetProxyId]; require( oldAssetProxy == currentAssetProxy, - ASSET_PROXY_MISMATCH + "ASSET_PROXY_MISMATCH" ); IAssetProxy assetProxy = IAssetProxy(newAssetProxy); @@ -61,7 +59,7 @@ contract MixinAssetProxyDispatcher is bytes4 newAssetProxyId = assetProxy.getProxyId(); require( newAssetProxyId == assetProxyId, - ASSET_PROXY_ID_MISMATCH + "ASSET_PROXY_ID_MISMATCH" ); } @@ -100,61 +98,89 @@ contract MixinAssetProxyDispatcher is { // Do nothing if no amount should be transferred. if (amount > 0) { - require(assetData.length >= 4, "ASSET_DATA_LENGTH"); + // Ensure assetData length is valid + require( + assetData.length > 3, + "LENGTH_GREATER_THAN_3_REQUIRED" + ); // Lookup assetProxy bytes4 assetProxyId; assembly { - assetProxyId := and(mload(add(assetData, 32)), -0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 + assetProxyId := and(mload( + add(assetData, 32)), + 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000 ) } IAssetProxy assetProxy = assetProxies[assetProxyId]; + // Ensure that assetProxy exists require( assetProxy != address(0), - ASSET_PROXY_DOES_NOT_EXIST + "ASSET_PROXY_DOES_NOT_EXIST" ); - /* - assetProxy.transferFrom( - assetData, - from, - to, - amount - ); - return; - */ - + // We construct calldata for the `assetProxy.transferFrom` ABI. + // The layout of this calldata is in the table below. + // + // | Area | Offset | Length | Contents | + // | -------- |--------|---------|-------------------------------------------- | + // | Header | 0 | 4 | function selector | + // | Params | | 4 * 32 | function parameters: | + // | | 4 | | 1. offset to assetData (*) | + // | | 36 | | 2. from | + // | | 68 | | 3. to | + // | | 100 | | 4. amount | + // | Data | | | assetData: | + // | | 132 | 32 | assetData Length | + // | | 164 | ** | assetData Contents | + bytes4 transferFromSelector = IAssetProxy(assetProxy).transferFrom.selector; - bool success; + bool success; assembly { - - let cdStart := mload(0x40) - let dataAreaLength := mul(div(add(mload(assetData), 63), 32), 32) + /////// Setup State /////// + // `cdStart` is the start of the calldata for `assetProxy.transferFrom` (equal to free memory ptr). + let cdStart := mload(64) + // `dataAreaLength` is the total number of words needed to store `assetData` + // As-per the ABI spec, this value is padded up to the nearest multiple of 32, + // and includes 32-bytes for length. + let dataAreaLength := and(add(mload(assetData), 63), 0xFFFFFFFFFFFE0) + // `cdEnd` is the end of the calldata for `assetProxy.transferFrom`. let cdEnd := add(cdStart, add(132, dataAreaLength)) + + /////// Setup Header Area /////// + // This area holds the 4-byte `transferFromSelector`. mstore(cdStart, transferFromSelector) + + /////// Setup Params Area /////// + // Each parameter is padded to 32-bytes. The entire Params Area is 128 bytes. + // Notes: + // 1. The offset to `assetData` is the length of the Params Area (128 bytes). + // 2. A 20-byte mask is applied to addresses to zero-out the unused bytes. mstore(add(cdStart, 4), 128) mstore(add(cdStart, 36), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) mstore(add(cdStart, 68), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) mstore(add(cdStart, 100), amount) + /////// Setup Data Area /////// + // This area holds `assetData`. let dataArea := add(cdStart, 132) for {} lt(dataArea, cdEnd) {} { mstore(dataArea, mload(assetData)) dataArea := add(dataArea, 32) assetData := add(assetData, 32) } - + + /////// Call `assetProxy.transferFrom` using the constructed calldata /////// success := call( - gas, - assetProxy, - 0, - cdStart, - sub(cdEnd, cdStart), - cdStart, - 0 + gas, // forward all gas + assetProxy, // call address of asset proxy + 0, // don't send any ETH + cdStart, // pointer to start of input + sub(cdEnd, cdStart), // length of input + cdStart, // write output over input + 0 // output size is 0 bytes ) } require( -- cgit v1.2.3 From a89908540f8bca266780c59f3cb1521e7fb16b73 Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Mon, 25 Jun 2018 16:01:34 -0700 Subject: Don't use variables for revert reasons --- .../protocol/AssetProxy/MixinAuthorizable.sol | 14 +++--- .../protocol/AssetProxy/MixinERC20Transfer.sol | 8 ++-- .../AssetProxy/libs/LibAssetProxyErrors.sol | 3 ++ .../protocol/AssetProxy/libs/LibTransferErrors.sol | 3 ++ .../protocol/Exchange/MixinExchangeCore.sol | 22 +++++----- .../current/protocol/Exchange/MixinMatchOrders.sol | 4 +- .../protocol/Exchange/MixinSignatureValidator.sol | 36 +++++++-------- .../protocol/Exchange/MixinTransactions.sol | 10 ++--- .../protocol/Exchange/MixinWrapperFunctions.sol | 4 +- .../protocol/Exchange/libs/LibExchangeErrors.sol | 5 +++ .../current/protocol/Exchange/libs/LibMath.sol | 1 - .../contracts/current/utils/LibBytes/LibBytes.sol | 51 ++++++++++++---------- .../contracts/current/utils/Ownable/Ownable.sol | 5 +-- packages/contracts/test/asset_proxy/proxies.ts | 6 ++- 14 files changed, 86 insertions(+), 86 deletions(-) diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol index 37c12f861..3b9584a44 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinAuthorizable.sol @@ -19,12 +19,10 @@ pragma solidity ^0.4.24; pragma experimental ABIEncoderV2; -import "./libs/LibAssetProxyErrors.sol"; import "../../utils/Ownable/Ownable.sol"; import "./mixins/MAuthorizable.sol"; contract MixinAuthorizable is - LibAssetProxyErrors, Ownable, MAuthorizable { @@ -33,7 +31,7 @@ contract MixinAuthorizable is modifier onlyAuthorized { require( authorized[msg.sender], - SENDER_NOT_AUTHORIZED + "SENDER_NOT_AUTHORIZED" ); _; } @@ -49,7 +47,7 @@ contract MixinAuthorizable is { require( !authorized[target], - TARGET_ALREADY_AUTHORIZED + "TARGET_ALREADY_AUTHORIZED" ); authorized[target] = true; @@ -65,7 +63,7 @@ contract MixinAuthorizable is { require( authorized[target], - TARGET_NOT_AUTHORIZED + "TARGET_NOT_AUTHORIZED" ); delete authorized[target]; @@ -91,15 +89,15 @@ contract MixinAuthorizable is { require( authorized[target], - TARGET_NOT_AUTHORIZED + "TARGET_NOT_AUTHORIZED" ); require( index < authorities.length, - INDEX_OUT_OF_BOUNDS + "INDEX_OUT_OF_BOUNDS" ); require( authorities[index] == target, - AUTHORIZED_ADDRESS_MISMATCH + "AUTHORIZED_ADDRESS_MISMATCH" ); delete authorized[target]; diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol index 32d5f394e..a09db43bd 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/MixinERC20Transfer.sol @@ -21,11 +21,9 @@ pragma experimental ABIEncoderV2; import "../../utils/LibBytes/LibBytes.sol"; import "../../tokens/ERC20Token/IERC20Token.sol"; -import "./libs/LibTransferErrors.sol"; -contract MixinERC20Transfer is - LibTransferErrors -{ +contract MixinERC20Transfer { + using LibBytes for bytes; /// @dev Internal version of `transferFrom`. @@ -109,7 +107,7 @@ contract MixinERC20Transfer is } require( success, - TRANSFER_FAILED + "TRANSFER_FAILED" ); } } 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 dca4f400f..b0b20c044 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibAssetProxyErrors.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibAssetProxyErrors.sol @@ -18,7 +18,10 @@ pragma solidity ^0.4.24; +/// @dev This contract documents the revert reasons used in the AssetProxy contracts. +/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons. contract LibAssetProxyErrors { + /// Authorizable errors /// string constant SENDER_NOT_AUTHORIZED = "SENDER_NOT_AUTHORIZED"; // Sender not authorized to call this method. string constant TARGET_NOT_AUTHORIZED = "TARGET_NOT_AUTHORIZED"; // Target address not authorized to call this method. diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibTransferErrors.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibTransferErrors.sol index ba784ab22..88187f196 100644 --- a/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibTransferErrors.sol +++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/libs/LibTransferErrors.sol @@ -18,7 +18,10 @@ pragma solidity ^0.4.24; +/// @dev This contract documents the revert reasons used in the `transferFrom` methods of different AssetProxy contracts. +/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons. contract LibTransferErrors { + /// Transfer errors /// string constant INVALID_AMOUNT = "INVALID_AMOUNT"; // Transfer amount must equal 1. string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Transfer failed. diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol index 38a73d885..c0ed023ac 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinExchangeCore.sol @@ -23,7 +23,6 @@ import "./libs/LibConstants.sol"; import "./libs/LibFillResults.sol"; import "./libs/LibOrder.sol"; import "./libs/LibMath.sol"; -import "./libs/LibExchangeErrors.sol"; import "./mixins/MExchangeCore.sol"; import "./mixins/MSignatureValidator.sol"; import "./mixins/MTransactions.sol"; @@ -34,7 +33,6 @@ contract MixinExchangeCore is LibMath, LibOrder, LibFillResults, - LibExchangeErrors, MAssetProxyDispatcher, MExchangeCore, MSignatureValidator, @@ -70,7 +68,7 @@ contract MixinExchangeCore is // Ensure orderEpoch is monotonically increasing require( newOrderEpoch > oldOrderEpoch, - INVALID_NEW_ORDER_EPOCH + "INVALID_NEW_ORDER_EPOCH" ); // Update orderEpoch @@ -282,20 +280,20 @@ contract MixinExchangeCore is // An order can only be filled if its status is FILLABLE. require( orderInfo.orderStatus == uint8(OrderStatus.FILLABLE), - ORDER_UNFILLABLE + "ORDER_UNFILLABLE" ); // Revert if fill amount is invalid require( takerAssetFillAmount != 0, - INVALID_TAKER_AMOUNT + "INVALID_TAKER_AMOUNT" ); // Validate sender is allowed to fill this order if (order.senderAddress != address(0)) { require( order.senderAddress == msg.sender, - INVALID_SENDER + "INVALID_SENDER" ); } @@ -303,7 +301,7 @@ contract MixinExchangeCore is if (order.takerAddress != address(0)) { require( order.takerAddress == takerAddress, - INVALID_TAKER + "INVALID_TAKER" ); } @@ -315,7 +313,7 @@ contract MixinExchangeCore is order.makerAddress, signature ), - INVALID_ORDER_SIGNATURE + "INVALID_ORDER_SIGNATURE" ); } @@ -326,7 +324,7 @@ contract MixinExchangeCore is order.takerAssetAmount, order.makerAssetAmount ), - ROUNDING_ERROR + "ROUNDING_ERROR" ); } @@ -344,14 +342,14 @@ contract MixinExchangeCore is // An order can only be cancelled if its status is FILLABLE. require( orderInfo.orderStatus == uint8(OrderStatus.FILLABLE), - ORDER_UNFILLABLE + "ORDER_UNFILLABLE" ); // Validate sender is allowed to cancel this order if (order.senderAddress != address(0)) { require( order.senderAddress == msg.sender, - INVALID_SENDER + "INVALID_SENDER" ); } @@ -359,7 +357,7 @@ contract MixinExchangeCore is address makerAddress = getCurrentContextAddress(); require( order.makerAddress == makerAddress, - INVALID_MAKER + "INVALID_MAKER" ); } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol index a3e8d8dde..1a43eec79 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinMatchOrders.sol @@ -18,7 +18,6 @@ import "./libs/LibConstants.sol"; import "./libs/LibMath.sol"; import "./libs/LibOrder.sol"; import "./libs/LibFillResults.sol"; -import "./libs/LibExchangeErrors.sol"; import "./mixins/MExchangeCore.sol"; import "./mixins/MMatchOrders.sol"; import "./mixins/MTransactions.sol"; @@ -27,7 +26,6 @@ import "./mixins/MAssetProxyDispatcher.sol"; contract MixinMatchOrders is LibConstants, LibMath, - LibExchangeErrors, MAssetProxyDispatcher, MExchangeCore, MMatchOrders, @@ -141,7 +139,7 @@ contract MixinMatchOrders is require( safeMul(leftOrder.makerAssetAmount, rightOrder.makerAssetAmount) >= safeMul(leftOrder.takerAssetAmount, rightOrder.takerAssetAmount), - NEGATIVE_SPREAD_REQUIRED + "NEGATIVE_SPREAD_REQUIRED" ); } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol index cbb55bfce..29172057a 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinSignatureValidator.sol @@ -19,23 +19,17 @@ pragma solidity ^0.4.24; import "../../utils/LibBytes/LibBytes.sol"; -import "./libs/LibExchangeErrors.sol"; import "./mixins/MSignatureValidator.sol"; import "./mixins/MTransactions.sol"; import "./interfaces/IWallet.sol"; import "./interfaces/IValidator.sol"; contract MixinSignatureValidator is - LibExchangeErrors, MSignatureValidator, MTransactions { using LibBytes for bytes; - // Personal message headers - string constant ETH_PERSONAL_MESSAGE = "\x19Ethereum Signed Message:\n32"; - string constant TREZOR_PERSONAL_MESSAGE = "\x19Ethereum Signed Message:\n\x20"; - // Mapping of hash => signer => signed mapping (bytes32 => mapping (address => bool)) public preSigned; @@ -59,7 +53,7 @@ contract MixinSignatureValidator is signerAddress, signature ), - INVALID_SIGNATURE + "INVALID_SIGNATURE" ); preSigned[hash][signerAddress] = true; } @@ -99,14 +93,14 @@ contract MixinSignatureValidator is // TODO: Domain separation: make hash depend on role. (Taker sig should not be valid as maker sig, etc.) require( signature.length > 0, - LENGTH_GREATER_THAN_0_REQUIRED + "LENGTH_GREATER_THAN_0_REQUIRED" ); // Ensure signature is supported uint8 signatureTypeRaw = uint8(signature.popLastByte()); require( signatureTypeRaw < uint8(SignatureType.NSignatureTypes), - SIGNATURE_UNSUPPORTED + "SIGNATURE_UNSUPPORTED" ); // Pop last byte off of signature byte array. @@ -124,7 +118,7 @@ contract MixinSignatureValidator is // it an explicit option. This aids testing and analysis. It is // also the initialization value for the enum type. if (signatureType == SignatureType.Illegal) { - revert(SIGNATURE_ILLEGAL); + revert("SIGNATURE_ILLEGAL"); // Always invalid signature. // Like Illegal, this is always implicitly available and therefore @@ -133,7 +127,7 @@ contract MixinSignatureValidator is } else if (signatureType == SignatureType.Invalid) { require( signature.length == 0, - LENGTH_0_REQUIRED + "LENGTH_0_REQUIRED" ); isValid = false; return isValid; @@ -142,7 +136,7 @@ contract MixinSignatureValidator is } else if (signatureType == SignatureType.EIP712) { require( signature.length == 65, - LENGTH_65_REQUIRED + "LENGTH_65_REQUIRED" ); v = uint8(signature[0]); r = signature.readBytes32(1); @@ -155,13 +149,16 @@ contract MixinSignatureValidator is } else if (signatureType == SignatureType.EthSign) { require( signature.length == 65, - LENGTH_65_REQUIRED + "LENGTH_65_REQUIRED" ); v = uint8(signature[0]); r = signature.readBytes32(1); s = signature.readBytes32(33); recovered = ecrecover( - keccak256(abi.encodePacked(ETH_PERSONAL_MESSAGE, hash)), + keccak256(abi.encodePacked( + "\x19Ethereum Signed Message:\n32", + hash + )), v, r, s @@ -180,7 +177,7 @@ contract MixinSignatureValidator is } else if (signatureType == SignatureType.Caller) { require( signature.length == 0, - LENGTH_0_REQUIRED + "LENGTH_0_REQUIRED" ); isValid = signerAddress == msg.sender; return isValid; @@ -230,13 +227,16 @@ contract MixinSignatureValidator is } else if (signatureType == SignatureType.Trezor) { require( signature.length == 65, - LENGTH_65_REQUIRED + "LENGTH_65_REQUIRED" ); v = uint8(signature[0]); r = signature.readBytes32(1); s = signature.readBytes32(33); recovered = ecrecover( - keccak256(abi.encodePacked(TREZOR_PERSONAL_MESSAGE, hash)), + keccak256(abi.encodePacked( + "\x19Ethereum Signed Message:\n\x20", + hash + )), v, r, s @@ -250,6 +250,6 @@ contract MixinSignatureValidator is // that we currently support. In this case returning false // may lead the caller to incorrectly believe that the // signature was invalid.) - revert(SIGNATURE_UNSUPPORTED); + revert("SIGNATURE_UNSUPPORTED"); } } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol index 20a4a12df..e0f450d0a 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinTransactions.sol @@ -20,12 +20,10 @@ pragma solidity ^0.4.24; import "./libs/LibExchangeErrors.sol"; import "./mixins/MSignatureValidator.sol"; import "./mixins/MTransactions.sol"; -import "./libs/LibExchangeErrors.sol"; import "./libs/LibEIP712.sol"; contract MixinTransactions is LibEIP712, - LibExchangeErrors, MSignatureValidator, MTransactions { @@ -90,7 +88,7 @@ contract MixinTransactions is // Prevent reentrancy require( currentContextAddress == address(0), - REENTRANCY_ILLEGAL + "REENTRANCY_ILLEGAL" ); bytes32 transactionHash = hashEIP712Message(hashZeroExTransaction( @@ -102,7 +100,7 @@ contract MixinTransactions is // Validate transaction has not been executed require( !transactions[transactionHash], - INVALID_TX_HASH + "INVALID_TX_HASH" ); // Transaction always valid if signer is sender of transaction @@ -114,7 +112,7 @@ contract MixinTransactions is signerAddress, signature ), - INVALID_TX_SIGNATURE + "INVALID_TX_SIGNATURE" ); // Set the current transaction signer @@ -125,7 +123,7 @@ contract MixinTransactions is transactions[transactionHash] = true; require( address(this).delegatecall(data), - FAILED_EXECUTION + "FAILED_EXECUTION" ); // Reset current transaction signer diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol index 9cb2cacd4..00668ca43 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/MixinWrapperFunctions.sol @@ -22,13 +22,11 @@ pragma experimental ABIEncoderV2; import "./libs/LibMath.sol"; import "./libs/LibOrder.sol"; import "./libs/LibFillResults.sol"; -import "./libs/LibExchangeErrors.sol"; import "./mixins/MExchangeCore.sol"; contract MixinWrapperFunctions is LibMath, LibFillResults, - LibExchangeErrors, MExchangeCore { /// @dev Fills the input order. Reverts if exact takerAssetFillAmount not filled. @@ -50,7 +48,7 @@ contract MixinWrapperFunctions is ); require( fillResults.takerAssetFilledAmount == takerAssetFillAmount, - COMPLETE_FILL_FAILED + "COMPLETE_FILL_FAILED" ); return fillResults; } 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 a43f0f927..e37f41ada 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibExchangeErrors.sol @@ -18,7 +18,10 @@ pragma solidity ^0.4.24; +/// @dev This contract documents the revert reasons used in the Exchange contract. +/// This contract is intended to serve as a reference, but is not actually used for efficiency reasons. contract LibExchangeErrors { + /// Order validation errors /// string constant ORDER_UNFILLABLE = "ORDER_UNFILLABLE"; // Order cannot be filled. string constant INVALID_MAKER = "INVALID_MAKER"; // Invalid makerAddress. @@ -56,9 +59,11 @@ contract LibExchangeErrors { /// dispatchTransferFrom errors /// string constant ASSET_PROXY_DOES_NOT_EXIST = "ASSET_PROXY_DOES_NOT_EXIST"; // No assetProxy registered at given id. + string constant TRANSFER_FAILED = "TRANSFER_FAILED"; // Asset transfer unsuccesful. /// Length validation errors /// string constant LENGTH_GREATER_THAN_0_REQUIRED = "LENGTH_GREATER_THAN_0_REQUIRED"; // Byte array must have a length greater than 0. + string constant LENGTH_GREATER_THAN_3_REQUIRED = "LENGTH_GREATER_THAN_3_REQUIRED"; // Byte array must have a length greater than 3. string constant LENGTH_0_REQUIRED = "LENGTH_0_REQUIRED"; // Byte array must have a length of 0. string constant LENGTH_65_REQUIRED = "LENGTH_65_REQUIRED"; // Byte array must have a length of 65. } diff --git a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol index 233547b9f..bfe2fd33f 100644 --- a/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol +++ b/packages/contracts/src/contracts/current/protocol/Exchange/libs/LibMath.sol @@ -23,7 +23,6 @@ import "../../../utils/SafeMath/SafeMath.sol"; contract LibMath is SafeMath { - string constant ROUNDING_ERROR_ON_PARTIAL_AMOUNT = "A rounding error occurred when calculating partial transfer amounts."; /// @dev Calculates partial value given a numerator and denominator. /// @param numerator Numerator. diff --git a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol index 289d086d6..78b1ddf7c 100644 --- a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol +++ b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol @@ -19,17 +19,8 @@ pragma solidity ^0.4.24; library LibBytes { - using LibBytes for bytes; - // Revert reasons - 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"; - string constant FROM_LESS_THAN_TO_REQUIRED = "FROM_LESS_THAN_TO_REQUIRED"; - string constant TO_LESS_THAN_LENGTH_REQUIRED = "TO_LESS_THAN_LENGTH_REQUIRED"; + using LibBytes for bytes; /// @dev Gets the memory address for a byte array. /// @param input Byte array to lookup. @@ -176,8 +167,14 @@ library LibBytes { pure returns (bytes memory result) { - require(from <= to, FROM_LESS_THAN_TO_REQUIRED); - require(to < b.length, TO_LESS_THAN_LENGTH_REQUIRED); + require( + from <= to, + "FROM_LESS_THAN_TO_REQUIRED" + ); + require( + to < b.length, + "TO_LESS_THAN_LENGTH_REQUIRED" + ); // Create a new bytes structure and copy contents result = new bytes(to - from); @@ -199,8 +196,14 @@ library LibBytes { pure returns (bytes memory result) { - require(from <= to, FROM_LESS_THAN_TO_REQUIRED); - require(to < b.length, TO_LESS_THAN_LENGTH_REQUIRED); + require( + from <= to, + "FROM_LESS_THAN_TO_REQUIRED" + ); + require( + to < b.length, + "TO_LESS_THAN_LENGTH_REQUIRED" + ); // Create a new bytes structure around [from, to) in-place. assembly { @@ -220,7 +223,7 @@ library LibBytes { { require( b.length > 0, - GREATER_THAN_ZERO_LENGTH_REQUIRED + "GREATER_THAN_ZERO_LENGTH_REQUIRED" ); // Store last byte. @@ -244,7 +247,7 @@ library LibBytes { { require( b.length >= 20, - GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED + "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED" ); // Store last 20 bytes. @@ -290,7 +293,7 @@ library LibBytes { { require( b.length >= index + 20, // 20 is length of address - GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED + "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED" ); // Add offset to index: @@ -322,7 +325,7 @@ library LibBytes { { require( b.length >= index + 20, // 20 is length of address - GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED + "GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED" ); // Add offset to index: @@ -365,7 +368,7 @@ library LibBytes { { require( b.length >= index + 32, - GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED + "GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED" ); // Arrays are prefixed by a 256 bit length parameter @@ -392,7 +395,7 @@ library LibBytes { { require( b.length >= index + 32, - GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED + "GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED" ); // Arrays are prefixed by a 256 bit length parameter @@ -447,7 +450,7 @@ library LibBytes { { require( b.length >= index + 4, - GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED + "GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED" ); assembly { result := mload(add(b, 32)) @@ -480,7 +483,7 @@ library LibBytes { // length of nested bytes require( b.length >= index + nestedBytesLength, - GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED + "GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED" ); // Return a pointer to the byte array as it exists inside `b` @@ -506,7 +509,7 @@ library LibBytes { // length of input require( b.length >= index + 32 /* 32 bytes to store length */ + input.length, - GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED + "GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED" ); // Copy into @@ -531,7 +534,7 @@ library LibBytes { // 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 + "GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED" ); memCopy( dest.contentAddress(), diff --git a/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol b/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol index 99b93d0d2..6f5761cc7 100644 --- a/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol +++ b/packages/contracts/src/contracts/current/utils/Ownable/Ownable.sol @@ -13,9 +13,6 @@ import "./IOwnable.sol"; contract Ownable is IOwnable { address public owner; - // Revert reasons - string constant ONLY_CONTRACT_OWNER = "ONLY_CONTRACT_OWNER"; - constructor () public { @@ -25,7 +22,7 @@ contract Ownable is IOwnable { modifier onlyOwner() { require( msg.sender == owner, - ONLY_CONTRACT_OWNER + "ONLY_CONTRACT_OWNER" ); _; } diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts index 43aea9a79..2313c0b0e 100644 --- a/packages/contracts/test/asset_proxy/proxies.ts +++ b/packages/contracts/test/asset_proxy/proxies.ts @@ -236,7 +236,8 @@ describe('Asset Transfer Proxies', () => { it('should have an id of 0xf47261b0', async () => { const proxyId = await erc20Proxy.getProxyId.callAsync(); - expect(proxyId).to.equal('0xf47261b0'); + const expectedProxyId = '0xf47261b0'; + expect(proxyId).to.equal(expectedProxyId); }); }); @@ -484,7 +485,8 @@ describe('Asset Transfer Proxies', () => { it('should have an id of 0x08e937fa', async () => { const proxyId = await erc721Proxy.getProxyId.callAsync(); - expect(proxyId).to.equal('0x08e937fa'); + const expectedProxyId = '0x08e937fa'; + expect(proxyId).to.equal(expectedProxyId); }); }); }); -- cgit v1.2.3